@mosaicoo/form-angular 0.6.0 → 0.8.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,7 +1,7 @@
1
1
  import { NgComponentOutlet, NgTemplateOutlet, DOCUMENT } from '@angular/common';
2
2
  import * as i0 from '@angular/core';
3
3
  import { InjectionToken, makeEnvironmentProviders, input, signal, inject, ChangeDetectorRef, reflectComponentType, effect, untracked, computed, ViewEncapsulation, ChangeDetectionStrategy, Component, output } from '@angular/core';
4
- import { maskValue, importForm, createLegacyImporter, createFormEngine, collectInputs } from '@mosaicoo/form-core';
4
+ import { maskValue, formatNumber, parseNumber, importForm, createLegacyImporter, createFormEngine, collectInputs } from '@mosaicoo/form-core';
5
5
 
6
6
  /** Multi-provider token: maps merge, later providers win on conflicts. */
7
7
  const MFORM_FIELD_COMPONENTS = new InjectionToken('MFORM_FIELD_COMPONENTS');
@@ -130,6 +130,8 @@ class FormNodeComponent {
130
130
  /** Discards resolutions that arrive after a newer request started. */
131
131
  optionsRequestSeq = 0;
132
132
  constructor() {
133
+ // Autocomplete fields refresh their cached results on observed changes.
134
+ effect(() => this.watchReferenceRefresh());
133
135
  effect(() => {
134
136
  const node = this.node();
135
137
  const registry = this.providers();
@@ -244,10 +246,28 @@ class FormNodeComponent {
244
246
  return 'array';
245
247
  if (node.type === 'tabs')
246
248
  return 'tabs';
249
+ if (node.type === 'accordion')
250
+ return 'accordion';
247
251
  if (node.type === 'columns' && node.columns?.length)
248
252
  return 'columns';
249
253
  return 'block';
250
254
  }
255
+ /** Open accordion sections; the first one starts expanded. */
256
+ openSections = signal(null, ...(ngDevMode ? [{ debugName: "openSections" }] : /* istanbul ignore next */ []));
257
+ isSectionOpen(index) {
258
+ const open = this.openSections();
259
+ return open === null ? index === 0 : open.has(index);
260
+ }
261
+ toggleSection(index) {
262
+ this.openSections.update((current) => {
263
+ const next = new Set(current ?? [0]);
264
+ if (next.has(index))
265
+ next.delete(index);
266
+ else
267
+ next.add(index);
268
+ return next;
269
+ });
270
+ }
251
271
  columns() {
252
272
  const node = this.container();
253
273
  return (node.columns ?? []).map((column) => column.children
@@ -268,11 +288,29 @@ class FormNodeComponent {
268
288
  const value = this.value();
269
289
  if (value === undefined || value === null)
270
290
  return '';
271
- const mask = this.field().format?.mask;
272
- if (mask && typeof value === 'string')
273
- return maskValue(value, mask).masked;
291
+ const format = this.field().format;
292
+ if (format?.mask && typeof value === 'string')
293
+ return maskValue(value, format.mask).masked;
294
+ // Numbers show formatted while idle and raw while being edited, so the
295
+ // caret never fights the thousands separators.
296
+ if (format?.number && !this.focused())
297
+ return formatNumber(value, format.number);
274
298
  return String(value);
275
299
  }
300
+ /** Tracks focus so numeric formatting steps aside during typing. */
301
+ focused = signal(false, ...(ngDevMode ? [{ debugName: "focused" }] : /* istanbul ignore next */ []));
302
+ onFocus() {
303
+ if (this.field().format?.number)
304
+ this.focused.set(true);
305
+ }
306
+ onBlur() {
307
+ this.focused.set(false);
308
+ this.touch();
309
+ }
310
+ /** Numeric inputs stay `text` when formatted (a number input rejects `R$`). */
311
+ numericFormatted() {
312
+ return this.field().format?.number !== undefined;
313
+ }
276
314
  mapValue() {
277
315
  const value = this.value();
278
316
  return value !== null && typeof value === 'object' && !Array.isArray(value)
@@ -331,11 +369,16 @@ class FormNodeComponent {
331
369
  setString(event) {
332
370
  const target = event.target;
333
371
  const raw = target.value;
334
- const mask = this.field().format?.mask;
335
- if (mask) {
336
- const { masked, raw: stripped } = maskValue(raw, mask);
372
+ const format = this.field().format;
373
+ if (format?.mask) {
374
+ const { masked, raw: stripped } = maskValue(raw, format.mask);
337
375
  target.value = masked; // the control always shows the masked shape
338
- this.setValue(this.field().format?.keepMask ? masked : stripped);
376
+ this.setValue(format.keepMask ? masked : stripped);
377
+ return;
378
+ }
379
+ if (format?.number) {
380
+ // Locale-aware parse: `1.234,56` and `R$ 1.234,56` both land as a number.
381
+ this.setValue(parseNumber(raw, format.number));
339
382
  return;
340
383
  }
341
384
  if (this.field().dataType === 'number') {
@@ -487,11 +530,41 @@ class FormNodeComponent {
487
530
  refTimer = null;
488
531
  /** Label of the chosen option (falls back to the raw value). */
489
532
  refLabel = signal(null, ...(ngDevMode ? [{ debugName: "refLabel" }] : /* istanbul ignore next */ []));
533
+ /** Last query typed, replayed when `refreshOn` invalidates the results. */
534
+ refQuery = '';
535
+ refRefreshKey = null;
536
+ /**
537
+ * `refreshOn` for autocomplete: when the observed data changes, cached
538
+ * results are dropped so the next search runs against the new context
539
+ * (e.g. properties filtered by the selected client).
540
+ */
541
+ watchReferenceRefresh() {
542
+ const field = this.field();
543
+ const source = field.optionsSource;
544
+ if (field.type !== 'reference' || source?.type !== 'provider' || !source.refreshOn)
545
+ return;
546
+ this.tick();
547
+ const engine = untracked(() => this.engine());
548
+ const key = source.refreshOn === 'data'
549
+ ? JSON.stringify(engine.getData())
550
+ : JSON.stringify(engine.getValue(source.refreshOn) ?? null);
551
+ if (this.refRefreshKey === null) {
552
+ this.refRefreshKey = key;
553
+ return;
554
+ }
555
+ if (key === this.refRefreshKey)
556
+ return;
557
+ this.refRefreshKey = key;
558
+ this.refOptions.set([]);
559
+ if (this.refOpen())
560
+ this.refResolve(this.refQuery);
561
+ }
490
562
  refDisplay() {
491
563
  return this.refLabel() ?? this.stringValue();
492
564
  }
493
565
  refSearch(event) {
494
566
  const query = event.target.value;
567
+ this.refQuery = query;
495
568
  this.refLabel.set(null);
496
569
  if (this.refTimer)
497
570
  clearTimeout(this.refTimer);
@@ -583,6 +656,42 @@ class FormNodeComponent {
583
656
  }
584
657
  </div>
585
658
  }
659
+ @case ('accordion') {
660
+ <div class="mform-accordion">
661
+ @for (section of container().children; track section.key; let i = $index) {
662
+ <section class="mform-acc-item" [class.is-open]="isSectionOpen(i)">
663
+ <h3 class="mform-acc-head">
664
+ <button
665
+ type="button"
666
+ class="mform-acc-toggle"
667
+ [attr.aria-expanded]="isSectionOpen(i)"
668
+ [attr.aria-controls]="controlId() + '-sec-' + i"
669
+ (click)="toggleSection(i)"
670
+ >
671
+ <span class="mform-acc-caret" aria-hidden="true">
672
+ {{ isSectionOpen(i) ? '▾' : '▸' }}
673
+ </span>
674
+ {{ section.label || 'Section ' + (i + 1) }}
675
+ </button>
676
+ </h3>
677
+ @if (isSectionOpen(i)) {
678
+ <div class="mform-acc-body" [id]="controlId() + '-sec-' + i" role="region">
679
+ <mform-node
680
+ [node]="section"
681
+ [engine]="engine()"
682
+ [tick]="tick()"
683
+ [providers]="providers()"
684
+ [mode]="mode()"
685
+ [notify]="notify()"
686
+ [scope]="childScope()"
687
+ [bare]="true"
688
+ />
689
+ </div>
690
+ }
691
+ </section>
692
+ }
693
+ </div>
694
+ }
586
695
  @case ('tabs') {
587
696
  <div class="mform-tabs">
588
697
  <div class="mform-tabbar" role="tablist">
@@ -843,18 +952,19 @@ class FormNodeComponent {
843
952
  }
844
953
  <input
845
954
  class="mform-control"
846
- [type]="inputType()"
955
+ [type]="numericFormatted() ? 'text' : inputType()"
847
956
  [id]="controlId()"
848
957
  [value]="stringValue()"
849
958
  [placeholder]="field().placeholder || ''"
850
959
  [disabled]="controlDisabled()"
851
- [attr.step]="field().type === 'currency' ? '0.01' : null"
852
- [attr.inputmode]="field().type === 'currency' ? 'decimal' : null"
960
+ [attr.step]="field().type === 'currency' && !numericFormatted() ? '0.01' : null"
961
+ [attr.inputmode]="field().type === 'currency' || numericFormatted() ? 'decimal' : null"
853
962
  [attr.aria-required]="required() || null"
854
963
  [attr.aria-invalid]="errors().length > 0 || null"
855
964
  [attr.aria-describedby]="describedBy()"
856
965
  (input)="setString($event)"
857
- (blur)="touch()"
966
+ (focus)="onFocus()"
967
+ (blur)="onBlur()"
858
968
  />
859
969
  @if (suffix(); as s) {
860
970
  <span class="mform-suffix" aria-hidden="true">{{ s }}</span>
@@ -918,17 +1028,11 @@ class FormNodeComponent {
918
1028
  </ul>
919
1029
  }
920
1030
  </ng-template>
921
- `, isInline: true, dependencies: [{ kind: "component", type: FormNodeComponent, selector: "mform-node", inputs: ["node", "engine", "tick", "scope", "bare", "providers", "mode", "notify"] }, { kind: "directive", type: NgComponentOutlet, selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletEnvironmentInjector", "ngComponentOutletContent", "ngComponentOutletNgModule"], exportAs: ["ngComponentOutlet"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
1031
+ `, isInline: true, styles: [".mform-node{display:contents}.mform-field{display:flex;flex-direction:column;gap:6px}.mform-label{font-size:var(--mform-label-size, 13px);font-weight:var(--mform-label-weight, 600)}.mform-req{color:var(--mform-danger, #c62842);margin-left:2px}.mform-control{font:inherit;width:100%;padding:var(--mform-control-padding, 9px 12px);border:1px solid var(--mform-border, #d9d5e6);border-radius:var(--mform-control-radius, 8px);background:var(--mform-control-bg, #fff);color:inherit;box-sizing:border-box}textarea.mform-control{min-height:72px;resize:vertical}.mform-control:focus-visible{outline:2px solid var(--mform-focus, #8e6ff0);outline-offset:1px;border-color:var(--mform-focus, #8e6ff0)}.mform-field.has-error .mform-control{border-color:var(--mform-danger, #c62842)}.mform-desc{font-size:12px;color:var(--mform-muted, #6b6580)}.mform-error{font-size:12px;color:var(--mform-danger, #c62842)}.mform-choices{display:flex;flex-direction:column;gap:6px}.mform-check{display:flex;align-items:center;gap:8px;font-size:14px;cursor:pointer}.mform-check input{width:16px;height:16px;accent-color:var(--mform-primary, #6d4bd0)}.mform-panel{border:1px solid var(--mform-border, #d9d5e6);border-radius:var(--mform-radius, 10px);overflow:hidden}.mform-panel-head{background:var(--mform-panel-head-bg, linear-gradient(90deg, #efeaff, #f8f6ff));color:var(--mform-panel-head-text, inherit);padding:10px 16px;font-weight:600;font-size:14px;border-bottom:1px solid var(--mform-border, #d9d5e6)}.mform-panel-body,.mform-group,.mform-column,.mform-tabpanel{display:flex;flex-direction:column;gap:var(--mform-gap, 16px)}.mform-panel-body{padding:var(--mform-panel-padding, 16px)}.mform-columns{display:grid;gap:var(--mform-gap, 16px)}@media(max-width:640px){.mform-columns{grid-template-columns:1fr!important}}.mform-affix{display:flex;align-items:center}.mform-affix.has-affix{border:1px solid var(--mform-border, #d9d5e6);border-radius:var(--mform-control-radius, 8px);background:var(--mform-control-bg, #fff)}.mform-affix.has-affix .mform-control{border:none;background:none;flex:1}.mform-affix.has-affix:focus-within{outline:2px solid var(--mform-focus, #8e6ff0);outline-offset:1px}.mform-prefix,.mform-suffix{padding:0 10px;color:var(--mform-muted, #6b6580);font-size:13px;white-space:nowrap}.mform-tags{display:flex;flex-wrap:wrap;gap:6px;align-items:center}.mform-tag{background:var(--mform-primary, #6d4bd0);color:var(--mform-primary-contrast, #fff);border-radius:999px;padding:3px 10px;font-size:12px;display:inline-flex;align-items:center;gap:6px}.mform-tag button{border:none;background:none;color:inherit;cursor:pointer;font-size:12px;padding:0;line-height:1}.mform-tag-input{flex:1;min-width:140px;width:auto}.mform-field.is-pending .mform-control{background-image:linear-gradient(90deg,transparent 0%,rgb(109 75 208 / 12%) 50%,transparent 100%);background-size:200% 100%;animation:mform-pending 1.2s linear infinite}@keyframes mform-pending{0%{background-position:200% 0}to{background-position:-200% 0}}.mform-files{list-style:none;margin:6px 0 0;padding:0;display:flex;flex-direction:column;gap:4px}.mform-files.is-gallery{flex-direction:row;flex-wrap:wrap;gap:10px}.mform-file{display:flex;align-items:center;gap:8px;font-size:12px;color:var(--mform-muted, #6b6580)}.mform-files.is-gallery .mform-file{flex-direction:column;gap:4px;width:var(--mform-thumb-size, 96px);text-align:center;position:relative}.mform-thumb{width:var(--mform-thumb-size, 96px);height:var(--mform-thumb-size, 96px);object-fit:cover;border-radius:var(--mform-control-radius, 8px);border:1px solid var(--mform-border, #d9d5e6);background:var(--mform-bg, #f6f5fa)}.mform-file-name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:100%}.mform-file-remove{border:none;background:none;color:var(--mform-muted, #6b6580);cursor:pointer;font-size:12px;padding:0 4px}.mform-file-remove:hover{color:var(--mform-danger, #c62842)}.mform-files.is-gallery .mform-file-remove{position:absolute;top:2px;right:2px;background:var(--mform-surface, #fff);border-radius:50%;width:20px;height:20px;line-height:1;box-shadow:0 1px 4px #1f1b2e33}.mform-reference{position:relative}.mform-ref-list{position:absolute;top:calc(100% + 4px);left:0;right:0;z-index:10;margin:0;padding:4px;list-style:none;background:var(--mform-control-bg, #fff);border:1px solid var(--mform-border, #d9d5e6);border-radius:var(--mform-control-radius, 8px);box-shadow:0 8px 24px #1f1b2e1f;max-height:220px;overflow-y:auto}.mform-ref-list button{display:block;width:100%;text-align:left;font:inherit;font-size:13px;padding:7px 10px;border:none;border-radius:6px;background:none;color:inherit;cursor:pointer}.mform-ref-list button:hover{background:var(--mform-bg, #f6f5fa)}.mform-accordion{display:flex;flex-direction:column;border:1px solid var(--mform-border, #d9d5e6);border-radius:var(--mform-radius, 10px);overflow:hidden}.mform-acc-item+.mform-acc-item{border-top:1px solid var(--mform-border, #d9d5e6)}.mform-acc-head{margin:0}.mform-acc-toggle{display:flex;align-items:center;gap:8px;width:100%;font:inherit;font-size:14px;font-weight:600;text-align:left;padding:12px 16px;border:none;background:var(--mform-panel-head-bg, linear-gradient(90deg, #efeaff, #f8f6ff));color:var(--mform-panel-head-text, inherit);cursor:pointer}.mform-acc-caret{font-size:11px;color:var(--mform-muted, #6b6580)}.mform-acc-body{padding:var(--mform-panel-padding, 16px);display:flex;flex-direction:column;gap:var(--mform-gap, 16px)}.mform-tabbar{display:flex;gap:4px;border-bottom:2px solid var(--mform-border, #d9d5e6);margin-bottom:14px}.mform-tab{padding:8px 18px;font:inherit;font-size:14px;border:none;background:none;color:var(--mform-muted, #6b6580);cursor:pointer}.mform-tab.is-active{color:var(--mform-primary, #6d4bd0);font-weight:600;border-bottom:2px solid var(--mform-primary, #6d4bd0);margin-bottom:-2px}.mform-grid-row{display:flex;align-items:flex-start;gap:8px;padding:10px 0;border-bottom:1px dashed var(--mform-border, #d9d5e6)}.mform-grid-fields{flex:1;display:grid;grid-template-columns:repeat(auto-fit,minmax(160px,1fr));gap:var(--mform-gap, 16px)}.mform-row-remove{border:none;background:none;color:var(--mform-muted, #6b6580);font:inherit;cursor:pointer;padding:6px 8px}.mform-row-remove:hover{color:var(--mform-danger, #c62842)}.mform-row-add{align-self:flex-start;background:none;border:1px dashed var(--mform-primary, #6d4bd0);color:var(--mform-primary, #6d4bd0);padding:7px 14px;border-radius:var(--mform-control-radius, 8px);font:inherit;font-size:13px;cursor:pointer}.mform-btn{align-self:flex-start;background:var(--mform-primary, #6d4bd0);color:var(--mform-primary-contrast, #fff);border:none;border-radius:var(--mform-control-radius, 8px);padding:11px 26px;font:inherit;font-weight:600;cursor:pointer}.mform-btn:hover{filter:brightness(1.08)}.mform-btn:focus-visible{outline:2px solid var(--mform-focus, #8e6ff0);outline-offset:2px}.mform-content{font-size:14px}.mform-unsupported{font-size:13px;color:var(--mform-muted, #6b6580);border:1px dashed var(--mform-border, #d9d5e6);border-radius:var(--mform-radius, 10px);padding:10px 14px}\n"], dependencies: [{ kind: "component", type: FormNodeComponent, selector: "mform-node", inputs: ["node", "engine", "tick", "scope", "bare", "providers", "mode", "notify"] }, { kind: "directive", type: NgComponentOutlet, selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletEnvironmentInjector", "ngComponentOutletContent", "ngComponentOutletNgModule"], exportAs: ["ngComponentOutlet"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
922
1032
  }
923
1033
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FormNodeComponent, decorators: [{
924
1034
  type: Component,
925
- args: [{
926
- selector: 'mform-node',
927
- changeDetection: ChangeDetectionStrategy.OnPush,
928
- encapsulation: ViewEncapsulation.None,
929
- host: { class: 'mform-node' },
930
- imports: [NgComponentOutlet, NgTemplateOutlet],
931
- template: `
1035
+ args: [{ selector: 'mform-node', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, host: { class: 'mform-node' }, imports: [NgComponentOutlet, NgTemplateOutlet], template: `
932
1036
  @let n = node();
933
1037
  @if (visible()) {
934
1038
  @switch (n.kind) {
@@ -963,6 +1067,42 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
963
1067
  }
964
1068
  </div>
965
1069
  }
1070
+ @case ('accordion') {
1071
+ <div class="mform-accordion">
1072
+ @for (section of container().children; track section.key; let i = $index) {
1073
+ <section class="mform-acc-item" [class.is-open]="isSectionOpen(i)">
1074
+ <h3 class="mform-acc-head">
1075
+ <button
1076
+ type="button"
1077
+ class="mform-acc-toggle"
1078
+ [attr.aria-expanded]="isSectionOpen(i)"
1079
+ [attr.aria-controls]="controlId() + '-sec-' + i"
1080
+ (click)="toggleSection(i)"
1081
+ >
1082
+ <span class="mform-acc-caret" aria-hidden="true">
1083
+ {{ isSectionOpen(i) ? '▾' : '▸' }}
1084
+ </span>
1085
+ {{ section.label || 'Section ' + (i + 1) }}
1086
+ </button>
1087
+ </h3>
1088
+ @if (isSectionOpen(i)) {
1089
+ <div class="mform-acc-body" [id]="controlId() + '-sec-' + i" role="region">
1090
+ <mform-node
1091
+ [node]="section"
1092
+ [engine]="engine()"
1093
+ [tick]="tick()"
1094
+ [providers]="providers()"
1095
+ [mode]="mode()"
1096
+ [notify]="notify()"
1097
+ [scope]="childScope()"
1098
+ [bare]="true"
1099
+ />
1100
+ </div>
1101
+ }
1102
+ </section>
1103
+ }
1104
+ </div>
1105
+ }
966
1106
  @case ('tabs') {
967
1107
  <div class="mform-tabs">
968
1108
  <div class="mform-tabbar" role="tablist">
@@ -1223,18 +1363,19 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
1223
1363
  }
1224
1364
  <input
1225
1365
  class="mform-control"
1226
- [type]="inputType()"
1366
+ [type]="numericFormatted() ? 'text' : inputType()"
1227
1367
  [id]="controlId()"
1228
1368
  [value]="stringValue()"
1229
1369
  [placeholder]="field().placeholder || ''"
1230
1370
  [disabled]="controlDisabled()"
1231
- [attr.step]="field().type === 'currency' ? '0.01' : null"
1232
- [attr.inputmode]="field().type === 'currency' ? 'decimal' : null"
1371
+ [attr.step]="field().type === 'currency' && !numericFormatted() ? '0.01' : null"
1372
+ [attr.inputmode]="field().type === 'currency' || numericFormatted() ? 'decimal' : null"
1233
1373
  [attr.aria-required]="required() || null"
1234
1374
  [attr.aria-invalid]="errors().length > 0 || null"
1235
1375
  [attr.aria-describedby]="describedBy()"
1236
1376
  (input)="setString($event)"
1237
- (blur)="touch()"
1377
+ (focus)="onFocus()"
1378
+ (blur)="onBlur()"
1238
1379
  />
1239
1380
  @if (suffix(); as s) {
1240
1381
  <span class="mform-suffix" aria-hidden="true">{{ s }}</span>
@@ -1298,8 +1439,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
1298
1439
  </ul>
1299
1440
  }
1300
1441
  </ng-template>
1301
- `,
1302
- }]
1442
+ `, styles: [".mform-node{display:contents}.mform-field{display:flex;flex-direction:column;gap:6px}.mform-label{font-size:var(--mform-label-size, 13px);font-weight:var(--mform-label-weight, 600)}.mform-req{color:var(--mform-danger, #c62842);margin-left:2px}.mform-control{font:inherit;width:100%;padding:var(--mform-control-padding, 9px 12px);border:1px solid var(--mform-border, #d9d5e6);border-radius:var(--mform-control-radius, 8px);background:var(--mform-control-bg, #fff);color:inherit;box-sizing:border-box}textarea.mform-control{min-height:72px;resize:vertical}.mform-control:focus-visible{outline:2px solid var(--mform-focus, #8e6ff0);outline-offset:1px;border-color:var(--mform-focus, #8e6ff0)}.mform-field.has-error .mform-control{border-color:var(--mform-danger, #c62842)}.mform-desc{font-size:12px;color:var(--mform-muted, #6b6580)}.mform-error{font-size:12px;color:var(--mform-danger, #c62842)}.mform-choices{display:flex;flex-direction:column;gap:6px}.mform-check{display:flex;align-items:center;gap:8px;font-size:14px;cursor:pointer}.mform-check input{width:16px;height:16px;accent-color:var(--mform-primary, #6d4bd0)}.mform-panel{border:1px solid var(--mform-border, #d9d5e6);border-radius:var(--mform-radius, 10px);overflow:hidden}.mform-panel-head{background:var(--mform-panel-head-bg, linear-gradient(90deg, #efeaff, #f8f6ff));color:var(--mform-panel-head-text, inherit);padding:10px 16px;font-weight:600;font-size:14px;border-bottom:1px solid var(--mform-border, #d9d5e6)}.mform-panel-body,.mform-group,.mform-column,.mform-tabpanel{display:flex;flex-direction:column;gap:var(--mform-gap, 16px)}.mform-panel-body{padding:var(--mform-panel-padding, 16px)}.mform-columns{display:grid;gap:var(--mform-gap, 16px)}@media(max-width:640px){.mform-columns{grid-template-columns:1fr!important}}.mform-affix{display:flex;align-items:center}.mform-affix.has-affix{border:1px solid var(--mform-border, #d9d5e6);border-radius:var(--mform-control-radius, 8px);background:var(--mform-control-bg, #fff)}.mform-affix.has-affix .mform-control{border:none;background:none;flex:1}.mform-affix.has-affix:focus-within{outline:2px solid var(--mform-focus, #8e6ff0);outline-offset:1px}.mform-prefix,.mform-suffix{padding:0 10px;color:var(--mform-muted, #6b6580);font-size:13px;white-space:nowrap}.mform-tags{display:flex;flex-wrap:wrap;gap:6px;align-items:center}.mform-tag{background:var(--mform-primary, #6d4bd0);color:var(--mform-primary-contrast, #fff);border-radius:999px;padding:3px 10px;font-size:12px;display:inline-flex;align-items:center;gap:6px}.mform-tag button{border:none;background:none;color:inherit;cursor:pointer;font-size:12px;padding:0;line-height:1}.mform-tag-input{flex:1;min-width:140px;width:auto}.mform-field.is-pending .mform-control{background-image:linear-gradient(90deg,transparent 0%,rgb(109 75 208 / 12%) 50%,transparent 100%);background-size:200% 100%;animation:mform-pending 1.2s linear infinite}@keyframes mform-pending{0%{background-position:200% 0}to{background-position:-200% 0}}.mform-files{list-style:none;margin:6px 0 0;padding:0;display:flex;flex-direction:column;gap:4px}.mform-files.is-gallery{flex-direction:row;flex-wrap:wrap;gap:10px}.mform-file{display:flex;align-items:center;gap:8px;font-size:12px;color:var(--mform-muted, #6b6580)}.mform-files.is-gallery .mform-file{flex-direction:column;gap:4px;width:var(--mform-thumb-size, 96px);text-align:center;position:relative}.mform-thumb{width:var(--mform-thumb-size, 96px);height:var(--mform-thumb-size, 96px);object-fit:cover;border-radius:var(--mform-control-radius, 8px);border:1px solid var(--mform-border, #d9d5e6);background:var(--mform-bg, #f6f5fa)}.mform-file-name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:100%}.mform-file-remove{border:none;background:none;color:var(--mform-muted, #6b6580);cursor:pointer;font-size:12px;padding:0 4px}.mform-file-remove:hover{color:var(--mform-danger, #c62842)}.mform-files.is-gallery .mform-file-remove{position:absolute;top:2px;right:2px;background:var(--mform-surface, #fff);border-radius:50%;width:20px;height:20px;line-height:1;box-shadow:0 1px 4px #1f1b2e33}.mform-reference{position:relative}.mform-ref-list{position:absolute;top:calc(100% + 4px);left:0;right:0;z-index:10;margin:0;padding:4px;list-style:none;background:var(--mform-control-bg, #fff);border:1px solid var(--mform-border, #d9d5e6);border-radius:var(--mform-control-radius, 8px);box-shadow:0 8px 24px #1f1b2e1f;max-height:220px;overflow-y:auto}.mform-ref-list button{display:block;width:100%;text-align:left;font:inherit;font-size:13px;padding:7px 10px;border:none;border-radius:6px;background:none;color:inherit;cursor:pointer}.mform-ref-list button:hover{background:var(--mform-bg, #f6f5fa)}.mform-accordion{display:flex;flex-direction:column;border:1px solid var(--mform-border, #d9d5e6);border-radius:var(--mform-radius, 10px);overflow:hidden}.mform-acc-item+.mform-acc-item{border-top:1px solid var(--mform-border, #d9d5e6)}.mform-acc-head{margin:0}.mform-acc-toggle{display:flex;align-items:center;gap:8px;width:100%;font:inherit;font-size:14px;font-weight:600;text-align:left;padding:12px 16px;border:none;background:var(--mform-panel-head-bg, linear-gradient(90deg, #efeaff, #f8f6ff));color:var(--mform-panel-head-text, inherit);cursor:pointer}.mform-acc-caret{font-size:11px;color:var(--mform-muted, #6b6580)}.mform-acc-body{padding:var(--mform-panel-padding, 16px);display:flex;flex-direction:column;gap:var(--mform-gap, 16px)}.mform-tabbar{display:flex;gap:4px;border-bottom:2px solid var(--mform-border, #d9d5e6);margin-bottom:14px}.mform-tab{padding:8px 18px;font:inherit;font-size:14px;border:none;background:none;color:var(--mform-muted, #6b6580);cursor:pointer}.mform-tab.is-active{color:var(--mform-primary, #6d4bd0);font-weight:600;border-bottom:2px solid var(--mform-primary, #6d4bd0);margin-bottom:-2px}.mform-grid-row{display:flex;align-items:flex-start;gap:8px;padding:10px 0;border-bottom:1px dashed var(--mform-border, #d9d5e6)}.mform-grid-fields{flex:1;display:grid;grid-template-columns:repeat(auto-fit,minmax(160px,1fr));gap:var(--mform-gap, 16px)}.mform-row-remove{border:none;background:none;color:var(--mform-muted, #6b6580);font:inherit;cursor:pointer;padding:6px 8px}.mform-row-remove:hover{color:var(--mform-danger, #c62842)}.mform-row-add{align-self:flex-start;background:none;border:1px dashed var(--mform-primary, #6d4bd0);color:var(--mform-primary, #6d4bd0);padding:7px 14px;border-radius:var(--mform-control-radius, 8px);font:inherit;font-size:13px;cursor:pointer}.mform-btn{align-self:flex-start;background:var(--mform-primary, #6d4bd0);color:var(--mform-primary-contrast, #fff);border:none;border-radius:var(--mform-control-radius, 8px);padding:11px 26px;font:inherit;font-weight:600;cursor:pointer}.mform-btn:hover{filter:brightness(1.08)}.mform-btn:focus-visible{outline:2px solid var(--mform-focus, #8e6ff0);outline-offset:2px}.mform-content{font-size:14px}.mform-unsupported{font-size:13px;color:var(--mform-muted, #6b6580);border:1px dashed var(--mform-border, #d9d5e6);border-radius:var(--mform-radius, 10px);padding:10px 14px}\n"] }]
1303
1443
  }], ctorParameters: () => [], propDecorators: { node: [{ type: i0.Input, args: [{ isSignal: true, alias: "node", required: true }] }], engine: [{ type: i0.Input, args: [{ isSignal: true, alias: "engine", required: true }] }], tick: [{ type: i0.Input, args: [{ isSignal: true, alias: "tick", required: true }] }], scope: [{ type: i0.Input, args: [{ isSignal: true, alias: "scope", required: false }] }], bare: [{ type: i0.Input, args: [{ isSignal: true, alias: "bare", required: false }] }], providers: [{ type: i0.Input, args: [{ isSignal: true, alias: "providers", required: false }] }], mode: [{ type: i0.Input, args: [{ isSignal: true, alias: "mode", required: false }] }], notify: [{ type: i0.Input, args: [{ isSignal: true, alias: "notify", required: false }] }] } });
1304
1444
 
1305
1445
  /**
@@ -1427,19 +1567,29 @@ class FormRendererComponent {
1427
1567
  this.errorSummary.set([]);
1428
1568
  this.currentStep.update((step) => Math.max(0, step - 1));
1429
1569
  }
1430
- nextStep() {
1570
+ /**
1571
+ * Advances only when the step is valid INCLUDING async rules, so a
1572
+ * server-side check can still block the transition.
1573
+ */
1574
+ async nextStep() {
1431
1575
  const engine = this.engine();
1432
1576
  const step = this.steps()[this.currentStep()];
1433
- if (!engine || !step)
1434
- return;
1435
- const errors = engine.validateContainer(step.key);
1436
- if (errors.length > 0) {
1437
- this.errorSummary.set(errors);
1438
- this.focusFirstError(errors);
1577
+ if (!engine || !step || this.submitting())
1439
1578
  return;
1579
+ this.submitting.set(true);
1580
+ try {
1581
+ const errors = await engine.validateContainerAsync(step.key);
1582
+ if (errors.length > 0) {
1583
+ this.errorSummary.set(errors);
1584
+ this.focusFirstError(errors);
1585
+ return;
1586
+ }
1587
+ this.errorSummary.set([]);
1588
+ this.currentStep.update((index) => Math.min(this.steps().length - 1, index + 1));
1589
+ }
1590
+ finally {
1591
+ this.submitting.set(false);
1440
1592
  }
1441
- this.errorSummary.set([]);
1442
- this.currentStep.update((index) => Math.min(this.steps().length - 1, index + 1));
1443
1593
  }
1444
1594
  // -- submission -----------------------------------------------------------
1445
1595
  async onSubmit(event) {
@@ -1536,8 +1686,8 @@ class FormRendererComponent {
1536
1686
  </button>
1537
1687
  }
1538
1688
  @if (currentStep() < steps().length - 1) {
1539
- <button type="button" class="mform-btn" (click)="nextStep()">
1540
- {{ labels().next }}
1689
+ <button type="button" class="mform-btn" [disabled]="submitting()" (click)="nextStep()">
1690
+ {{ submitting() ? '…' : labels().next }}
1541
1691
  </button>
1542
1692
  } @else if (mode() === 'edit') {
1543
1693
  <button type="submit" class="mform-btn" [disabled]="submitting()">
@@ -1555,7 +1705,7 @@ class FormRendererComponent {
1555
1705
  }
1556
1706
  </form>
1557
1707
  }
1558
- `, isInline: true, styles: [".mform-root{display:flex;flex-direction:column;gap:var(--mform-gap, 16px);font-family:var(--mform-font, system-ui, \"Segoe UI\", sans-serif);color:var(--mform-text, #1f1b2e)}.mform-node{display:contents}.mform-field{display:flex;flex-direction:column;gap:6px}.mform-label{font-size:var(--mform-label-size, 13px);font-weight:var(--mform-label-weight, 600)}.mform-req{color:var(--mform-danger, #c62842);margin-left:2px}.mform-control{font:inherit;width:100%;padding:var(--mform-control-padding, 9px 12px);border:1px solid var(--mform-border, #d9d5e6);border-radius:var(--mform-control-radius, 8px);background:var(--mform-control-bg, #fff);color:inherit;box-sizing:border-box}textarea.mform-control{min-height:72px;resize:vertical}.mform-control:focus-visible{outline:2px solid var(--mform-focus, #8e6ff0);outline-offset:1px;border-color:var(--mform-focus, #8e6ff0)}.mform-field.has-error .mform-control{border-color:var(--mform-danger, #c62842)}.mform-desc{font-size:12px;color:var(--mform-muted, #6b6580)}.mform-error{font-size:12px;color:var(--mform-danger, #c62842)}.mform-choices{display:flex;flex-direction:column;gap:6px}.mform-check{display:flex;align-items:center;gap:8px;font-size:14px;cursor:pointer}.mform-check input{width:16px;height:16px;accent-color:var(--mform-primary, #6d4bd0)}.mform-panel{border:1px solid var(--mform-border, #d9d5e6);border-radius:var(--mform-radius, 10px);overflow:hidden}.mform-panel-head{background:var(--mform-panel-head-bg, linear-gradient(90deg, #efeaff, #f8f6ff));color:var(--mform-panel-head-text, inherit);padding:10px 16px;font-weight:600;font-size:14px;border-bottom:1px solid var(--mform-border, #d9d5e6)}.mform-panel-body,.mform-group,.mform-column,.mform-tabpanel{display:flex;flex-direction:column;gap:var(--mform-gap, 16px)}.mform-panel-body{padding:var(--mform-panel-padding, 16px)}.mform-columns{display:grid;gap:var(--mform-gap, 16px)}@media(max-width:640px){.mform-columns{grid-template-columns:1fr!important}}.mform-affix{display:flex;align-items:center}.mform-affix.has-affix{border:1px solid var(--mform-border, #d9d5e6);border-radius:var(--mform-control-radius, 8px);background:var(--mform-control-bg, #fff)}.mform-affix.has-affix .mform-control{border:none;background:none;flex:1}.mform-affix.has-affix:focus-within{outline:2px solid var(--mform-focus, #8e6ff0);outline-offset:1px}.mform-prefix,.mform-suffix{padding:0 10px;color:var(--mform-muted, #6b6580);font-size:13px;white-space:nowrap}.mform-tags{display:flex;flex-wrap:wrap;gap:6px;align-items:center}.mform-tag{background:var(--mform-primary, #6d4bd0);color:var(--mform-primary-contrast, #fff);border-radius:999px;padding:3px 10px;font-size:12px;display:inline-flex;align-items:center;gap:6px}.mform-tag button{border:none;background:none;color:inherit;cursor:pointer;font-size:12px;padding:0;line-height:1}.mform-tag-input{flex:1;min-width:140px;width:auto}.mform-field.is-pending .mform-control{background-image:linear-gradient(90deg,transparent 0%,rgb(109 75 208 / 12%) 50%,transparent 100%);background-size:200% 100%;animation:mform-pending 1.2s linear infinite}@keyframes mform-pending{0%{background-position:200% 0}to{background-position:-200% 0}}.mform-files{list-style:none;margin:6px 0 0;padding:0;display:flex;flex-direction:column;gap:4px}.mform-files.is-gallery{flex-direction:row;flex-wrap:wrap;gap:10px}.mform-file{display:flex;align-items:center;gap:8px;font-size:12px;color:var(--mform-muted, #6b6580)}.mform-files.is-gallery .mform-file{flex-direction:column;gap:4px;width:var(--mform-thumb-size, 96px);text-align:center;position:relative}.mform-thumb{width:var(--mform-thumb-size, 96px);height:var(--mform-thumb-size, 96px);object-fit:cover;border-radius:var(--mform-control-radius, 8px);border:1px solid var(--mform-border, #d9d5e6);background:var(--mform-bg, #f6f5fa)}.mform-file-name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:100%}.mform-file-remove{border:none;background:none;color:var(--mform-muted, #6b6580);cursor:pointer;font-size:12px;padding:0 4px}.mform-file-remove:hover{color:var(--mform-danger, #c62842)}.mform-files.is-gallery .mform-file-remove{position:absolute;top:2px;right:2px;background:var(--mform-surface, #fff);border-radius:50%;width:20px;height:20px;line-height:1;box-shadow:0 1px 4px #1f1b2e33}.mform-reference{position:relative}.mform-ref-list{position:absolute;top:calc(100% + 4px);left:0;right:0;z-index:10;margin:0;padding:4px;list-style:none;background:var(--mform-control-bg, #fff);border:1px solid var(--mform-border, #d9d5e6);border-radius:var(--mform-control-radius, 8px);box-shadow:0 8px 24px #1f1b2e1f;max-height:220px;overflow-y:auto}.mform-ref-list button{display:block;width:100%;text-align:left;font:inherit;font-size:13px;padding:7px 10px;border:none;border-radius:6px;background:none;color:inherit;cursor:pointer}.mform-ref-list button:hover{background:var(--mform-bg, #f6f5fa)}.mform-tabbar{display:flex;gap:4px;border-bottom:2px solid var(--mform-border, #d9d5e6);margin-bottom:14px}.mform-tab{padding:8px 18px;font:inherit;font-size:14px;border:none;background:none;color:var(--mform-muted, #6b6580);cursor:pointer}.mform-tab.is-active{color:var(--mform-primary, #6d4bd0);font-weight:600;border-bottom:2px solid var(--mform-primary, #6d4bd0);margin-bottom:-2px}.mform-grid-row{display:flex;align-items:flex-start;gap:8px;padding:10px 0;border-bottom:1px dashed var(--mform-border, #d9d5e6)}.mform-grid-fields{flex:1;display:grid;grid-template-columns:repeat(auto-fit,minmax(160px,1fr));gap:var(--mform-gap, 16px)}.mform-row-remove{border:none;background:none;color:var(--mform-muted, #6b6580);font:inherit;cursor:pointer;padding:6px 8px}.mform-row-remove:hover{color:var(--mform-danger, #c62842)}.mform-row-add{align-self:flex-start;background:none;border:1px dashed var(--mform-primary, #6d4bd0);color:var(--mform-primary, #6d4bd0);padding:7px 14px;border-radius:var(--mform-control-radius, 8px);font:inherit;font-size:13px;cursor:pointer}.mform-btn{align-self:flex-start;background:var(--mform-primary, #6d4bd0);color:var(--mform-primary-contrast, #fff);border:none;border-radius:var(--mform-control-radius, 8px);padding:11px 26px;font:inherit;font-weight:600;cursor:pointer}.mform-btn:hover{filter:brightness(1.08)}.mform-btn:focus-visible{outline:2px solid var(--mform-focus, #8e6ff0);outline-offset:2px}.mform-content{font-size:14px}.mform-unsupported{font-size:13px;color:var(--mform-muted, #6b6580);border:1px dashed var(--mform-border, #d9d5e6);border-radius:var(--mform-radius, 10px);padding:10px 14px}.mform-steps{display:flex;gap:18px;list-style:none;margin:0 0 4px;padding:0 0 12px;border-bottom:1px solid var(--mform-border, #d9d5e6);flex-wrap:wrap}.mform-step{display:flex;align-items:center;gap:8px;font-size:13px;color:var(--mform-muted, #6b6580)}.mform-step-index{display:inline-flex;align-items:center;justify-content:center;width:22px;height:22px;border-radius:50%;border:1.5px solid var(--mform-border, #d9d5e6);font-size:11px;font-weight:600}.mform-step.is-active{color:var(--mform-primary, #6d4bd0);font-weight:600}.mform-step.is-active .mform-step-index{border-color:var(--mform-primary, #6d4bd0);background:var(--mform-primary, #6d4bd0);color:var(--mform-primary-contrast, #fff)}.mform-step.is-done .mform-step-index{border-color:var(--mform-primary, #6d4bd0);color:var(--mform-primary, #6d4bd0)}.mform-wizard-nav{display:flex;gap:10px;padding-top:4px}.mform-btn-secondary{background:none;border:1px solid var(--mform-primary, #6d4bd0);color:var(--mform-primary, #6d4bd0)}.mform-summary{border:1px solid var(--mform-danger, #c62842);background:var(--mform-danger-bg, #fdf0f2);color:var(--mform-danger, #c62842);border-radius:var(--mform-radius, 10px);padding:12px 16px;font-size:13px}.mform-summary ul{margin:6px 0 0;padding-left:18px}\n"], dependencies: [{ kind: "component", type: FormNodeComponent, selector: "mform-node", inputs: ["node", "engine", "tick", "scope", "bare", "providers", "mode", "notify"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
1708
+ `, isInline: true, styles: [".mform-root{display:flex;flex-direction:column;gap:var(--mform-gap, 16px);font-family:var(--mform-font, system-ui, \"Segoe UI\", sans-serif);color:var(--mform-text, #1f1b2e)}.mform-steps{display:flex;gap:18px;list-style:none;margin:0 0 4px;padding:0 0 12px;border-bottom:1px solid var(--mform-border, #d9d5e6);flex-wrap:wrap}.mform-step{display:flex;align-items:center;gap:8px;font-size:13px;color:var(--mform-muted, #6b6580)}.mform-step-index{display:inline-flex;align-items:center;justify-content:center;width:22px;height:22px;border-radius:50%;border:1.5px solid var(--mform-border, #d9d5e6);font-size:11px;font-weight:600}.mform-step.is-active{color:var(--mform-primary, #6d4bd0);font-weight:600}.mform-step.is-active .mform-step-index{border-color:var(--mform-primary, #6d4bd0);background:var(--mform-primary, #6d4bd0);color:var(--mform-primary-contrast, #fff)}.mform-step.is-done .mform-step-index{border-color:var(--mform-primary, #6d4bd0);color:var(--mform-primary, #6d4bd0)}.mform-wizard-nav{display:flex;gap:10px;padding-top:4px}.mform-btn-secondary{background:none;border:1px solid var(--mform-primary, #6d4bd0);color:var(--mform-primary, #6d4bd0)}.mform-summary{border:1px solid var(--mform-danger, #c62842);background:var(--mform-danger-bg, #fdf0f2);color:var(--mform-danger, #c62842);border-radius:var(--mform-radius, 10px);padding:12px 16px;font-size:13px}.mform-summary ul{margin:6px 0 0;padding-left:18px}\n"], dependencies: [{ kind: "component", type: FormNodeComponent, selector: "mform-node", inputs: ["node", "engine", "tick", "scope", "bare", "providers", "mode", "notify"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
1559
1709
  }
1560
1710
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: FormRendererComponent, decorators: [{
1561
1711
  type: Component,
@@ -1603,8 +1753,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
1603
1753
  </button>
1604
1754
  }
1605
1755
  @if (currentStep() < steps().length - 1) {
1606
- <button type="button" class="mform-btn" (click)="nextStep()">
1607
- {{ labels().next }}
1756
+ <button type="button" class="mform-btn" [disabled]="submitting()" (click)="nextStep()">
1757
+ {{ submitting() ? '…' : labels().next }}
1608
1758
  </button>
1609
1759
  } @else if (mode() === 'edit') {
1610
1760
  <button type="submit" class="mform-btn" [disabled]="submitting()">
@@ -1622,7 +1772,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImpo
1622
1772
  }
1623
1773
  </form>
1624
1774
  }
1625
- `, styles: [".mform-root{display:flex;flex-direction:column;gap:var(--mform-gap, 16px);font-family:var(--mform-font, system-ui, \"Segoe UI\", sans-serif);color:var(--mform-text, #1f1b2e)}.mform-node{display:contents}.mform-field{display:flex;flex-direction:column;gap:6px}.mform-label{font-size:var(--mform-label-size, 13px);font-weight:var(--mform-label-weight, 600)}.mform-req{color:var(--mform-danger, #c62842);margin-left:2px}.mform-control{font:inherit;width:100%;padding:var(--mform-control-padding, 9px 12px);border:1px solid var(--mform-border, #d9d5e6);border-radius:var(--mform-control-radius, 8px);background:var(--mform-control-bg, #fff);color:inherit;box-sizing:border-box}textarea.mform-control{min-height:72px;resize:vertical}.mform-control:focus-visible{outline:2px solid var(--mform-focus, #8e6ff0);outline-offset:1px;border-color:var(--mform-focus, #8e6ff0)}.mform-field.has-error .mform-control{border-color:var(--mform-danger, #c62842)}.mform-desc{font-size:12px;color:var(--mform-muted, #6b6580)}.mform-error{font-size:12px;color:var(--mform-danger, #c62842)}.mform-choices{display:flex;flex-direction:column;gap:6px}.mform-check{display:flex;align-items:center;gap:8px;font-size:14px;cursor:pointer}.mform-check input{width:16px;height:16px;accent-color:var(--mform-primary, #6d4bd0)}.mform-panel{border:1px solid var(--mform-border, #d9d5e6);border-radius:var(--mform-radius, 10px);overflow:hidden}.mform-panel-head{background:var(--mform-panel-head-bg, linear-gradient(90deg, #efeaff, #f8f6ff));color:var(--mform-panel-head-text, inherit);padding:10px 16px;font-weight:600;font-size:14px;border-bottom:1px solid var(--mform-border, #d9d5e6)}.mform-panel-body,.mform-group,.mform-column,.mform-tabpanel{display:flex;flex-direction:column;gap:var(--mform-gap, 16px)}.mform-panel-body{padding:var(--mform-panel-padding, 16px)}.mform-columns{display:grid;gap:var(--mform-gap, 16px)}@media(max-width:640px){.mform-columns{grid-template-columns:1fr!important}}.mform-affix{display:flex;align-items:center}.mform-affix.has-affix{border:1px solid var(--mform-border, #d9d5e6);border-radius:var(--mform-control-radius, 8px);background:var(--mform-control-bg, #fff)}.mform-affix.has-affix .mform-control{border:none;background:none;flex:1}.mform-affix.has-affix:focus-within{outline:2px solid var(--mform-focus, #8e6ff0);outline-offset:1px}.mform-prefix,.mform-suffix{padding:0 10px;color:var(--mform-muted, #6b6580);font-size:13px;white-space:nowrap}.mform-tags{display:flex;flex-wrap:wrap;gap:6px;align-items:center}.mform-tag{background:var(--mform-primary, #6d4bd0);color:var(--mform-primary-contrast, #fff);border-radius:999px;padding:3px 10px;font-size:12px;display:inline-flex;align-items:center;gap:6px}.mform-tag button{border:none;background:none;color:inherit;cursor:pointer;font-size:12px;padding:0;line-height:1}.mform-tag-input{flex:1;min-width:140px;width:auto}.mform-field.is-pending .mform-control{background-image:linear-gradient(90deg,transparent 0%,rgb(109 75 208 / 12%) 50%,transparent 100%);background-size:200% 100%;animation:mform-pending 1.2s linear infinite}@keyframes mform-pending{0%{background-position:200% 0}to{background-position:-200% 0}}.mform-files{list-style:none;margin:6px 0 0;padding:0;display:flex;flex-direction:column;gap:4px}.mform-files.is-gallery{flex-direction:row;flex-wrap:wrap;gap:10px}.mform-file{display:flex;align-items:center;gap:8px;font-size:12px;color:var(--mform-muted, #6b6580)}.mform-files.is-gallery .mform-file{flex-direction:column;gap:4px;width:var(--mform-thumb-size, 96px);text-align:center;position:relative}.mform-thumb{width:var(--mform-thumb-size, 96px);height:var(--mform-thumb-size, 96px);object-fit:cover;border-radius:var(--mform-control-radius, 8px);border:1px solid var(--mform-border, #d9d5e6);background:var(--mform-bg, #f6f5fa)}.mform-file-name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:100%}.mform-file-remove{border:none;background:none;color:var(--mform-muted, #6b6580);cursor:pointer;font-size:12px;padding:0 4px}.mform-file-remove:hover{color:var(--mform-danger, #c62842)}.mform-files.is-gallery .mform-file-remove{position:absolute;top:2px;right:2px;background:var(--mform-surface, #fff);border-radius:50%;width:20px;height:20px;line-height:1;box-shadow:0 1px 4px #1f1b2e33}.mform-reference{position:relative}.mform-ref-list{position:absolute;top:calc(100% + 4px);left:0;right:0;z-index:10;margin:0;padding:4px;list-style:none;background:var(--mform-control-bg, #fff);border:1px solid var(--mform-border, #d9d5e6);border-radius:var(--mform-control-radius, 8px);box-shadow:0 8px 24px #1f1b2e1f;max-height:220px;overflow-y:auto}.mform-ref-list button{display:block;width:100%;text-align:left;font:inherit;font-size:13px;padding:7px 10px;border:none;border-radius:6px;background:none;color:inherit;cursor:pointer}.mform-ref-list button:hover{background:var(--mform-bg, #f6f5fa)}.mform-tabbar{display:flex;gap:4px;border-bottom:2px solid var(--mform-border, #d9d5e6);margin-bottom:14px}.mform-tab{padding:8px 18px;font:inherit;font-size:14px;border:none;background:none;color:var(--mform-muted, #6b6580);cursor:pointer}.mform-tab.is-active{color:var(--mform-primary, #6d4bd0);font-weight:600;border-bottom:2px solid var(--mform-primary, #6d4bd0);margin-bottom:-2px}.mform-grid-row{display:flex;align-items:flex-start;gap:8px;padding:10px 0;border-bottom:1px dashed var(--mform-border, #d9d5e6)}.mform-grid-fields{flex:1;display:grid;grid-template-columns:repeat(auto-fit,minmax(160px,1fr));gap:var(--mform-gap, 16px)}.mform-row-remove{border:none;background:none;color:var(--mform-muted, #6b6580);font:inherit;cursor:pointer;padding:6px 8px}.mform-row-remove:hover{color:var(--mform-danger, #c62842)}.mform-row-add{align-self:flex-start;background:none;border:1px dashed var(--mform-primary, #6d4bd0);color:var(--mform-primary, #6d4bd0);padding:7px 14px;border-radius:var(--mform-control-radius, 8px);font:inherit;font-size:13px;cursor:pointer}.mform-btn{align-self:flex-start;background:var(--mform-primary, #6d4bd0);color:var(--mform-primary-contrast, #fff);border:none;border-radius:var(--mform-control-radius, 8px);padding:11px 26px;font:inherit;font-weight:600;cursor:pointer}.mform-btn:hover{filter:brightness(1.08)}.mform-btn:focus-visible{outline:2px solid var(--mform-focus, #8e6ff0);outline-offset:2px}.mform-content{font-size:14px}.mform-unsupported{font-size:13px;color:var(--mform-muted, #6b6580);border:1px dashed var(--mform-border, #d9d5e6);border-radius:var(--mform-radius, 10px);padding:10px 14px}.mform-steps{display:flex;gap:18px;list-style:none;margin:0 0 4px;padding:0 0 12px;border-bottom:1px solid var(--mform-border, #d9d5e6);flex-wrap:wrap}.mform-step{display:flex;align-items:center;gap:8px;font-size:13px;color:var(--mform-muted, #6b6580)}.mform-step-index{display:inline-flex;align-items:center;justify-content:center;width:22px;height:22px;border-radius:50%;border:1.5px solid var(--mform-border, #d9d5e6);font-size:11px;font-weight:600}.mform-step.is-active{color:var(--mform-primary, #6d4bd0);font-weight:600}.mform-step.is-active .mform-step-index{border-color:var(--mform-primary, #6d4bd0);background:var(--mform-primary, #6d4bd0);color:var(--mform-primary-contrast, #fff)}.mform-step.is-done .mform-step-index{border-color:var(--mform-primary, #6d4bd0);color:var(--mform-primary, #6d4bd0)}.mform-wizard-nav{display:flex;gap:10px;padding-top:4px}.mform-btn-secondary{background:none;border:1px solid var(--mform-primary, #6d4bd0);color:var(--mform-primary, #6d4bd0)}.mform-summary{border:1px solid var(--mform-danger, #c62842);background:var(--mform-danger-bg, #fdf0f2);color:var(--mform-danger, #c62842);border-radius:var(--mform-radius, 10px);padding:12px 16px;font-size:13px}.mform-summary ul{margin:6px 0 0;padding-left:18px}\n"] }]
1775
+ `, styles: [".mform-root{display:flex;flex-direction:column;gap:var(--mform-gap, 16px);font-family:var(--mform-font, system-ui, \"Segoe UI\", sans-serif);color:var(--mform-text, #1f1b2e)}.mform-steps{display:flex;gap:18px;list-style:none;margin:0 0 4px;padding:0 0 12px;border-bottom:1px solid var(--mform-border, #d9d5e6);flex-wrap:wrap}.mform-step{display:flex;align-items:center;gap:8px;font-size:13px;color:var(--mform-muted, #6b6580)}.mform-step-index{display:inline-flex;align-items:center;justify-content:center;width:22px;height:22px;border-radius:50%;border:1.5px solid var(--mform-border, #d9d5e6);font-size:11px;font-weight:600}.mform-step.is-active{color:var(--mform-primary, #6d4bd0);font-weight:600}.mform-step.is-active .mform-step-index{border-color:var(--mform-primary, #6d4bd0);background:var(--mform-primary, #6d4bd0);color:var(--mform-primary-contrast, #fff)}.mform-step.is-done .mform-step-index{border-color:var(--mform-primary, #6d4bd0);color:var(--mform-primary, #6d4bd0)}.mform-wizard-nav{display:flex;gap:10px;padding-top:4px}.mform-btn-secondary{background:none;border:1px solid var(--mform-primary, #6d4bd0);color:var(--mform-primary, #6d4bd0)}.mform-summary{border:1px solid var(--mform-danger, #c62842);background:var(--mform-danger-bg, #fdf0f2);color:var(--mform-danger, #c62842);border-radius:var(--mform-radius, 10px);padding:12px 16px;font-size:13px}.mform-summary ul{margin:6px 0 0;padding-left:18px}\n"] }]
1626
1776
  }], ctorParameters: () => [], propDecorators: { schema: [{ type: i0.Input, args: [{ isSignal: true, alias: "schema", required: false }] }], source: [{ type: i0.Input, args: [{ isSignal: true, alias: "source", required: false }] }], initialData: [{ type: i0.Input, args: [{ isSignal: true, alias: "initialData", required: false }] }], optionsProviders: [{ type: i0.Input, args: [{ isSignal: true, alias: "optionsProviders", required: false }] }], messages: [{ type: i0.Input, args: [{ isSignal: true, alias: "messages", required: false }] }], validators: [{ type: i0.Input, args: [{ isSignal: true, alias: "validators", required: false }] }], mode: [{ type: i0.Input, args: [{ isSignal: true, alias: "mode", required: false }] }], submitHandler: [{ type: i0.Input, args: [{ isSignal: true, alias: "submitHandler", required: false }] }], labels: [{ type: i0.Input, args: [{ isSignal: true, alias: "labels", required: false }] }], submitted: [{ type: i0.Output, args: ["submitted"] }], valueChanged: [{ type: i0.Output, args: ["valueChanged"] }] } });
1627
1777
 
1628
1778
  /*