@elle96/ng-json-schema-manager 0.3.0 → 0.4.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,1308 +1,1153 @@
1
- import * as i0 from '@angular/core';
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
- 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 i4 from '@angular/common';
11
- import { CommonModule, isPlatformBrowser } 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.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
- }
280
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", 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.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
- }
455
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", 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
- /**
475
- * Validates the schema itself against its JSON Schema meta-schema.
476
- * Returns a list of SchemaError describing what is wrong in the schema definition.
477
- */
478
- validateSchema(schema) {
479
- const draft = this.detectDraft(schema);
480
- const ajv = this.getAjv(draft);
481
- try {
482
- const valid = ajv.validateSchema(schema);
483
- if (valid)
484
- return [];
485
- return (ajv.errors ?? []).map((e) => ({
486
- path: e.instancePath || '/',
487
- message: e.message ?? e.keyword,
488
- keyword: e.keyword,
489
- }));
490
- }
491
- catch (e) {
492
- // ajv.validateSchema throws when the schema is so broken it cannot be compiled.
493
- // The error message contains comma-separated individual violations — split them.
494
- const raw = e instanceof Error ? e.message : String(e);
495
- // Strip the "schema is invalid: " prefix if present
496
- const body = raw.replace(/^schema is invalid:\s*/i, '');
497
- // Each violation looks like "data/some/path must ..." — extract path and message
498
- return body.split(', ').map((msg) => {
499
- const trimmed = msg.trim();
500
- const match = trimmed.match(/^(data[^\s]*)\s+(.+)$/);
501
- return match
502
- ? { path: match[1].replace(/^data/, '') || '/', message: match[2], keyword: 'schema' }
503
- : { path: '/', message: trimmed, keyword: 'schema' };
504
- });
505
- }
506
- }
507
- detectDraft(schema) {
508
- const schemaId = schema.$schema ?? '';
509
- if (schemaId.includes('draft-04')) {
510
- return 'draft4';
511
- }
512
- if (schemaId.includes('draft-06')) {
513
- return 'draft6';
514
- }
515
- if (schemaId.includes('draft-07')) {
516
- return 'draft7';
517
- }
518
- if (schemaId.includes('2019-09')) {
519
- return 'draft2019';
520
- }
521
- if (schemaId.includes('2020-12')) {
522
- return 'draft2020';
523
- }
524
- if (schema.$defs) {
525
- return 'draft2020';
526
- }
527
- if (schema.definitions) {
528
- return 'draft7';
529
- }
530
- return 'draft2020';
531
- }
532
- getAjv(draft) {
533
- if (this.ajvCache.has(draft)) {
534
- return this.ajvCache.get(draft);
535
- }
536
- let ajv;
537
- switch (draft) {
538
- case 'draft4':
539
- ajv = new AjvDraft04({
540
- strict: false,
541
- allErrors: true,
542
- allowUnionTypes: true,
543
- });
544
- break;
545
- case 'draft2019':
546
- ajv = new Ajv2019({
547
- strict: false,
548
- allErrors: true,
549
- allowUnionTypes: true,
550
- });
551
- break;
552
- case 'draft6':
553
- case 'draft7':
554
- ajv = new Ajv({
555
- strict: false,
556
- allErrors: true,
557
- allowUnionTypes: true,
558
- });
559
- break;
560
- case 'draft2020':
561
- default:
562
- ajv = new Ajv2020({
563
- strict: false,
564
- allErrors: true,
565
- allowUnionTypes: true,
566
- });
567
- }
568
- addFormats(ajv);
569
- this.ajvCache.set(draft, ajv);
570
- return ajv;
571
- }
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
- }
575
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: JsonSchemaValidationService, decorators: [{
576
- type: Injectable,
577
- args: [{
578
- providedIn: 'root',
579
- }]
580
- }] });
581
-
1
+ import * as i0 from "@angular/core";
2
+ import { ChangeDetectionStrategy, Component, Directive, EventEmitter, Inject, Injectable, Input, NgModule, Optional, Output, PLATFORM_ID, ViewEncapsulation, forwardRef } from "@angular/core";
3
+ import * as i5 from "@angular/forms";
4
+ import { FormArray, FormControl, FormGroup, FormsModule, NG_VALUE_ACCESSOR, ReactiveFormsModule, Validators } 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 i4 from "@angular/common";
11
+ import { CommonModule, isPlatformBrowser } from "@angular/common";
12
+ var JsonSchemaFormService = class JsonSchemaFormService {
13
+ buildControl(schema, value, required = false) {
14
+ const effectiveSchema = this.applyCombinators(schema);
15
+ const resolvedType = this.resolveType(effectiveSchema);
16
+ if (resolvedType === "object") {
17
+ const group = {};
18
+ const properties = effectiveSchema.properties ?? {};
19
+ const requiredFields = new Set(effectiveSchema.required ?? []);
20
+ const valueObject = this.isRecord(value) ? value : {};
21
+ for (const [key, propertySchema] of Object.entries(properties)) {
22
+ const childValue = valueObject[key];
23
+ group[key] = this.buildControl(propertySchema, childValue, requiredFields.has(key));
24
+ }
25
+ for (const key of Object.keys(valueObject)) {
26
+ if (group[key]) continue;
27
+ const dynamicSchema = this.resolveDynamicSchema(effectiveSchema, key);
28
+ group[key] = this.buildControl(dynamicSchema ?? {}, valueObject[key], false);
29
+ }
30
+ const control = new FormGroup(group, { validators: this.buildValidators(effectiveSchema, required) });
31
+ if (effectiveSchema.readOnly) control.disable({ emitEvent: false });
32
+ return control;
33
+ }
34
+ if (resolvedType === "array") {
35
+ const controls = [];
36
+ const initialItems = Array.isArray(value) ? value : [];
37
+ const minItems = effectiveSchema.minItems ?? 0;
38
+ const prefixItems = effectiveSchema.prefixItems ?? [];
39
+ const tupleLength = Math.max(prefixItems.length, minItems, initialItems.length);
40
+ for (let i = 0; i < tupleLength; i += 1) {
41
+ const itemSchema = prefixItems[i] ?? effectiveSchema.items;
42
+ if (itemSchema === false) continue;
43
+ const itemValue = initialItems[i];
44
+ controls.push(this.buildControl(itemSchema ?? {}, itemValue, false));
45
+ }
46
+ const control = new FormArray(controls, { validators: this.buildValidators(effectiveSchema, required) });
47
+ if (effectiveSchema.readOnly) control.disable({ emitEvent: false });
48
+ return control;
49
+ }
50
+ const initialValue = this.resolveInitialValue(effectiveSchema, value);
51
+ const control = new FormControl(initialValue, {
52
+ validators: this.buildValidators(effectiveSchema, required, resolvedType),
53
+ nonNullable: false
54
+ });
55
+ if (effectiveSchema.readOnly || effectiveSchema.const !== void 0) control.disable({ emitEvent: false });
56
+ return control;
57
+ }
58
+ resolveType(schema) {
59
+ if (schema.enum) return "enum";
60
+ if (schema.oneOf?.length) return this.resolveType(schema.oneOf[0]);
61
+ if (schema.anyOf?.length) return this.resolveType(schema.anyOf[0]);
62
+ if (schema.allOf?.length) return this.resolveType(schema.allOf[0]);
63
+ if (schema.type) {
64
+ if (Array.isArray(schema.type)) return schema.type.find((item) => item !== "null") ?? schema.type[0];
65
+ return schema.type;
66
+ }
67
+ if (schema.properties || schema.patternProperties || schema.additionalProperties) return "object";
68
+ if (schema.items || schema.prefixItems) return "array";
69
+ return "string";
70
+ }
71
+ applyCombinators(schema) {
72
+ let effective = schema;
73
+ if (schema.allOf?.length) effective = this.mergeSchemas([schema, ...schema.allOf]);
74
+ if (schema.oneOf?.length) effective = schema.oneOf[0];
75
+ if (schema.anyOf?.length) effective = schema.anyOf[0];
76
+ return effective;
77
+ }
78
+ resolveDynamicSchema(schema, key) {
79
+ const patternSchemas = schema.patternProperties ?? {};
80
+ for (const [pattern, patternSchema] of Object.entries(patternSchemas)) try {
81
+ if (new RegExp(pattern).test(key)) return patternSchema;
82
+ } catch {}
83
+ if (schema.additionalProperties === true || schema.additionalProperties === void 0) return {};
84
+ if (schema.additionalProperties && typeof schema.additionalProperties === "object") return schema.additionalProperties;
85
+ if (schema.unevaluatedProperties && typeof schema.unevaluatedProperties === "object") return schema.unevaluatedProperties;
86
+ if (schema.unevaluatedProperties === true) return {};
87
+ }
88
+ resolveInitialValue(schema, value) {
89
+ if (value !== void 0) return value;
90
+ if (schema.const !== void 0) return schema.const;
91
+ if (schema.default !== void 0) return schema.default;
92
+ if (schema.enum && schema.enum.length > 0) return schema.enum[0];
93
+ if (schema.type === "boolean") return false;
94
+ if (schema.type === "array") return [];
95
+ if (schema.type === "object") return {};
96
+ return null;
97
+ }
98
+ buildValidators(schema, required, resolvedType) {
99
+ const validators = [];
100
+ if (required) validators.push(Validators.required);
101
+ if (schema.minLength !== void 0) validators.push(Validators.minLength(schema.minLength));
102
+ if (schema.maxLength !== void 0) validators.push(Validators.maxLength(schema.maxLength));
103
+ if (schema.minimum !== void 0) validators.push(Validators.min(schema.minimum));
104
+ if (schema.maximum !== void 0) validators.push(Validators.max(schema.maximum));
105
+ if (schema.exclusiveMinimum !== void 0) validators.push((control) => control.value === null || control.value === void 0 || control.value === "" ? null : Number(control.value) > schema.exclusiveMinimum ? null : { exclusiveMinimum: true });
106
+ if (schema.exclusiveMaximum !== void 0) validators.push((control) => control.value === null || control.value === void 0 || control.value === "" ? null : Number(control.value) < schema.exclusiveMaximum ? null : { exclusiveMaximum: true });
107
+ if (schema.multipleOf !== void 0) validators.push((control) => {
108
+ if (control.value === null || control.value === void 0 || control.value === "") return null;
109
+ return Number(control.value) % schema.multipleOf === 0 ? null : { multipleOf: true };
110
+ });
111
+ if (schema.pattern) validators.push(Validators.pattern(schema.pattern));
112
+ if (schema.format === "email") validators.push(Validators.email);
113
+ if (schema.minItems !== void 0) validators.push((control) => Array.isArray(control.value) && control.value.length < schema.minItems ? { minItems: true } : null);
114
+ if (schema.maxItems !== void 0) validators.push((control) => Array.isArray(control.value) && control.value.length > schema.maxItems ? { maxItems: true } : null);
115
+ if (schema.uniqueItems) validators.push((control) => {
116
+ if (!Array.isArray(control.value)) return null;
117
+ return new Set(control.value.map((item) => JSON.stringify(item))).size === control.value.length ? null : { uniqueItems: true };
118
+ });
119
+ if (schema.minProperties !== void 0) validators.push((control) => {
120
+ if (!control.value || typeof control.value !== "object") return null;
121
+ return Object.keys(control.value).length < schema.minProperties ? { minProperties: true } : null;
122
+ });
123
+ if (schema.maxProperties !== void 0) validators.push((control) => {
124
+ if (!control.value || typeof control.value !== "object") return null;
125
+ return Object.keys(control.value).length > schema.maxProperties ? { maxProperties: true } : null;
126
+ });
127
+ if (resolvedType === "integer") validators.push((control) => {
128
+ if (control.value === null || control.value === void 0 || control.value === "") return null;
129
+ const numericValue = Number(control.value);
130
+ return Number.isInteger(numericValue) ? null : { integer: true };
131
+ });
132
+ return validators;
133
+ }
134
+ isRecord(value) {
135
+ return !!value && typeof value === "object" && !Array.isArray(value);
136
+ }
137
+ mergeSchemas(schemas) {
138
+ return schemas.reduce((acc, schema) => {
139
+ return {
140
+ ...acc,
141
+ ...schema,
142
+ properties: {
143
+ ...acc.properties ?? {},
144
+ ...schema.properties ?? {}
145
+ },
146
+ patternProperties: {
147
+ ...acc.patternProperties ?? {},
148
+ ...schema.patternProperties ?? {}
149
+ },
150
+ required: Array.from(/* @__PURE__ */ new Set([...acc.required ?? [], ...schema.required ?? []]))
151
+ };
152
+ }, {});
153
+ }
154
+ static ɵfac = i0.ɵɵngDeclareFactory({
155
+ minVersion: "12.0.0",
156
+ version: "22.2.3",
157
+ ngImport: i0,
158
+ type: JsonSchemaFormService,
159
+ deps: [],
160
+ target: i0.ɵɵFactoryTarget.Injectable
161
+ });
162
+ static ɵprov = i0.ɵɵngDeclareInjectable({
163
+ minVersion: "12.0.0",
164
+ version: "22.2.3",
165
+ ngImport: i0,
166
+ type: JsonSchemaFormService,
167
+ providedIn: "root"
168
+ });
169
+ };
170
+ i0.ɵɵngDeclareClassMetadata({
171
+ minVersion: "12.0.0",
172
+ version: "22.2.3",
173
+ ngImport: i0,
174
+ type: JsonSchemaFormService,
175
+ decorators: [{
176
+ type: Injectable,
177
+ args: [{ providedIn: "root" }]
178
+ }]
179
+ });
180
+ var JsonSchemaResolverService = class JsonSchemaResolverService {
181
+ async resolve(schema, baseUri) {
182
+ const rootBase = baseUri ?? schema.$id ?? "";
183
+ const localCache = /* @__PURE__ */ new Map();
184
+ const externalCache = /* @__PURE__ */ new Map();
185
+ return this.resolveRefs(schema, schema, rootBase, localCache, externalCache);
186
+ }
187
+ async resolveRefs(current, root, baseUri, localCache, externalCache) {
188
+ if (!current || typeof current !== "object") return current;
189
+ if (current.$ref) {
190
+ const ref = current.$ref;
191
+ if (ref.startsWith("#/")) {
192
+ if (localCache.has(ref)) return localCache.get(ref);
193
+ const resolved = this.resolvePointer(root, ref);
194
+ if (resolved) {
195
+ localCache.set(ref, resolved);
196
+ return this.resolveRefs(resolved, root, baseUri, localCache, externalCache);
197
+ }
198
+ } else {
199
+ const resolved = await this.resolveExternalRef(ref, baseUri, externalCache, localCache);
200
+ if (resolved) return resolved;
201
+ }
202
+ }
203
+ const next = { ...current };
204
+ if (next.properties) {
205
+ const entries = await Promise.all(Object.entries(next.properties).map(async ([key, value]) => [key, await this.resolveRefs(value, root, baseUri, localCache, externalCache)]));
206
+ next.properties = Object.fromEntries(entries);
207
+ }
208
+ if (next.patternProperties) {
209
+ const entries = await Promise.all(Object.entries(next.patternProperties).map(async ([key, value]) => [key, await this.resolveRefs(value, root, baseUri, localCache, externalCache)]));
210
+ next.patternProperties = Object.fromEntries(entries);
211
+ }
212
+ if (next.items && typeof next.items === "object") next.items = await this.resolveRefs(next.items, root, baseUri, localCache, externalCache);
213
+ if (next.prefixItems) next.prefixItems = await Promise.all(next.prefixItems.map((item) => this.resolveRefs(item, root, baseUri, localCache, externalCache)));
214
+ if (next.additionalProperties && typeof next.additionalProperties === "object") next.additionalProperties = await this.resolveRefs(next.additionalProperties, root, baseUri, localCache, externalCache);
215
+ if (next.additionalItems && typeof next.additionalItems === "object") next.additionalItems = await this.resolveRefs(next.additionalItems, root, baseUri, localCache, externalCache);
216
+ if (next.unevaluatedProperties && typeof next.unevaluatedProperties === "object") next.unevaluatedProperties = await this.resolveRefs(next.unevaluatedProperties, root, baseUri, localCache, externalCache);
217
+ if (next.unevaluatedItems && typeof next.unevaluatedItems === "object") next.unevaluatedItems = await this.resolveRefs(next.unevaluatedItems, root, baseUri, localCache, externalCache);
218
+ if (next.contains) next.contains = await this.resolveRefs(next.contains, root, baseUri, localCache, externalCache);
219
+ if (next.oneOf) next.oneOf = await Promise.all(next.oneOf.map((item) => this.resolveRefs(item, root, baseUri, localCache, externalCache)));
220
+ if (next.anyOf) next.anyOf = await Promise.all(next.anyOf.map((item) => this.resolveRefs(item, root, baseUri, localCache, externalCache)));
221
+ if (next.allOf) next.allOf = await Promise.all(next.allOf.map((item) => this.resolveRefs(item, root, baseUri, localCache, externalCache)));
222
+ if (next.not) next.not = await this.resolveRefs(next.not, root, baseUri, localCache, externalCache);
223
+ if (next.if) next.if = await this.resolveRefs(next.if, root, baseUri, localCache, externalCache);
224
+ if (next.then) next.then = await this.resolveRefs(next.then, root, baseUri, localCache, externalCache);
225
+ if (next.else) next.else = await this.resolveRefs(next.else, root, baseUri, localCache, externalCache);
226
+ if (next.dependencies) {
227
+ const entries = await Promise.all(Object.entries(next.dependencies).map(async ([key, value]) => {
228
+ if (value && typeof value === "object" && !Array.isArray(value)) return [key, await this.resolveRefs(value, root, baseUri, localCache, externalCache)];
229
+ return [key, value];
230
+ }));
231
+ next.dependencies = Object.fromEntries(entries);
232
+ }
233
+ if (next.dependentSchemas) {
234
+ const entries = await Promise.all(Object.entries(next.dependentSchemas).map(async ([key, value]) => [key, await this.resolveRefs(value, root, baseUri, localCache, externalCache)]));
235
+ next.dependentSchemas = Object.fromEntries(entries);
236
+ }
237
+ if (next.$defs) {
238
+ const entries = await Promise.all(Object.entries(next.$defs).map(async ([key, value]) => [key, await this.resolveRefs(value, root, baseUri, localCache, externalCache)]));
239
+ next.$defs = Object.fromEntries(entries);
240
+ }
241
+ if (next.definitions) {
242
+ const entries = await Promise.all(Object.entries(next.definitions).map(async ([key, value]) => [key, await this.resolveRefs(value, root, baseUri, localCache, externalCache)]));
243
+ next.definitions = Object.fromEntries(entries);
244
+ }
245
+ return next;
246
+ }
247
+ async resolveExternalRef(ref, baseUri, externalCache, localCache) {
248
+ try {
249
+ const url = new URL(ref, baseUri || window.location.href).toString();
250
+ if (externalCache.has(url)) return externalCache.get(url);
251
+ const { schema, fragment } = await this.fetchSchema(url);
252
+ if (!schema) return null;
253
+ const target = fragment ? this.resolvePointer(schema, fragment) : schema;
254
+ if (!target) return null;
255
+ const resolved = await this.resolveRefs(target, schema, schema.$id ?? url, localCache, externalCache);
256
+ externalCache.set(url, resolved);
257
+ return resolved;
258
+ } catch {
259
+ return null;
260
+ }
261
+ }
262
+ async fetchSchema(url) {
263
+ const [base, hash] = url.split("#");
264
+ const fragment = hash ? `#/${hash.replace(/^\//, "")}` : null;
265
+ const response = await fetch(base, { headers: { Accept: "application/json" } });
266
+ if (!response.ok) return {
267
+ schema: null,
268
+ fragment: null
269
+ };
270
+ return {
271
+ schema: await response.json(),
272
+ fragment
273
+ };
274
+ }
275
+ resolvePointer(root, ref) {
276
+ const parts = ref.replace(/^#\//, "").split("/").map((part) => part.replace(/~1/g, "/").replace(/~0/g, "~"));
277
+ let current = root;
278
+ for (const part of parts) if (current && typeof current === "object" && part in current) current = current[part];
279
+ else return null;
280
+ return current;
281
+ }
282
+ static ɵfac = i0.ɵɵngDeclareFactory({
283
+ minVersion: "12.0.0",
284
+ version: "22.2.3",
285
+ ngImport: i0,
286
+ type: JsonSchemaResolverService,
287
+ deps: [],
288
+ target: i0.ɵɵFactoryTarget.Injectable
289
+ });
290
+ static ɵprov = i0.ɵɵngDeclareInjectable({
291
+ minVersion: "12.0.0",
292
+ version: "22.2.3",
293
+ ngImport: i0,
294
+ type: JsonSchemaResolverService,
295
+ providedIn: "root"
296
+ });
297
+ };
298
+ i0.ɵɵngDeclareClassMetadata({
299
+ minVersion: "12.0.0",
300
+ version: "22.2.3",
301
+ ngImport: i0,
302
+ type: JsonSchemaResolverService,
303
+ decorators: [{
304
+ type: Injectable,
305
+ args: [{ providedIn: "root" }]
306
+ }]
307
+ });
308
+ var JsonSchemaValidationService = class JsonSchemaValidationService {
309
+ ajvCache = /* @__PURE__ */ new Map();
310
+ validate(schema, value) {
311
+ const draft = this.detectDraft(schema);
312
+ const validate = this.getAjv(draft).compile(schema);
313
+ if (validate(value)) return [];
314
+ return validate.errors ?? [];
315
+ }
316
+ validateSchema(schema) {
317
+ const draft = this.detectDraft(schema);
318
+ const ajv = this.getAjv(draft);
319
+ try {
320
+ if (ajv.validateSchema(schema)) return [];
321
+ return (ajv.errors ?? []).map((e) => ({
322
+ path: e.instancePath || "/",
323
+ message: e.message ?? e.keyword,
324
+ keyword: e.keyword
325
+ }));
326
+ } catch (e) {
327
+ return (e instanceof Error ? e.message : String(e)).replace(/^schema is invalid:\s*/i, "").split(", ").map((msg) => {
328
+ const trimmed = msg.trim();
329
+ const match = trimmed.match(/^(data[^\s]*)\s+(.+)$/);
330
+ return match ? {
331
+ path: match[1].replace(/^data/, "") || "/",
332
+ message: match[2],
333
+ keyword: "schema"
334
+ } : {
335
+ path: "/",
336
+ message: trimmed,
337
+ keyword: "schema"
338
+ };
339
+ });
340
+ }
341
+ }
342
+ detectDraft(schema) {
343
+ const schemaId = schema.$schema ?? "";
344
+ if (schemaId.includes("draft-04")) return "draft4";
345
+ if (schemaId.includes("draft-06")) return "draft6";
346
+ if (schemaId.includes("draft-07")) return "draft7";
347
+ if (schemaId.includes("2019-09")) return "draft2019";
348
+ if (schemaId.includes("2020-12")) return "draft2020";
349
+ if (schema.$defs) return "draft2020";
350
+ if (schema.definitions) return "draft7";
351
+ return "draft2020";
352
+ }
353
+ getAjv(draft) {
354
+ if (this.ajvCache.has(draft)) return this.ajvCache.get(draft);
355
+ let ajv;
356
+ switch (draft) {
357
+ case "draft4":
358
+ ajv = new AjvDraft04({
359
+ strict: false,
360
+ allErrors: true,
361
+ allowUnionTypes: true
362
+ });
363
+ break;
364
+ case "draft2019":
365
+ ajv = new Ajv2019({
366
+ strict: false,
367
+ allErrors: true,
368
+ allowUnionTypes: true
369
+ });
370
+ break;
371
+ case "draft6":
372
+ case "draft7":
373
+ ajv = new Ajv({
374
+ strict: false,
375
+ allErrors: true,
376
+ allowUnionTypes: true
377
+ });
378
+ break;
379
+ default: ajv = new Ajv2020({
380
+ strict: false,
381
+ allErrors: true,
382
+ allowUnionTypes: true
383
+ });
384
+ }
385
+ addFormats(ajv);
386
+ this.ajvCache.set(draft, ajv);
387
+ return ajv;
388
+ }
389
+ static ɵfac = i0.ɵɵngDeclareFactory({
390
+ minVersion: "12.0.0",
391
+ version: "22.2.3",
392
+ ngImport: i0,
393
+ type: JsonSchemaValidationService,
394
+ deps: [],
395
+ target: i0.ɵɵFactoryTarget.Injectable
396
+ });
397
+ static ɵprov = i0.ɵɵngDeclareInjectable({
398
+ minVersion: "12.0.0",
399
+ version: "22.2.3",
400
+ ngImport: i0,
401
+ type: JsonSchemaValidationService,
402
+ providedIn: "root"
403
+ });
404
+ };
405
+ i0.ɵɵngDeclareClassMetadata({
406
+ minVersion: "12.0.0",
407
+ version: "22.2.3",
408
+ ngImport: i0,
409
+ type: JsonSchemaValidationService,
410
+ decorators: [{
411
+ type: Injectable,
412
+ args: [{ providedIn: "root" }]
413
+ }]
414
+ });
582
415
  const DEFAULT_LABELS = {
583
- formTitle: 'Form',
584
- resolving: 'Resolving schema…',
585
- schemaErrorsTitle: 'Schema errors',
586
- schemaErrorsHint: 'The schema has structural issues. Fix them to get a valid form.',
587
- addItem: 'Add item',
588
- arrayItem: 'Item {n}',
589
- remove: 'Remove',
590
- add: 'Add',
591
- addProperty: 'Add property',
592
- propertyName: 'Property name',
593
- noItems: 'No items yet — click "Add item" to start.',
594
- mustContain: 'Must contain matching item(s)',
595
- min: 'min {n}',
596
- max: 'max {n}',
597
- option: 'Option {n}',
598
- section: 'Section {n}',
599
- example: 'e.g.',
600
- propertyNameRequired: 'Property name is required.',
601
- propertyNameInvalid: 'Property name does not match schema constraints.',
602
- propertyExists: 'Property already exists.',
603
- propertyNotAllowed: 'This property is not allowed by the schema.',
604
- errorRequired: 'This field is required',
605
- errorMinLength: 'Minimum length is {n}',
606
- errorMaxLength: 'Maximum length is {n}',
607
- errorMin: 'Minimum value is {n}',
608
- errorMax: 'Maximum value is {n}',
609
- errorEmail: 'Invalid email address',
610
- errorPattern: 'Value does not match the required pattern',
611
- errorInteger: 'Value must be an integer',
612
- errorExclusiveMin: 'Value must be greater than the minimum',
613
- errorExclusiveMax: 'Value must be less than the maximum',
614
- errorMultipleOf: 'Value must be a multiple of the required number',
615
- errorMinItems: 'Minimum {n} items required',
616
- errorMaxItems: 'Maximum {n} items allowed',
617
- errorUniqueItems: 'Items must be unique',
416
+ formTitle: "Form",
417
+ resolving: "Resolving schema…",
418
+ schemaErrorsTitle: "Schema errors",
419
+ schemaErrorsHint: "The schema has structural issues. Fix them to get a valid form.",
420
+ addItem: "Add item",
421
+ arrayItem: "Item {n}",
422
+ remove: "Remove",
423
+ add: "Add",
424
+ addProperty: "Add property",
425
+ propertyName: "Property name",
426
+ noItems: "No items yet — click \"Add item\" to start.",
427
+ mustContain: "Must contain matching item(s)",
428
+ min: "min {n}",
429
+ max: "max {n}",
430
+ option: "Option {n}",
431
+ section: "Section {n}",
432
+ example: "e.g.",
433
+ propertyNameRequired: "Property name is required.",
434
+ propertyNameInvalid: "Property name does not match schema constraints.",
435
+ propertyExists: "Property already exists.",
436
+ propertyNotAllowed: "This property is not allowed by the schema.",
437
+ errorRequired: "This field is required",
438
+ errorMinLength: "Minimum length is {n}",
439
+ errorMaxLength: "Maximum length is {n}",
440
+ errorMin: "Minimum value is {n}",
441
+ errorMax: "Maximum value is {n}",
442
+ errorEmail: "Invalid email address",
443
+ errorPattern: "Value does not match the required pattern",
444
+ errorInteger: "Value must be an integer",
445
+ errorExclusiveMin: "Value must be greater than the minimum",
446
+ errorExclusiveMax: "Value must be less than the maximum",
447
+ errorMultipleOf: "Value must be a multiple of the required number",
448
+ errorMinItems: "Minimum {n} items required",
449
+ errorMaxItems: "Maximum {n} items allowed",
450
+ errorUniqueItems: "Items must be unique"
618
451
  };
619
- /** Replaces `{key}` placeholders in a label template. */
620
452
  function formatLabel(template, values = {}) {
621
- return template.replace(/\{(\w+)\}/g, (match, key) => (key in values ? String(values[key]) : match));
622
- }
623
-
624
- /**
625
- * Options shared by every node of a form. Provided by `jsm-json-schema-form`, so each form instance has its own.
626
- */
627
- class JsonSchemaFormOptions {
628
- decimalSeparator = '.';
629
- labels = DEFAULT_LABELS;
630
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: JsonSchemaFormOptions, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
631
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: JsonSchemaFormOptions });
453
+ return template.replace(/\{(\w+)\}/g, (match, key) => key in values ? String(values[key]) : match);
632
454
  }
633
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: JsonSchemaFormOptions, decorators: [{
634
- type: Injectable
635
- }] });
455
+ var JsonSchemaFormOptions = class JsonSchemaFormOptions {
456
+ decimalSeparator = ".";
457
+ labels = DEFAULT_LABELS;
458
+ static ɵfac = i0.ɵɵngDeclareFactory({
459
+ minVersion: "12.0.0",
460
+ version: "22.2.3",
461
+ ngImport: i0,
462
+ type: JsonSchemaFormOptions,
463
+ deps: [],
464
+ target: i0.ɵɵFactoryTarget.Injectable
465
+ });
466
+ static ɵprov = i0.ɵɵngDeclareInjectable({
467
+ minVersion: "12.0.0",
468
+ version: "22.2.3",
469
+ ngImport: i0,
470
+ type: JsonSchemaFormOptions
471
+ });
472
+ };
473
+ i0.ɵɵngDeclareClassMetadata({
474
+ minVersion: "12.0.0",
475
+ version: "22.2.3",
476
+ ngImport: i0,
477
+ type: JsonSchemaFormOptions,
478
+ decorators: [{ type: Injectable }]
479
+ });
636
480
  const COMPLETE_NUMBER = /^-?(\d+[.,]?\d*|[.,]\d+)$/;
637
- /**
638
- * Keeps digits, a leading minus and a single decimal separator (written with `separator`);
639
- * `,` and `.` are both accepted as input.
640
- */
641
481
  function sanitizeNumericText(text, separator) {
642
- let result = '';
643
- let hasDecimal = false;
644
- for (const char of text) {
645
- if (char >= '0' && char <= '9') {
646
- result += char;
647
- }
648
- else if (char === '-' && result === '') {
649
- result += char;
650
- }
651
- else if ((char === ',' || char === '.') && !hasDecimal) {
652
- hasDecimal = true;
653
- result += separator;
654
- }
655
- }
656
- return result;
482
+ let result = "";
483
+ let hasDecimal = false;
484
+ for (const char of text) if (char >= "0" && char <= "9") result += char;
485
+ else if (char === "-" && result === "") result += char;
486
+ else if ((char === "," || char === ".") && !hasDecimal) {
487
+ hasDecimal = true;
488
+ result += separator;
489
+ }
490
+ return result;
657
491
  }
658
- /** Text → model: empty → `null`, a complete number → number, anything else stays a string (validation reports it). */
659
492
  function parseNumericText(text) {
660
- const trimmed = text.trim();
661
- if (trimmed === '') {
662
- return null;
663
- }
664
- if (COMPLETE_NUMBER.test(trimmed)) {
665
- return Number(trimmed.replace(',', '.'));
666
- }
667
- return trimmed;
493
+ const trimmed = text.trim();
494
+ if (trimmed === "") return null;
495
+ if (COMPLETE_NUMBER.test(trimmed)) return Number(trimmed.replace(",", "."));
496
+ return trimmed;
668
497
  }
669
- /** Model → text shown in the input. */
670
498
  function formatNumeric(value, separator) {
671
- if (value === null || value === undefined) {
672
- return '';
673
- }
674
- return typeof value === 'number' ? String(value).replace('.', separator) : String(value);
499
+ if (value === null || value === void 0) return "";
500
+ return typeof value === "number" ? String(value).replace(".", separator) : String(value);
675
501
  }
676
- /** Shows decimals with the configured separator inside messages ("must be >= 0.1" → "must be >= 0,1"). */
677
502
  function localizeNumbers(message, separator) {
678
- return separator === ',' ? message.replace(/(\d)\.(\d)/g, '$1,$2') : message;
679
- }
680
- const toParsed = (value) => (typeof value === 'string' ? parseNumericText(value) : (value ?? null));
681
- /**
682
- * `ControlValueAccessor` for `number`/`integer` fields. The input is a text field (`inputmode="decimal"`), so the
683
- * decimal comma is accepted, and the model always holds a JSON number once the text is a complete number.
684
- * It never writes to the control while rendering or when the text is unchanged.
685
- */
686
- class NumericInputDirective {
687
- _el;
688
- _renderer;
689
- jsmDecimalSeparator = '.';
690
- _model = null;
691
- _onChange = () => undefined;
692
- _onTouched = () => undefined;
693
- constructor(_el, _renderer) {
694
- this._el = _el;
695
- this._renderer = _renderer;
696
- }
697
- _show(text) {
698
- this._renderer.setProperty(this._el.nativeElement, 'value', text);
699
- }
700
- ngOnChanges(changes) {
701
- if (changes['jsmDecimalSeparator'] && !changes['jsmDecimalSeparator'].firstChange && typeof this._model === 'number') {
702
- this._show(formatNumeric(this._model, this.jsmDecimalSeparator));
703
- }
704
- }
705
- writeValue(value) {
706
- this._model = value ?? null;
707
- // Keep the text being typed when it already means this number ("45," for 45): rewriting it would drop the separator.
708
- if (typeof value === 'number' && parseNumericText(this._el.nativeElement.value) === value) {
709
- return;
710
- }
711
- this._show(formatNumeric(value, this.jsmDecimalSeparator));
712
- }
713
- registerOnChange(fn) {
714
- this._onChange = fn;
715
- }
716
- registerOnTouched(fn) {
717
- this._onTouched = fn;
718
- }
719
- setDisabledState(isDisabled) {
720
- this._renderer.setProperty(this._el.nativeElement, 'disabled', isDisabled);
721
- }
722
- onInput() {
723
- const input = this._el.nativeElement;
724
- const raw = input.value;
725
- const cleaned = sanitizeNumericText(raw, this.jsmDecimalSeparator);
726
- if (cleaned !== raw) {
727
- const caret = Math.max(0, (input.selectionStart ?? raw.length) - (raw.length - cleaned.length));
728
- input.value = cleaned;
729
- input.setSelectionRange(caret, caret);
730
- }
731
- this._model = parseNumericText(cleaned);
732
- this._onChange(this._model);
733
- }
734
- onBlur() {
735
- const input = this._el.nativeElement;
736
- let text = input.value.trim();
737
- if (text.endsWith(',') || text.endsWith('.')) {
738
- text = text.slice(0, -1);
739
- }
740
- if (text === '-') {
741
- text = '';
742
- }
743
- const parsed = parseNumericText(text);
744
- this._show(typeof parsed === 'number' ? formatNumeric(parsed, this.jsmDecimalSeparator) : text);
745
- // Update the model only when the value really changed ("13" and 13 are the same value).
746
- if (parsed !== toParsed(this._model)) {
747
- this._model = parsed;
748
- this._onChange(parsed);
749
- }
750
- this._onTouched();
751
- }
752
- 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 });
753
- 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 });
503
+ return separator === "," ? message.replace(/(\d)\.(\d)/g, "$1,$2") : message;
754
504
  }
755
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: NumericInputDirective, decorators: [{
756
- type: Directive,
757
- args: [{
758
- selector: 'input[jsmNumeric]',
759
- standalone: true,
760
- host: { '(input)': 'onInput()', '(blur)': 'onBlur()' },
761
- providers: [{ provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => NumericInputDirective), multi: true }],
762
- }]
763
- }], ctorParameters: () => [{ type: i0.ElementRef }, { type: i0.Renderer2 }], propDecorators: { jsmDecimalSeparator: [{
764
- type: Input
765
- }] } });
766
-
505
+ const toParsed = (value) => typeof value === "string" ? parseNumericText(value) : value ?? null;
506
+ var NumericInputDirective = class NumericInputDirective {
507
+ _el;
508
+ _renderer;
509
+ jsmDecimalSeparator = ".";
510
+ _model = null;
511
+ _onChange = () => void 0;
512
+ _onTouched = () => void 0;
513
+ constructor(_el, _renderer) {
514
+ this._el = _el;
515
+ this._renderer = _renderer;
516
+ }
517
+ _show(text) {
518
+ this._renderer.setProperty(this._el.nativeElement, "value", text);
519
+ }
520
+ ngOnChanges(changes) {
521
+ if (changes["jsmDecimalSeparator"] && !changes["jsmDecimalSeparator"].firstChange && typeof this._model === "number") this._show(formatNumeric(this._model, this.jsmDecimalSeparator));
522
+ }
523
+ writeValue(value) {
524
+ this._model = value ?? null;
525
+ if (typeof value === "number" && parseNumericText(this._el.nativeElement.value) === value) return;
526
+ this._show(formatNumeric(value, this.jsmDecimalSeparator));
527
+ }
528
+ registerOnChange(fn) {
529
+ this._onChange = fn;
530
+ }
531
+ registerOnTouched(fn) {
532
+ this._onTouched = fn;
533
+ }
534
+ setDisabledState(isDisabled) {
535
+ this._renderer.setProperty(this._el.nativeElement, "disabled", isDisabled);
536
+ }
537
+ onInput() {
538
+ const input = this._el.nativeElement;
539
+ const raw = input.value;
540
+ const cleaned = sanitizeNumericText(raw, this.jsmDecimalSeparator);
541
+ if (cleaned !== raw) {
542
+ const caret = Math.max(0, (input.selectionStart ?? raw.length) - (raw.length - cleaned.length));
543
+ input.value = cleaned;
544
+ input.setSelectionRange(caret, caret);
545
+ }
546
+ this._model = parseNumericText(cleaned);
547
+ this._onChange(this._model);
548
+ }
549
+ onBlur() {
550
+ let text = this._el.nativeElement.value.trim();
551
+ if (text.endsWith(",") || text.endsWith(".")) text = text.slice(0, -1);
552
+ if (text === "-") text = "";
553
+ const parsed = parseNumericText(text);
554
+ this._show(typeof parsed === "number" ? formatNumeric(parsed, this.jsmDecimalSeparator) : text);
555
+ if (parsed !== toParsed(this._model)) {
556
+ this._model = parsed;
557
+ this._onChange(parsed);
558
+ }
559
+ this._onTouched();
560
+ }
561
+ static ɵfac = i0.ɵɵngDeclareFactory({
562
+ minVersion: "12.0.0",
563
+ version: "22.2.3",
564
+ ngImport: i0,
565
+ type: NumericInputDirective,
566
+ deps: [{ token: i0.ElementRef }, { token: i0.Renderer2 }],
567
+ target: i0.ɵɵFactoryTarget.Directive
568
+ });
569
+ static ɵdir = i0.ɵɵngDeclareDirective({
570
+ minVersion: "14.0.0",
571
+ version: "22.2.3",
572
+ type: NumericInputDirective,
573
+ isStandalone: true,
574
+ selector: "input[jsmNumeric]",
575
+ inputs: { jsmDecimalSeparator: "jsmDecimalSeparator" },
576
+ host: { listeners: {
577
+ "input": "onInput()",
578
+ "blur": "onBlur()"
579
+ } },
580
+ providers: [{
581
+ provide: NG_VALUE_ACCESSOR,
582
+ useExisting: forwardRef(() => NumericInputDirective),
583
+ multi: true
584
+ }],
585
+ usesOnChanges: true,
586
+ ngImport: i0
587
+ });
588
+ };
589
+ i0.ɵɵngDeclareClassMetadata({
590
+ minVersion: "12.0.0",
591
+ version: "22.2.3",
592
+ ngImport: i0,
593
+ type: NumericInputDirective,
594
+ decorators: [{
595
+ type: Directive,
596
+ args: [{
597
+ selector: "input[jsmNumeric]",
598
+ standalone: true,
599
+ host: {
600
+ "(input)": "onInput()",
601
+ "(blur)": "onBlur()"
602
+ },
603
+ providers: [{
604
+ provide: NG_VALUE_ACCESSOR,
605
+ useExisting: forwardRef(() => NumericInputDirective),
606
+ multi: true
607
+ }]
608
+ }]
609
+ }],
610
+ ctorParameters: () => [{ type: i0.ElementRef }, { type: i0.Renderer2 }],
611
+ propDecorators: { jsmDecimalSeparator: [{ type: Input }] }
612
+ });
767
613
  let nextNodeId = 0;
768
- class JsonSchemaNodeComponent {
769
- schemaService;
770
- validation;
771
- cdr;
772
- options;
773
- schema;
774
- control;
775
- label = '';
776
- required = false;
777
- path = '';
778
- errorsMap = new Map();
779
- parent;
780
- controlKey;
781
- allowAdditionalProperties = false;
782
- /** When true, errors are shown regardless of touched/dirty state (e.g. triggered by an external save button). */
783
- showErrors = false;
784
- controlReplaced = new EventEmitter();
785
- selectedOneOf = 0;
786
- selectedAnyOf = new Set();
787
- newPropertyKey = '';
788
- propertyError = '';
789
- /** Tracks whether this primitive control has been touched (for OnPush CD). */
790
- isTouched = false;
791
- /** Unique DOM id base of this node: links labels, hints and errors to the control (WCAG 1.3.1, 4.1.2). */
792
- nodeId = `jsm-n${++nextNodeId}`;
793
- valueSub;
794
- constructor(schemaService, validation, cdr, options) {
795
- this.schemaService = schemaService;
796
- this.validation = validation;
797
- this.cdr = cdr;
798
- this.options = options;
799
- }
800
- ngOnInit() {
801
- if (this.schema.anyOf?.length)
802
- this.selectedAnyOf.add(0);
803
- // markForCheck is enough for value changes — Angular will re-render in the next CD cycle
804
- // without recreating DOM nodes, so the focused input keeps its focus.
805
- this.valueSub = this.control.valueChanges.subscribe(() => this.cdr.markForCheck());
806
- }
807
- ngOnDestroy() {
808
- this.valueSub?.unsubscribe();
809
- }
810
- /** Called on blur of primitive inputs to trigger CD after Angular marks the control as touched. */
811
- onBlur() {
812
- this.isTouched = true;
813
- this.cdr.markForCheck();
814
- }
815
- isSimpleChild(key) {
816
- const s = this.schemaForKey(key);
817
- if (s.oneOf?.length || s.anyOf?.length || s.allOf?.length)
818
- return false;
819
- const t = this.schemaService.resolveType(s);
820
- return t !== 'object' && t !== 'array';
821
- }
822
- badgeClass(badge) {
823
- if (badge === 'Deprecated')
824
- return 'jsm-badge--deprecated';
825
- if (badge === 'Read-only' || badge === 'Nullable')
826
- return 'jsm-badge--readonly';
827
- if (badge === 'Write-only')
828
- return 'jsm-badge--writeonly';
829
- if (badge === 'Not constraint')
830
- return 'jsm-badge--not';
831
- return 'jsm-badge--nullable';
832
- }
833
- get nodeKind() {
834
- if (this.schema.oneOf?.length)
835
- return 'oneOf';
836
- if (this.schema.anyOf?.length)
837
- return 'anyOf';
838
- if (this.schema.allOf?.length)
839
- return 'allOf';
840
- const r = this.resolvedType;
841
- if (r === 'object')
842
- return 'object';
843
- if (r === 'array')
844
- return 'array';
845
- return 'primitive';
846
- }
847
- get resolvedType() {
848
- return this.schemaService.resolveType(this.effectiveSchema) ?? 'string';
849
- }
850
- get decimalSeparator() {
851
- return this.options?.decimalSeparator ?? '.';
852
- }
853
- /** Texts shown by the form (see `labels` of `jsm-json-schema-form`). */
854
- get l() {
855
- return this.options?.labels ?? DEFAULT_LABELS;
856
- }
857
- fmt(template, values = {}) {
858
- return formatLabel(template, values);
859
- }
860
- localize(message) {
861
- return localizeNumbers(message, this.decimalSeparator);
862
- }
863
- optionLabel(index) {
864
- return this.fmt(this.l.option, { n: index + 1 });
865
- }
866
- sectionLabel(index) {
867
- return this.fmt(this.l.section, { n: index + 1 });
868
- }
869
- /** DOM id of a dynamic key label (keys may contain characters that are not valid in ids). */
870
- keyId(key) {
871
- return `${this.nodeId}-key-${key.replace(/[^A-Za-z0-9_-]/g, (c) => `_${c.charCodeAt(0).toString(16)}`)}`;
872
- }
873
- /** True when the error list of this primitive is shown (same gate as the error styling). */
874
- get visibleError() {
875
- return this.allErrors.length > 0 && (this.control.dirty || this.isTouched || this.showErrors);
876
- }
877
- /** Ids of the hint, example, media type and error texts that describe this control. */
878
- get describedBy() {
879
- const ids = [];
880
- if (this.schema.description)
881
- ids.push(`${this.nodeId}-hint`);
882
- if (this.effectiveSchema.examples?.length)
883
- ids.push(`${this.nodeId}-example`);
884
- if (this.effectiveSchema.contentMediaType)
885
- ids.push(`${this.nodeId}-media`);
886
- if (this.visibleError)
887
- ids.push(`${this.nodeId}-error`);
888
- return ids.length ? ids.join(' ') : null;
889
- }
890
- selectOneOf(index) {
891
- this.selectedOneOf = index;
892
- this.onOneOfChange();
893
- }
894
- /** WAI-ARIA tabs keyboard support: arrows, Home and End move between the oneOf options. */
895
- onOneOfKeydown(event) {
896
- const count = this.schema.oneOf?.length ?? 0;
897
- if (!count)
898
- return;
899
- let next = this.selectedOneOf;
900
- switch (event.key) {
901
- case 'ArrowRight':
902
- case 'ArrowDown':
903
- next = (next + 1) % count;
904
- break;
905
- case 'ArrowLeft':
906
- case 'ArrowUp':
907
- next = (next - 1 + count) % count;
908
- break;
909
- case 'Home':
910
- next = 0;
911
- break;
912
- case 'End':
913
- next = count - 1;
914
- break;
915
- default: return;
916
- }
917
- event.preventDefault();
918
- this.selectOneOf(next);
919
- setTimeout(() => document.getElementById(`${this.nodeId}-tab-${next}`)?.focus());
920
- }
921
- /** `multipleOf` of this node's own schema, exposed as step metadata. */
922
- get numericStep() {
923
- return this.effectiveSchema.multipleOf ? String(this.effectiveSchema.multipleOf) : 'any';
924
- }
925
- get inputKind() {
926
- if (this.effectiveSchema.enum)
927
- return 'select';
928
- if (this.resolvedType === 'boolean')
929
- return 'checkbox';
930
- if (this.resolvedType === 'number' || this.resolvedType === 'integer')
931
- return 'number';
932
- if (this.effectiveSchema.format === 'textarea' || (this.effectiveSchema.maxLength ?? 0) > 200)
933
- return 'textarea';
934
- return 'text';
935
- }
936
- get inputType() {
937
- if (this.effectiveSchema.format === 'email')
938
- return 'email';
939
- if (this.effectiveSchema.format === 'date')
940
- return 'date';
941
- if (this.effectiveSchema.format === 'time')
942
- return 'time';
943
- if (this.effectiveSchema.format === 'date-time')
944
- return 'datetime-local';
945
- if (this.resolvedType === 'number' || this.resolvedType === 'integer')
946
- return 'number';
947
- if (this.effectiveSchema.format === 'password')
948
- return 'password';
949
- return 'text';
950
- }
951
- get controlAsGroup() { return this.control; }
952
- get controlAsFormControl() { return this.control; }
953
- get controlAsArray() { return this.control; }
954
- get arrayControls() { return this.controlAsArray.controls; }
955
- get objectKeys() { return Object.keys(this.controlAsGroup.controls); }
956
- get simpleKeys() { return this.objectKeys.filter(k => this.isSimpleChild(k)); }
957
- get complexKeys() { return this.objectKeys.filter(k => !this.isSimpleChild(k)); }
958
- get canAddProperty() {
959
- if (!this.allowAdditionalProperties)
960
- return false;
961
- return this.effectiveSchema.additionalProperties !== false || !!this.effectiveSchema.patternProperties;
962
- }
963
- get errorsForPath() { return this.errorsMap.get(this.path) ?? []; }
964
- /** Combined errors: Ajv errors from errorsMap + Angular validator errors on the control. */
965
- get allErrors() {
966
- const ajvErrors = this.errorsForPath;
967
- const ctrl = this.control;
968
- if (!ctrl.errors)
969
- return ajvErrors;
970
- const angularErrors = [];
971
- const errs = ctrl.errors;
972
- const l = this.l;
973
- if (errs['required'])
974
- angularErrors.push(l.errorRequired);
975
- if (errs['minlength'])
976
- angularErrors.push(this.fmt(l.errorMinLength, { n: errs['minlength'].requiredLength }));
977
- if (errs['maxlength'])
978
- angularErrors.push(this.fmt(l.errorMaxLength, { n: errs['maxlength'].requiredLength }));
979
- if (errs['min'])
980
- angularErrors.push(this.fmt(l.errorMin, { n: errs['min'].min }));
981
- if (errs['max'])
982
- angularErrors.push(this.fmt(l.errorMax, { n: errs['max'].max }));
983
- if (errs['email'])
984
- angularErrors.push(l.errorEmail);
985
- if (errs['pattern'])
986
- angularErrors.push(l.errorPattern);
987
- if (errs['integer'])
988
- angularErrors.push(l.errorInteger);
989
- if (errs['exclusiveMinimum'])
990
- angularErrors.push(l.errorExclusiveMin);
991
- if (errs['exclusiveMaximum'])
992
- angularErrors.push(l.errorExclusiveMax);
993
- if (errs['multipleOf'])
994
- angularErrors.push(l.errorMultipleOf);
995
- if (errs['minItems'])
996
- angularErrors.push(this.fmt(l.errorMinItems, { n: errs['minItems'].requiredLength ?? '' }));
997
- if (errs['maxItems'])
998
- angularErrors.push(this.fmt(l.errorMaxItems, { n: errs['maxItems'].requiredLength ?? '' }));
999
- if (errs['uniqueItems'])
1000
- angularErrors.push(l.errorUniqueItems);
1001
- // Merge: prefer Ajv messages, fall back to Angular messages for errors not covered by Ajv
1002
- const messages = ajvErrors.length > 0 ? ajvErrors : angularErrors;
1003
- return messages.map((message) => localizeNumbers(message, this.decimalSeparator));
1004
- }
1005
- get metaBadges() {
1006
- const b = [];
1007
- if (this.effectiveSchema.deprecated)
1008
- b.push('Deprecated');
1009
- if (this.effectiveSchema.readOnly)
1010
- b.push('Read-only');
1011
- if (this.effectiveSchema.writeOnly)
1012
- b.push('Write-only');
1013
- if (this.effectiveSchema.nullable)
1014
- b.push('Nullable');
1015
- if (this.effectiveSchema.not)
1016
- b.push('Not constraint');
1017
- return b;
1018
- }
1019
- get activeVariant() {
1020
- if (this.schema.oneOf?.length)
1021
- return this.schema.oneOf[this.selectedOneOf] ?? null;
1022
- if (this.schema.anyOf?.length) {
1023
- const sel = Array.from(this.selectedAnyOf).map(i => this.schema.anyOf[i]);
1024
- return sel.length ? this.mergeSchemas(sel) : this.schema.anyOf[0] ?? null;
1025
- }
1026
- if (this.schema.allOf?.length)
1027
- return this.mergeSchemas([this.schema, ...this.schema.allOf]);
1028
- return this.effectiveSchema;
1029
- }
1030
- /**
1031
- * Sections rendered for an allOf: the schema's own keywords (without the allOf itself, which would render
1032
- * this node again forever) when it declares any, followed by every allOf entry.
1033
- */
1034
- get mergedAllOf() {
1035
- const { allOf, ...own } = this.schema;
1036
- if (!allOf?.length)
1037
- return [this.schema];
1038
- const declaresOwn = Object.keys(own).some((key) => !['title', 'description', '$id', '$schema', '$comment'].includes(key));
1039
- return declaresOwn ? [own, ...allOf] : [...allOf];
1040
- }
1041
- isRequired(key) { return this.effectiveSchema.required?.includes(key) ?? false; }
1042
- schemaForKey(key) {
1043
- return this.effectiveSchema.properties?.[key] ?? this.schemaService.resolveDynamicSchema(this.effectiveSchema, key) ?? {};
1044
- }
1045
- isDynamicKey(key) { return !this.effectiveSchema.properties?.[key]; }
1046
- pathForChild(key) {
1047
- return this.path === '' ? `/${key}` : `${this.path}/${key}`;
1048
- }
1049
- addDynamicProperty() {
1050
- this.propertyError = '';
1051
- const key = this.newPropertyKey.trim();
1052
- if (!key) {
1053
- this.propertyError = this.l.propertyNameRequired;
1054
- return;
1055
- }
1056
- if (this.effectiveSchema.propertyNames) {
1057
- if (this.validation.validate(this.effectiveSchema.propertyNames, key).length) {
1058
- this.propertyError = this.l.propertyNameInvalid;
1059
- return;
1060
- }
1061
- }
1062
- if (this.controlAsGroup.contains(key)) {
1063
- this.propertyError = this.l.propertyExists;
1064
- return;
1065
- }
1066
- const s = this.schemaService.resolveDynamicSchema(this.effectiveSchema, key);
1067
- if (!s) {
1068
- this.propertyError = this.l.propertyNotAllowed;
1069
- return;
1070
- }
1071
- this.controlAsGroup.addControl(key, this.schemaService.buildControl(s, undefined, false));
1072
- this.newPropertyKey = '';
1073
- }
1074
- removeDynamicKey(key) { this.controlAsGroup.removeControl(key); }
1075
- schemaForIndex(index) {
1076
- const p = this.effectiveSchema.prefixItems ?? [];
1077
- if (p[index])
1078
- return p[index];
1079
- if (this.effectiveSchema.items && this.effectiveSchema.items !== true)
1080
- return this.effectiveSchema.items;
1081
- if (this.effectiveSchema.additionalItems && this.effectiveSchema.additionalItems !== true)
1082
- return this.effectiveSchema.additionalItems;
1083
- if (this.effectiveSchema.unevaluatedItems && this.effectiveSchema.unevaluatedItems !== true)
1084
- return this.effectiveSchema.unevaluatedItems;
1085
- return {};
1086
- }
1087
- get canAddArrayItem() {
1088
- return !(this.effectiveSchema.items === false || this.effectiveSchema.additionalItems === false || this.effectiveSchema.unevaluatedItems === false);
1089
- }
1090
- addArrayItem() {
1091
- if (!this.canAddArrayItem)
1092
- return;
1093
- const fa = this.controlAsArray;
1094
- fa.push(this.schemaService.buildControl(this.schemaForIndex(fa.length), undefined, false));
1095
- fa.markAsDirty();
1096
- }
1097
- removeArrayItem(index) {
1098
- this.controlAsArray.removeAt(index);
1099
- this.controlAsArray.markAsDirty();
1100
- }
1101
- onOneOfChange() {
1102
- if (!this.activeVariant)
1103
- return;
1104
- this.replaceControl(this.schemaService.buildControl(this.activeVariant, this.control.value, this.required));
1105
- }
1106
- toggleAnyOf(index) {
1107
- if (this.selectedAnyOf.has(index))
1108
- this.selectedAnyOf.delete(index);
1109
- else
1110
- this.selectedAnyOf.add(index);
1111
- const v = this.activeVariant;
1112
- if (v)
1113
- this.replaceControl(this.schemaService.buildControl(v, this.control.value, this.required));
1114
- }
1115
- replaceControl(control) {
1116
- if (this.parent) {
1117
- if (this.parent instanceof FormGroup && typeof this.controlKey === 'string')
1118
- this.parent.setControl(this.controlKey, control);
1119
- if (this.parent instanceof FormArray && typeof this.controlKey === 'number')
1120
- this.parent.setControl(this.controlKey, control);
1121
- this.control = control;
1122
- return;
1123
- }
1124
- this.control = control;
1125
- this.controlReplaced.emit(control);
1126
- }
1127
- childControl(key) { return this.controlAsGroup.get(key); }
1128
- get effectiveSchema() {
1129
- let s = this.schema;
1130
- if (s.allOf?.length)
1131
- s = this.mergeSchemas([s, ...s.allOf]);
1132
- s = this.applyConditional(s);
1133
- s = this.applyDependencies(s);
1134
- return s;
1135
- }
1136
- applyConditional(schema) {
1137
- if (!schema.if)
1138
- return schema;
1139
- const errors = this.validation.validate(schema.if, this.control.value);
1140
- const branch = errors.length === 0 ? schema.then : schema.else;
1141
- if (!branch)
1142
- return schema;
1143
- return this.mergeSchemas([schema, branch]);
1144
- }
1145
- applyDependencies(schema) {
1146
- const value = this.control.value;
1147
- if (!value || typeof value !== 'object' || Array.isArray(value))
1148
- return schema;
1149
- let merged = { ...schema };
1150
- const keys = Object.keys(value);
1151
- if (schema.dependentRequired) {
1152
- for (const k of keys) {
1153
- const r = schema.dependentRequired[k];
1154
- if (r?.length)
1155
- merged = this.mergeSchemas([merged, { required: r }]);
1156
- }
1157
- }
1158
- if (schema.dependentSchemas) {
1159
- for (const k of keys) {
1160
- const d = schema.dependentSchemas[k];
1161
- if (d)
1162
- merged = this.mergeSchemas([merged, d]);
1163
- }
1164
- }
1165
- if (schema.dependencies) {
1166
- for (const k of keys) {
1167
- const d = schema.dependencies[k];
1168
- if (Array.isArray(d))
1169
- merged = this.mergeSchemas([merged, { required: d }]);
1170
- else if (d)
1171
- merged = this.mergeSchemas([merged, d]);
1172
- }
1173
- }
1174
- return merged;
1175
- }
1176
- mergeSchemas(schemas) {
1177
- return schemas.reduce((acc, s) => ({
1178
- ...acc, ...s,
1179
- properties: { ...(acc.properties ?? {}), ...(s.properties ?? {}) },
1180
- patternProperties: { ...(acc.patternProperties ?? {}), ...(s.patternProperties ?? {}) },
1181
- required: Array.from(new Set([...(acc.required ?? []), ...(s.required ?? [])])),
1182
- ...(acc.type && s.type ? {
1183
- type: Array.from(new Set([
1184
- ...(Array.isArray(acc.type) ? acc.type : [acc.type]),
1185
- ...(Array.isArray(s.type) ? s.type : [s.type]),
1186
- ]))
1187
- } : {}),
1188
- }), {});
1189
- }
1190
- 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 });
1191
- 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: `
1192
- <ng-container [ngSwitch]="nodeKind">
1193
-
1194
- <!-- ═══ oneOf ═══ -->
1195
- <div *ngSwitchCase="'oneOf'" class="jsm-combinator">
1196
- <div class="jsm-combinator-header">
1197
- <div>
1198
- <p class="jsm-combinator-title">{{ schema.title || label }}</p>
1199
- <p *ngIf="schema.description" class="jsm-combinator-hint">{{ schema.description }}</p>
1200
- </div>
1201
- <span class="jsm-combinator-badge jsm-badge--oneof">oneOf</span>
614
+ var JsonSchemaNodeComponent = class JsonSchemaNodeComponent {
615
+ schemaService;
616
+ validation;
617
+ cdr;
618
+ options;
619
+ schema;
620
+ control;
621
+ label = "";
622
+ required = false;
623
+ path = "";
624
+ errorsMap = /* @__PURE__ */ new Map();
625
+ parent;
626
+ controlKey;
627
+ allowAdditionalProperties = false;
628
+ showErrors = false;
629
+ controlReplaced = new EventEmitter();
630
+ selectedOneOf = 0;
631
+ selectedAnyOf = /* @__PURE__ */ new Set();
632
+ newPropertyKey = "";
633
+ propertyError = "";
634
+ isTouched = false;
635
+ nodeId = `jsm-n${++nextNodeId}`;
636
+ valueSub;
637
+ constructor(schemaService, validation, cdr, options) {
638
+ this.schemaService = schemaService;
639
+ this.validation = validation;
640
+ this.cdr = cdr;
641
+ this.options = options;
642
+ }
643
+ ngOnInit() {
644
+ if (this.schema.anyOf?.length) this.selectedAnyOf.add(0);
645
+ this.valueSub = this.control.valueChanges.subscribe(() => this.cdr.markForCheck());
646
+ }
647
+ ngOnDestroy() {
648
+ this.valueSub?.unsubscribe();
649
+ }
650
+ onBlur() {
651
+ this.isTouched = true;
652
+ this.cdr.markForCheck();
653
+ }
654
+ isSimpleChild(key) {
655
+ const s = this.schemaForKey(key);
656
+ if (s.oneOf?.length || s.anyOf?.length || s.allOf?.length) return false;
657
+ const t = this.schemaService.resolveType(s);
658
+ return t !== "object" && t !== "array";
659
+ }
660
+ badgeClass(badge) {
661
+ if (badge === "Deprecated") return "jsm-badge--deprecated";
662
+ if (badge === "Read-only" || badge === "Nullable") return "jsm-badge--readonly";
663
+ if (badge === "Write-only") return "jsm-badge--writeonly";
664
+ if (badge === "Not constraint") return "jsm-badge--not";
665
+ return "jsm-badge--nullable";
666
+ }
667
+ get nodeKind() {
668
+ if (this.schema.oneOf?.length) return "oneOf";
669
+ if (this.schema.anyOf?.length) return "anyOf";
670
+ if (this.schema.allOf?.length) return "allOf";
671
+ const r = this.resolvedType;
672
+ if (r === "object") return "object";
673
+ if (r === "array") return "array";
674
+ return "primitive";
675
+ }
676
+ get resolvedType() {
677
+ return this.schemaService.resolveType(this.effectiveSchema) ?? "string";
678
+ }
679
+ get decimalSeparator() {
680
+ return this.options?.decimalSeparator ?? ".";
681
+ }
682
+ get l() {
683
+ return this.options?.labels ?? DEFAULT_LABELS;
684
+ }
685
+ fmt(template, values = {}) {
686
+ return formatLabel(template, values);
687
+ }
688
+ localize(message) {
689
+ return localizeNumbers(message, this.decimalSeparator);
690
+ }
691
+ optionLabel(index) {
692
+ return this.fmt(this.l.option, { n: index + 1 });
693
+ }
694
+ sectionLabel(index) {
695
+ return this.fmt(this.l.section, { n: index + 1 });
696
+ }
697
+ keyId(key) {
698
+ return `${this.nodeId}-key-${key.replace(/[^A-Za-z0-9_-]/g, (c) => `_${c.charCodeAt(0).toString(16)}`)}`;
699
+ }
700
+ get visibleError() {
701
+ return this.allErrors.length > 0 && (this.control.dirty || this.isTouched || this.showErrors);
702
+ }
703
+ get describedBy() {
704
+ const ids = [];
705
+ if (this.schema.description) ids.push(`${this.nodeId}-hint`);
706
+ if (this.effectiveSchema.examples?.length) ids.push(`${this.nodeId}-example`);
707
+ if (this.effectiveSchema.contentMediaType) ids.push(`${this.nodeId}-media`);
708
+ if (this.visibleError) ids.push(`${this.nodeId}-error`);
709
+ return ids.length ? ids.join(" ") : null;
710
+ }
711
+ selectOneOf(index) {
712
+ this.selectedOneOf = index;
713
+ this.onOneOfChange();
714
+ }
715
+ onOneOfKeydown(event) {
716
+ const count = this.schema.oneOf?.length ?? 0;
717
+ if (!count) return;
718
+ let next = this.selectedOneOf;
719
+ switch (event.key) {
720
+ case "ArrowRight":
721
+ case "ArrowDown":
722
+ next = (next + 1) % count;
723
+ break;
724
+ case "ArrowLeft":
725
+ case "ArrowUp":
726
+ next = (next - 1 + count) % count;
727
+ break;
728
+ case "Home":
729
+ next = 0;
730
+ break;
731
+ case "End":
732
+ next = count - 1;
733
+ break;
734
+ default: return;
735
+ }
736
+ event.preventDefault();
737
+ this.selectOneOf(next);
738
+ setTimeout(() => document.getElementById(`${this.nodeId}-tab-${next}`)?.focus());
739
+ }
740
+ get numericStep() {
741
+ return this.effectiveSchema.multipleOf ? String(this.effectiveSchema.multipleOf) : "any";
742
+ }
743
+ get inputKind() {
744
+ if (this.effectiveSchema.enum) return "select";
745
+ if (this.resolvedType === "boolean") return "checkbox";
746
+ if (this.resolvedType === "number" || this.resolvedType === "integer") return "number";
747
+ if (this.effectiveSchema.format === "textarea" || (this.effectiveSchema.maxLength ?? 0) > 200) return "textarea";
748
+ return "text";
749
+ }
750
+ get inputType() {
751
+ if (this.effectiveSchema.format === "email") return "email";
752
+ if (this.effectiveSchema.format === "date") return "date";
753
+ if (this.effectiveSchema.format === "time") return "time";
754
+ if (this.effectiveSchema.format === "date-time") return "datetime-local";
755
+ if (this.resolvedType === "number" || this.resolvedType === "integer") return "number";
756
+ if (this.effectiveSchema.format === "password") return "password";
757
+ return "text";
758
+ }
759
+ get controlAsGroup() {
760
+ return this.control;
761
+ }
762
+ get controlAsFormControl() {
763
+ return this.control;
764
+ }
765
+ get controlAsArray() {
766
+ return this.control;
767
+ }
768
+ get arrayControls() {
769
+ return this.controlAsArray.controls;
770
+ }
771
+ get objectKeys() {
772
+ return Object.keys(this.controlAsGroup.controls);
773
+ }
774
+ get simpleKeys() {
775
+ return this.objectKeys.filter((k) => this.isSimpleChild(k));
776
+ }
777
+ get complexKeys() {
778
+ return this.objectKeys.filter((k) => !this.isSimpleChild(k));
779
+ }
780
+ get canAddProperty() {
781
+ if (!this.allowAdditionalProperties) return false;
782
+ return this.effectiveSchema.additionalProperties !== false || !!this.effectiveSchema.patternProperties;
783
+ }
784
+ get errorsForPath() {
785
+ return this.errorsMap.get(this.path) ?? [];
786
+ }
787
+ get allErrors() {
788
+ const ajvErrors = this.errorsForPath;
789
+ const ctrl = this.control;
790
+ if (!ctrl.errors) return ajvErrors;
791
+ const angularErrors = [];
792
+ const errs = ctrl.errors;
793
+ const l = this.l;
794
+ if (errs["required"]) angularErrors.push(l.errorRequired);
795
+ if (errs["minlength"]) angularErrors.push(this.fmt(l.errorMinLength, { n: errs["minlength"].requiredLength }));
796
+ if (errs["maxlength"]) angularErrors.push(this.fmt(l.errorMaxLength, { n: errs["maxlength"].requiredLength }));
797
+ if (errs["min"]) angularErrors.push(this.fmt(l.errorMin, { n: errs["min"].min }));
798
+ if (errs["max"]) angularErrors.push(this.fmt(l.errorMax, { n: errs["max"].max }));
799
+ if (errs["email"]) angularErrors.push(l.errorEmail);
800
+ if (errs["pattern"]) angularErrors.push(l.errorPattern);
801
+ if (errs["integer"]) angularErrors.push(l.errorInteger);
802
+ if (errs["exclusiveMinimum"]) angularErrors.push(l.errorExclusiveMin);
803
+ if (errs["exclusiveMaximum"]) angularErrors.push(l.errorExclusiveMax);
804
+ if (errs["multipleOf"]) angularErrors.push(l.errorMultipleOf);
805
+ if (errs["minItems"]) angularErrors.push(this.fmt(l.errorMinItems, { n: errs["minItems"].requiredLength ?? "" }));
806
+ if (errs["maxItems"]) angularErrors.push(this.fmt(l.errorMaxItems, { n: errs["maxItems"].requiredLength ?? "" }));
807
+ if (errs["uniqueItems"]) angularErrors.push(l.errorUniqueItems);
808
+ return (ajvErrors.length > 0 ? ajvErrors : angularErrors).map((message) => localizeNumbers(message, this.decimalSeparator));
809
+ }
810
+ get metaBadges() {
811
+ const b = [];
812
+ if (this.effectiveSchema.deprecated) b.push("Deprecated");
813
+ if (this.effectiveSchema.readOnly) b.push("Read-only");
814
+ if (this.effectiveSchema.writeOnly) b.push("Write-only");
815
+ if (this.effectiveSchema.nullable) b.push("Nullable");
816
+ if (this.effectiveSchema.not) b.push("Not constraint");
817
+ return b;
818
+ }
819
+ get activeVariant() {
820
+ if (this.schema.oneOf?.length) return this.schema.oneOf[this.selectedOneOf] ?? null;
821
+ if (this.schema.anyOf?.length) {
822
+ const sel = Array.from(this.selectedAnyOf).map((i) => this.schema.anyOf[i]);
823
+ return sel.length ? this.mergeSchemas(sel) : this.schema.anyOf[0] ?? null;
824
+ }
825
+ if (this.schema.allOf?.length) return this.mergeSchemas([this.schema, ...this.schema.allOf]);
826
+ return this.effectiveSchema;
827
+ }
828
+ get mergedAllOf() {
829
+ const { allOf, ...own } = this.schema;
830
+ if (!allOf?.length) return [this.schema];
831
+ return Object.keys(own).some((key) => ![
832
+ "title",
833
+ "description",
834
+ "$id",
835
+ "$schema",
836
+ "$comment"
837
+ ].includes(key)) ? [own, ...allOf] : [...allOf];
838
+ }
839
+ isRequired(key) {
840
+ return this.effectiveSchema.required?.includes(key) ?? false;
841
+ }
842
+ schemaForKey(key) {
843
+ return this.effectiveSchema.properties?.[key] ?? this.schemaService.resolveDynamicSchema(this.effectiveSchema, key) ?? {};
844
+ }
845
+ isDynamicKey(key) {
846
+ return !this.effectiveSchema.properties?.[key];
847
+ }
848
+ pathForChild(key) {
849
+ return this.path === "" ? `/${key}` : `${this.path}/${key}`;
850
+ }
851
+ addDynamicProperty() {
852
+ this.propertyError = "";
853
+ const key = this.newPropertyKey.trim();
854
+ if (!key) {
855
+ this.propertyError = this.l.propertyNameRequired;
856
+ return;
857
+ }
858
+ if (this.effectiveSchema.propertyNames) {
859
+ if (this.validation.validate(this.effectiveSchema.propertyNames, key).length) {
860
+ this.propertyError = this.l.propertyNameInvalid;
861
+ return;
862
+ }
863
+ }
864
+ if (this.controlAsGroup.contains(key)) {
865
+ this.propertyError = this.l.propertyExists;
866
+ return;
867
+ }
868
+ const s = this.schemaService.resolveDynamicSchema(this.effectiveSchema, key);
869
+ if (!s) {
870
+ this.propertyError = this.l.propertyNotAllowed;
871
+ return;
872
+ }
873
+ this.controlAsGroup.addControl(key, this.schemaService.buildControl(s, void 0, false));
874
+ this.newPropertyKey = "";
875
+ }
876
+ removeDynamicKey(key) {
877
+ this.controlAsGroup.removeControl(key);
878
+ }
879
+ schemaForIndex(index) {
880
+ const p = this.effectiveSchema.prefixItems ?? [];
881
+ if (p[index]) return p[index];
882
+ if (this.effectiveSchema.items && this.effectiveSchema.items !== true) return this.effectiveSchema.items;
883
+ if (this.effectiveSchema.additionalItems && this.effectiveSchema.additionalItems !== true) return this.effectiveSchema.additionalItems;
884
+ if (this.effectiveSchema.unevaluatedItems && this.effectiveSchema.unevaluatedItems !== true) return this.effectiveSchema.unevaluatedItems;
885
+ return {};
886
+ }
887
+ get canAddArrayItem() {
888
+ return !(this.effectiveSchema.items === false || this.effectiveSchema.additionalItems === false || this.effectiveSchema.unevaluatedItems === false);
889
+ }
890
+ addArrayItem() {
891
+ if (!this.canAddArrayItem) return;
892
+ const fa = this.controlAsArray;
893
+ fa.push(this.schemaService.buildControl(this.schemaForIndex(fa.length), void 0, false));
894
+ fa.markAsDirty();
895
+ }
896
+ removeArrayItem(index) {
897
+ this.controlAsArray.removeAt(index);
898
+ this.controlAsArray.markAsDirty();
899
+ }
900
+ onOneOfChange() {
901
+ if (!this.activeVariant) return;
902
+ this.replaceControl(this.schemaService.buildControl(this.activeVariant, this.control.value, this.required));
903
+ }
904
+ toggleAnyOf(index) {
905
+ if (this.selectedAnyOf.has(index)) this.selectedAnyOf.delete(index);
906
+ else this.selectedAnyOf.add(index);
907
+ const v = this.activeVariant;
908
+ if (v) this.replaceControl(this.schemaService.buildControl(v, this.control.value, this.required));
909
+ }
910
+ replaceControl(control) {
911
+ if (this.parent) {
912
+ if (this.parent instanceof FormGroup && typeof this.controlKey === "string") this.parent.setControl(this.controlKey, control);
913
+ if (this.parent instanceof FormArray && typeof this.controlKey === "number") this.parent.setControl(this.controlKey, control);
914
+ this.control = control;
915
+ return;
916
+ }
917
+ this.control = control;
918
+ this.controlReplaced.emit(control);
919
+ }
920
+ childControl(key) {
921
+ return this.controlAsGroup.get(key);
922
+ }
923
+ get effectiveSchema() {
924
+ let s = this.schema;
925
+ if (s.allOf?.length) s = this.mergeSchemas([s, ...s.allOf]);
926
+ s = this.applyConditional(s);
927
+ s = this.applyDependencies(s);
928
+ return s;
929
+ }
930
+ applyConditional(schema) {
931
+ if (!schema.if) return schema;
932
+ const branch = this.validation.validate(schema.if, this.control.value).length === 0 ? schema.then : schema.else;
933
+ if (!branch) return schema;
934
+ return this.mergeSchemas([schema, branch]);
935
+ }
936
+ applyDependencies(schema) {
937
+ const value = this.control.value;
938
+ if (!value || typeof value !== "object" || Array.isArray(value)) return schema;
939
+ let merged = { ...schema };
940
+ const keys = Object.keys(value);
941
+ if (schema.dependentRequired) for (const k of keys) {
942
+ const r = schema.dependentRequired[k];
943
+ if (r?.length) merged = this.mergeSchemas([merged, { required: r }]);
944
+ }
945
+ if (schema.dependentSchemas) for (const k of keys) {
946
+ const d = schema.dependentSchemas[k];
947
+ if (d) merged = this.mergeSchemas([merged, d]);
948
+ }
949
+ if (schema.dependencies) for (const k of keys) {
950
+ const d = schema.dependencies[k];
951
+ if (Array.isArray(d)) merged = this.mergeSchemas([merged, { required: d }]);
952
+ else if (d) merged = this.mergeSchemas([merged, d]);
953
+ }
954
+ return merged;
955
+ }
956
+ mergeSchemas(schemas) {
957
+ return schemas.reduce((acc, s) => ({
958
+ ...acc,
959
+ ...s,
960
+ properties: {
961
+ ...acc.properties ?? {},
962
+ ...s.properties ?? {}
963
+ },
964
+ patternProperties: {
965
+ ...acc.patternProperties ?? {},
966
+ ...s.patternProperties ?? {}
967
+ },
968
+ required: Array.from(/* @__PURE__ */ new Set([...acc.required ?? [], ...s.required ?? []])),
969
+ ...acc.type && s.type ? { type: Array.from(/* @__PURE__ */ new Set([...Array.isArray(acc.type) ? acc.type : [acc.type], ...Array.isArray(s.type) ? s.type : [s.type]])) } : {}
970
+ }), {});
971
+ }
972
+ static ɵfac = i0.ɵɵngDeclareFactory({
973
+ minVersion: "12.0.0",
974
+ version: "22.2.3",
975
+ ngImport: i0,
976
+ type: JsonSchemaNodeComponent,
977
+ deps: [
978
+ { token: JsonSchemaFormService },
979
+ { token: JsonSchemaValidationService },
980
+ { token: i0.ChangeDetectorRef },
981
+ {
982
+ token: JsonSchemaFormOptions,
983
+ optional: true
984
+ }
985
+ ],
986
+ target: i0.ɵɵFactoryTarget.Component
987
+ });
988
+ static ɵcmp = i0.ɵɵngDeclareComponent({
989
+ minVersion: "17.0.0",
990
+ version: "22.2.3",
991
+ type: JsonSchemaNodeComponent,
992
+ isStandalone: true,
993
+ selector: "jsm-schema-node",
994
+ inputs: {
995
+ schema: "schema",
996
+ control: "control",
997
+ label: "label",
998
+ required: "required",
999
+ path: "path",
1000
+ errorsMap: "errorsMap",
1001
+ parent: "parent",
1002
+ controlKey: "controlKey",
1003
+ allowAdditionalProperties: "allowAdditionalProperties",
1004
+ showErrors: "showErrors"
1005
+ },
1006
+ outputs: { controlReplaced: "controlReplaced" },
1007
+ ngImport: i0,
1008
+ template: `
1009
+ @switch (nodeKind) {
1010
+ <!-- ═══ oneOf ═══ -->
1011
+ @case ('oneOf') {
1012
+ <div class="jsm-combinator">
1013
+ <div class="jsm-combinator-header">
1014
+ <div>
1015
+ <p class="jsm-combinator-title">{{ schema.title || label }}</p>
1016
+ @if (schema.description) {
1017
+ <p class="jsm-combinator-hint">{{ schema.description }}</p>
1018
+ }
1202
1019
  </div>
1203
- <div class="jsm-oneof-tabs" role="tablist" [attr.aria-label]="schema.title || label || null" (keydown)="onOneOfKeydown($event)">
1204
- <button *ngFor="let option of schema.oneOf; let i = index" type="button" role="tab"
1020
+ <span class="jsm-combinator-badge jsm-badge--oneof">oneOf</span>
1021
+ </div>
1022
+ <div class="jsm-oneof-tabs" role="tablist" [attr.aria-label]="schema.title || label || null" (keydown)="onOneOfKeydown($event)">
1023
+ @for (option of schema.oneOf; track option; let i = $index) {
1024
+ <button type="button" role="tab"
1205
1025
  class="jsm-tab" [class.jsm-tab--active]="selectedOneOf === i"
1206
1026
  [id]="nodeId + '-tab-' + i" [attr.aria-selected]="selectedOneOf === i"
1207
1027
  [attr.aria-controls]="nodeId + '-panel'" [attr.tabindex]="selectedOneOf === i ? 0 : -1"
1208
1028
  (click)="selectOneOf(i)">
1209
1029
  {{ option.title || optionLabel(i) }}
1210
1030
  </button>
1211
- </div>
1212
- <div class="jsm-combinator-body" *ngIf="activeVariant" role="tabpanel" [id]="nodeId + '-panel'"
1031
+ }
1032
+ </div>
1033
+ @if (activeVariant) {
1034
+ <div class="jsm-combinator-body" role="tabpanel" [id]="nodeId + '-panel'"
1213
1035
  [attr.aria-labelledby]="nodeId + '-tab-' + selectedOneOf">
1214
1036
  <jsm-schema-node [schema]="activeVariant" [control]="control" [parent]="parent" [controlKey]="controlKey"
1215
1037
  [path]="path" [errorsMap]="errorsMap" [label]="label" [required]="required"
1216
1038
  [allowAdditionalProperties]="allowAdditionalProperties" [showErrors]="showErrors"
1217
- (controlReplaced)="controlReplaced.emit($event)"></jsm-schema-node>
1039
+ (controlReplaced)="controlReplaced.emit($event)"></jsm-schema-node>
1218
1040
  </div>
1219
- </div>
1220
-
1221
- <!-- ═══ anyOf ═══ -->
1222
- <div *ngSwitchCase="'anyOf'" class="jsm-combinator">
1223
- <div class="jsm-combinator-header">
1224
- <div>
1225
- <p class="jsm-combinator-title">{{ schema.title || label }}</p>
1226
- <p *ngIf="schema.description" class="jsm-combinator-hint">{{ schema.description }}</p>
1227
- </div>
1228
- <span class="jsm-combinator-badge jsm-badge--anyof">anyOf</span>
1041
+ }
1042
+ </div>
1043
+ }
1044
+ <!-- ═══ anyOf ═══ -->
1045
+ @case ('anyOf') {
1046
+ <div class="jsm-combinator">
1047
+ <div class="jsm-combinator-header">
1048
+ <div>
1049
+ <p class="jsm-combinator-title">{{ schema.title || label }}</p>
1050
+ @if (schema.description) {
1051
+ <p class="jsm-combinator-hint">{{ schema.description }}</p>
1052
+ }
1229
1053
  </div>
1230
- <div class="jsm-anyof-options" role="group" [attr.aria-label]="schema.title || label || null">
1231
- <label *ngFor="let option of schema.anyOf; let i = index"
1054
+ <span class="jsm-combinator-badge jsm-badge--anyof">anyOf</span>
1055
+ </div>
1056
+ <div class="jsm-anyof-options" role="group" [attr.aria-label]="schema.title || label || null">
1057
+ @for (option of schema.anyOf; track option; let i = $index) {
1058
+ <label
1232
1059
  class="jsm-anyof-option" [class.jsm-anyof-option--active]="selectedAnyOf.has(i)">
1233
1060
  <input type="checkbox" class="sr-only" [checked]="selectedAnyOf.has(i)" (change)="toggleAnyOf(i)" />
1234
1061
  {{ option.title || optionLabel(i) }}
1235
1062
  </label>
1236
- </div>
1237
- <div class="jsm-combinator-body" *ngIf="activeVariant">
1063
+ }
1064
+ </div>
1065
+ @if (activeVariant) {
1066
+ <div class="jsm-combinator-body">
1238
1067
  <jsm-schema-node [schema]="activeVariant" [control]="control" [parent]="parent" [controlKey]="controlKey"
1239
1068
  [path]="path" [errorsMap]="errorsMap" [label]="label" [required]="required"
1240
1069
  [allowAdditionalProperties]="allowAdditionalProperties" [showErrors]="showErrors"
1241
- (controlReplaced)="controlReplaced.emit($event)"></jsm-schema-node>
1070
+ (controlReplaced)="controlReplaced.emit($event)"></jsm-schema-node>
1242
1071
  </div>
1243
- </div>
1244
-
1245
- <!-- ═══ allOf ═══ -->
1246
- <div *ngSwitchCase="'allOf'" class="jsm-combinator">
1247
- <div class="jsm-combinator-header">
1248
- <div>
1249
- <p class="jsm-combinator-title">{{ schema.title || label }}</p>
1250
- <p *ngIf="schema.description" class="jsm-combinator-hint">{{ schema.description }}</p>
1251
- </div>
1252
- <span class="jsm-combinator-badge jsm-badge--allof">allOf</span>
1072
+ }
1073
+ </div>
1074
+ }
1075
+ <!-- ═══ allOf ═══ -->
1076
+ @case ('allOf') {
1077
+ <div class="jsm-combinator">
1078
+ <div class="jsm-combinator-header">
1079
+ <div>
1080
+ <p class="jsm-combinator-title">{{ schema.title || label }}</p>
1081
+ @if (schema.description) {
1082
+ <p class="jsm-combinator-hint">{{ schema.description }}</p>
1083
+ }
1253
1084
  </div>
1254
- <div class="jsm-combinator-body">
1255
- <div *ngFor="let subSchema of mergedAllOf; let i = index" class="jsm-allof-section">
1085
+ <span class="jsm-combinator-badge jsm-badge--allof">allOf</span>
1086
+ </div>
1087
+ <div class="jsm-combinator-body">
1088
+ @for (subSchema of mergedAllOf; track subSchema; let i = $index) {
1089
+ <div class="jsm-allof-section">
1256
1090
  <jsm-schema-node [schema]="subSchema" [control]="control" [parent]="parent" [controlKey]="controlKey"
1257
1091
  [path]="path" [errorsMap]="errorsMap" [label]="subSchema.title || sectionLabel(i)" [required]="required"
1258
1092
  [allowAdditionalProperties]="allowAdditionalProperties" [showErrors]="showErrors"
1259
- (controlReplaced)="controlReplaced.emit($event)"></jsm-schema-node>
1093
+ (controlReplaced)="controlReplaced.emit($event)"></jsm-schema-node>
1260
1094
  </div>
1261
- </div>
1095
+ }
1262
1096
  </div>
1263
-
1264
- <!-- ═══ object ═══ -->
1265
- <fieldset *ngSwitchCase="'object'" class="jsm-fieldset">
1266
- <legend class="jsm-legend">{{ schema.title || label }}</legend>
1267
- <p *ngIf="schema.description" class="jsm-field-hint" style="margin-bottom:.75rem;margin-top:.25rem;">{{ schema.description }}</p>
1268
-
1269
- <div *ngIf="metaBadges.length" class="jsm-badge-row" style="margin-bottom:.75rem;">
1270
- <span *ngFor="let badge of metaBadges" class="jsm-badge" [ngClass]="badgeClass(badge)">{{ badge }}</span>
1097
+ </div>
1098
+ }
1099
+ <!-- ═══ object ═══ -->
1100
+ @case ('object') {
1101
+ <fieldset class="jsm-fieldset">
1102
+ <legend class="jsm-legend">{{ schema.title || label }}</legend>
1103
+ @if (schema.description) {
1104
+ <p class="jsm-field-hint" style="margin-bottom:.75rem;margin-top:.25rem;">{{ schema.description }}</p>
1105
+ }
1106
+ @if (metaBadges.length) {
1107
+ <div class="jsm-badge-row" style="margin-bottom:.75rem;">
1108
+ @for (badge of metaBadges; track badge) {
1109
+ <span class="jsm-badge" [ngClass]="badgeClass(badge)">{{ badge }}</span>
1110
+ }
1271
1111
  </div>
1272
-
1273
- <div class="jsm-obj-grid" [formGroup]="controlAsGroup">
1274
- <ng-container *ngFor="let key of simpleKeys">
1275
- <div style="display:flex;flex-direction:column;gap:.25rem;">
1276
- <div *ngIf="isDynamicKey(key)" class="jsm-dynamic-key-header">
1112
+ }
1113
+ <div class="jsm-obj-grid" [formGroup]="controlAsGroup">
1114
+ @for (key of simpleKeys; track key) {
1115
+ <div style="display:flex;flex-direction:column;gap:.25rem;">
1116
+ @if (isDynamicKey(key)) {
1117
+ <div class="jsm-dynamic-key-header">
1277
1118
  <span class="jsm-dynamic-key-label" [id]="keyId(key)">{{ key }}</span>
1278
1119
  <button type="button" class="jsm-btn-danger" [id]="keyId(key) + '-remove'"
1279
- [attr.aria-labelledby]="keyId(key) + '-remove ' + keyId(key)" (click)="removeDynamicKey(key)">{{ l.remove }}</button>
1120
+ [attr.aria-labelledby]="keyId(key) + '-remove ' + keyId(key)" (click)="removeDynamicKey(key)">{{ l.remove }}</button>
1280
1121
  </div>
1281
- <jsm-schema-node [schema]="schemaForKey(key)" [control]="childControl(key)" [parent]="controlAsGroup"
1282
- [controlKey]="key" [path]="pathForChild(key)" [errorsMap]="errorsMap" [label]="key"
1283
- [required]="isRequired(key)" [allowAdditionalProperties]="allowAdditionalProperties" [showErrors]="showErrors"
1284
- (controlReplaced)="controlReplaced.emit($event)"></jsm-schema-node>
1285
- </div>
1286
- </ng-container>
1287
- </div>
1288
-
1289
- <div class="jsm-obj-complex" *ngIf="complexKeys.length > 0" [formGroup]="controlAsGroup">
1290
- <ng-container *ngFor="let key of complexKeys">
1122
+ }
1123
+ <jsm-schema-node [schema]="schemaForKey(key)" [control]="childControl(key)" [parent]="controlAsGroup"
1124
+ [controlKey]="key" [path]="pathForChild(key)" [errorsMap]="errorsMap" [label]="key"
1125
+ [required]="isRequired(key)" [allowAdditionalProperties]="allowAdditionalProperties" [showErrors]="showErrors"
1126
+ (controlReplaced)="controlReplaced.emit($event)"></jsm-schema-node>
1127
+ </div>
1128
+ }
1129
+ </div>
1130
+ @if (complexKeys.length > 0) {
1131
+ <div class="jsm-obj-complex" [formGroup]="controlAsGroup">
1132
+ @for (key of complexKeys; track key) {
1291
1133
  <div style="display:flex;flex-direction:column;gap:.25rem;">
1292
- <div *ngIf="isDynamicKey(key)" class="jsm-dynamic-key-header">
1293
- <span class="jsm-dynamic-key-label" [id]="keyId(key)">{{ key }}</span>
1294
- <button type="button" class="jsm-btn-danger" [id]="keyId(key) + '-remove'"
1134
+ @if (isDynamicKey(key)) {
1135
+ <div class="jsm-dynamic-key-header">
1136
+ <span class="jsm-dynamic-key-label" [id]="keyId(key)">{{ key }}</span>
1137
+ <button type="button" class="jsm-btn-danger" [id]="keyId(key) + '-remove'"
1295
1138
  [attr.aria-labelledby]="keyId(key) + '-remove ' + keyId(key)" (click)="removeDynamicKey(key)">{{ l.remove }}</button>
1296
- </div>
1139
+ </div>
1140
+ }
1297
1141
  <jsm-schema-node [schema]="schemaForKey(key)" [control]="childControl(key)" [parent]="controlAsGroup"
1298
1142
  [controlKey]="key" [path]="pathForChild(key)" [errorsMap]="errorsMap" [label]="key"
1299
1143
  [required]="isRequired(key)" [allowAdditionalProperties]="allowAdditionalProperties" [showErrors]="showErrors"
1300
- (controlReplaced)="controlReplaced.emit($event)"></jsm-schema-node>
1144
+ (controlReplaced)="controlReplaced.emit($event)"></jsm-schema-node>
1301
1145
  </div>
1302
- </ng-container>
1146
+ }
1303
1147
  </div>
1304
-
1305
- <div *ngIf="canAddProperty" class="jsm-add-prop" role="group" [attr.aria-labelledby]="nodeId + '-addprop'">
1148
+ }
1149
+ @if (canAddProperty) {
1150
+ <div class="jsm-add-prop" role="group" [attr.aria-labelledby]="nodeId + '-addprop'">
1306
1151
  <p class="jsm-add-prop-label" [id]="nodeId + '-addprop'">{{ l.addProperty }}</p>
1307
1152
  <div class="jsm-add-prop-row">
1308
1153
  <input class="jsm-input jsm-input--sm" style="flex:1;" [(ngModel)]="newPropertyKey" [ngModelOptions]="{standalone: true}"
@@ -1310,274 +1155,487 @@ class JsonSchemaNodeComponent {
1310
1155
  [attr.aria-invalid]="propertyError ? 'true' : null" [attr.aria-describedby]="propertyError ? nodeId + '-prop-error' : null" />
1311
1156
  <button type="button" class="jsm-btn-primary" (click)="addDynamicProperty()">{{ l.add }}</button>
1312
1157
  </div>
1313
- <p *ngIf="propertyError" class="jsm-error-msg" role="alert" [id]="nodeId + '-prop-error'">
1314
- <svg class="jsm-error-icon" viewBox="0 0 16 16" fill="none" aria-hidden="true"><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>
1315
- {{ propertyError }}
1316
- </p>
1317
- </div>
1318
-
1319
- <div *ngIf="errorsForPath.length" class="jsm-error-list" role="alert" style="margin-top:.75rem;">
1320
- <p *ngFor="let error of errorsForPath" class="jsm-error-msg">
1321
- <svg class="jsm-error-icon" viewBox="0 0 16 16" fill="none" aria-hidden="true"><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>
1322
- {{ localize(error) }}
1323
- </p>
1324
- </div>
1325
- </fieldset>
1326
-
1327
- <!-- ═══ array ═══ -->
1328
- <div *ngSwitchCase="'array'" class="jsm-array-block" role="group" [attr.aria-labelledby]="nodeId + '-title'">
1329
- <div class="jsm-array-header">
1330
- <div>
1331
- <p class="jsm-array-title" [id]="nodeId + '-title'">
1332
- {{ schema.title || label }}<span *ngIf="required" class="jsm-required" aria-hidden="true">*</span>
1158
+ @if (propertyError) {
1159
+ <p class="jsm-error-msg" role="alert" [id]="nodeId + '-prop-error'">
1160
+ <svg class="jsm-error-icon" viewBox="0 0 16 16" fill="none" aria-hidden="true"><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>
1161
+ {{ propertyError }}
1333
1162
  </p>
1334
- <p *ngIf="schema.description" class="jsm-field-hint">{{ schema.description }}</p>
1335
- </div>
1336
- <button type="button" class="jsm-btn-primary" [id]="nodeId + '-add'"
1337
- [attr.aria-labelledby]="nodeId + '-add ' + nodeId + '-title'" (click)="addArrayItem()" [disabled]="!canAddArrayItem">
1338
- <svg style="width:.875rem;height:.875rem;flex-shrink:0;" viewBox="0 0 16 16" fill="none" aria-hidden="true"><path d="M8 3v10M3 8h10" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></svg>
1339
- {{ l.addItem }}
1340
- </button>
1341
- </div>
1342
-
1343
- <div *ngIf="effectiveSchema.contains" class="jsm-contains-hint">
1344
- {{ l.mustContain }}
1345
- <span *ngIf="effectiveSchema.minContains"> · {{ fmt(l.min, { n: effectiveSchema.minContains }) }}</span>
1346
- <span *ngIf="effectiveSchema.maxContains"> · {{ fmt(l.max, { n: effectiveSchema.maxContains }) }}</span>
1163
+ }
1347
1164
  </div>
1348
-
1349
- <div *ngIf="metaBadges.length" class="jsm-badge-row">
1350
- <span *ngFor="let badge of metaBadges" class="jsm-badge" [ngClass]="badgeClass(badge)">{{ badge }}</span>
1165
+ }
1166
+ @if (errorsForPath.length) {
1167
+ <div class="jsm-error-list" role="alert" style="margin-top:.75rem;">
1168
+ @for (error of errorsForPath; track error) {
1169
+ <p class="jsm-error-msg">
1170
+ <svg class="jsm-error-icon" viewBox="0 0 16 16" fill="none" aria-hidden="true"><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>
1171
+ {{ localize(error) }}
1172
+ </p>
1173
+ }
1351
1174
  </div>
1352
-
1353
- <div class="jsm-array-items" *ngIf="arrayControls.length > 0; else emptyArray">
1354
- <div *ngFor="let item of arrayControls; let i = index" class="jsm-array-item" role="group" [attr.aria-labelledby]="nodeId + '-item-' + i">
1175
+ }
1176
+ </fieldset>
1177
+ }
1178
+ <!-- ═══ array ═══ -->
1179
+ @case ('array') {
1180
+ <div class="jsm-array-block" role="group" [attr.aria-labelledby]="nodeId + '-title'">
1181
+ <div class="jsm-array-header">
1182
+ <div>
1183
+ <p class="jsm-array-title" [id]="nodeId + '-title'">
1184
+ {{ schema.title || label }}@if (required) {
1185
+ <span class="jsm-required" aria-hidden="true">*</span>
1186
+ }
1187
+ </p>
1188
+ @if (schema.description) {
1189
+ <p class="jsm-field-hint">{{ schema.description }}</p>
1190
+ }
1191
+ </div>
1192
+ <button type="button" class="jsm-btn-primary" [id]="nodeId + '-add'"
1193
+ [attr.aria-labelledby]="nodeId + '-add ' + nodeId + '-title'" (click)="addArrayItem()" [disabled]="!canAddArrayItem">
1194
+ <svg style="width:.875rem;height:.875rem;flex-shrink:0;" viewBox="0 0 16 16" fill="none" aria-hidden="true"><path d="M8 3v10M3 8h10" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></svg>
1195
+ {{ l.addItem }}
1196
+ </button>
1197
+ </div>
1198
+ @if (effectiveSchema.contains) {
1199
+ <div class="jsm-contains-hint">
1200
+ {{ l.mustContain }}
1201
+ @if (effectiveSchema.minContains) {
1202
+ <span> · {{ fmt(l.min, { n: effectiveSchema.minContains }) }}</span>
1203
+ }
1204
+ @if (effectiveSchema.maxContains) {
1205
+ <span> · {{ fmt(l.max, { n: effectiveSchema.maxContains }) }}</span>
1206
+ }
1207
+ </div>
1208
+ }
1209
+ @if (metaBadges.length) {
1210
+ <div class="jsm-badge-row">
1211
+ @for (badge of metaBadges; track badge) {
1212
+ <span class="jsm-badge" [ngClass]="badgeClass(badge)">{{ badge }}</span>
1213
+ }
1214
+ </div>
1215
+ }
1216
+ @if (arrayControls.length > 0) {
1217
+ <div class="jsm-array-items">
1218
+ @for (item of arrayControls; track item; let i = $index) {
1219
+ <div class="jsm-array-item" role="group" [attr.aria-labelledby]="nodeId + '-item-' + i">
1355
1220
  <div class="jsm-array-item-header">
1356
1221
  <span class="jsm-array-index" aria-hidden="true">{{ i + 1 }}</span>
1357
1222
  <span class="sr-only" [id]="nodeId + '-item-' + i">{{ fmt(l.arrayItem, { n: i + 1 }) }}</span>
1358
1223
  <button type="button" class="jsm-btn-danger" [id]="nodeId + '-remove-' + i"
1359
- [attr.aria-labelledby]="nodeId + '-remove-' + i + ' ' + nodeId + '-item-' + i" (click)="removeArrayItem(i)">{{ l.remove }}</button>
1224
+ [attr.aria-labelledby]="nodeId + '-remove-' + i + ' ' + nodeId + '-item-' + i" (click)="removeArrayItem(i)">{{ l.remove }}</button>
1360
1225
  </div>
1361
- <jsm-schema-node [schema]="schemaForIndex(i)" [control]="item" [parent]="controlAsArray"
1362
- [controlKey]="i" [path]="pathForChild(i)" [errorsMap]="errorsMap" [label]="label + ' item'"
1363
- [allowAdditionalProperties]="allowAdditionalProperties" [showErrors]="showErrors"
1364
- (controlReplaced)="controlReplaced.emit($event)"></jsm-schema-node>
1365
- </div>
1366
- </div>
1367
-
1368
- <ng-template #emptyArray>
1369
- <div class="jsm-empty-array">{{ l.noItems }}</div>
1370
- </ng-template>
1371
-
1372
- <div *ngIf="errorsForPath.length" class="jsm-error-list" role="alert">
1373
- <p *ngFor="let error of errorsForPath" class="jsm-error-msg">
1374
- <svg class="jsm-error-icon" viewBox="0 0 16 16" fill="none" aria-hidden="true"><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>
1375
- {{ localize(error) }}
1376
- </p>
1377
- </div>
1378
- </div>
1379
-
1380
- <!-- ═══ primitive ═══ -->
1381
- <div *ngSwitchDefault class="jsm-field">
1382
- <ng-container *ngIf="inputKind !== 'checkbox'">
1383
- <label class="jsm-field-label" [attr.for]="nodeId">
1384
- {{ schema.title || label }}<span *ngIf="required" class="jsm-required" aria-hidden="true">*</span>
1385
- </label>
1386
- <p *ngIf="schema.description" class="jsm-field-hint" [id]="nodeId + '-hint'">{{ schema.description }}</p>
1387
- </ng-container>
1388
-
1389
- <div *ngIf="metaBadges.length" class="jsm-badge-row">
1390
- <span *ngFor="let badge of metaBadges" class="jsm-badge" [ngClass]="badgeClass(badge)">{{ badge }}</span>
1391
- </div>
1392
-
1393
- <ng-container>
1394
- <input *ngIf="inputKind === 'number'" jsmNumeric type="text" inputmode="decimal" class="jsm-input" [id]="nodeId"
1395
- [class.jsm-input--error]="controlAsFormControl.invalid && (controlAsFormControl.dirty || isTouched || showErrors)"
1396
- [jsmDecimalSeparator]="decimalSeparator" [attr.data-step]="numericStep"
1397
- [attr.aria-required]="required ? 'true' : null" [attr.aria-invalid]="visibleError ? 'true' : null"
1398
- [attr.aria-describedby]="describedBy"
1399
- [formControl]="controlAsFormControl"
1400
- (blur)="onBlur()" />
1401
-
1402
- <input *ngIf="inputKind === 'text'" class="jsm-input" [id]="nodeId"
1403
- [class.jsm-input--error]="controlAsFormControl.invalid && (controlAsFormControl.dirty || isTouched || showErrors)"
1404
- [attr.type]="inputType"
1405
- [attr.aria-required]="required ? 'true' : null" [attr.aria-invalid]="visibleError ? 'true' : null"
1406
- [attr.aria-describedby]="describedBy"
1407
- [formControl]="controlAsFormControl"
1408
- (blur)="onBlur()" />
1409
-
1410
- <textarea *ngIf="inputKind === 'textarea'" rows="4" class="jsm-textarea" [id]="nodeId"
1411
- [class.jsm-textarea--error]="controlAsFormControl.invalid && (controlAsFormControl.dirty || isTouched || showErrors)"
1412
- [attr.aria-required]="required ? 'true' : null" [attr.aria-invalid]="visibleError ? 'true' : null"
1413
- [attr.aria-describedby]="describedBy"
1414
- [formControl]="controlAsFormControl"
1415
- (blur)="onBlur()"></textarea>
1416
-
1417
- <div *ngIf="inputKind === 'select'" class="jsm-select-wrap">
1418
- <select class="jsm-select" [id]="nodeId"
1419
- [class.jsm-select--error]="controlAsFormControl.invalid && (controlAsFormControl.dirty || isTouched || showErrors)"
1420
- [attr.aria-required]="required ? 'true' : null" [attr.aria-invalid]="visibleError ? 'true' : null"
1421
- [attr.aria-describedby]="describedBy"
1422
- [formControl]="controlAsFormControl"
1423
- (blur)="onBlur()">
1424
- <option *ngFor="let option of effectiveSchema.enum" [ngValue]="option">{{ option }}</option>
1425
- </select>
1426
- <svg class="jsm-select-chevron" viewBox="0 0 16 16" fill="none" aria-hidden="true">
1427
- <path d="M4 6l4 4 4-4" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
1428
- </svg>
1226
+ <jsm-schema-node [schema]="schemaForIndex(i)" [control]="item" [parent]="controlAsArray"
1227
+ [controlKey]="i" [path]="pathForChild(i)" [errorsMap]="errorsMap" [label]="label + ' item'"
1228
+ [allowAdditionalProperties]="allowAdditionalProperties" [showErrors]="showErrors"
1229
+ (controlReplaced)="controlReplaced.emit($event)"></jsm-schema-node>
1429
1230
  </div>
1430
-
1431
- <label *ngIf="inputKind === 'checkbox'" class="jsm-toggle-label">
1432
- <span class="jsm-toggle-track" [class.jsm-on]="controlAsFormControl.value">
1433
- <input type="checkbox" class="sr-only" [id]="nodeId" [formControl]="controlAsFormControl"
1434
- [attr.aria-required]="required ? 'true' : null" [attr.aria-invalid]="visibleError ? 'true' : null"
1435
- [attr.aria-describedby]="describedBy" (blur)="onBlur()" />
1436
- <span class="jsm-toggle-thumb"></span>
1437
- </span>
1438
- <span class="jsm-toggle-text">
1439
- {{ schema.title || label }}<span *ngIf="required" class="jsm-required" aria-hidden="true">*</span>
1440
- </span>
1441
- </label>
1442
- </ng-container>
1443
-
1444
- <p *ngIf="inputKind === 'checkbox' && schema.description" class="jsm-field-hint" [id]="nodeId + '-hint'">{{ schema.description }}</p>
1445
- <p *ngIf="effectiveSchema.examples?.length" class="jsm-example" [id]="nodeId + '-example'">{{ l.example }} {{ effectiveSchema.examples?.[0] }}</p>
1446
- <p *ngIf="effectiveSchema.contentMediaType" class="jsm-example" [id]="nodeId + '-media'">
1447
- {{ effectiveSchema.contentMediaType }}<span *ngIf="effectiveSchema.contentEncoding"> ({{ effectiveSchema.contentEncoding }})</span>
1448
- </p>
1449
-
1450
- <div *ngIf="visibleError" class="jsm-error-list" role="alert" [id]="nodeId + '-error'">
1451
- <p *ngFor="let error of allErrors" class="jsm-error-msg">
1452
- <svg class="jsm-error-icon" viewBox="0 0 16 16" fill="none" aria-hidden="true">
1453
- <circle cx="8" cy="8" r="6.5" stroke="currentColor" stroke-width="1.5"/>
1454
- <path d="M8 5v3.5M8 11v.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
1455
- </svg>
1456
- {{ error }}
1231
+ }
1232
+ </div>
1233
+ } @else {
1234
+ <div class="jsm-empty-array">{{ l.noItems }}</div>
1235
+ }
1236
+ @if (errorsForPath.length) {
1237
+ <div class="jsm-error-list" role="alert">
1238
+ @for (error of errorsForPath; track error) {
1239
+ <p class="jsm-error-msg">
1240
+ <svg class="jsm-error-icon" viewBox="0 0 16 16" fill="none" aria-hidden="true"><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>
1241
+ {{ localize(error) }}
1457
1242
  </p>
1458
- </div>
1243
+ }
1459
1244
  </div>
1460
-
1461
- </ng-container>
1462
- `, 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:#dc2626;margin-left:.125rem}.jsm-example{font-size:.6875rem;color:#6b7280;margin:0}.jsm-input,.jsm-textarea,.jsm-select{width:100%;border-radius:.5rem;border:1.5px solid #6b7280;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:#4b5563}.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:#dc2626!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:#6b7280;pointer-events:none}.jsm-toggle-label{display:inline-flex;align-items:center;gap:.625rem;cursor:pointer;-webkit-user-select:none;user-select:none;min-height:1.5rem}.jsm-toggle-track{position:relative;display:inline-flex;align-items:center;width:2.25rem;height:1.25rem;border-radius:9999px;flex-shrink:0;background:#6b7280;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:#b91c1c;min-height:1.5rem;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:#4b5563}.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:#6b7280;font-size:.8125rem;text-align:center}.jsm-contains-hint{display:flex;align-items:center;gap:.375rem;font-size:.75rem;color:#4f46e5;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 #6b7280;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}.jsm-input:focus-visible,.jsm-textarea:focus-visible,.jsm-select:focus-visible,.jsm-btn-primary:focus-visible,.jsm-btn-danger:focus-visible,.jsm-tab:focus-visible{outline:2px solid #4f46e5;outline-offset:2px}.jsm-toggle-label:has(input:focus-visible) .jsm-toggle-track,.jsm-anyof-option:has(input:focus-visible){outline:2px solid #4f46e5;outline-offset:2px}.dark .jsm-field-label{color:#d1d5db}.dark .jsm-required{color:#f87171}.dark .jsm-example{color:#9ca3af}.dark .jsm-input:focus-visible,.dark .jsm-textarea:focus-visible,.dark .jsm-select:focus-visible,.dark .jsm-btn-primary:focus-visible,.dark .jsm-btn-danger:focus-visible,.dark .jsm-tab:focus-visible,.dark .jsm-toggle-label:has(input:focus-visible) .jsm-toggle-track,.dark .jsm-anyof-option:has(input:focus-visible){outline-color:#818cf8}.dark .jsm-btn-danger{color:#f87171;border-color:#f87171}.dark .jsm-btn-danger:hover{background:#f8717126}.dark .jsm-field-hint{color:#9ca3af}.dark .jsm-toggle-track{background:#64748b}.dark .jsm-toggle-track.jsm-on{background:#6366f1}.dark .jsm-toggle-text{color:#d1d5db}.dark .jsm-input,.dark .jsm-textarea,.dark .jsm-select{border-color:#64748b;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:#64748b;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:#94a3b8}.dark .jsm-contains-hint{background:#6366f126;color:#818cf8}.dark .jsm-add-prop{background:#1e293b66;border-color:#475569}.dark .jsm-add-prop-label{color:#94a3b8}.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:#94a3b8}\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 });
1245
+ }
1246
+ </div>
1463
1247
  }
1464
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: JsonSchemaNodeComponent, decorators: [{
1465
- type: Component,
1466
- args: [{ selector: 'jsm-schema-node', standalone: true, imports: [CommonModule, FormsModule, ReactiveFormsModule, NumericInputDirective], encapsulation: ViewEncapsulation.None, template: `
1467
- <ng-container [ngSwitch]="nodeKind">
1468
-
1469
- <!-- ═══ oneOf ═══ -->
1470
- <div *ngSwitchCase="'oneOf'" class="jsm-combinator">
1471
- <div class="jsm-combinator-header">
1472
- <div>
1473
- <p class="jsm-combinator-title">{{ schema.title || label }}</p>
1474
- <p *ngIf="schema.description" class="jsm-combinator-hint">{{ schema.description }}</p>
1475
- </div>
1476
- <span class="jsm-combinator-badge jsm-badge--oneof">oneOf</span>
1248
+ <!-- ═══ primitive ═══ -->
1249
+ @default {
1250
+ <div class="jsm-field">
1251
+ @if (inputKind !== 'checkbox') {
1252
+ <label class="jsm-field-label" [attr.for]="nodeId">
1253
+ {{ schema.title || label }}@if (required) {
1254
+ <span class="jsm-required" aria-hidden="true">*</span>
1255
+ }
1256
+ </label>
1257
+ @if (schema.description) {
1258
+ <p class="jsm-field-hint" [id]="nodeId + '-hint'">{{ schema.description }}</p>
1259
+ }
1260
+ }
1261
+ @if (metaBadges.length) {
1262
+ <div class="jsm-badge-row">
1263
+ @for (badge of metaBadges; track badge) {
1264
+ <span class="jsm-badge" [ngClass]="badgeClass(badge)">{{ badge }}</span>
1265
+ }
1266
+ </div>
1267
+ }
1268
+ <ng-container>
1269
+ @if (inputKind === 'number') {
1270
+ <input jsmNumeric type="text" inputmode="decimal" class="jsm-input" [id]="nodeId"
1271
+ [class.jsm-input--error]="controlAsFormControl.invalid && (controlAsFormControl.dirty || isTouched || showErrors)"
1272
+ [jsmDecimalSeparator]="decimalSeparator" [attr.data-step]="numericStep"
1273
+ [attr.aria-required]="required ? 'true' : null" [attr.aria-invalid]="visibleError ? 'true' : null"
1274
+ [attr.aria-describedby]="describedBy"
1275
+ [formControl]="controlAsFormControl"
1276
+ (blur)="onBlur()" />
1277
+ }
1278
+ @if (inputKind === 'text') {
1279
+ <input class="jsm-input" [id]="nodeId"
1280
+ [class.jsm-input--error]="controlAsFormControl.invalid && (controlAsFormControl.dirty || isTouched || showErrors)"
1281
+ [attr.type]="inputType"
1282
+ [attr.aria-required]="required ? 'true' : null" [attr.aria-invalid]="visibleError ? 'true' : null"
1283
+ [attr.aria-describedby]="describedBy"
1284
+ [formControl]="controlAsFormControl"
1285
+ (blur)="onBlur()" />
1286
+ }
1287
+ @if (inputKind === 'textarea') {
1288
+ <textarea rows="4" class="jsm-textarea" [id]="nodeId"
1289
+ [class.jsm-textarea--error]="controlAsFormControl.invalid && (controlAsFormControl.dirty || isTouched || showErrors)"
1290
+ [attr.aria-required]="required ? 'true' : null" [attr.aria-invalid]="visibleError ? 'true' : null"
1291
+ [attr.aria-describedby]="describedBy"
1292
+ [formControl]="controlAsFormControl"
1293
+ (blur)="onBlur()"></textarea>
1294
+ }
1295
+ @if (inputKind === 'select') {
1296
+ <div class="jsm-select-wrap">
1297
+ <select class="jsm-select" [id]="nodeId"
1298
+ [class.jsm-select--error]="controlAsFormControl.invalid && (controlAsFormControl.dirty || isTouched || showErrors)"
1299
+ [attr.aria-required]="required ? 'true' : null" [attr.aria-invalid]="visibleError ? 'true' : null"
1300
+ [attr.aria-describedby]="describedBy"
1301
+ [formControl]="controlAsFormControl"
1302
+ (blur)="onBlur()">
1303
+ @for (option of effectiveSchema.enum; track option) {
1304
+ <option [ngValue]="option">{{ option }}</option>
1305
+ }
1306
+ </select>
1307
+ <svg class="jsm-select-chevron" viewBox="0 0 16 16" fill="none" aria-hidden="true">
1308
+ <path d="M4 6l4 4 4-4" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
1309
+ </svg>
1310
+ </div>
1311
+ }
1312
+ @if (inputKind === 'checkbox') {
1313
+ <label class="jsm-toggle-label">
1314
+ <span class="jsm-toggle-track" [class.jsm-on]="controlAsFormControl.value">
1315
+ <input type="checkbox" class="sr-only" [id]="nodeId" [formControl]="controlAsFormControl"
1316
+ [attr.aria-required]="required ? 'true' : null" [attr.aria-invalid]="visibleError ? 'true' : null"
1317
+ [attr.aria-describedby]="describedBy" (blur)="onBlur()" />
1318
+ <span class="jsm-toggle-thumb"></span>
1319
+ </span>
1320
+ <span class="jsm-toggle-text">
1321
+ {{ schema.title || label }}@if (required) {
1322
+ <span class="jsm-required" aria-hidden="true">*</span>
1323
+ }
1324
+ </span>
1325
+ </label>
1326
+ }
1327
+ </ng-container>
1328
+ @if (inputKind === 'checkbox' && schema.description) {
1329
+ <p class="jsm-field-hint" [id]="nodeId + '-hint'">{{ schema.description }}</p>
1330
+ }
1331
+ @if (effectiveSchema.examples?.length) {
1332
+ <p class="jsm-example" [id]="nodeId + '-example'">{{ l.example }} {{ effectiveSchema.examples?.[0] }}</p>
1333
+ }
1334
+ @if (effectiveSchema.contentMediaType) {
1335
+ <p class="jsm-example" [id]="nodeId + '-media'">
1336
+ {{ effectiveSchema.contentMediaType }}@if (effectiveSchema.contentEncoding) {
1337
+ <span> ({{ effectiveSchema.contentEncoding }})</span>
1338
+ }
1339
+ </p>
1340
+ }
1341
+ @if (visibleError) {
1342
+ <div class="jsm-error-list" role="alert" [id]="nodeId + '-error'">
1343
+ @for (error of allErrors; track error) {
1344
+ <p class="jsm-error-msg">
1345
+ <svg class="jsm-error-icon" viewBox="0 0 16 16" fill="none" aria-hidden="true">
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
+ }
1352
+ </div>
1353
+ }
1354
+ </div>
1355
+ }
1356
+ }
1357
+ `,
1358
+ isInline: true,
1359
+ 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:#dc2626;margin-left:.125rem}.jsm-example{font-size:.6875rem;color:#6b7280;margin:0}.jsm-input,.jsm-textarea,.jsm-select{width:100%;border-radius:.5rem;border:1.5px solid #6b7280;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:#4b5563}.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:#dc2626!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:#6b7280;pointer-events:none}.jsm-toggle-label{display:inline-flex;align-items:center;gap:.625rem;cursor:pointer;-webkit-user-select:none;user-select:none;min-height:1.5rem}.jsm-toggle-track{position:relative;display:inline-flex;align-items:center;width:2.25rem;height:1.25rem;border-radius:9999px;flex-shrink:0;background:#6b7280;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:#b91c1c;min-height:1.5rem;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:#4b5563}.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:#6b7280;font-size:.8125rem;text-align:center}.jsm-contains-hint{display:flex;align-items:center;gap:.375rem;font-size:.75rem;color:#4f46e5;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 #6b7280;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}.jsm-input:focus-visible,.jsm-textarea:focus-visible,.jsm-select:focus-visible,.jsm-btn-primary:focus-visible,.jsm-btn-danger:focus-visible,.jsm-tab:focus-visible{outline:2px solid #4f46e5;outline-offset:2px}.jsm-toggle-label:has(input:focus-visible) .jsm-toggle-track,.jsm-anyof-option:has(input:focus-visible){outline:2px solid #4f46e5;outline-offset:2px}.dark .jsm-field-label{color:#d1d5db}.dark .jsm-required{color:#f87171}.dark .jsm-example{color:#9ca3af}.dark .jsm-input:focus-visible,.dark .jsm-textarea:focus-visible,.dark .jsm-select:focus-visible,.dark .jsm-btn-primary:focus-visible,.dark .jsm-btn-danger:focus-visible,.dark .jsm-tab:focus-visible,.dark .jsm-toggle-label:has(input:focus-visible) .jsm-toggle-track,.dark .jsm-anyof-option:has(input:focus-visible){outline-color:#818cf8}.dark .jsm-btn-danger{color:#f87171;border-color:#f87171}.dark .jsm-btn-danger:hover{background:#f8717126}.dark .jsm-field-hint{color:#9ca3af}.dark .jsm-toggle-track{background:#64748b}.dark .jsm-toggle-track.jsm-on{background:#6366f1}.dark .jsm-toggle-text{color:#d1d5db}.dark .jsm-input,.dark .jsm-textarea,.dark .jsm-select{border-color:#64748b;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:#64748b;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:#94a3b8}.dark .jsm-contains-hint{background:#6366f126;color:#818cf8}.dark .jsm-add-prop{background:#1e293b66;border-color:#475569}.dark .jsm-add-prop-label{color:#94a3b8}.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:#94a3b8}\n"],
1360
+ dependencies: [
1361
+ {
1362
+ kind: "component",
1363
+ type: JsonSchemaNodeComponent,
1364
+ selector: "jsm-schema-node",
1365
+ inputs: [
1366
+ "schema",
1367
+ "control",
1368
+ "label",
1369
+ "required",
1370
+ "path",
1371
+ "errorsMap",
1372
+ "parent",
1373
+ "controlKey",
1374
+ "allowAdditionalProperties",
1375
+ "showErrors"
1376
+ ],
1377
+ outputs: ["controlReplaced"]
1378
+ },
1379
+ {
1380
+ kind: "ngmodule",
1381
+ type: CommonModule
1382
+ },
1383
+ {
1384
+ kind: "directive",
1385
+ type: i4.NgClass,
1386
+ selector: "[ngClass]",
1387
+ inputs: ["class", "ngClass"]
1388
+ },
1389
+ {
1390
+ kind: "ngmodule",
1391
+ type: FormsModule
1392
+ },
1393
+ {
1394
+ kind: "directive",
1395
+ type: i5.NgSelectOption,
1396
+ selector: "option",
1397
+ inputs: ["ngValue", "value"]
1398
+ },
1399
+ {
1400
+ kind: "directive",
1401
+ type: i5.ɵNgSelectMultipleOption,
1402
+ selector: "option",
1403
+ inputs: ["ngValue", "value"]
1404
+ },
1405
+ {
1406
+ kind: "directive",
1407
+ type: i5.DefaultValueAccessor,
1408
+ selector: "input:not([type=checkbox]):not([ngNoCva])[formControlName],textarea:not([ngNoCva])[formControlName],input:not([type=checkbox]):not([ngNoCva])[formControl],textarea:not([ngNoCva])[formControl],input:not([type=checkbox]):not([ngNoCva])[ngModel],textarea:not([ngNoCva])[ngModel],[ngDefaultControl]"
1409
+ },
1410
+ {
1411
+ kind: "directive",
1412
+ type: i5.CheckboxControlValueAccessor,
1413
+ selector: "input[type=checkbox]:not([ngNoCva])[formControlName],input[type=checkbox]:not([ngNoCva])[formControl],input[type=checkbox]:not([ngNoCva])[ngModel]"
1414
+ },
1415
+ {
1416
+ kind: "directive",
1417
+ type: i5.SelectControlValueAccessor,
1418
+ selector: "select:not([multiple]):not([ngNoCva])[formControlName],select:not([multiple]):not([ngNoCva])[formControl],select:not([multiple]):not([ngNoCva])[ngModel]",
1419
+ inputs: ["compareWith"]
1420
+ },
1421
+ {
1422
+ kind: "directive",
1423
+ type: i5.NgControlStatus,
1424
+ selector: "[formControlName],[ngModel],[formControl]"
1425
+ },
1426
+ {
1427
+ kind: "directive",
1428
+ type: i5.NgControlStatusGroup,
1429
+ selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]"
1430
+ },
1431
+ {
1432
+ kind: "directive",
1433
+ type: i5.NgModel,
1434
+ selector: "[ngModel]:not([formControlName]):not([formControl])",
1435
+ inputs: [
1436
+ "name",
1437
+ "disabled",
1438
+ "ngModel",
1439
+ "ngModelOptions"
1440
+ ],
1441
+ outputs: ["ngModelChange"],
1442
+ exportAs: ["ngModel"]
1443
+ },
1444
+ {
1445
+ kind: "ngmodule",
1446
+ type: ReactiveFormsModule
1447
+ },
1448
+ {
1449
+ kind: "directive",
1450
+ type: i5.FormControlDirective,
1451
+ selector: "[formControl]",
1452
+ inputs: [
1453
+ "formControl",
1454
+ "disabled",
1455
+ "ngModel"
1456
+ ],
1457
+ outputs: ["ngModelChange"],
1458
+ exportAs: ["ngForm"]
1459
+ },
1460
+ {
1461
+ kind: "directive",
1462
+ type: i5.FormGroupDirective,
1463
+ selector: "[formGroup]",
1464
+ inputs: ["formGroup"],
1465
+ outputs: ["ngSubmit"],
1466
+ exportAs: ["ngForm"]
1467
+ },
1468
+ {
1469
+ kind: "directive",
1470
+ type: NumericInputDirective,
1471
+ selector: "input[jsmNumeric]",
1472
+ inputs: ["jsmDecimalSeparator"]
1473
+ }
1474
+ ],
1475
+ changeDetection: i0.ChangeDetectionStrategy.OnPush,
1476
+ encapsulation: i0.ViewEncapsulation.None
1477
+ });
1478
+ };
1479
+ i0.ɵɵngDeclareClassMetadata({
1480
+ minVersion: "12.0.0",
1481
+ version: "22.2.3",
1482
+ ngImport: i0,
1483
+ type: JsonSchemaNodeComponent,
1484
+ decorators: [{
1485
+ type: Component,
1486
+ args: [{
1487
+ selector: "jsm-schema-node",
1488
+ standalone: true,
1489
+ imports: [
1490
+ CommonModule,
1491
+ FormsModule,
1492
+ ReactiveFormsModule,
1493
+ NumericInputDirective
1494
+ ],
1495
+ encapsulation: ViewEncapsulation.None,
1496
+ template: `
1497
+ @switch (nodeKind) {
1498
+ <!-- ═══ oneOf ═══ -->
1499
+ @case ('oneOf') {
1500
+ <div class="jsm-combinator">
1501
+ <div class="jsm-combinator-header">
1502
+ <div>
1503
+ <p class="jsm-combinator-title">{{ schema.title || label }}</p>
1504
+ @if (schema.description) {
1505
+ <p class="jsm-combinator-hint">{{ schema.description }}</p>
1506
+ }
1477
1507
  </div>
1478
- <div class="jsm-oneof-tabs" role="tablist" [attr.aria-label]="schema.title || label || null" (keydown)="onOneOfKeydown($event)">
1479
- <button *ngFor="let option of schema.oneOf; let i = index" type="button" role="tab"
1508
+ <span class="jsm-combinator-badge jsm-badge--oneof">oneOf</span>
1509
+ </div>
1510
+ <div class="jsm-oneof-tabs" role="tablist" [attr.aria-label]="schema.title || label || null" (keydown)="onOneOfKeydown($event)">
1511
+ @for (option of schema.oneOf; track option; let i = $index) {
1512
+ <button type="button" role="tab"
1480
1513
  class="jsm-tab" [class.jsm-tab--active]="selectedOneOf === i"
1481
1514
  [id]="nodeId + '-tab-' + i" [attr.aria-selected]="selectedOneOf === i"
1482
1515
  [attr.aria-controls]="nodeId + '-panel'" [attr.tabindex]="selectedOneOf === i ? 0 : -1"
1483
1516
  (click)="selectOneOf(i)">
1484
1517
  {{ option.title || optionLabel(i) }}
1485
1518
  </button>
1486
- </div>
1487
- <div class="jsm-combinator-body" *ngIf="activeVariant" role="tabpanel" [id]="nodeId + '-panel'"
1519
+ }
1520
+ </div>
1521
+ @if (activeVariant) {
1522
+ <div class="jsm-combinator-body" role="tabpanel" [id]="nodeId + '-panel'"
1488
1523
  [attr.aria-labelledby]="nodeId + '-tab-' + selectedOneOf">
1489
1524
  <jsm-schema-node [schema]="activeVariant" [control]="control" [parent]="parent" [controlKey]="controlKey"
1490
1525
  [path]="path" [errorsMap]="errorsMap" [label]="label" [required]="required"
1491
1526
  [allowAdditionalProperties]="allowAdditionalProperties" [showErrors]="showErrors"
1492
- (controlReplaced)="controlReplaced.emit($event)"></jsm-schema-node>
1527
+ (controlReplaced)="controlReplaced.emit($event)"></jsm-schema-node>
1493
1528
  </div>
1494
- </div>
1495
-
1496
- <!-- ═══ anyOf ═══ -->
1497
- <div *ngSwitchCase="'anyOf'" class="jsm-combinator">
1498
- <div class="jsm-combinator-header">
1499
- <div>
1500
- <p class="jsm-combinator-title">{{ schema.title || label }}</p>
1501
- <p *ngIf="schema.description" class="jsm-combinator-hint">{{ schema.description }}</p>
1502
- </div>
1503
- <span class="jsm-combinator-badge jsm-badge--anyof">anyOf</span>
1529
+ }
1530
+ </div>
1531
+ }
1532
+ <!-- ═══ anyOf ═══ -->
1533
+ @case ('anyOf') {
1534
+ <div class="jsm-combinator">
1535
+ <div class="jsm-combinator-header">
1536
+ <div>
1537
+ <p class="jsm-combinator-title">{{ schema.title || label }}</p>
1538
+ @if (schema.description) {
1539
+ <p class="jsm-combinator-hint">{{ schema.description }}</p>
1540
+ }
1504
1541
  </div>
1505
- <div class="jsm-anyof-options" role="group" [attr.aria-label]="schema.title || label || null">
1506
- <label *ngFor="let option of schema.anyOf; let i = index"
1542
+ <span class="jsm-combinator-badge jsm-badge--anyof">anyOf</span>
1543
+ </div>
1544
+ <div class="jsm-anyof-options" role="group" [attr.aria-label]="schema.title || label || null">
1545
+ @for (option of schema.anyOf; track option; let i = $index) {
1546
+ <label
1507
1547
  class="jsm-anyof-option" [class.jsm-anyof-option--active]="selectedAnyOf.has(i)">
1508
1548
  <input type="checkbox" class="sr-only" [checked]="selectedAnyOf.has(i)" (change)="toggleAnyOf(i)" />
1509
1549
  {{ option.title || optionLabel(i) }}
1510
1550
  </label>
1511
- </div>
1512
- <div class="jsm-combinator-body" *ngIf="activeVariant">
1551
+ }
1552
+ </div>
1553
+ @if (activeVariant) {
1554
+ <div class="jsm-combinator-body">
1513
1555
  <jsm-schema-node [schema]="activeVariant" [control]="control" [parent]="parent" [controlKey]="controlKey"
1514
1556
  [path]="path" [errorsMap]="errorsMap" [label]="label" [required]="required"
1515
1557
  [allowAdditionalProperties]="allowAdditionalProperties" [showErrors]="showErrors"
1516
- (controlReplaced)="controlReplaced.emit($event)"></jsm-schema-node>
1558
+ (controlReplaced)="controlReplaced.emit($event)"></jsm-schema-node>
1517
1559
  </div>
1518
- </div>
1519
-
1520
- <!-- ═══ allOf ═══ -->
1521
- <div *ngSwitchCase="'allOf'" class="jsm-combinator">
1522
- <div class="jsm-combinator-header">
1523
- <div>
1524
- <p class="jsm-combinator-title">{{ schema.title || label }}</p>
1525
- <p *ngIf="schema.description" class="jsm-combinator-hint">{{ schema.description }}</p>
1526
- </div>
1527
- <span class="jsm-combinator-badge jsm-badge--allof">allOf</span>
1560
+ }
1561
+ </div>
1562
+ }
1563
+ <!-- ═══ allOf ═══ -->
1564
+ @case ('allOf') {
1565
+ <div class="jsm-combinator">
1566
+ <div class="jsm-combinator-header">
1567
+ <div>
1568
+ <p class="jsm-combinator-title">{{ schema.title || label }}</p>
1569
+ @if (schema.description) {
1570
+ <p class="jsm-combinator-hint">{{ schema.description }}</p>
1571
+ }
1528
1572
  </div>
1529
- <div class="jsm-combinator-body">
1530
- <div *ngFor="let subSchema of mergedAllOf; let i = index" class="jsm-allof-section">
1573
+ <span class="jsm-combinator-badge jsm-badge--allof">allOf</span>
1574
+ </div>
1575
+ <div class="jsm-combinator-body">
1576
+ @for (subSchema of mergedAllOf; track subSchema; let i = $index) {
1577
+ <div class="jsm-allof-section">
1531
1578
  <jsm-schema-node [schema]="subSchema" [control]="control" [parent]="parent" [controlKey]="controlKey"
1532
1579
  [path]="path" [errorsMap]="errorsMap" [label]="subSchema.title || sectionLabel(i)" [required]="required"
1533
1580
  [allowAdditionalProperties]="allowAdditionalProperties" [showErrors]="showErrors"
1534
- (controlReplaced)="controlReplaced.emit($event)"></jsm-schema-node>
1581
+ (controlReplaced)="controlReplaced.emit($event)"></jsm-schema-node>
1535
1582
  </div>
1536
- </div>
1583
+ }
1537
1584
  </div>
1538
-
1539
- <!-- ═══ object ═══ -->
1540
- <fieldset *ngSwitchCase="'object'" class="jsm-fieldset">
1541
- <legend class="jsm-legend">{{ schema.title || label }}</legend>
1542
- <p *ngIf="schema.description" class="jsm-field-hint" style="margin-bottom:.75rem;margin-top:.25rem;">{{ schema.description }}</p>
1543
-
1544
- <div *ngIf="metaBadges.length" class="jsm-badge-row" style="margin-bottom:.75rem;">
1545
- <span *ngFor="let badge of metaBadges" class="jsm-badge" [ngClass]="badgeClass(badge)">{{ badge }}</span>
1585
+ </div>
1586
+ }
1587
+ <!-- ═══ object ═══ -->
1588
+ @case ('object') {
1589
+ <fieldset class="jsm-fieldset">
1590
+ <legend class="jsm-legend">{{ schema.title || label }}</legend>
1591
+ @if (schema.description) {
1592
+ <p class="jsm-field-hint" style="margin-bottom:.75rem;margin-top:.25rem;">{{ schema.description }}</p>
1593
+ }
1594
+ @if (metaBadges.length) {
1595
+ <div class="jsm-badge-row" style="margin-bottom:.75rem;">
1596
+ @for (badge of metaBadges; track badge) {
1597
+ <span class="jsm-badge" [ngClass]="badgeClass(badge)">{{ badge }}</span>
1598
+ }
1546
1599
  </div>
1547
-
1548
- <div class="jsm-obj-grid" [formGroup]="controlAsGroup">
1549
- <ng-container *ngFor="let key of simpleKeys">
1550
- <div style="display:flex;flex-direction:column;gap:.25rem;">
1551
- <div *ngIf="isDynamicKey(key)" class="jsm-dynamic-key-header">
1600
+ }
1601
+ <div class="jsm-obj-grid" [formGroup]="controlAsGroup">
1602
+ @for (key of simpleKeys; track key) {
1603
+ <div style="display:flex;flex-direction:column;gap:.25rem;">
1604
+ @if (isDynamicKey(key)) {
1605
+ <div class="jsm-dynamic-key-header">
1552
1606
  <span class="jsm-dynamic-key-label" [id]="keyId(key)">{{ key }}</span>
1553
1607
  <button type="button" class="jsm-btn-danger" [id]="keyId(key) + '-remove'"
1554
- [attr.aria-labelledby]="keyId(key) + '-remove ' + keyId(key)" (click)="removeDynamicKey(key)">{{ l.remove }}</button>
1608
+ [attr.aria-labelledby]="keyId(key) + '-remove ' + keyId(key)" (click)="removeDynamicKey(key)">{{ l.remove }}</button>
1555
1609
  </div>
1556
- <jsm-schema-node [schema]="schemaForKey(key)" [control]="childControl(key)" [parent]="controlAsGroup"
1557
- [controlKey]="key" [path]="pathForChild(key)" [errorsMap]="errorsMap" [label]="key"
1558
- [required]="isRequired(key)" [allowAdditionalProperties]="allowAdditionalProperties" [showErrors]="showErrors"
1559
- (controlReplaced)="controlReplaced.emit($event)"></jsm-schema-node>
1560
- </div>
1561
- </ng-container>
1562
- </div>
1563
-
1564
- <div class="jsm-obj-complex" *ngIf="complexKeys.length > 0" [formGroup]="controlAsGroup">
1565
- <ng-container *ngFor="let key of complexKeys">
1610
+ }
1611
+ <jsm-schema-node [schema]="schemaForKey(key)" [control]="childControl(key)" [parent]="controlAsGroup"
1612
+ [controlKey]="key" [path]="pathForChild(key)" [errorsMap]="errorsMap" [label]="key"
1613
+ [required]="isRequired(key)" [allowAdditionalProperties]="allowAdditionalProperties" [showErrors]="showErrors"
1614
+ (controlReplaced)="controlReplaced.emit($event)"></jsm-schema-node>
1615
+ </div>
1616
+ }
1617
+ </div>
1618
+ @if (complexKeys.length > 0) {
1619
+ <div class="jsm-obj-complex" [formGroup]="controlAsGroup">
1620
+ @for (key of complexKeys; track key) {
1566
1621
  <div style="display:flex;flex-direction:column;gap:.25rem;">
1567
- <div *ngIf="isDynamicKey(key)" class="jsm-dynamic-key-header">
1568
- <span class="jsm-dynamic-key-label" [id]="keyId(key)">{{ key }}</span>
1569
- <button type="button" class="jsm-btn-danger" [id]="keyId(key) + '-remove'"
1622
+ @if (isDynamicKey(key)) {
1623
+ <div class="jsm-dynamic-key-header">
1624
+ <span class="jsm-dynamic-key-label" [id]="keyId(key)">{{ key }}</span>
1625
+ <button type="button" class="jsm-btn-danger" [id]="keyId(key) + '-remove'"
1570
1626
  [attr.aria-labelledby]="keyId(key) + '-remove ' + keyId(key)" (click)="removeDynamicKey(key)">{{ l.remove }}</button>
1571
- </div>
1627
+ </div>
1628
+ }
1572
1629
  <jsm-schema-node [schema]="schemaForKey(key)" [control]="childControl(key)" [parent]="controlAsGroup"
1573
1630
  [controlKey]="key" [path]="pathForChild(key)" [errorsMap]="errorsMap" [label]="key"
1574
1631
  [required]="isRequired(key)" [allowAdditionalProperties]="allowAdditionalProperties" [showErrors]="showErrors"
1575
- (controlReplaced)="controlReplaced.emit($event)"></jsm-schema-node>
1632
+ (controlReplaced)="controlReplaced.emit($event)"></jsm-schema-node>
1576
1633
  </div>
1577
- </ng-container>
1634
+ }
1578
1635
  </div>
1579
-
1580
- <div *ngIf="canAddProperty" class="jsm-add-prop" role="group" [attr.aria-labelledby]="nodeId + '-addprop'">
1636
+ }
1637
+ @if (canAddProperty) {
1638
+ <div class="jsm-add-prop" role="group" [attr.aria-labelledby]="nodeId + '-addprop'">
1581
1639
  <p class="jsm-add-prop-label" [id]="nodeId + '-addprop'">{{ l.addProperty }}</p>
1582
1640
  <div class="jsm-add-prop-row">
1583
1641
  <input class="jsm-input jsm-input--sm" style="flex:1;" [(ngModel)]="newPropertyKey" [ngModelOptions]="{standalone: true}"
@@ -1585,654 +1643,783 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImpo
1585
1643
  [attr.aria-invalid]="propertyError ? 'true' : null" [attr.aria-describedby]="propertyError ? nodeId + '-prop-error' : null" />
1586
1644
  <button type="button" class="jsm-btn-primary" (click)="addDynamicProperty()">{{ l.add }}</button>
1587
1645
  </div>
1588
- <p *ngIf="propertyError" class="jsm-error-msg" role="alert" [id]="nodeId + '-prop-error'">
1589
- <svg class="jsm-error-icon" viewBox="0 0 16 16" fill="none" aria-hidden="true"><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>
1590
- {{ propertyError }}
1591
- </p>
1592
- </div>
1593
-
1594
- <div *ngIf="errorsForPath.length" class="jsm-error-list" role="alert" style="margin-top:.75rem;">
1595
- <p *ngFor="let error of errorsForPath" class="jsm-error-msg">
1596
- <svg class="jsm-error-icon" viewBox="0 0 16 16" fill="none" aria-hidden="true"><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>
1597
- {{ localize(error) }}
1598
- </p>
1599
- </div>
1600
- </fieldset>
1601
-
1602
- <!-- ═══ array ═══ -->
1603
- <div *ngSwitchCase="'array'" class="jsm-array-block" role="group" [attr.aria-labelledby]="nodeId + '-title'">
1604
- <div class="jsm-array-header">
1605
- <div>
1606
- <p class="jsm-array-title" [id]="nodeId + '-title'">
1607
- {{ schema.title || label }}<span *ngIf="required" class="jsm-required" aria-hidden="true">*</span>
1646
+ @if (propertyError) {
1647
+ <p class="jsm-error-msg" role="alert" [id]="nodeId + '-prop-error'">
1648
+ <svg class="jsm-error-icon" viewBox="0 0 16 16" fill="none" aria-hidden="true"><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>
1649
+ {{ propertyError }}
1608
1650
  </p>
1609
- <p *ngIf="schema.description" class="jsm-field-hint">{{ schema.description }}</p>
1610
- </div>
1611
- <button type="button" class="jsm-btn-primary" [id]="nodeId + '-add'"
1612
- [attr.aria-labelledby]="nodeId + '-add ' + nodeId + '-title'" (click)="addArrayItem()" [disabled]="!canAddArrayItem">
1613
- <svg style="width:.875rem;height:.875rem;flex-shrink:0;" viewBox="0 0 16 16" fill="none" aria-hidden="true"><path d="M8 3v10M3 8h10" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></svg>
1614
- {{ l.addItem }}
1615
- </button>
1616
- </div>
1617
-
1618
- <div *ngIf="effectiveSchema.contains" class="jsm-contains-hint">
1619
- {{ l.mustContain }}
1620
- <span *ngIf="effectiveSchema.minContains"> · {{ fmt(l.min, { n: effectiveSchema.minContains }) }}</span>
1621
- <span *ngIf="effectiveSchema.maxContains"> · {{ fmt(l.max, { n: effectiveSchema.maxContains }) }}</span>
1651
+ }
1622
1652
  </div>
1623
-
1624
- <div *ngIf="metaBadges.length" class="jsm-badge-row">
1625
- <span *ngFor="let badge of metaBadges" class="jsm-badge" [ngClass]="badgeClass(badge)">{{ badge }}</span>
1653
+ }
1654
+ @if (errorsForPath.length) {
1655
+ <div class="jsm-error-list" role="alert" style="margin-top:.75rem;">
1656
+ @for (error of errorsForPath; track error) {
1657
+ <p class="jsm-error-msg">
1658
+ <svg class="jsm-error-icon" viewBox="0 0 16 16" fill="none" aria-hidden="true"><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>
1659
+ {{ localize(error) }}
1660
+ </p>
1661
+ }
1626
1662
  </div>
1627
-
1628
- <div class="jsm-array-items" *ngIf="arrayControls.length > 0; else emptyArray">
1629
- <div *ngFor="let item of arrayControls; let i = index" class="jsm-array-item" role="group" [attr.aria-labelledby]="nodeId + '-item-' + i">
1663
+ }
1664
+ </fieldset>
1665
+ }
1666
+ <!-- ═══ array ═══ -->
1667
+ @case ('array') {
1668
+ <div class="jsm-array-block" role="group" [attr.aria-labelledby]="nodeId + '-title'">
1669
+ <div class="jsm-array-header">
1670
+ <div>
1671
+ <p class="jsm-array-title" [id]="nodeId + '-title'">
1672
+ {{ schema.title || label }}@if (required) {
1673
+ <span class="jsm-required" aria-hidden="true">*</span>
1674
+ }
1675
+ </p>
1676
+ @if (schema.description) {
1677
+ <p class="jsm-field-hint">{{ schema.description }}</p>
1678
+ }
1679
+ </div>
1680
+ <button type="button" class="jsm-btn-primary" [id]="nodeId + '-add'"
1681
+ [attr.aria-labelledby]="nodeId + '-add ' + nodeId + '-title'" (click)="addArrayItem()" [disabled]="!canAddArrayItem">
1682
+ <svg style="width:.875rem;height:.875rem;flex-shrink:0;" viewBox="0 0 16 16" fill="none" aria-hidden="true"><path d="M8 3v10M3 8h10" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></svg>
1683
+ {{ l.addItem }}
1684
+ </button>
1685
+ </div>
1686
+ @if (effectiveSchema.contains) {
1687
+ <div class="jsm-contains-hint">
1688
+ {{ l.mustContain }}
1689
+ @if (effectiveSchema.minContains) {
1690
+ <span> · {{ fmt(l.min, { n: effectiveSchema.minContains }) }}</span>
1691
+ }
1692
+ @if (effectiveSchema.maxContains) {
1693
+ <span> · {{ fmt(l.max, { n: effectiveSchema.maxContains }) }}</span>
1694
+ }
1695
+ </div>
1696
+ }
1697
+ @if (metaBadges.length) {
1698
+ <div class="jsm-badge-row">
1699
+ @for (badge of metaBadges; track badge) {
1700
+ <span class="jsm-badge" [ngClass]="badgeClass(badge)">{{ badge }}</span>
1701
+ }
1702
+ </div>
1703
+ }
1704
+ @if (arrayControls.length > 0) {
1705
+ <div class="jsm-array-items">
1706
+ @for (item of arrayControls; track item; let i = $index) {
1707
+ <div class="jsm-array-item" role="group" [attr.aria-labelledby]="nodeId + '-item-' + i">
1630
1708
  <div class="jsm-array-item-header">
1631
1709
  <span class="jsm-array-index" aria-hidden="true">{{ i + 1 }}</span>
1632
1710
  <span class="sr-only" [id]="nodeId + '-item-' + i">{{ fmt(l.arrayItem, { n: i + 1 }) }}</span>
1633
1711
  <button type="button" class="jsm-btn-danger" [id]="nodeId + '-remove-' + i"
1634
- [attr.aria-labelledby]="nodeId + '-remove-' + i + ' ' + nodeId + '-item-' + i" (click)="removeArrayItem(i)">{{ l.remove }}</button>
1712
+ [attr.aria-labelledby]="nodeId + '-remove-' + i + ' ' + nodeId + '-item-' + i" (click)="removeArrayItem(i)">{{ l.remove }}</button>
1635
1713
  </div>
1636
1714
  <jsm-schema-node [schema]="schemaForIndex(i)" [control]="item" [parent]="controlAsArray"
1637
1715
  [controlKey]="i" [path]="pathForChild(i)" [errorsMap]="errorsMap" [label]="label + ' item'"
1638
1716
  [allowAdditionalProperties]="allowAdditionalProperties" [showErrors]="showErrors"
1639
- (controlReplaced)="controlReplaced.emit($event)"></jsm-schema-node>
1717
+ (controlReplaced)="controlReplaced.emit($event)"></jsm-schema-node>
1640
1718
  </div>
1641
- </div>
1642
-
1643
- <ng-template #emptyArray>
1644
- <div class="jsm-empty-array">{{ l.noItems }}</div>
1645
- </ng-template>
1646
-
1647
- <div *ngIf="errorsForPath.length" class="jsm-error-list" role="alert">
1648
- <p *ngFor="let error of errorsForPath" class="jsm-error-msg">
1719
+ }
1720
+ </div>
1721
+ } @else {
1722
+ <div class="jsm-empty-array">{{ l.noItems }}</div>
1723
+ }
1724
+ @if (errorsForPath.length) {
1725
+ <div class="jsm-error-list" role="alert">
1726
+ @for (error of errorsForPath; track error) {
1727
+ <p class="jsm-error-msg">
1649
1728
  <svg class="jsm-error-icon" viewBox="0 0 16 16" fill="none" aria-hidden="true"><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>
1650
1729
  {{ localize(error) }}
1651
1730
  </p>
1652
- </div>
1731
+ }
1653
1732
  </div>
1654
-
1655
- <!-- ═══ primitive ═══ -->
1656
- <div *ngSwitchDefault class="jsm-field">
1657
- <ng-container *ngIf="inputKind !== 'checkbox'">
1658
- <label class="jsm-field-label" [attr.for]="nodeId">
1659
- {{ schema.title || label }}<span *ngIf="required" class="jsm-required" aria-hidden="true">*</span>
1660
- </label>
1661
- <p *ngIf="schema.description" class="jsm-field-hint" [id]="nodeId + '-hint'">{{ schema.description }}</p>
1662
- </ng-container>
1663
-
1664
- <div *ngIf="metaBadges.length" class="jsm-badge-row">
1665
- <span *ngFor="let badge of metaBadges" class="jsm-badge" [ngClass]="badgeClass(badge)">{{ badge }}</span>
1666
- </div>
1667
-
1668
- <ng-container>
1669
- <input *ngIf="inputKind === 'number'" jsmNumeric type="text" inputmode="decimal" class="jsm-input" [id]="nodeId"
1670
- [class.jsm-input--error]="controlAsFormControl.invalid && (controlAsFormControl.dirty || isTouched || showErrors)"
1671
- [jsmDecimalSeparator]="decimalSeparator" [attr.data-step]="numericStep"
1672
- [attr.aria-required]="required ? 'true' : null" [attr.aria-invalid]="visibleError ? 'true' : null"
1673
- [attr.aria-describedby]="describedBy"
1674
- [formControl]="controlAsFormControl"
1675
- (blur)="onBlur()" />
1676
-
1677
- <input *ngIf="inputKind === 'text'" class="jsm-input" [id]="nodeId"
1678
- [class.jsm-input--error]="controlAsFormControl.invalid && (controlAsFormControl.dirty || isTouched || showErrors)"
1679
- [attr.type]="inputType"
1680
- [attr.aria-required]="required ? 'true' : null" [attr.aria-invalid]="visibleError ? 'true' : null"
1681
- [attr.aria-describedby]="describedBy"
1682
- [formControl]="controlAsFormControl"
1683
- (blur)="onBlur()" />
1684
-
1685
- <textarea *ngIf="inputKind === 'textarea'" rows="4" class="jsm-textarea" [id]="nodeId"
1686
- [class.jsm-textarea--error]="controlAsFormControl.invalid && (controlAsFormControl.dirty || isTouched || showErrors)"
1687
- [attr.aria-required]="required ? 'true' : null" [attr.aria-invalid]="visibleError ? 'true' : null"
1688
- [attr.aria-describedby]="describedBy"
1689
- [formControl]="controlAsFormControl"
1690
- (blur)="onBlur()"></textarea>
1691
-
1692
- <div *ngIf="inputKind === 'select'" class="jsm-select-wrap">
1693
- <select class="jsm-select" [id]="nodeId"
1694
- [class.jsm-select--error]="controlAsFormControl.invalid && (controlAsFormControl.dirty || isTouched || showErrors)"
1695
- [attr.aria-required]="required ? 'true' : null" [attr.aria-invalid]="visibleError ? 'true' : null"
1696
- [attr.aria-describedby]="describedBy"
1697
- [formControl]="controlAsFormControl"
1698
- (blur)="onBlur()">
1699
- <option *ngFor="let option of effectiveSchema.enum" [ngValue]="option">{{ option }}</option>
1700
- </select>
1701
- <svg class="jsm-select-chevron" viewBox="0 0 16 16" fill="none" aria-hidden="true">
1702
- <path d="M4 6l4 4 4-4" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
1703
- </svg>
1704
- </div>
1705
-
1706
- <label *ngIf="inputKind === 'checkbox'" class="jsm-toggle-label">
1707
- <span class="jsm-toggle-track" [class.jsm-on]="controlAsFormControl.value">
1708
- <input type="checkbox" class="sr-only" [id]="nodeId" [formControl]="controlAsFormControl"
1709
- [attr.aria-required]="required ? 'true' : null" [attr.aria-invalid]="visibleError ? 'true' : null"
1710
- [attr.aria-describedby]="describedBy" (blur)="onBlur()" />
1711
- <span class="jsm-toggle-thumb"></span>
1712
- </span>
1713
- <span class="jsm-toggle-text">
1714
- {{ schema.title || label }}<span *ngIf="required" class="jsm-required" aria-hidden="true">*</span>
1715
- </span>
1716
- </label>
1717
- </ng-container>
1718
-
1719
- <p *ngIf="inputKind === 'checkbox' && schema.description" class="jsm-field-hint" [id]="nodeId + '-hint'">{{ schema.description }}</p>
1720
- <p *ngIf="effectiveSchema.examples?.length" class="jsm-example" [id]="nodeId + '-example'">{{ l.example }} {{ effectiveSchema.examples?.[0] }}</p>
1721
- <p *ngIf="effectiveSchema.contentMediaType" class="jsm-example" [id]="nodeId + '-media'">
1722
- {{ effectiveSchema.contentMediaType }}<span *ngIf="effectiveSchema.contentEncoding"> ({{ effectiveSchema.contentEncoding }})</span>
1723
- </p>
1724
-
1725
- <div *ngIf="visibleError" class="jsm-error-list" role="alert" [id]="nodeId + '-error'">
1726
- <p *ngFor="let error of allErrors" class="jsm-error-msg">
1727
- <svg class="jsm-error-icon" viewBox="0 0 16 16" fill="none" aria-hidden="true">
1728
- <circle cx="8" cy="8" r="6.5" stroke="currentColor" stroke-width="1.5"/>
1729
- <path d="M8 5v3.5M8 11v.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
1730
- </svg>
1731
- {{ error }}
1732
- </p>
1733
- </div>
1733
+ }
1734
+ </div>
1735
+ }
1736
+ <!-- ═══ primitive ═══ -->
1737
+ @default {
1738
+ <div class="jsm-field">
1739
+ @if (inputKind !== 'checkbox') {
1740
+ <label class="jsm-field-label" [attr.for]="nodeId">
1741
+ {{ schema.title || label }}@if (required) {
1742
+ <span class="jsm-required" aria-hidden="true">*</span>
1743
+ }
1744
+ </label>
1745
+ @if (schema.description) {
1746
+ <p class="jsm-field-hint" [id]="nodeId + '-hint'">{{ schema.description }}</p>
1747
+ }
1748
+ }
1749
+ @if (metaBadges.length) {
1750
+ <div class="jsm-badge-row">
1751
+ @for (badge of metaBadges; track badge) {
1752
+ <span class="jsm-badge" [ngClass]="badgeClass(badge)">{{ badge }}</span>
1753
+ }
1754
+ </div>
1755
+ }
1756
+ <ng-container>
1757
+ @if (inputKind === 'number') {
1758
+ <input jsmNumeric type="text" inputmode="decimal" class="jsm-input" [id]="nodeId"
1759
+ [class.jsm-input--error]="controlAsFormControl.invalid && (controlAsFormControl.dirty || isTouched || showErrors)"
1760
+ [jsmDecimalSeparator]="decimalSeparator" [attr.data-step]="numericStep"
1761
+ [attr.aria-required]="required ? 'true' : null" [attr.aria-invalid]="visibleError ? 'true' : null"
1762
+ [attr.aria-describedby]="describedBy"
1763
+ [formControl]="controlAsFormControl"
1764
+ (blur)="onBlur()" />
1765
+ }
1766
+ @if (inputKind === 'text') {
1767
+ <input class="jsm-input" [id]="nodeId"
1768
+ [class.jsm-input--error]="controlAsFormControl.invalid && (controlAsFormControl.dirty || isTouched || showErrors)"
1769
+ [attr.type]="inputType"
1770
+ [attr.aria-required]="required ? 'true' : null" [attr.aria-invalid]="visibleError ? 'true' : null"
1771
+ [attr.aria-describedby]="describedBy"
1772
+ [formControl]="controlAsFormControl"
1773
+ (blur)="onBlur()" />
1774
+ }
1775
+ @if (inputKind === 'textarea') {
1776
+ <textarea rows="4" class="jsm-textarea" [id]="nodeId"
1777
+ [class.jsm-textarea--error]="controlAsFormControl.invalid && (controlAsFormControl.dirty || isTouched || showErrors)"
1778
+ [attr.aria-required]="required ? 'true' : null" [attr.aria-invalid]="visibleError ? 'true' : null"
1779
+ [attr.aria-describedby]="describedBy"
1780
+ [formControl]="controlAsFormControl"
1781
+ (blur)="onBlur()"></textarea>
1782
+ }
1783
+ @if (inputKind === 'select') {
1784
+ <div class="jsm-select-wrap">
1785
+ <select class="jsm-select" [id]="nodeId"
1786
+ [class.jsm-select--error]="controlAsFormControl.invalid && (controlAsFormControl.dirty || isTouched || showErrors)"
1787
+ [attr.aria-required]="required ? 'true' : null" [attr.aria-invalid]="visibleError ? 'true' : null"
1788
+ [attr.aria-describedby]="describedBy"
1789
+ [formControl]="controlAsFormControl"
1790
+ (blur)="onBlur()">
1791
+ @for (option of effectiveSchema.enum; track option) {
1792
+ <option [ngValue]="option">{{ option }}</option>
1793
+ }
1794
+ </select>
1795
+ <svg class="jsm-select-chevron" viewBox="0 0 16 16" fill="none" aria-hidden="true">
1796
+ <path d="M4 6l4 4 4-4" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
1797
+ </svg>
1734
1798
  </div>
1735
-
1736
- </ng-container>
1737
- `, 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:#dc2626;margin-left:.125rem}.jsm-example{font-size:.6875rem;color:#6b7280;margin:0}.jsm-input,.jsm-textarea,.jsm-select{width:100%;border-radius:.5rem;border:1.5px solid #6b7280;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:#4b5563}.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:#dc2626!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:#6b7280;pointer-events:none}.jsm-toggle-label{display:inline-flex;align-items:center;gap:.625rem;cursor:pointer;-webkit-user-select:none;user-select:none;min-height:1.5rem}.jsm-toggle-track{position:relative;display:inline-flex;align-items:center;width:2.25rem;height:1.25rem;border-radius:9999px;flex-shrink:0;background:#6b7280;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:#b91c1c;min-height:1.5rem;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:#4b5563}.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:#6b7280;font-size:.8125rem;text-align:center}.jsm-contains-hint{display:flex;align-items:center;gap:.375rem;font-size:.75rem;color:#4f46e5;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 #6b7280;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}.jsm-input:focus-visible,.jsm-textarea:focus-visible,.jsm-select:focus-visible,.jsm-btn-primary:focus-visible,.jsm-btn-danger:focus-visible,.jsm-tab:focus-visible{outline:2px solid #4f46e5;outline-offset:2px}.jsm-toggle-label:has(input:focus-visible) .jsm-toggle-track,.jsm-anyof-option:has(input:focus-visible){outline:2px solid #4f46e5;outline-offset:2px}.dark .jsm-field-label{color:#d1d5db}.dark .jsm-required{color:#f87171}.dark .jsm-example{color:#9ca3af}.dark .jsm-input:focus-visible,.dark .jsm-textarea:focus-visible,.dark .jsm-select:focus-visible,.dark .jsm-btn-primary:focus-visible,.dark .jsm-btn-danger:focus-visible,.dark .jsm-tab:focus-visible,.dark .jsm-toggle-label:has(input:focus-visible) .jsm-toggle-track,.dark .jsm-anyof-option:has(input:focus-visible){outline-color:#818cf8}.dark .jsm-btn-danger{color:#f87171;border-color:#f87171}.dark .jsm-btn-danger:hover{background:#f8717126}.dark .jsm-field-hint{color:#9ca3af}.dark .jsm-toggle-track{background:#64748b}.dark .jsm-toggle-track.jsm-on{background:#6366f1}.dark .jsm-toggle-text{color:#d1d5db}.dark .jsm-input,.dark .jsm-textarea,.dark .jsm-select{border-color:#64748b;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:#64748b;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:#94a3b8}.dark .jsm-contains-hint{background:#6366f126;color:#818cf8}.dark .jsm-add-prop{background:#1e293b66;border-color:#475569}.dark .jsm-add-prop-label{color:#94a3b8}.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:#94a3b8}\n"] }]
1738
- }], ctorParameters: () => [{ type: JsonSchemaFormService }, { type: JsonSchemaValidationService }, { type: i0.ChangeDetectorRef }, { type: JsonSchemaFormOptions, decorators: [{
1739
- type: Optional
1740
- }] }], propDecorators: { schema: [{
1741
- type: Input,
1742
- args: [{ required: true }]
1743
- }], control: [{
1744
- type: Input,
1745
- args: [{ required: true }]
1746
- }], label: [{
1747
- type: Input
1748
- }], required: [{
1749
- type: Input
1750
- }], path: [{
1751
- type: Input
1752
- }], errorsMap: [{
1753
- type: Input
1754
- }], parent: [{
1755
- type: Input
1756
- }], controlKey: [{
1757
- type: Input
1758
- }], allowAdditionalProperties: [{
1759
- type: Input
1760
- }], showErrors: [{
1761
- type: Input
1762
- }], controlReplaced: [{
1763
- type: Output
1764
- }] } });
1765
-
1766
- const OMIT = Symbol('omit');
1767
- /** Complete decimal numbers only: "13", "-0.5", "0,5", "3," (comma or dot as separator). */
1799
+ }
1800
+ @if (inputKind === 'checkbox') {
1801
+ <label class="jsm-toggle-label">
1802
+ <span class="jsm-toggle-track" [class.jsm-on]="controlAsFormControl.value">
1803
+ <input type="checkbox" class="sr-only" [id]="nodeId" [formControl]="controlAsFormControl"
1804
+ [attr.aria-required]="required ? 'true' : null" [attr.aria-invalid]="visibleError ? 'true' : null"
1805
+ [attr.aria-describedby]="describedBy" (blur)="onBlur()" />
1806
+ <span class="jsm-toggle-thumb"></span>
1807
+ </span>
1808
+ <span class="jsm-toggle-text">
1809
+ {{ schema.title || label }}@if (required) {
1810
+ <span class="jsm-required" aria-hidden="true">*</span>
1811
+ }
1812
+ </span>
1813
+ </label>
1814
+ }
1815
+ </ng-container>
1816
+ @if (inputKind === 'checkbox' && schema.description) {
1817
+ <p class="jsm-field-hint" [id]="nodeId + '-hint'">{{ schema.description }}</p>
1818
+ }
1819
+ @if (effectiveSchema.examples?.length) {
1820
+ <p class="jsm-example" [id]="nodeId + '-example'">{{ l.example }} {{ effectiveSchema.examples?.[0] }}</p>
1821
+ }
1822
+ @if (effectiveSchema.contentMediaType) {
1823
+ <p class="jsm-example" [id]="nodeId + '-media'">
1824
+ {{ effectiveSchema.contentMediaType }}@if (effectiveSchema.contentEncoding) {
1825
+ <span> ({{ effectiveSchema.contentEncoding }})</span>
1826
+ }
1827
+ </p>
1828
+ }
1829
+ @if (visibleError) {
1830
+ <div class="jsm-error-list" role="alert" [id]="nodeId + '-error'">
1831
+ @for (error of allErrors; track error) {
1832
+ <p class="jsm-error-msg">
1833
+ <svg class="jsm-error-icon" viewBox="0 0 16 16" fill="none" aria-hidden="true">
1834
+ <circle cx="8" cy="8" r="6.5" stroke="currentColor" stroke-width="1.5"/>
1835
+ <path d="M8 5v3.5M8 11v.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
1836
+ </svg>
1837
+ {{ error }}
1838
+ </p>
1839
+ }
1840
+ </div>
1841
+ }
1842
+ </div>
1843
+ }
1844
+ }
1845
+ `,
1846
+ changeDetection: ChangeDetectionStrategy.OnPush,
1847
+ 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:#dc2626;margin-left:.125rem}.jsm-example{font-size:.6875rem;color:#6b7280;margin:0}.jsm-input,.jsm-textarea,.jsm-select{width:100%;border-radius:.5rem;border:1.5px solid #6b7280;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:#4b5563}.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:#dc2626!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:#6b7280;pointer-events:none}.jsm-toggle-label{display:inline-flex;align-items:center;gap:.625rem;cursor:pointer;-webkit-user-select:none;user-select:none;min-height:1.5rem}.jsm-toggle-track{position:relative;display:inline-flex;align-items:center;width:2.25rem;height:1.25rem;border-radius:9999px;flex-shrink:0;background:#6b7280;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:#b91c1c;min-height:1.5rem;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:#4b5563}.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:#6b7280;font-size:.8125rem;text-align:center}.jsm-contains-hint{display:flex;align-items:center;gap:.375rem;font-size:.75rem;color:#4f46e5;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 #6b7280;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}.jsm-input:focus-visible,.jsm-textarea:focus-visible,.jsm-select:focus-visible,.jsm-btn-primary:focus-visible,.jsm-btn-danger:focus-visible,.jsm-tab:focus-visible{outline:2px solid #4f46e5;outline-offset:2px}.jsm-toggle-label:has(input:focus-visible) .jsm-toggle-track,.jsm-anyof-option:has(input:focus-visible){outline:2px solid #4f46e5;outline-offset:2px}.dark .jsm-field-label{color:#d1d5db}.dark .jsm-required{color:#f87171}.dark .jsm-example{color:#9ca3af}.dark .jsm-input:focus-visible,.dark .jsm-textarea:focus-visible,.dark .jsm-select:focus-visible,.dark .jsm-btn-primary:focus-visible,.dark .jsm-btn-danger:focus-visible,.dark .jsm-tab:focus-visible,.dark .jsm-toggle-label:has(input:focus-visible) .jsm-toggle-track,.dark .jsm-anyof-option:has(input:focus-visible){outline-color:#818cf8}.dark .jsm-btn-danger{color:#f87171;border-color:#f87171}.dark .jsm-btn-danger:hover{background:#f8717126}.dark .jsm-field-hint{color:#9ca3af}.dark .jsm-toggle-track{background:#64748b}.dark .jsm-toggle-track.jsm-on{background:#6366f1}.dark .jsm-toggle-text{color:#d1d5db}.dark .jsm-input,.dark .jsm-textarea,.dark .jsm-select{border-color:#64748b;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:#64748b;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:#94a3b8}.dark .jsm-contains-hint{background:#6366f126;color:#818cf8}.dark .jsm-add-prop{background:#1e293b66;border-color:#475569}.dark .jsm-add-prop-label{color:#94a3b8}.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:#94a3b8}\n"]
1848
+ }]
1849
+ }],
1850
+ ctorParameters: () => [
1851
+ { type: JsonSchemaFormService },
1852
+ { type: JsonSchemaValidationService },
1853
+ { type: i0.ChangeDetectorRef },
1854
+ {
1855
+ type: JsonSchemaFormOptions,
1856
+ decorators: [{ type: Optional }]
1857
+ }
1858
+ ],
1859
+ propDecorators: {
1860
+ schema: [{
1861
+ type: Input,
1862
+ args: [{ required: true }]
1863
+ }],
1864
+ control: [{
1865
+ type: Input,
1866
+ args: [{ required: true }]
1867
+ }],
1868
+ label: [{ type: Input }],
1869
+ required: [{ type: Input }],
1870
+ path: [{ type: Input }],
1871
+ errorsMap: [{ type: Input }],
1872
+ parent: [{ type: Input }],
1873
+ controlKey: [{ type: Input }],
1874
+ allowAdditionalProperties: [{ type: Input }],
1875
+ showErrors: [{ type: Input }],
1876
+ controlReplaced: [{ type: Output }]
1877
+ }
1878
+ });
1879
+ const OMIT = Symbol("omit");
1768
1880
  const NUMERIC_STRING = /^[+-]?(\d+([.,]\d*)?|[.,]\d+)$/;
1769
- const isSchema = (schema) => !!schema && typeof schema === 'object';
1770
- const isRecord = (value) => !!value && typeof value === 'object' && !Array.isArray(value);
1881
+ const isSchema = (schema) => !!schema && typeof schema === "object";
1882
+ const isRecord = (value) => !!value && typeof value === "object" && !Array.isArray(value);
1771
1883
  const primaryType = (schema) => {
1772
- const type = schema.type;
1773
- if (Array.isArray(type)) {
1774
- return type.find((item) => item !== 'null') ?? type[0];
1775
- }
1776
- return type;
1884
+ const type = schema.type;
1885
+ if (Array.isArray(type)) return type.find((item) => item !== "null") ?? type[0];
1886
+ return type;
1777
1887
  };
1778
- const isNumericType = (type) => type === 'number' || type === 'integer';
1779
- /** Converts a numeric string to a number; empty values are omitted; anything else is left as typed. */
1888
+ const isNumericType = (type) => type === "number" || type === "integer";
1780
1889
  function normalizeNumber(value) {
1781
- if (value === null || value === undefined) {
1782
- return OMIT;
1783
- }
1784
- if (typeof value !== 'string') {
1785
- return value;
1786
- }
1787
- const text = value.trim();
1788
- if (text === '') {
1789
- return OMIT;
1790
- }
1791
- if (NUMERIC_STRING.test(text)) {
1792
- return Number(text.replace(',', '.'));
1793
- }
1794
- return value;
1890
+ if (value === null || value === void 0) return OMIT;
1891
+ if (typeof value !== "string") return value;
1892
+ const text = value.trim();
1893
+ if (text === "") return OMIT;
1894
+ if (NUMERIC_STRING.test(text)) return Number(text.replace(",", "."));
1895
+ return value;
1795
1896
  }
1796
- /** Picks the oneOf/anyOf variant that declares the most of the object's keys. */
1797
1897
  function pickVariant(variants, value) {
1798
- let best;
1799
- let bestScore = 0;
1800
- for (const variant of variants) {
1801
- const declared = Object.keys(variant.properties ?? {});
1802
- const score = declared.filter((key) => key in value).length;
1803
- if (score > bestScore) {
1804
- best = variant;
1805
- bestScore = score;
1806
- }
1807
- }
1808
- return best;
1898
+ let best;
1899
+ let bestScore = 0;
1900
+ for (const variant of variants) {
1901
+ const score = Object.keys(variant.properties ?? {}).filter((key) => key in value).length;
1902
+ if (score > bestScore) {
1903
+ best = variant;
1904
+ bestScore = score;
1905
+ }
1906
+ }
1907
+ return best;
1809
1908
  }
1810
1909
  function normalizeObject(schema, value) {
1811
- const result = { ...value };
1812
- const patterns = Object.entries(schema.patternProperties ?? {}).map(([pattern, sub]) => [new RegExp(pattern), sub]);
1813
- for (const key of Object.keys(result)) {
1814
- let sub = schema.properties?.[key];
1815
- if (sub === undefined) {
1816
- sub = patterns.find(([regex]) => regex.test(key))?.[1];
1817
- }
1818
- if (sub === undefined && isSchema(schema.additionalProperties)) {
1819
- sub = schema.additionalProperties;
1820
- }
1821
- if (sub === undefined) {
1822
- continue;
1823
- }
1824
- const next = normalizeNode(sub, result[key]);
1825
- if (next === OMIT) {
1826
- delete result[key];
1827
- }
1828
- else {
1829
- result[key] = next;
1830
- }
1831
- }
1832
- return result;
1910
+ const result = { ...value };
1911
+ const patterns = Object.entries(schema.patternProperties ?? {}).map(([pattern, sub]) => [new RegExp(pattern), sub]);
1912
+ for (const key of Object.keys(result)) {
1913
+ let sub = schema.properties?.[key];
1914
+ if (sub === void 0) sub = patterns.find(([regex]) => regex.test(key))?.[1];
1915
+ if (sub === void 0 && isSchema(schema.additionalProperties)) sub = schema.additionalProperties;
1916
+ if (sub === void 0) continue;
1917
+ const next = normalizeNode(sub, result[key]);
1918
+ if (next === OMIT) delete result[key];
1919
+ else result[key] = next;
1920
+ }
1921
+ return result;
1833
1922
  }
1834
1923
  function normalizeArray(schema, value) {
1835
- const prefixItems = schema.prefixItems ?? [];
1836
- return value.map((item, index) => {
1837
- const sub = (prefixItems[index] ?? schema.items);
1838
- const next = normalizeNode(sub, item);
1839
- // Array positions cannot be dropped without shifting the others.
1840
- return next === OMIT ? null : next;
1841
- });
1924
+ const prefixItems = schema.prefixItems ?? [];
1925
+ return value.map((item, index) => {
1926
+ const next = normalizeNode(prefixItems[index] ?? schema.items, item);
1927
+ return next === OMIT ? null : next;
1928
+ });
1842
1929
  }
1843
1930
  function normalizeNode(schema, value) {
1844
- if (!isSchema(schema)) {
1845
- return value;
1846
- }
1847
- const type = primaryType(schema);
1848
- if (isNumericType(type)) {
1849
- return normalizeNumber(value);
1850
- }
1851
- let result = value;
1852
- for (const sub of schema.allOf ?? []) {
1853
- if (result === OMIT) {
1854
- return OMIT;
1855
- }
1856
- result = normalizeNode(sub, result);
1857
- }
1858
- if (result === OMIT) {
1859
- return OMIT;
1860
- }
1861
- const variants = [...(schema.oneOf ?? []), ...(schema.anyOf ?? [])];
1862
- if (variants.length > 0 && type === undefined) {
1863
- if (isRecord(result)) {
1864
- const variant = pickVariant(variants, result);
1865
- if (variant) {
1866
- result = normalizeNode(variant, result);
1867
- }
1868
- }
1869
- else if (!Array.isArray(result)) {
1870
- // Primitive: convert only when the variants are numeric and none accepts a string.
1871
- const types = variants.map((variant) => primaryType(variant));
1872
- if (types.some(isNumericType) && !types.includes('string')) {
1873
- result = normalizeNumber(result);
1874
- }
1875
- }
1876
- }
1877
- if (isRecord(result)) {
1878
- return normalizeObject(schema, result);
1879
- }
1880
- if (Array.isArray(result)) {
1881
- return normalizeArray(schema, result);
1882
- }
1883
- return result;
1931
+ if (!isSchema(schema)) return value;
1932
+ const type = primaryType(schema);
1933
+ if (isNumericType(type)) return normalizeNumber(value);
1934
+ let result = value;
1935
+ for (const sub of schema.allOf ?? []) {
1936
+ if (result === OMIT) return OMIT;
1937
+ result = normalizeNode(sub, result);
1938
+ }
1939
+ if (result === OMIT) return OMIT;
1940
+ const variants = [...schema.oneOf ?? [], ...schema.anyOf ?? []];
1941
+ if (variants.length > 0 && type === void 0) {
1942
+ if (isRecord(result)) {
1943
+ const variant = pickVariant(variants, result);
1944
+ if (variant) result = normalizeNode(variant, result);
1945
+ } else if (!Array.isArray(result)) {
1946
+ const types = variants.map((variant) => primaryType(variant));
1947
+ if (types.some(isNumericType) && !types.includes("string")) result = normalizeNumber(result);
1948
+ }
1949
+ }
1950
+ if (isRecord(result)) return normalizeObject(schema, result);
1951
+ if (Array.isArray(result)) return normalizeArray(schema, result);
1952
+ return result;
1884
1953
  }
1885
- /**
1886
- * Coerces values to the types declared by the schema so that the emitted value respects it:
1887
- * - `number`/`integer` numeric strings become JSON numbers (comma or dot as separator);
1888
- * - empty numeric values (`""`, whitespace, `null`) leave the key absent, never `""` or `0`;
1889
- * - integers with decimals are converted to their number but never truncated (validation reports them);
1890
- * - non-numeric strings are left unchanged (validation reports them).
1891
- * Descends into properties, patternProperties, additionalProperties, items, prefixItems and combinators.
1892
- * The same function feeds both the Ajv validation and the emitted value.
1893
- */
1894
1954
  function normalizeValue(schema, value) {
1895
- const normalized = normalizeNode(schema, value);
1896
- return normalized === OMIT ? null : normalized;
1897
- }
1898
-
1899
- // AUTO-GENERATED — do not edit manually. Run: node scripts/generate-lib-styles.mjs
1900
- 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";
1901
-
1902
- const STYLE_ID = 'jsm-tailwind-styles';
1903
- class JsonSchemaStylesService {
1904
- platformId;
1905
- injected = false;
1906
- constructor(platformId) {
1907
- this.platformId = platformId;
1908
- }
1909
- inject() {
1910
- if (this.injected || !isPlatformBrowser(this.platformId))
1911
- return;
1912
- if (typeof document === 'undefined')
1913
- return;
1914
- if (document.getElementById(STYLE_ID)) {
1915
- this.injected = true;
1916
- return;
1917
- }
1918
- const style = document.createElement('style');
1919
- style.id = STYLE_ID;
1920
- style.textContent = JSM_CSS;
1921
- document.head.appendChild(style);
1922
- this.injected = true;
1923
- }
1924
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: JsonSchemaStylesService, deps: [{ token: PLATFORM_ID }], target: i0.ɵɵFactoryTarget.Injectable });
1925
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: JsonSchemaStylesService, providedIn: 'root' });
1955
+ const normalized = normalizeNode(schema, value);
1956
+ return normalized === OMIT ? null : normalized;
1926
1957
  }
1927
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: JsonSchemaStylesService, decorators: [{
1928
- type: Injectable,
1929
- args: [{ providedIn: 'root' }]
1930
- }], ctorParameters: () => [{ type: undefined, decorators: [{
1931
- type: Inject,
1932
- args: [PLATFORM_ID]
1933
- }] }] });
1934
-
1935
- class JsonSchemaFormComponent {
1936
- schemaService;
1937
- resolver;
1938
- validation;
1939
- cdr;
1940
- options;
1941
- schema;
1942
- value;
1943
- data;
1944
- allowAdditionalProperties = false;
1945
- /**
1946
- * Decimal separator of number/integer fields. `,` shows and accepts `0,5` (both `,` and `.` are accepted as input
1947
- * either way); the emitted value is always a JSON number.
1948
- */
1949
- decimalSeparator = '.';
1950
- /** Texts shown by the form (translate them here); omitted keys keep the English default. */
1951
- labels = {};
1952
- formReady = new EventEmitter();
1953
- valueChange = new EventEmitter();
1954
- schemaReady = new EventEmitter();
1955
- schemaErrors = new EventEmitter();
1956
- form;
1957
- rootControl;
1958
- resolvedSchema;
1959
- errorsMap = new Map();
1960
- schemaErrorList = [];
1961
- loading = false;
1962
- /** When true, all field errors are shown regardless of touched/dirty state. */
1963
- showErrors = false;
1964
- valueSub;
1965
- /** Last value emitted through valueChange (same reference the parent gets back through `data`/`value`). */
1966
- lastEmitted;
1967
- constructor(schemaService, resolver, validation, cdr, options, stylesService) {
1968
- this.schemaService = schemaService;
1969
- this.resolver = resolver;
1970
- this.validation = validation;
1971
- this.cdr = cdr;
1972
- this.options = options;
1973
- stylesService.inject();
1974
- }
1975
- ngOnChanges(changes) {
1976
- if (changes['decimalSeparator']) {
1977
- this.options.decimalSeparator = this.decimalSeparator;
1978
- }
1979
- if (changes['labels']) {
1980
- this.options.labels = { ...DEFAULT_LABELS, ...this.labels };
1981
- }
1982
- if (changes['schema']) {
1983
- // Schema changed — full rebuild required
1984
- void this.buildForm();
1985
- }
1986
- else if ((changes['value'] || changes['data']) && this.form) {
1987
- // Only data changed and form already exists — patch values without rebuilding
1988
- const newData = this.value !== undefined ? this.value : this.data;
1989
- // The parent usually feeds back what valueChange just emitted: patching it would overwrite the text being
1990
- // typed (e.g. "45," would become "45"), so a round trip of the emitted value is ignored.
1991
- if (newData !== this.lastEmitted) {
1992
- this.form.patchValue(newData, { emitEvent: false });
1958
+ const JSM_CSS = "/*! tailwindcss v4.2.2 | MIT License | https://tailwindcss.com */\n@layer properties;\n@layer theme, base, components, utilities;\n@layer theme {\n :root, :host {\n --font-sans: ui-sans-serif, system-ui, sans-serif, \"Apple Color Emoji\",\n \"Segoe UI Emoji\", \"Segoe UI Symbol\", \"Noto Color Emoji\";\n --font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, \"Liberation Mono\",\n \"Courier New\", monospace;\n --color-red-50: oklch(97.1% 0.013 17.38);\n --color-red-300: oklch(80.8% 0.114 19.571);\n --color-red-700: oklch(50.5% 0.213 27.518);\n --color-red-900: oklch(39.6% 0.141 25.723);\n --color-amber-50: oklch(98.7% 0.022 95.277);\n --color-amber-200: oklch(92.4% 0.12 95.746);\n --color-amber-300: oklch(87.9% 0.169 91.605);\n --color-amber-400: oklch(82.8% 0.189 84.429);\n --color-amber-600: oklch(66.6% 0.179 58.318);\n --color-amber-700: oklch(55.5% 0.163 48.998);\n --color-amber-800: oklch(47.3% 0.137 46.201);\n --color-amber-900: oklch(41.4% 0.112 45.904);\n --color-emerald-50: oklch(97.9% 0.021 166.113);\n --color-emerald-400: oklch(76.5% 0.177 163.223);\n --color-emerald-700: oklch(50.8% 0.118 165.612);\n --color-emerald-900: oklch(37.8% 0.077 168.94);\n --color-indigo-50: oklch(96.2% 0.018 272.314);\n --color-indigo-100: oklch(93% 0.034 272.788);\n --color-indigo-200: oklch(87% 0.065 274.039);\n --color-indigo-300: oklch(78.5% 0.115 274.713);\n --color-indigo-400: oklch(67.3% 0.182 276.935);\n --color-indigo-500: oklch(58.5% 0.233 277.117);\n --color-indigo-600: oklch(51.1% 0.262 276.966);\n --color-indigo-700: oklch(45.7% 0.24 277.023);\n --color-indigo-900: oklch(35.9% 0.144 278.697);\n --color-rose-50: oklch(96.9% 0.015 12.422);\n --color-rose-400: oklch(71.2% 0.194 13.428);\n --color-rose-500: oklch(64.5% 0.246 16.439);\n --color-rose-600: oklch(58.6% 0.253 17.585);\n --color-rose-700: oklch(51.4% 0.222 16.935);\n --color-rose-900: oklch(41% 0.159 10.272);\n --color-slate-50: oklch(98.4% 0.003 247.858);\n --color-slate-100: oklch(96.8% 0.007 247.896);\n --color-slate-200: oklch(92.9% 0.013 255.508);\n --color-slate-300: oklch(86.9% 0.022 252.894);\n --color-slate-400: oklch(70.4% 0.04 256.788);\n --color-slate-500: oklch(55.4% 0.046 257.417);\n --color-slate-600: oklch(44.6% 0.043 257.281);\n --color-slate-700: oklch(37.2% 0.044 257.287);\n --color-slate-800: oklch(27.9% 0.041 260.031);\n --color-slate-900: oklch(20.8% 0.042 265.755);\n --color-slate-950: oklch(12.9% 0.042 264.695);\n --color-white: #fff;\n --spacing: 0.25rem;\n --container-2xl: 42rem;\n --container-3xl: 48rem;\n --container-6xl: 72rem;\n --text-xs: 0.75rem;\n --text-xs--line-height: calc(1 / 0.75);\n --text-sm: 0.875rem;\n --text-sm--line-height: calc(1.25 / 0.875);\n --text-lg: 1.125rem;\n --text-lg--line-height: calc(1.75 / 1.125);\n --text-xl: 1.25rem;\n --text-xl--line-height: calc(1.75 / 1.25);\n --text-3xl: 1.875rem;\n --text-3xl--line-height: calc(2.25 / 1.875);\n --text-4xl: 2.25rem;\n --text-4xl--line-height: calc(2.5 / 2.25);\n --font-weight-medium: 500;\n --font-weight-semibold: 600;\n --font-weight-bold: 700;\n --tracking-tight: -0.025em;\n --tracking-wide: 0.025em;\n --radius-lg: 0.5rem;\n --radius-xl: 0.75rem;\n --radius-2xl: 1rem;\n --animate-spin: spin 1s linear infinite;\n --default-transition-duration: 150ms;\n --default-transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);\n --default-font-family: var(--font-sans);\n --default-mono-font-family: var(--font-mono);\n }\n}\n@layer base {\n *, ::after, ::before, ::backdrop, ::file-selector-button {\n box-sizing: border-box;\n margin: 0;\n padding: 0;\n border: 0 solid;\n }\n html, :host {\n line-height: 1.5;\n -webkit-text-size-adjust: 100%;\n tab-size: 4;\n font-family: var(--default-font-family, ui-sans-serif, system-ui, sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\", \"Noto Color Emoji\");\n font-feature-settings: var(--default-font-feature-settings, normal);\n font-variation-settings: var(--default-font-variation-settings, normal);\n -webkit-tap-highlight-color: transparent;\n }\n hr {\n height: 0;\n color: inherit;\n border-top-width: 1px;\n }\n abbr:where([title]) {\n -webkit-text-decoration: underline dotted;\n text-decoration: underline dotted;\n }\n h1, h2, h3, h4, h5, h6 {\n font-size: inherit;\n font-weight: inherit;\n }\n a {\n color: inherit;\n -webkit-text-decoration: inherit;\n text-decoration: inherit;\n }\n b, strong {\n font-weight: bolder;\n }\n code, kbd, samp, pre {\n font-family: var(--default-mono-font-family, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, \"Liberation Mono\", \"Courier New\", monospace);\n font-feature-settings: var(--default-mono-font-feature-settings, normal);\n font-variation-settings: var(--default-mono-font-variation-settings, normal);\n font-size: 1em;\n }\n small {\n font-size: 80%;\n }\n sub, sup {\n font-size: 75%;\n line-height: 0;\n position: relative;\n vertical-align: baseline;\n }\n sub {\n bottom: -0.25em;\n }\n sup {\n top: -0.5em;\n }\n table {\n text-indent: 0;\n border-color: inherit;\n border-collapse: collapse;\n }\n :-moz-focusring {\n outline: auto;\n }\n progress {\n vertical-align: baseline;\n }\n summary {\n display: list-item;\n }\n ol, ul, menu {\n list-style: none;\n }\n img, svg, video, canvas, audio, iframe, embed, object {\n display: block;\n vertical-align: middle;\n }\n img, video {\n max-width: 100%;\n height: auto;\n }\n button, input, select, optgroup, textarea, ::file-selector-button {\n font: inherit;\n font-feature-settings: inherit;\n font-variation-settings: inherit;\n letter-spacing: inherit;\n color: inherit;\n border-radius: 0;\n background-color: transparent;\n opacity: 1;\n }\n :where(select:is([multiple], [size])) optgroup {\n font-weight: bolder;\n }\n :where(select:is([multiple], [size])) optgroup option {\n padding-inline-start: 20px;\n }\n ::file-selector-button {\n margin-inline-end: 4px;\n }\n ::placeholder {\n opacity: 1;\n }\n @supports (not (-webkit-appearance: -apple-pay-button)) or (contain-intrinsic-size: 1px) {\n ::placeholder {\n color: currentcolor;\n @supports (color: color-mix(in lab, red, red)) {\n color: color-mix(in oklab, currentcolor 50%, transparent);\n }\n }\n }\n textarea {\n resize: vertical;\n }\n ::-webkit-search-decoration {\n -webkit-appearance: none;\n }\n ::-webkit-date-and-time-value {\n min-height: 1lh;\n text-align: inherit;\n }\n ::-webkit-datetime-edit {\n display: inline-flex;\n }\n ::-webkit-datetime-edit-fields-wrapper {\n padding: 0;\n }\n ::-webkit-datetime-edit, ::-webkit-datetime-edit-year-field, ::-webkit-datetime-edit-month-field, ::-webkit-datetime-edit-day-field, ::-webkit-datetime-edit-hour-field, ::-webkit-datetime-edit-minute-field, ::-webkit-datetime-edit-second-field, ::-webkit-datetime-edit-millisecond-field, ::-webkit-datetime-edit-meridiem-field {\n padding-block: 0;\n }\n ::-webkit-calendar-picker-indicator {\n line-height: 1;\n }\n :-moz-ui-invalid {\n box-shadow: none;\n }\n button, input:where([type=\"button\"], [type=\"reset\"], [type=\"submit\"]), ::file-selector-button {\n appearance: button;\n }\n ::-webkit-inner-spin-button, ::-webkit-outer-spin-button {\n height: auto;\n }\n [hidden]:where(:not([hidden=\"until-found\"])) {\n display: none !important;\n }\n}\n@layer utilities {\n .visible {\n visibility: visible;\n }\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 .fixed {\n position: fixed;\n }\n .relative {\n position: relative;\n }\n .start {\n inset-inline-start: var(--spacing);\n }\n .col-span-2 {\n grid-column: span 2 / span 2;\n }\n .mx-auto {\n margin-inline: auto;\n }\n .mb-2 {\n margin-bottom: calc(var(--spacing) * 2);\n }\n .mb-4 {\n margin-bottom: calc(var(--spacing) * 4);\n }\n .contents {\n display: contents;\n }\n .flex {\n display: flex;\n }\n .grid {\n display: grid;\n }\n .hidden {\n display: none;\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 .truncate {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n .rounded {\n border-radius: 0.25rem;\n }\n .rounded-2xl {\n border-radius: var(--radius-2xl);\n }\n .rounded-full {\n border-radius: calc(infinity * 1px);\n }\n .rounded-lg {\n border-radius: var(--radius-lg);\n }\n .rounded-xl {\n border-radius: var(--radius-xl);\n }\n .border {\n border-style: var(--tw-border-style);\n border-width: 1px;\n }\n .border-2 {\n border-style: var(--tw-border-style);\n border-width: 2px;\n }\n .border-amber-200 {\n border-color: var(--color-amber-200);\n }\n .border-amber-300 {\n border-color: var(--color-amber-300);\n }\n .border-indigo-200 {\n border-color: var(--color-indigo-200);\n }\n .border-red-300 {\n border-color: var(--color-red-300);\n }\n .border-red-700 {\n border-color: var(--color-red-700);\n }\n .border-slate-100 {\n border-color: var(--color-slate-100);\n }\n .border-slate-200 {\n border-color: var(--color-slate-200);\n }\n .border-slate-300 {\n border-color: var(--color-slate-300);\n }\n .border-t-slate-900 {\n border-top-color: var(--color-slate-900);\n }\n .bg-amber-50 {\n background-color: var(--color-amber-50);\n }\n .bg-emerald-50 {\n background-color: var(--color-emerald-50);\n }\n .bg-indigo-50 {\n background-color: var(--color-indigo-50);\n }\n .bg-indigo-600 {\n background-color: var(--color-indigo-600);\n }\n .bg-red-50 {\n background-color: var(--color-red-50);\n }\n .bg-red-900 {\n background-color: var(--color-red-900);\n }\n .bg-red-900\\/20 {\n background-color: color-mix(in srgb, oklch(39.6% 0.141 25.723) 20%, transparent);\n @supports (color: color-mix(in lab, red, red)) {\n background-color: color-mix(in oklab, var(--color-red-900) 20%, transparent);\n }\n }\n .bg-rose-50 {\n background-color: var(--color-rose-50);\n }\n .bg-slate-50 {\n background-color: var(--color-slate-50);\n }\n .bg-slate-300 {\n background-color: var(--color-slate-300);\n }\n .bg-slate-900 {\n background-color: var(--color-slate-900);\n }\n .bg-white {\n background-color: var(--color-white);\n }\n .bg-white\\/70 {\n background-color: color-mix(in srgb, #fff 70%, transparent);\n @supports (color: color-mix(in lab, red, red)) {\n background-color: color-mix(in oklab, var(--color-white) 70%, transparent);\n }\n }\n .bg-white\\/90 {\n background-color: color-mix(in srgb, #fff 90%, transparent);\n @supports (color: color-mix(in lab, red, red)) {\n background-color: color-mix(in oklab, var(--color-white) 90%, transparent);\n }\n }\n .p-3 {\n padding: calc(var(--spacing) * 3);\n }\n .p-4 {\n padding: calc(var(--spacing) * 4);\n }\n .p-5 {\n padding: calc(var(--spacing) * 5);\n }\n .p-6 {\n padding: calc(var(--spacing) * 6);\n }\n .px-2 {\n padding-inline: calc(var(--spacing) * 2);\n }\n .px-3 {\n padding-inline: calc(var(--spacing) * 3);\n }\n .px-6 {\n padding-inline: calc(var(--spacing) * 6);\n }\n .py-1 {\n padding-block: calc(var(--spacing) * 1);\n }\n .py-1\\.5 {\n padding-block: calc(var(--spacing) * 1.5);\n }\n .py-2 {\n padding-block: calc(var(--spacing) * 2);\n }\n .py-2\\.5 {\n padding-block: calc(var(--spacing) * 2.5);\n }\n .py-6 {\n padding-block: calc(var(--spacing) * 6);\n }\n .py-8 {\n padding-block: calc(var(--spacing) * 8);\n }\n .py-10 {\n padding-block: calc(var(--spacing) * 10);\n }\n .font-mono {\n font-family: var(--font-mono);\n }\n .text-3xl {\n font-size: var(--text-3xl);\n line-height: var(--tw-leading, var(--text-3xl--line-height));\n }\n .text-lg {\n font-size: var(--text-lg);\n line-height: var(--tw-leading, var(--text-lg--line-height));\n }\n .text-sm {\n font-size: var(--text-sm);\n line-height: var(--tw-leading, var(--text-sm--line-height));\n }\n .text-xl {\n font-size: var(--text-xl);\n line-height: var(--tw-leading, var(--text-xl--line-height));\n }\n .text-xs {\n font-size: var(--text-xs);\n line-height: var(--tw-leading, var(--text-xs--line-height));\n }\n .text-\\[10px\\] {\n font-size: 10px;\n }\n .text-\\[11px\\] {\n font-size: 11px;\n }\n .font-bold {\n --tw-font-weight: var(--font-weight-bold);\n font-weight: var(--font-weight-bold);\n }\n .font-medium {\n --tw-font-weight: var(--font-weight-medium);\n font-weight: var(--font-weight-medium);\n }\n .font-semibold {\n --tw-font-weight: var(--font-weight-semibold);\n font-weight: var(--font-weight-semibold);\n }\n .tracking-\\[0\\.2em\\] {\n --tw-tracking: 0.2em;\n letter-spacing: 0.2em;\n }\n .tracking-tight {\n --tw-tracking: var(--tracking-tight);\n letter-spacing: var(--tracking-tight);\n }\n .tracking-wide {\n --tw-tracking: var(--tracking-wide);\n letter-spacing: var(--tracking-wide);\n }\n .whitespace-pre-wrap {\n white-space: pre-wrap;\n }\n .text-amber-600 {\n color: var(--color-amber-600);\n }\n .text-amber-700 {\n color: var(--color-amber-700);\n }\n .text-amber-800 {\n color: var(--color-amber-800);\n }\n .text-emerald-700 {\n color: var(--color-emerald-700);\n }\n .text-indigo-500 {\n color: var(--color-indigo-500);\n }\n .text-indigo-700 {\n color: var(--color-indigo-700);\n }\n .text-rose-500 {\n color: var(--color-rose-500);\n }\n .text-rose-600 {\n color: var(--color-rose-600);\n }\n .text-rose-700 {\n color: var(--color-rose-700);\n }\n .text-slate-400 {\n color: var(--color-slate-400);\n }\n .text-slate-500 {\n color: var(--color-slate-500);\n }\n .text-slate-600 {\n color: var(--color-slate-600);\n }\n .text-slate-700 {\n color: var(--color-slate-700);\n }\n .text-slate-800 {\n color: var(--color-slate-800);\n }\n .text-slate-900 {\n color: var(--color-slate-900);\n }\n .text-white {\n color: var(--color-white);\n }\n .uppercase {\n text-transform: uppercase;\n }\n .italic {\n font-style: italic;\n }\n .underline {\n text-decoration-line: underline;\n }\n .opacity-25 {\n opacity: 25%;\n }\n .opacity-75 {\n opacity: 75%;\n }\n .shadow {\n --tw-shadow: 0 1px 3px 0 var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 1px 2px -1px var(--tw-shadow-color, rgb(0 0 0 / 0.1));\n box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);\n }\n .shadow-sm {\n --tw-shadow: 0 1px 3px 0 var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 1px 2px -1px var(--tw-shadow-color, rgb(0 0 0 / 0.1));\n box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);\n }\n .outline {\n outline-style: var(--tw-outline-style);\n outline-width: 1px;\n }\n .blur {\n --tw-blur: blur(8px);\n filter: var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,);\n }\n .filter {\n filter: var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,);\n }\n .backdrop-blur {\n --tw-backdrop-blur: blur(8px);\n -webkit-backdrop-filter: var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);\n backdrop-filter: var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);\n }\n .backdrop-filter {\n -webkit-backdrop-filter: var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);\n backdrop-filter: var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);\n }\n .transition {\n transition-property: color, background-color, border-color, outline-color, text-decoration-color, fill, stroke, --tw-gradient-from, --tw-gradient-via, --tw-gradient-to, opacity, box-shadow, transform, translate, scale, rotate, filter, -webkit-backdrop-filter, backdrop-filter, display, content-visibility, overlay, pointer-events;\n transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));\n transition-duration: var(--tw-duration, var(--default-transition-duration));\n }\n .transition-all {\n transition-property: all;\n transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));\n transition-duration: var(--tw-duration, var(--default-transition-duration));\n }\n .transition-colors {\n transition-property: color, background-color, border-color, outline-color, text-decoration-color, fill, stroke, --tw-gradient-from, --tw-gradient-via, --tw-gradient-to;\n transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));\n transition-duration: var(--tw-duration, var(--default-transition-duration));\n }\n .transition-transform {\n transition-property: transform, translate, scale, rotate;\n transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));\n transition-duration: var(--tw-duration, var(--default-transition-duration));\n }\n .duration-200 {\n --tw-duration: 200ms;\n transition-duration: 200ms;\n }\n .focus-within\\:border-indigo-300 {\n &:focus-within {\n border-color: var(--color-indigo-300);\n }\n }\n .focus-within\\:ring-2 {\n &:focus-within {\n --tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor);\n box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);\n }\n }\n .hover\\:-translate-y-0\\.5 {\n &:hover {\n @media (hover: hover) {\n --tw-translate-y: calc(var(--spacing) * -0.5);\n translate: var(--tw-translate-x) var(--tw-translate-y);\n }\n }\n }\n .hover\\:border-slate-300 {\n &:hover {\n @media (hover: hover) {\n border-color: var(--color-slate-300);\n }\n }\n }\n .hover\\:border-slate-400 {\n &:hover {\n @media (hover: hover) {\n border-color: var(--color-slate-400);\n }\n }\n }\n .hover\\:bg-indigo-100 {\n &:hover {\n @media (hover: hover) {\n background-color: var(--color-indigo-100);\n }\n }\n }\n .hover\\:bg-slate-50 {\n &:hover {\n @media (hover: hover) {\n background-color: var(--color-slate-50);\n }\n }\n }\n .hover\\:bg-slate-800 {\n &:hover {\n @media (hover: hover) {\n background-color: var(--color-slate-800);\n }\n }\n }\n .hover\\:text-rose-700 {\n &:hover {\n @media (hover: hover) {\n color: var(--color-rose-700);\n }\n }\n }\n .hover\\:shadow {\n &:hover {\n @media (hover: hover) {\n --tw-shadow: 0 1px 3px 0 var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 1px 2px -1px var(--tw-shadow-color, rgb(0 0 0 / 0.1));\n box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);\n }\n }\n }\n .hover\\:shadow-lg {\n &:hover {\n @media (hover: hover) {\n --tw-shadow: 0 10px 15px -3px var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 4px 6px -4px var(--tw-shadow-color, rgb(0 0 0 / 0.1));\n box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);\n }\n }\n }\n .hover\\:shadow-md {\n &:hover {\n @media (hover: hover) {\n --tw-shadow: 0 4px 6px -1px var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 2px 4px -2px var(--tw-shadow-color, rgb(0 0 0 / 0.1));\n box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);\n }\n }\n }\n .focus\\:border-indigo-400 {\n &:focus {\n border-color: var(--color-indigo-400);\n }\n }\n .focus\\:ring-2 {\n &:focus {\n --tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor);\n box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);\n }\n }\n .focus\\:ring-indigo-100 {\n &:focus {\n --tw-ring-color: var(--color-indigo-100);\n }\n }\n .focus\\:ring-indigo-500 {\n &:focus {\n --tw-ring-color: var(--color-indigo-500);\n }\n }\n .focus\\:ring-slate-900 {\n &:focus {\n --tw-ring-color: var(--color-slate-900);\n }\n }\n .focus\\:outline-none {\n &:focus {\n --tw-outline-style: none;\n outline-style: none;\n }\n }\n .sm\\:grid-cols-2 {\n @media (width >= 40rem) {\n grid-template-columns: repeat(2, minmax(0, 1fr));\n }\n }\n .md\\:grid-cols-2 {\n @media (width >= 48rem) {\n grid-template-columns: repeat(2, minmax(0, 1fr));\n }\n }\n .md\\:flex-row {\n @media (width >= 48rem) {\n flex-direction: row;\n }\n }\n .md\\:items-center {\n @media (width >= 48rem) {\n align-items: center;\n }\n }\n .md\\:justify-between {\n @media (width >= 48rem) {\n justify-content: space-between;\n }\n }\n .md\\:p-6 {\n @media (width >= 48rem) {\n padding: calc(var(--spacing) * 6);\n }\n }\n .md\\:text-4xl {\n @media (width >= 48rem) {\n font-size: var(--text-4xl);\n line-height: var(--tw-leading, var(--text-4xl--line-height));\n }\n }\n .lg\\:grid-cols-\\[360px\\,1fr\\] {\n @media (width >= 64rem) {\n grid-template-columns: 360px,1fr;\n }\n }\n .dark\\:border-amber-700 {\n &:where(.dark, .dark *) {\n border-color: var(--color-amber-700);\n }\n }\n .dark\\:border-amber-800 {\n &:where(.dark, .dark *) {\n border-color: var(--color-amber-800);\n }\n }\n .dark\\:border-indigo-700 {\n &:where(.dark, .dark *) {\n border-color: var(--color-indigo-700);\n }\n }\n .dark\\:border-slate-600 {\n &:where(.dark, .dark *) {\n border-color: var(--color-slate-600);\n }\n }\n .dark\\:border-slate-700 {\n &:where(.dark, .dark *) {\n border-color: var(--color-slate-700);\n }\n }\n .dark\\:border-t-indigo-400 {\n &:where(.dark, .dark *) {\n border-top-color: var(--color-indigo-400);\n }\n }\n .dark\\:bg-amber-900\\/20 {\n &:where(.dark, .dark *) {\n background-color: color-mix(in srgb, oklch(41.4% 0.112 45.904) 20%, transparent);\n @supports (color: color-mix(in lab, red, red)) {\n background-color: color-mix(in oklab, var(--color-amber-900) 20%, transparent);\n }\n }\n }\n .dark\\:bg-emerald-900\\/30 {\n &:where(.dark, .dark *) {\n background-color: color-mix(in srgb, oklch(37.8% 0.077 168.94) 30%, transparent);\n @supports (color: color-mix(in lab, red, red)) {\n background-color: color-mix(in oklab, var(--color-emerald-900) 30%, transparent);\n }\n }\n }\n .dark\\:bg-indigo-600 {\n &:where(.dark, .dark *) {\n background-color: var(--color-indigo-600);\n }\n }\n .dark\\:bg-indigo-900\\/30 {\n &:where(.dark, .dark *) {\n background-color: color-mix(in srgb, oklch(35.9% 0.144 278.697) 30%, transparent);\n @supports (color: color-mix(in lab, red, red)) {\n background-color: color-mix(in oklab, var(--color-indigo-900) 30%, transparent);\n }\n }\n }\n .dark\\:bg-rose-900\\/30 {\n &:where(.dark, .dark *) {\n background-color: color-mix(in srgb, oklch(41% 0.159 10.272) 30%, transparent);\n @supports (color: color-mix(in lab, red, red)) {\n background-color: color-mix(in oklab, var(--color-rose-900) 30%, transparent);\n }\n }\n }\n .dark\\:bg-slate-600 {\n &:where(.dark, .dark *) {\n background-color: var(--color-slate-600);\n }\n }\n .dark\\:bg-slate-700 {\n &:where(.dark, .dark *) {\n background-color: var(--color-slate-700);\n }\n }\n .dark\\:bg-slate-800 {\n &:where(.dark, .dark *) {\n background-color: var(--color-slate-800);\n }\n }\n .dark\\:bg-slate-800\\/50 {\n &:where(.dark, .dark *) {\n background-color: color-mix(in srgb, oklch(27.9% 0.041 260.031) 50%, transparent);\n @supports (color: color-mix(in lab, red, red)) {\n background-color: color-mix(in oklab, var(--color-slate-800) 50%, transparent);\n }\n }\n }\n .dark\\:bg-slate-800\\/60 {\n &:where(.dark, .dark *) {\n background-color: color-mix(in srgb, oklch(27.9% 0.041 260.031) 60%, transparent);\n @supports (color: color-mix(in lab, red, red)) {\n background-color: color-mix(in oklab, var(--color-slate-800) 60%, transparent);\n }\n }\n }\n .dark\\:bg-slate-900 {\n &:where(.dark, .dark *) {\n background-color: var(--color-slate-900);\n }\n }\n .dark\\:bg-slate-900\\/90 {\n &:where(.dark, .dark *) {\n background-color: color-mix(in srgb, oklch(20.8% 0.042 265.755) 90%, transparent);\n @supports (color: color-mix(in lab, red, red)) {\n background-color: color-mix(in oklab, var(--color-slate-900) 90%, transparent);\n }\n }\n }\n .dark\\:bg-slate-950 {\n &:where(.dark, .dark *) {\n background-color: var(--color-slate-950);\n }\n }\n .dark\\:text-amber-300 {\n &:where(.dark, .dark *) {\n color: var(--color-amber-300);\n }\n }\n .dark\\:text-amber-400 {\n &:where(.dark, .dark *) {\n color: var(--color-amber-400);\n }\n }\n .dark\\:text-emerald-400 {\n &:where(.dark, .dark *) {\n color: var(--color-emerald-400);\n }\n }\n .dark\\:text-indigo-300 {\n &:where(.dark, .dark *) {\n color: var(--color-indigo-300);\n }\n }\n .dark\\:text-indigo-400 {\n &:where(.dark, .dark *) {\n color: var(--color-indigo-400);\n }\n }\n .dark\\:text-rose-400 {\n &:where(.dark, .dark *) {\n color: var(--color-rose-400);\n }\n }\n .dark\\:text-slate-100 {\n &:where(.dark, .dark *) {\n color: var(--color-slate-100);\n }\n }\n .dark\\:text-slate-200 {\n &:where(.dark, .dark *) {\n color: var(--color-slate-200);\n }\n }\n .dark\\:text-slate-300 {\n &:where(.dark, .dark *) {\n color: var(--color-slate-300);\n }\n }\n .dark\\:text-slate-400 {\n &:where(.dark, .dark *) {\n color: var(--color-slate-400);\n }\n }\n .dark\\:text-slate-500 {\n &:where(.dark, .dark *) {\n color: var(--color-slate-500);\n }\n }\n .dark\\:text-white {\n &:where(.dark, .dark *) {\n color: var(--color-white);\n }\n }\n .dark\\:hover\\:border-slate-500 {\n &:where(.dark, .dark *) {\n &:hover {\n @media (hover: hover) {\n border-color: var(--color-slate-500);\n }\n }\n }\n }\n .dark\\:hover\\:bg-indigo-700 {\n &:where(.dark, .dark *) {\n &:hover {\n @media (hover: hover) {\n background-color: var(--color-indigo-700);\n }\n }\n }\n }\n .dark\\:hover\\:bg-indigo-900\\/50 {\n &:where(.dark, .dark *) {\n &:hover {\n @media (hover: hover) {\n background-color: color-mix(in srgb, oklch(35.9% 0.144 278.697) 50%, transparent);\n @supports (color: color-mix(in lab, red, red)) {\n background-color: color-mix(in oklab, var(--color-indigo-900) 50%, transparent);\n }\n }\n }\n }\n }\n .dark\\:hover\\:bg-slate-700 {\n &:where(.dark, .dark *) {\n &:hover {\n @media (hover: hover) {\n background-color: var(--color-slate-700);\n }\n }\n }\n }\n .dark\\:focus\\:ring-indigo-900\\/40 {\n &:where(.dark, .dark *) {\n &:focus {\n --tw-ring-color: color-mix(in srgb, oklch(35.9% 0.144 278.697) 40%, transparent);\n @supports (color: color-mix(in lab, red, red)) {\n --tw-ring-color: color-mix(in oklab, var(--color-indigo-900) 40%, transparent);\n }\n }\n }\n }\n}\n@property --tw-translate-x {\n syntax: \"*\";\n inherits: false;\n initial-value: 0;\n}\n@property --tw-translate-y {\n syntax: \"*\";\n inherits: false;\n initial-value: 0;\n}\n@property --tw-translate-z {\n syntax: \"*\";\n inherits: false;\n initial-value: 0;\n}\n@property --tw-rotate-x {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-rotate-y {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-rotate-z {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-skew-x {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-skew-y {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-space-y-reverse {\n syntax: \"*\";\n inherits: false;\n initial-value: 0;\n}\n@property --tw-border-style {\n syntax: \"*\";\n inherits: false;\n initial-value: solid;\n}\n@property --tw-font-weight {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-tracking {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-shadow {\n syntax: \"*\";\n inherits: false;\n initial-value: 0 0 #0000;\n}\n@property --tw-shadow-color {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-shadow-alpha {\n syntax: \"<percentage>\";\n inherits: false;\n initial-value: 100%;\n}\n@property --tw-inset-shadow {\n syntax: \"*\";\n inherits: false;\n initial-value: 0 0 #0000;\n}\n@property --tw-inset-shadow-color {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-inset-shadow-alpha {\n syntax: \"<percentage>\";\n inherits: false;\n initial-value: 100%;\n}\n@property --tw-ring-color {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-ring-shadow {\n syntax: \"*\";\n inherits: false;\n initial-value: 0 0 #0000;\n}\n@property --tw-inset-ring-color {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-inset-ring-shadow {\n syntax: \"*\";\n inherits: false;\n initial-value: 0 0 #0000;\n}\n@property --tw-ring-inset {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-ring-offset-width {\n syntax: \"<length>\";\n inherits: false;\n initial-value: 0px;\n}\n@property --tw-ring-offset-color {\n syntax: \"*\";\n inherits: false;\n initial-value: #fff;\n}\n@property --tw-ring-offset-shadow {\n syntax: \"*\";\n inherits: false;\n initial-value: 0 0 #0000;\n}\n@property --tw-outline-style {\n syntax: \"*\";\n inherits: false;\n initial-value: solid;\n}\n@property --tw-blur {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-brightness {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-contrast {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-grayscale {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-hue-rotate {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-invert {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-opacity {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-saturate {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-sepia {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-drop-shadow {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-drop-shadow-color {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-drop-shadow-alpha {\n syntax: \"<percentage>\";\n inherits: false;\n initial-value: 100%;\n}\n@property --tw-drop-shadow-size {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-backdrop-blur {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-backdrop-brightness {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-backdrop-contrast {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-backdrop-grayscale {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-backdrop-hue-rotate {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-backdrop-invert {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-backdrop-opacity {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-backdrop-saturate {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-backdrop-sepia {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-duration {\n syntax: \"*\";\n inherits: false;\n}\n@keyframes spin {\n to {\n transform: rotate(360deg);\n }\n}\n@layer properties {\n @supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b)))) {\n *, ::before, ::after, ::backdrop {\n --tw-translate-x: 0;\n --tw-translate-y: 0;\n --tw-translate-z: 0;\n --tw-rotate-x: initial;\n --tw-rotate-y: initial;\n --tw-rotate-z: initial;\n --tw-skew-x: initial;\n --tw-skew-y: initial;\n --tw-space-y-reverse: 0;\n --tw-border-style: solid;\n --tw-font-weight: initial;\n --tw-tracking: initial;\n --tw-shadow: 0 0 #0000;\n --tw-shadow-color: initial;\n --tw-shadow-alpha: 100%;\n --tw-inset-shadow: 0 0 #0000;\n --tw-inset-shadow-color: initial;\n --tw-inset-shadow-alpha: 100%;\n --tw-ring-color: initial;\n --tw-ring-shadow: 0 0 #0000;\n --tw-inset-ring-color: initial;\n --tw-inset-ring-shadow: 0 0 #0000;\n --tw-ring-inset: initial;\n --tw-ring-offset-width: 0px;\n --tw-ring-offset-color: #fff;\n --tw-ring-offset-shadow: 0 0 #0000;\n --tw-outline-style: solid;\n --tw-blur: initial;\n --tw-brightness: initial;\n --tw-contrast: initial;\n --tw-grayscale: initial;\n --tw-hue-rotate: initial;\n --tw-invert: initial;\n --tw-opacity: initial;\n --tw-saturate: initial;\n --tw-sepia: initial;\n --tw-drop-shadow: initial;\n --tw-drop-shadow-color: initial;\n --tw-drop-shadow-alpha: 100%;\n --tw-drop-shadow-size: initial;\n --tw-backdrop-blur: initial;\n --tw-backdrop-brightness: initial;\n --tw-backdrop-contrast: initial;\n --tw-backdrop-grayscale: initial;\n --tw-backdrop-hue-rotate: initial;\n --tw-backdrop-invert: initial;\n --tw-backdrop-opacity: initial;\n --tw-backdrop-saturate: initial;\n --tw-backdrop-sepia: initial;\n --tw-duration: initial;\n }\n }\n}\n";
1959
+ const STYLE_ID = "jsm-tailwind-styles";
1960
+ var JsonSchemaStylesService = class JsonSchemaStylesService {
1961
+ platformId;
1962
+ injected = false;
1963
+ constructor(platformId) {
1964
+ this.platformId = platformId;
1965
+ }
1966
+ inject() {
1967
+ if (this.injected || !isPlatformBrowser(this.platformId)) return;
1968
+ if (typeof document === "undefined") return;
1969
+ if (document.getElementById(STYLE_ID)) {
1970
+ this.injected = true;
1971
+ return;
1972
+ }
1973
+ const style = document.createElement("style");
1974
+ style.id = STYLE_ID;
1975
+ style.textContent = JSM_CSS;
1976
+ document.head.appendChild(style);
1977
+ this.injected = true;
1978
+ }
1979
+ static ɵfac = i0.ɵɵngDeclareFactory({
1980
+ minVersion: "12.0.0",
1981
+ version: "22.2.3",
1982
+ ngImport: i0,
1983
+ type: JsonSchemaStylesService,
1984
+ deps: [{ token: PLATFORM_ID }],
1985
+ target: i0.ɵɵFactoryTarget.Injectable
1986
+ });
1987
+ static ɵprov = i0.ɵɵngDeclareInjectable({
1988
+ minVersion: "12.0.0",
1989
+ version: "22.2.3",
1990
+ ngImport: i0,
1991
+ type: JsonSchemaStylesService,
1992
+ providedIn: "root"
1993
+ });
1994
+ };
1995
+ i0.ɵɵngDeclareClassMetadata({
1996
+ minVersion: "12.0.0",
1997
+ version: "22.2.3",
1998
+ ngImport: i0,
1999
+ type: JsonSchemaStylesService,
2000
+ decorators: [{
2001
+ type: Injectable,
2002
+ args: [{ providedIn: "root" }]
2003
+ }],
2004
+ ctorParameters: () => [{
2005
+ type: void 0,
2006
+ decorators: [{
2007
+ type: Inject,
2008
+ args: [PLATFORM_ID]
2009
+ }]
2010
+ }]
2011
+ });
2012
+ var JsonSchemaFormComponent = class JsonSchemaFormComponent {
2013
+ schemaService;
2014
+ resolver;
2015
+ validation;
2016
+ cdr;
2017
+ options;
2018
+ schema;
2019
+ value;
2020
+ data;
2021
+ allowAdditionalProperties = false;
2022
+ decimalSeparator = ".";
2023
+ labels = {};
2024
+ formReady = new EventEmitter();
2025
+ valueChange = new EventEmitter();
2026
+ schemaReady = new EventEmitter();
2027
+ schemaErrors = new EventEmitter();
2028
+ form;
2029
+ rootControl;
2030
+ resolvedSchema;
2031
+ errorsMap = /* @__PURE__ */ new Map();
2032
+ schemaErrorList = [];
2033
+ loading = false;
2034
+ showErrors = false;
2035
+ valueSub;
2036
+ lastEmitted;
2037
+ constructor(schemaService, resolver, validation, cdr, options, stylesService) {
2038
+ this.schemaService = schemaService;
2039
+ this.resolver = resolver;
2040
+ this.validation = validation;
2041
+ this.cdr = cdr;
2042
+ this.options = options;
2043
+ stylesService.inject();
2044
+ }
2045
+ ngOnChanges(changes) {
2046
+ if (changes["decimalSeparator"]) this.options.decimalSeparator = this.decimalSeparator;
2047
+ if (changes["labels"]) this.options.labels = {
2048
+ ...DEFAULT_LABELS,
2049
+ ...this.labels
2050
+ };
2051
+ if (changes["schema"]) this.buildForm();
2052
+ else if ((changes["value"] || changes["data"]) && this.form) {
2053
+ const newData = this.value !== void 0 ? this.value : this.data;
2054
+ if (newData !== this.lastEmitted) this.form.patchValue(newData, { emitEvent: false });
2055
+ } else if (changes["value"] || changes["data"]) this.buildForm();
2056
+ }
2057
+ ngOnDestroy() {
2058
+ this.valueSub?.unsubscribe();
2059
+ }
2060
+ validate() {
2061
+ if (!this.form) return false;
2062
+ this.form.markAllAsTouched();
2063
+ this.showErrors = true;
2064
+ this.updateErrors();
2065
+ this.cdr.detectChanges();
2066
+ return this.form.valid;
2067
+ }
2068
+ async buildForm() {
2069
+ this.valueSub?.unsubscribe();
2070
+ this.loading = true;
2071
+ this.resolvedSchema = await this.resolver.resolve(this.schema, this.schema?.$id);
2072
+ const schemaErrs = this.validation.validateSchema(this.resolvedSchema);
2073
+ this.schemaErrorList = schemaErrs;
2074
+ this.schemaErrors.emit(schemaErrs);
2075
+ this.schemaReady.emit(this.resolvedSchema);
2076
+ if (schemaErrs.length > 0) {
2077
+ this.form = void 0;
2078
+ this.loading = false;
2079
+ return;
2080
+ }
2081
+ const initialData = this.value !== void 0 ? this.value : this.data;
2082
+ this.rootControl = this.schemaService.buildControl(this.resolvedSchema, initialData);
2083
+ this.form = this.rootControl instanceof FormGroup ? this.rootControl : new FormGroup({ value: this.rootControl });
2084
+ this.formReady.emit(this.form);
2085
+ this.loading = false;
2086
+ this.attachValueSub();
2087
+ }
2088
+ updateErrors() {
2089
+ if (!this.resolvedSchema) {
2090
+ this.errorsMap = /* @__PURE__ */ new Map();
2091
+ return;
2092
+ }
2093
+ const normalized = normalizeValue(this.resolvedSchema, this.rootControl.value);
2094
+ const errors = this.validation.validate(this.resolvedSchema, normalized);
2095
+ const map = /* @__PURE__ */ new Map();
2096
+ for (const error of errors) {
2097
+ const path = error.instancePath ?? "";
2098
+ const messages = map.get(path) ?? [];
2099
+ messages.push(localizeNumbers(error.message ?? error.keyword, this.decimalSeparator));
2100
+ map.set(path, messages);
2101
+ }
2102
+ this.errorsMap = map;
2103
+ }
2104
+ onRootReplaced(control) {
2105
+ this.rootControl = control;
2106
+ if (control instanceof FormGroup) this.form = control;
2107
+ else if (!(this.form instanceof FormGroup) || !this.form.contains("value")) this.form = new FormGroup({ value: control });
2108
+ else this.form.setControl("value", control);
2109
+ this.attachValueSub();
2110
+ }
2111
+ attachValueSub() {
2112
+ this.valueSub?.unsubscribe();
2113
+ this.updateErrors();
2114
+ this.valueSub = this.form.valueChanges.subscribe(() => {
2115
+ this.updateErrors();
2116
+ this.lastEmitted = this.resolvedSchema ? normalizeValue(this.resolvedSchema, this.rootControl.value) : this.rootControl.value;
2117
+ this.valueChange.emit(this.lastEmitted);
2118
+ });
2119
+ }
2120
+ static ɵfac = i0.ɵɵngDeclareFactory({
2121
+ minVersion: "12.0.0",
2122
+ version: "22.2.3",
2123
+ ngImport: i0,
2124
+ type: JsonSchemaFormComponent,
2125
+ deps: [
2126
+ { token: JsonSchemaFormService },
2127
+ { token: JsonSchemaResolverService },
2128
+ { token: JsonSchemaValidationService },
2129
+ { token: i0.ChangeDetectorRef },
2130
+ { token: JsonSchemaFormOptions },
2131
+ { token: JsonSchemaStylesService }
2132
+ ],
2133
+ target: i0.ɵɵFactoryTarget.Component
2134
+ });
2135
+ static ɵcmp = i0.ɵɵngDeclareComponent({
2136
+ minVersion: "17.0.0",
2137
+ version: "22.2.3",
2138
+ type: JsonSchemaFormComponent,
2139
+ isStandalone: true,
2140
+ selector: "jsm-json-schema-form",
2141
+ inputs: {
2142
+ schema: "schema",
2143
+ value: "value",
2144
+ data: "data",
2145
+ allowAdditionalProperties: "allowAdditionalProperties",
2146
+ decimalSeparator: "decimalSeparator",
2147
+ labels: "labels"
2148
+ },
2149
+ outputs: {
2150
+ formReady: "formReady",
2151
+ valueChange: "valueChange",
2152
+ schemaReady: "schemaReady",
2153
+ schemaErrors: "schemaErrors"
2154
+ },
2155
+ providers: [JsonSchemaFormOptions],
2156
+ usesOnChanges: true,
2157
+ ngImport: i0,
2158
+ template: `
2159
+ <div class="jsm-root">
2160
+ <form class="w-full space-y-5">
2161
+ @if (resolvedSchema?.title || resolvedSchema?.description) {
2162
+ <div class="space-y-0.5">
2163
+ <h2 class="text-lg font-bold tracking-tight text-slate-900 dark:text-white">{{ resolvedSchema?.title || options.labels.formTitle }}</h2>
2164
+ @if (resolvedSchema?.description) {
2165
+ <p class="text-sm text-slate-500 dark:text-slate-400">{{ resolvedSchema?.description }}</p>
1993
2166
  }
2167
+ </div>
1994
2168
  }
1995
- else if (changes['value'] || changes['data']) {
1996
- // Form not built yet — build it
1997
- void this.buildForm();
1998
- }
1999
- }
2000
- ngOnDestroy() {
2001
- this.valueSub?.unsubscribe();
2002
- }
2003
- /**
2004
- * Marks all fields as touched and forces error display across the entire form.
2005
- * Call this from an external "Save" or "Submit" button to show all validation errors at once.
2006
- * Returns true if the form is valid, false otherwise.
2007
- */
2008
- validate() {
2009
- if (!this.form)
2010
- return false;
2011
- this.form.markAllAsTouched();
2012
- this.showErrors = true;
2013
- this.updateErrors();
2014
- this.cdr.detectChanges();
2015
- return this.form.valid;
2016
- }
2017
- async buildForm() {
2018
- this.valueSub?.unsubscribe();
2019
- this.loading = true;
2020
- this.resolvedSchema = await this.resolver.resolve(this.schema, this.schema?.$id);
2021
- // Validate the schema itself and emit any structural errors
2022
- const schemaErrs = this.validation.validateSchema(this.resolvedSchema);
2023
- this.schemaErrorList = schemaErrs;
2024
- this.schemaErrors.emit(schemaErrs);
2025
- this.schemaReady.emit(this.resolvedSchema);
2026
- // If the schema has structural errors, stop here — don't try to build the form
2027
- if (schemaErrs.length > 0) {
2028
- this.form = undefined;
2029
- this.loading = false;
2030
- return;
2031
- }
2032
- const initialData = this.value !== undefined ? this.value : this.data;
2033
- this.rootControl = this.schemaService.buildControl(this.resolvedSchema, initialData);
2034
- this.form = this.rootControl instanceof FormGroup ? this.rootControl : new FormGroup({ value: this.rootControl });
2035
- this.formReady.emit(this.form);
2036
- this.loading = false;
2037
- this.attachValueSub();
2038
- }
2039
- updateErrors() {
2040
- if (!this.resolvedSchema) {
2041
- this.errorsMap = new Map();
2042
- return;
2169
+
2170
+ @if (loading) {
2171
+ <div class="flex items-center gap-2 py-6 text-sm text-slate-500 dark:text-slate-400" role="status">
2172
+ <svg class="h-4 w-4 animate-spin text-indigo-500 dark:text-indigo-400" viewBox="0 0 24 24" fill="none" aria-hidden="true">
2173
+ <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"/>
2174
+ <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8v4a4 4 0 00-4 4H4z"/>
2175
+ </svg>
2176
+ {{ options.labels.resolving }}
2177
+ </div>
2043
2178
  }
2044
- const normalized = normalizeValue(this.resolvedSchema, this.rootControl.value);
2045
- const errors = this.validation.validate(this.resolvedSchema, normalized);
2046
- const map = new Map();
2047
- for (const error of errors) {
2048
- const path = error.instancePath ?? '';
2049
- const messages = map.get(path) ?? [];
2050
- messages.push(localizeNumbers(error.message ?? error.keyword, this.decimalSeparator));
2051
- map.set(path, messages);
2179
+
2180
+ <!-- schema structural errors panel -->
2181
+ @if (schemaErrorList.length > 0 && !loading) {
2182
+ <div
2183
+ class="rounded-xl border border-amber-300 dark:border-amber-700 bg-amber-50 dark:bg-amber-900/20 p-4 space-y-3"
2184
+ role="alert">
2185
+ <div class="flex items-center gap-2">
2186
+ <svg class="w-4 h-4 shrink-0 text-amber-600 dark:text-amber-400" viewBox="0 0 16 16" fill="none" aria-hidden="true">
2187
+ <path d="M8 2L1.5 13.5h13L8 2z" stroke="currentColor" stroke-width="1.5" stroke-linejoin="round"/>
2188
+ <path d="M8 6v3.5M8 11.5v.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
2189
+ </svg>
2190
+ <span class="text-sm font-semibold text-amber-800 dark:text-amber-300">
2191
+ {{ options.labels.schemaErrorsTitle }} ({{ schemaErrorList.length }})
2192
+ </span>
2193
+ </div>
2194
+ <p class="text-xs text-amber-700 dark:text-amber-400">
2195
+ {{ options.labels.schemaErrorsHint }}
2196
+ </p>
2197
+ <div class="space-y-2">
2198
+ @for (err of schemaErrorList; track err) {
2199
+ <div
2200
+ 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">
2201
+ <p class="font-mono text-[11px] font-semibold text-amber-700 dark:text-amber-400">{{ err.path }}</p>
2202
+ <p class="text-xs text-slate-700 dark:text-slate-300">{{ err.message }}</p>
2203
+ <p class="text-[10px] uppercase tracking-wide text-slate-400 dark:text-slate-500">keyword: {{ err.keyword }}</p>
2204
+ </div>
2205
+ }
2206
+ </div>
2207
+ </div>
2052
2208
  }
2053
- this.errorsMap = map;
2054
- }
2055
- onRootReplaced(control) {
2056
- this.rootControl = control;
2057
- if (control instanceof FormGroup) {
2058
- this.form = control;
2209
+
2210
+ @if (form && resolvedSchema && !loading) {
2211
+ <div class="space-y-5" [formGroup]="form">
2212
+ <jsm-schema-node
2213
+ [schema]="resolvedSchema"
2214
+ [control]="rootControl"
2215
+ [path]="''"
2216
+ [errorsMap]="errorsMap"
2217
+ [allowAdditionalProperties]="allowAdditionalProperties"
2218
+ [showErrors]="showErrors"
2219
+ label="Root"
2220
+ (controlReplaced)="onRootReplaced($event)"
2221
+ ></jsm-schema-node>
2222
+ </div>
2059
2223
  }
2060
- else {
2061
- if (!(this.form instanceof FormGroup) || !this.form.contains('value')) {
2062
- this.form = new FormGroup({ value: control });
2063
- }
2064
- else {
2065
- this.form.setControl('value', control);
2224
+ </form>
2225
+ </div>
2226
+ `,
2227
+ isInline: true,
2228
+ 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"],
2229
+ dependencies: [
2230
+ {
2231
+ kind: "ngmodule",
2232
+ type: ReactiveFormsModule
2233
+ },
2234
+ {
2235
+ kind: "directive",
2236
+ type: i5.ɵNgNoValidate,
2237
+ selector: "form:not([ngNoForm]):not([ngNativeValidate])"
2238
+ },
2239
+ {
2240
+ kind: "directive",
2241
+ type: i5.NgControlStatusGroup,
2242
+ selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]"
2243
+ },
2244
+ {
2245
+ kind: "directive",
2246
+ type: i5.FormGroupDirective,
2247
+ selector: "[formGroup]",
2248
+ inputs: ["formGroup"],
2249
+ outputs: ["ngSubmit"],
2250
+ exportAs: ["ngForm"]
2251
+ },
2252
+ {
2253
+ kind: "component",
2254
+ type: JsonSchemaNodeComponent,
2255
+ selector: "jsm-schema-node",
2256
+ inputs: [
2257
+ "schema",
2258
+ "control",
2259
+ "label",
2260
+ "required",
2261
+ "path",
2262
+ "errorsMap",
2263
+ "parent",
2264
+ "controlKey",
2265
+ "allowAdditionalProperties",
2266
+ "showErrors"
2267
+ ],
2268
+ outputs: ["controlReplaced"]
2269
+ }
2270
+ ],
2271
+ changeDetection: i0.ChangeDetectionStrategy.OnPush,
2272
+ encapsulation: i0.ViewEncapsulation.None
2273
+ });
2274
+ };
2275
+ i0.ɵɵngDeclareClassMetadata({
2276
+ minVersion: "12.0.0",
2277
+ version: "22.2.3",
2278
+ ngImport: i0,
2279
+ type: JsonSchemaFormComponent,
2280
+ decorators: [{
2281
+ type: Component,
2282
+ args: [{
2283
+ selector: "jsm-json-schema-form",
2284
+ standalone: true,
2285
+ imports: [ReactiveFormsModule, JsonSchemaNodeComponent],
2286
+ providers: [JsonSchemaFormOptions],
2287
+ encapsulation: ViewEncapsulation.None,
2288
+ template: `
2289
+ <div class="jsm-root">
2290
+ <form class="w-full space-y-5">
2291
+ @if (resolvedSchema?.title || resolvedSchema?.description) {
2292
+ <div class="space-y-0.5">
2293
+ <h2 class="text-lg font-bold tracking-tight text-slate-900 dark:text-white">{{ resolvedSchema?.title || options.labels.formTitle }}</h2>
2294
+ @if (resolvedSchema?.description) {
2295
+ <p class="text-sm text-slate-500 dark:text-slate-400">{{ resolvedSchema?.description }}</p>
2066
2296
  }
2297
+ </div>
2067
2298
  }
2068
- this.attachValueSub();
2069
- }
2070
- attachValueSub() {
2071
- this.valueSub?.unsubscribe();
2072
- this.updateErrors();
2073
- this.valueSub = this.form.valueChanges.subscribe(() => {
2074
- this.updateErrors();
2075
- this.lastEmitted = this.resolvedSchema ? normalizeValue(this.resolvedSchema, this.rootControl.value) : this.rootControl.value;
2076
- this.valueChange.emit(this.lastEmitted);
2077
- });
2078
- }
2079
- 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 });
2080
- 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", labels: "labels" }, outputs: { formReady: "formReady", valueChange: "valueChange", schemaReady: "schemaReady", schemaErrors: "schemaErrors" }, providers: [JsonSchemaFormOptions], usesOnChanges: true, ngImport: i0, template: `
2081
- <div class="jsm-root">
2082
- <form class="w-full space-y-5">
2083
- <div *ngIf="resolvedSchema?.title || resolvedSchema?.description" class="space-y-0.5">
2084
- <h2 class="text-lg font-bold tracking-tight text-slate-900 dark:text-white">{{ resolvedSchema?.title || options.labels.formTitle }}</h2>
2085
- <p *ngIf="resolvedSchema?.description" class="text-sm text-slate-500 dark:text-slate-400">{{ resolvedSchema?.description }}</p>
2086
- </div>
2087
-
2088
- <div *ngIf="loading" class="flex items-center gap-2 py-6 text-sm text-slate-500 dark:text-slate-400" role="status">
2089
- <svg class="h-4 w-4 animate-spin text-indigo-500 dark:text-indigo-400" viewBox="0 0 24 24" fill="none" aria-hidden="true">
2090
- <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"/>
2091
- <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8v4a4 4 0 00-4 4H4z"/>
2092
- </svg>
2093
- {{ options.labels.resolving }}
2094
- </div>
2095
-
2096
- <!-- schema structural errors panel -->
2097
- <div *ngIf="schemaErrorList.length > 0 && !loading"
2098
- class="rounded-xl border border-amber-300 dark:border-amber-700 bg-amber-50 dark:bg-amber-900/20 p-4 space-y-3"
2099
- role="alert">
2100
- <div class="flex items-center gap-2">
2101
- <svg class="w-4 h-4 shrink-0 text-amber-600 dark:text-amber-400" viewBox="0 0 16 16" fill="none" aria-hidden="true">
2102
- <path d="M8 2L1.5 13.5h13L8 2z" stroke="currentColor" stroke-width="1.5" stroke-linejoin="round"/>
2103
- <path d="M8 6v3.5M8 11.5v.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
2104
- </svg>
2105
- <span class="text-sm font-semibold text-amber-800 dark:text-amber-300">
2106
- {{ options.labels.schemaErrorsTitle }} ({{ schemaErrorList.length }})
2107
- </span>
2108
- </div>
2109
- <p class="text-xs text-amber-700 dark:text-amber-400">
2110
- {{ options.labels.schemaErrorsHint }}
2111
- </p>
2112
- <div class="space-y-2">
2113
- <div *ngFor="let err of schemaErrorList"
2114
- 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">
2115
- <p class="font-mono text-[11px] font-semibold text-amber-700 dark:text-amber-400">{{ err.path }}</p>
2116
- <p class="text-xs text-slate-700 dark:text-slate-300">{{ err.message }}</p>
2117
- <p class="text-[10px] uppercase tracking-wide text-slate-400 dark:text-slate-500">keyword: {{ err.keyword }}</p>
2299
+
2300
+ @if (loading) {
2301
+ <div class="flex items-center gap-2 py-6 text-sm text-slate-500 dark:text-slate-400" role="status">
2302
+ <svg class="h-4 w-4 animate-spin text-indigo-500 dark:text-indigo-400" viewBox="0 0 24 24" fill="none" aria-hidden="true">
2303
+ <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"/>
2304
+ <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8v4a4 4 0 00-4 4H4z"/>
2305
+ </svg>
2306
+ {{ options.labels.resolving }}
2118
2307
  </div>
2119
- </div>
2120
- </div>
2121
-
2122
- <div *ngIf="form && resolvedSchema && !loading" class="space-y-5" [formGroup]="form">
2123
- <jsm-schema-node
2124
- [schema]="resolvedSchema"
2125
- [control]="rootControl"
2126
- [path]="''"
2127
- [errorsMap]="errorsMap"
2128
- [allowAdditionalProperties]="allowAdditionalProperties"
2129
- [showErrors]="showErrors"
2130
- label="Root"
2131
- (controlReplaced)="onRootReplaced($event)"
2132
- ></jsm-schema-node>
2133
- </div>
2134
- </form>
2135
- </div>
2136
- `, 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 });
2137
- }
2138
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: JsonSchemaFormComponent, decorators: [{
2139
- type: Component,
2140
- args: [{ selector: 'jsm-json-schema-form', standalone: true, imports: [CommonModule, ReactiveFormsModule, JsonSchemaNodeComponent], providers: [JsonSchemaFormOptions], encapsulation: ViewEncapsulation.None, template: `
2141
- <div class="jsm-root">
2142
- <form class="w-full space-y-5">
2143
- <div *ngIf="resolvedSchema?.title || resolvedSchema?.description" class="space-y-0.5">
2144
- <h2 class="text-lg font-bold tracking-tight text-slate-900 dark:text-white">{{ resolvedSchema?.title || options.labels.formTitle }}</h2>
2145
- <p *ngIf="resolvedSchema?.description" class="text-sm text-slate-500 dark:text-slate-400">{{ resolvedSchema?.description }}</p>
2146
- </div>
2147
-
2148
- <div *ngIf="loading" class="flex items-center gap-2 py-6 text-sm text-slate-500 dark:text-slate-400" role="status">
2149
- <svg class="h-4 w-4 animate-spin text-indigo-500 dark:text-indigo-400" viewBox="0 0 24 24" fill="none" aria-hidden="true">
2150
- <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"/>
2151
- <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8v4a4 4 0 00-4 4H4z"/>
2152
- </svg>
2153
- {{ options.labels.resolving }}
2154
- </div>
2155
-
2156
- <!-- schema structural errors panel -->
2157
- <div *ngIf="schemaErrorList.length > 0 && !loading"
2158
- class="rounded-xl border border-amber-300 dark:border-amber-700 bg-amber-50 dark:bg-amber-900/20 p-4 space-y-3"
2159
- role="alert">
2160
- <div class="flex items-center gap-2">
2161
- <svg class="w-4 h-4 shrink-0 text-amber-600 dark:text-amber-400" viewBox="0 0 16 16" fill="none" aria-hidden="true">
2162
- <path d="M8 2L1.5 13.5h13L8 2z" stroke="currentColor" stroke-width="1.5" stroke-linejoin="round"/>
2163
- <path d="M8 6v3.5M8 11.5v.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
2164
- </svg>
2165
- <span class="text-sm font-semibold text-amber-800 dark:text-amber-300">
2166
- {{ options.labels.schemaErrorsTitle }} ({{ schemaErrorList.length }})
2167
- </span>
2168
- </div>
2169
- <p class="text-xs text-amber-700 dark:text-amber-400">
2170
- {{ options.labels.schemaErrorsHint }}
2171
- </p>
2172
- <div class="space-y-2">
2173
- <div *ngFor="let err of schemaErrorList"
2174
- 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">
2175
- <p class="font-mono text-[11px] font-semibold text-amber-700 dark:text-amber-400">{{ err.path }}</p>
2176
- <p class="text-xs text-slate-700 dark:text-slate-300">{{ err.message }}</p>
2177
- <p class="text-[10px] uppercase tracking-wide text-slate-400 dark:text-slate-500">keyword: {{ err.keyword }}</p>
2308
+ }
2309
+
2310
+ <!-- schema structural errors panel -->
2311
+ @if (schemaErrorList.length > 0 && !loading) {
2312
+ <div
2313
+ class="rounded-xl border border-amber-300 dark:border-amber-700 bg-amber-50 dark:bg-amber-900/20 p-4 space-y-3"
2314
+ role="alert">
2315
+ <div class="flex items-center gap-2">
2316
+ <svg class="w-4 h-4 shrink-0 text-amber-600 dark:text-amber-400" viewBox="0 0 16 16" fill="none" aria-hidden="true">
2317
+ <path d="M8 2L1.5 13.5h13L8 2z" stroke="currentColor" stroke-width="1.5" stroke-linejoin="round"/>
2318
+ <path d="M8 6v3.5M8 11.5v.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
2319
+ </svg>
2320
+ <span class="text-sm font-semibold text-amber-800 dark:text-amber-300">
2321
+ {{ options.labels.schemaErrorsTitle }} ({{ schemaErrorList.length }})
2322
+ </span>
2323
+ </div>
2324
+ <p class="text-xs text-amber-700 dark:text-amber-400">
2325
+ {{ options.labels.schemaErrorsHint }}
2326
+ </p>
2327
+ <div class="space-y-2">
2328
+ @for (err of schemaErrorList; track err) {
2329
+ <div
2330
+ 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">
2331
+ <p class="font-mono text-[11px] font-semibold text-amber-700 dark:text-amber-400">{{ err.path }}</p>
2332
+ <p class="text-xs text-slate-700 dark:text-slate-300">{{ err.message }}</p>
2333
+ <p class="text-[10px] uppercase tracking-wide text-slate-400 dark:text-slate-500">keyword: {{ err.keyword }}</p>
2334
+ </div>
2335
+ }
2336
+ </div>
2178
2337
  </div>
2179
- </div>
2180
- </div>
2181
-
2182
- <div *ngIf="form && resolvedSchema && !loading" class="space-y-5" [formGroup]="form">
2183
- <jsm-schema-node
2184
- [schema]="resolvedSchema"
2185
- [control]="rootControl"
2186
- [path]="''"
2187
- [errorsMap]="errorsMap"
2188
- [allowAdditionalProperties]="allowAdditionalProperties"
2189
- [showErrors]="showErrors"
2190
- label="Root"
2191
- (controlReplaced)="onRootReplaced($event)"
2192
- ></jsm-schema-node>
2193
- </div>
2194
- </form>
2338
+ }
2339
+
2340
+ @if (form && resolvedSchema && !loading) {
2341
+ <div class="space-y-5" [formGroup]="form">
2342
+ <jsm-schema-node
2343
+ [schema]="resolvedSchema"
2344
+ [control]="rootControl"
2345
+ [path]="''"
2346
+ [errorsMap]="errorsMap"
2347
+ [allowAdditionalProperties]="allowAdditionalProperties"
2348
+ [showErrors]="showErrors"
2349
+ label="Root"
2350
+ (controlReplaced)="onRootReplaced($event)"
2351
+ ></jsm-schema-node>
2352
+ </div>
2353
+ }
2354
+ </form>
2195
2355
  </div>
2196
- `, 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"] }]
2197
- }], ctorParameters: () => [{ type: JsonSchemaFormService }, { type: JsonSchemaResolverService }, { type: JsonSchemaValidationService }, { type: i0.ChangeDetectorRef }, { type: JsonSchemaFormOptions }, { type: JsonSchemaStylesService }], propDecorators: { schema: [{
2198
- type: Input,
2199
- args: [{ required: true }]
2200
- }], value: [{
2201
- type: Input
2202
- }], data: [{
2203
- type: Input
2204
- }], allowAdditionalProperties: [{
2205
- type: Input
2206
- }], decimalSeparator: [{
2207
- type: Input
2208
- }], labels: [{
2209
- type: Input
2210
- }], formReady: [{
2211
- type: Output
2212
- }], valueChange: [{
2213
- type: Output
2214
- }], schemaReady: [{
2215
- type: Output
2216
- }], schemaErrors: [{
2217
- type: Output
2218
- }] } });
2219
-
2220
- class JsonSchemaFormModule {
2221
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: JsonSchemaFormModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
2222
- static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "20.3.18", ngImport: i0, type: JsonSchemaFormModule, imports: [JsonSchemaFormComponent, JsonSchemaNodeComponent], exports: [JsonSchemaFormComponent, JsonSchemaNodeComponent] });
2223
- static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: JsonSchemaFormModule, imports: [JsonSchemaFormComponent, JsonSchemaNodeComponent] });
2224
- }
2225
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: JsonSchemaFormModule, decorators: [{
2226
- type: NgModule,
2227
- args: [{
2228
- imports: [JsonSchemaFormComponent, JsonSchemaNodeComponent],
2229
- exports: [JsonSchemaFormComponent, JsonSchemaNodeComponent],
2230
- }]
2231
- }] });
2232
-
2233
- /**
2234
- * Generated bundle index. Do not edit.
2235
- */
2236
-
2356
+ `,
2357
+ changeDetection: ChangeDetectionStrategy.OnPush,
2358
+ 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"]
2359
+ }]
2360
+ }],
2361
+ ctorParameters: () => [
2362
+ { type: JsonSchemaFormService },
2363
+ { type: JsonSchemaResolverService },
2364
+ { type: JsonSchemaValidationService },
2365
+ { type: i0.ChangeDetectorRef },
2366
+ { type: JsonSchemaFormOptions },
2367
+ { type: JsonSchemaStylesService }
2368
+ ],
2369
+ propDecorators: {
2370
+ schema: [{
2371
+ type: Input,
2372
+ args: [{ required: true }]
2373
+ }],
2374
+ value: [{ type: Input }],
2375
+ data: [{ type: Input }],
2376
+ allowAdditionalProperties: [{ type: Input }],
2377
+ decimalSeparator: [{ type: Input }],
2378
+ labels: [{ type: Input }],
2379
+ formReady: [{ type: Output }],
2380
+ valueChange: [{ type: Output }],
2381
+ schemaReady: [{ type: Output }],
2382
+ schemaErrors: [{ type: Output }]
2383
+ }
2384
+ });
2385
+ var JsonSchemaFormModule = class JsonSchemaFormModule {
2386
+ static ɵfac = i0.ɵɵngDeclareFactory({
2387
+ minVersion: "12.0.0",
2388
+ version: "22.2.3",
2389
+ ngImport: i0,
2390
+ type: JsonSchemaFormModule,
2391
+ deps: [],
2392
+ target: i0.ɵɵFactoryTarget.NgModule
2393
+ });
2394
+ static ɵmod = i0.ɵɵngDeclareNgModule({
2395
+ minVersion: "14.0.0",
2396
+ version: "22.2.3",
2397
+ ngImport: i0,
2398
+ type: JsonSchemaFormModule,
2399
+ imports: [JsonSchemaFormComponent, JsonSchemaNodeComponent],
2400
+ exports: [JsonSchemaFormComponent, JsonSchemaNodeComponent]
2401
+ });
2402
+ static ɵinj = i0.ɵɵngDeclareInjector({
2403
+ minVersion: "12.0.0",
2404
+ version: "22.2.3",
2405
+ ngImport: i0,
2406
+ type: JsonSchemaFormModule,
2407
+ imports: [JsonSchemaFormComponent, JsonSchemaNodeComponent]
2408
+ });
2409
+ };
2410
+ i0.ɵɵngDeclareClassMetadata({
2411
+ minVersion: "12.0.0",
2412
+ version: "22.2.3",
2413
+ ngImport: i0,
2414
+ type: JsonSchemaFormModule,
2415
+ decorators: [{
2416
+ type: NgModule,
2417
+ args: [{
2418
+ imports: [JsonSchemaFormComponent, JsonSchemaNodeComponent],
2419
+ exports: [JsonSchemaFormComponent, JsonSchemaNodeComponent]
2420
+ }]
2421
+ }]
2422
+ });
2237
2423
  export { DEFAULT_LABELS, JsonSchemaFormComponent, JsonSchemaFormModule, JsonSchemaFormOptions, JsonSchemaFormService, JsonSchemaNodeComponent, JsonSchemaResolverService, JsonSchemaStylesService, JsonSchemaValidationService, NumericInputDirective, formatLabel, formatNumeric, localizeNumbers, normalizeValue, parseNumericText, sanitizeNumericText };
2238
- //# sourceMappingURL=elle96-ng-json-schema-manager.mjs.map
2424
+
2425
+ //# sourceMappingURL=elle96-ng-json-schema-manager.mjs.map