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