@praxisui/manual-form 10.0.0-rc.33 → 10.0.0-rc.35

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,8 +1,8 @@
1
1
  {
2
2
  "schemaVersion": "1.0.0",
3
- "generatedAt": "2026-10-18T21:58:32.782Z",
3
+ "generatedAt": "2026-10-21T13:10:38.195Z",
4
4
  "packageName": "@praxisui/manual-form",
5
- "packageVersion": "10.0.0-rc.33",
5
+ "packageVersion": "10.0.0-rc.35",
6
6
  "sourceRegistry": "praxis-component-registry-ingestion",
7
7
  "sourceRegistryVersion": "1.0.0",
8
8
  "componentCount": 1,
@@ -3655,10 +3655,26 @@ class ManualFormConfigEditorComponent {
3655
3655
  hintsModel = createDefaultHints();
3656
3656
  hooksText = createDefaultHooksText();
3657
3657
  hooksErrors = {};
3658
+ baselineHooks = {};
3658
3659
  formRulesText = '';
3659
3660
  formRulesStateText = '';
3660
3661
  formRulesError = '';
3661
3662
  formRulesStateError = '';
3663
+ hookErrorMatchers = {
3664
+ beforeInit: { isErrorState: () => !!this.hooksErrors.beforeInit },
3665
+ afterInit: { isErrorState: () => !!this.hooksErrors.afterInit },
3666
+ beforeValidate: { isErrorState: () => !!this.hooksErrors.beforeValidate },
3667
+ afterValidate: { isErrorState: () => !!this.hooksErrors.afterValidate },
3668
+ beforeSubmit: { isErrorState: () => !!this.hooksErrors.beforeSubmit },
3669
+ afterSubmit: { isErrorState: () => !!this.hooksErrors.afterSubmit },
3670
+ onError: { isErrorState: () => !!this.hooksErrors.onError },
3671
+ };
3672
+ formRulesErrorMatcher = {
3673
+ isErrorState: () => !!this.formRulesError,
3674
+ };
3675
+ formRulesStateErrorMatcher = {
3676
+ isErrorState: () => !!this.formRulesStateError,
3677
+ };
3662
3678
  hookStages = [
3663
3679
  'beforeInit',
3664
3680
  'afterInit',
@@ -3694,6 +3710,7 @@ class ManualFormConfigEditorComponent {
3694
3710
  return this.tx('editor.rules.formRulesPlaceholder', '[{"id":"rule-1","name":"Required rule","targetType":"field","targets":["field"],"effect":{"condition":{"!==":[{"var":"field"},null]},"properties":{"required":true}}}]');
3695
3711
  }
3696
3712
  ngOnInit() {
3713
+ this.baselineHooks = deepClone(this.instance.currentConfig.hooks || {});
3697
3714
  this.refresh();
3698
3715
  }
3699
3716
  close() { this.ref.close('cancel'); }
@@ -3851,11 +3868,44 @@ class ManualFormConfigEditorComponent {
3851
3868
  return map[stage] || stage;
3852
3869
  }
3853
3870
  onHookTextChange(stage, value) {
3854
- this.hooksText = { ...this.hooksText, [stage]: value };
3871
+ this.hooksText = { ...this.hooksText, [stage]: value ?? '' };
3872
+ const nextErrors = { ...this.hooksErrors };
3873
+ const raw = (this.hooksText[stage] || '').trim();
3874
+ const currentHooks = deepClone(this.instance.currentConfig.hooks || {});
3875
+ if (!raw) {
3876
+ delete nextErrors[stage];
3877
+ delete currentHooks[stage];
3878
+ this.hooksErrors = nextErrors;
3879
+ this.applySilentConfigPatch({ hooks: currentHooks });
3880
+ }
3881
+ else {
3882
+ try {
3883
+ const parsed = JSON.parse(raw);
3884
+ if (!Array.isArray(parsed)) {
3885
+ nextErrors[stage] = this.tx('editor.validation.jsonArray', 'Must be a JSON array.');
3886
+ this.hooksErrors = nextErrors;
3887
+ }
3888
+ else {
3889
+ delete nextErrors[stage];
3890
+ currentHooks[stage] = parsed;
3891
+ this.hooksErrors = nextErrors;
3892
+ this.applySilentConfigPatch({ hooks: currentHooks });
3893
+ }
3894
+ }
3895
+ catch {
3896
+ nextErrors[stage] = this.tx('editor.validation.invalidJson', 'Invalid JSON.');
3897
+ this.hooksErrors = nextErrors;
3898
+ }
3899
+ }
3900
+ this.isDirty$.next(true);
3901
+ this.syncValidationState();
3855
3902
  }
3856
3903
  resetHooks() {
3857
- this.hooksText = buildHooksText(this.instance.currentConfig.hooks);
3904
+ const restored = deepClone(this.baselineHooks);
3905
+ this.hooksText = buildHooksText(restored);
3858
3906
  this.hooksErrors = {};
3907
+ this.applySilentConfigPatch({ hooks: restored });
3908
+ this.syncValidationState();
3859
3909
  }
3860
3910
  applyHooks() {
3861
3911
  const next = {};
@@ -3877,16 +3927,57 @@ class ManualFormConfigEditorComponent {
3877
3927
  }
3878
3928
  }
3879
3929
  this.hooksErrors = errors;
3930
+ this.syncValidationState();
3880
3931
  if (Object.keys(errors).length > 0) {
3881
3932
  return;
3882
3933
  }
3934
+ this.baselineHooks = deepClone(next);
3883
3935
  this.applyConfigPatch({ hooks: next });
3884
3936
  }
3885
3937
  onRulesTextChange(value) {
3886
- this.formRulesText = value;
3938
+ this.formRulesText = value ?? '';
3939
+ const rulesRaw = this.formRulesText.trim();
3940
+ if (!rulesRaw) {
3941
+ this.formRulesError = '';
3942
+ this.applySilentConfigPatch({ formRules: undefined });
3943
+ }
3944
+ else {
3945
+ try {
3946
+ const parsed = JSON.parse(rulesRaw);
3947
+ if (!Array.isArray(parsed)) {
3948
+ this.formRulesError = this.tx('editor.validation.formRulesArray', 'formRules must be a JSON array.');
3949
+ }
3950
+ else {
3951
+ this.formRulesError = '';
3952
+ this.applySilentConfigPatch({ formRules: parsed });
3953
+ }
3954
+ }
3955
+ catch {
3956
+ this.formRulesError = this.tx('editor.validation.formRulesInvalid', 'Invalid JSON in formRules.');
3957
+ }
3958
+ }
3959
+ this.isDirty$.next(true);
3960
+ this.syncValidationState();
3887
3961
  }
3888
3962
  onRulesStateTextChange(value) {
3889
- this.formRulesStateText = value;
3963
+ this.formRulesStateText = value ?? '';
3964
+ const stateRaw = this.formRulesStateText.trim();
3965
+ if (!stateRaw) {
3966
+ this.formRulesStateError = '';
3967
+ this.applySilentConfigPatch({ formRulesState: undefined });
3968
+ }
3969
+ else {
3970
+ try {
3971
+ const parsed = JSON.parse(stateRaw);
3972
+ this.formRulesStateError = '';
3973
+ this.applySilentConfigPatch({ formRulesState: parsed });
3974
+ }
3975
+ catch {
3976
+ this.formRulesStateError = this.tx('editor.validation.formRulesStateInvalid', 'Invalid JSON in formRulesState.');
3977
+ }
3978
+ }
3979
+ this.isDirty$.next(true);
3980
+ this.syncValidationState();
3890
3981
  }
3891
3982
  applyRules() {
3892
3983
  this.formRulesError = '';
@@ -3899,12 +3990,14 @@ class ManualFormConfigEditorComponent {
3899
3990
  const parsed = JSON.parse(rulesRaw);
3900
3991
  if (!Array.isArray(parsed)) {
3901
3992
  this.formRulesError = this.tx('editor.validation.formRulesArray', 'formRules must be a JSON array.');
3993
+ this.syncValidationState();
3902
3994
  return;
3903
3995
  }
3904
3996
  rules = parsed;
3905
3997
  }
3906
3998
  catch {
3907
3999
  this.formRulesError = this.tx('editor.validation.formRulesInvalid', 'Invalid JSON in formRules.');
4000
+ this.syncValidationState();
3908
4001
  return;
3909
4002
  }
3910
4003
  }
@@ -3915,9 +4008,11 @@ class ManualFormConfigEditorComponent {
3915
4008
  }
3916
4009
  catch {
3917
4010
  this.formRulesStateError = this.tx('editor.validation.formRulesStateInvalid', 'Invalid JSON in formRulesState.');
4011
+ this.syncValidationState();
3918
4012
  return;
3919
4013
  }
3920
4014
  }
4015
+ this.syncValidationState();
3921
4016
  this.applyConfigPatch({ formRules: rules, formRulesState: rulesState });
3922
4017
  }
3923
4018
  applyCascadePatch(patch) {
@@ -3965,15 +4060,29 @@ class ManualFormConfigEditorComponent {
3965
4060
  this.isDirty$.next(false);
3966
4061
  return value;
3967
4062
  }
4063
+ syncValidationState() {
4064
+ const hasHookError = Object.values(this.hooksErrors).some((msg) => !!msg);
4065
+ const valid = !hasHookError && !this.formRulesError && !this.formRulesStateError;
4066
+ this.isValid$.next(valid);
4067
+ }
3968
4068
  refresh() {
3969
4069
  this.all = (this.instance.currentConfig.fieldMetadata || []).map(f => ({ ...f }));
3970
4070
  this.actionsModel = this.normalizeActions(this.instance.currentConfig.actions);
3971
4071
  this.messagesModel = deepClone(this.instance.currentConfig.messages || {});
3972
4072
  this.behaviorModel = deepClone(this.instance.currentConfig.behavior || {});
3973
4073
  this.hintsModel = createHints(this.instance.currentConfig.hints);
3974
- this.hooksText = buildHooksText(this.instance.currentConfig.hooks);
3975
- this.formRulesText = stringifyJson(this.instance.currentConfig.formRules, true);
3976
- this.formRulesStateText = stringifyJson(this.instance.currentConfig.formRulesState, false);
4074
+ const nextHooksText = buildHooksText(this.instance.currentConfig.hooks);
4075
+ for (const stage of this.hookStages) {
4076
+ if (!this.hooksErrors[stage]) {
4077
+ this.hooksText = { ...this.hooksText, [stage]: nextHooksText[stage] };
4078
+ }
4079
+ }
4080
+ if (!this.formRulesError) {
4081
+ this.formRulesText = stringifyJson(this.instance.currentConfig.formRules, true);
4082
+ }
4083
+ if (!this.formRulesStateError) {
4084
+ this.formRulesStateText = stringifyJson(this.instance.currentConfig.formRulesState, false);
4085
+ }
3977
4086
  this.applyFilter();
3978
4087
  }
3979
4088
  normalizeActions(source) {
@@ -4003,6 +4112,15 @@ class ManualFormConfigEditorComponent {
4003
4112
  resetButtonLabel: source.resetButtonLabel ?? base.resetButtonLabel,
4004
4113
  };
4005
4114
  }
4115
+ applySilentConfigPatch(patch) {
4116
+ try {
4117
+ const config = deepClone(this.instance.currentConfig);
4118
+ const next = { ...config, ...patch };
4119
+ this.instance.replaceConfig(next);
4120
+ this.instance.saveDraft();
4121
+ }
4122
+ catch { }
4123
+ }
4006
4124
  applyConfigPatch(patch) {
4007
4125
  try {
4008
4126
  const config = deepClone(this.instance.currentConfig);
@@ -4027,15 +4145,6 @@ class ManualFormConfigEditorComponent {
4027
4145
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: ManualFormConfigEditorComponent, deps: [{ token: SETTINGS_PANEL_DATA }, { token: SETTINGS_PANEL_REF }], target: i0.ɵɵFactoryTarget.Component });
4028
4146
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.14", type: ManualFormConfigEditorComponent, isStandalone: true, selector: "praxis-manual-form-config-editor", providers: [providePraxisManualFormI18n()], ngImport: i0, template: `
4029
4147
  <div class="mf-editor">
4030
- <div class="mf-editor__toolbar">
4031
- <div class="mf-editor__title">
4032
- <span>{{ tx('editor.title', 'Form settings') }}</span>
4033
- <small class="mf-editor__subtitle">{{ tx('editor.subtitle', 'Fields, actions, messages and behavior') }}</small>
4034
- </div>
4035
- <div class="spacer"></div>
4036
- <button type="button" class="mf-btn" (click)="close()">{{ tx('editor.close', 'Close') }}</button>
4037
- </div>
4038
-
4039
4148
  <mat-tab-group class="mf-tabs">
4040
4149
  <mat-tab [label]="tx('editor.tabs.fields', 'Fields')">
4041
4150
  <div class="mf-tab">
@@ -4059,27 +4168,47 @@ class ManualFormConfigEditorComponent {
4059
4168
  <div class="col col--name"><code>{{ f.name }}</code></div>
4060
4169
  <div class="col col--label">{{ f.label || f.name }}</div>
4061
4170
  <div class="col col--type">{{ f.controlType }}</div>
4062
- <div class="col col--vis">
4171
+ <div class="col col--vis" [attr.data-label]="tx('editor.fields.visible', 'Visible')">
4063
4172
  <label class="toggle">
4064
- <input type="checkbox" [checked]="!f.hidden" (change)="toggleVisibility(f, $any($event.target).checked)" />
4173
+ <input
4174
+ type="checkbox"
4175
+ [checked]="!f.hidden"
4176
+ [attr.aria-label]="(f.label || f.name) + ' - ' + tx('editor.fields.visible', 'Visible')"
4177
+ (change)="toggleVisibility(f, $any($event.target).checked)"
4178
+ />
4065
4179
  <span>{{ f.hidden ? tx('editor.fields.hiddenState', 'Hidden') : tx('editor.fields.visibleState', 'Visible') }}</span>
4066
4180
  </label>
4067
4181
  </div>
4068
- <div class="col col--req">
4182
+ <div class="col col--req" [attr.data-label]="tx('editor.fields.required', 'Required')">
4069
4183
  <label class="toggle">
4070
- <input type="checkbox" [checked]="!!f.required" (change)="toggleRequired(f, $any($event.target).checked)" />
4184
+ <input
4185
+ type="checkbox"
4186
+ [checked]="!!f.required"
4187
+ [attr.aria-label]="(f.label || f.name) + ' - ' + tx('editor.fields.required', 'Required')"
4188
+ (change)="toggleRequired(f, $any($event.target).checked)"
4189
+ />
4071
4190
  <span>{{ f.required ? tx('editor.shared.yes', 'Yes') : tx('editor.shared.no', 'No') }}</span>
4072
4191
  </label>
4073
4192
  </div>
4074
- <div class="col col--ro">
4193
+ <div class="col col--ro" [attr.data-label]="tx('editor.fields.readOnly', 'Read-only')">
4075
4194
  <label class="toggle">
4076
- <input type="checkbox" [checked]="!!f.readOnly" (change)="toggleReadOnly(f, $any($event.target).checked)" />
4195
+ <input
4196
+ type="checkbox"
4197
+ [checked]="!!f.readOnly"
4198
+ [attr.aria-label]="(f.label || f.name) + ' - ' + tx('editor.fields.readOnly', 'Read-only')"
4199
+ (change)="toggleReadOnly(f, $any($event.target).checked)"
4200
+ />
4077
4201
  <span>{{ f.readOnly ? tx('editor.shared.yes', 'Yes') : tx('editor.shared.no', 'No') }}</span>
4078
4202
  </label>
4079
4203
  </div>
4080
- <div class="col col--dis">
4204
+ <div class="col col--dis" [attr.data-label]="tx('editor.fields.disabled', 'Disabled')">
4081
4205
  <label class="toggle">
4082
- <input type="checkbox" [checked]="!!f.disabled" (change)="toggleDisabled(f, $any($event.target).checked)" />
4206
+ <input
4207
+ type="checkbox"
4208
+ [checked]="!!f.disabled"
4209
+ [attr.aria-label]="(f.label || f.name) + ' - ' + tx('editor.fields.disabled', 'Disabled')"
4210
+ (change)="toggleDisabled(f, $any($event.target).checked)"
4211
+ />
4083
4212
  <span>{{ f.disabled ? tx('editor.shared.yes', 'Yes') : tx('editor.shared.no', 'No') }}</span>
4084
4213
  </label>
4085
4214
  </div>
@@ -4128,6 +4257,7 @@ class ManualFormConfigEditorComponent {
4128
4257
  <mat-label>{{ tx('editor.actions.field.variant', 'Variant') }}</mat-label>
4129
4258
  <mat-select
4130
4259
  [value]="actionsModel[block.key].variant || null"
4260
+ [placeholder]="tx('editor.actions.variant.default', 'Default')"
4131
4261
  (selectionChange)="updateActionField(block.key, 'variant', $event.value)"
4132
4262
  >
4133
4263
  <mat-option [value]="null">{{ tx('editor.actions.variant.default', 'Default') }}</mat-option>
@@ -4141,6 +4271,7 @@ class ManualFormConfigEditorComponent {
4141
4271
  <mat-label>{{ tx('editor.actions.field.color', 'Color') }}</mat-label>
4142
4272
  <mat-select
4143
4273
  [value]="actionsModel[block.key].color || null"
4274
+ [placeholder]="tx('editor.actions.color.default', 'Default')"
4144
4275
  (selectionChange)="updateActionField(block.key, 'color', $event.value)"
4145
4276
  >
4146
4277
  <mat-option [value]="null">{{ tx('editor.actions.color.default', 'Default') }}</mat-option>
@@ -4293,6 +4424,7 @@ class ManualFormConfigEditorComponent {
4293
4424
  <mat-label>{{ tx('editor.actions.field.variant', 'Variant') }}</mat-label>
4294
4425
  <mat-select
4295
4426
  [value]="action.variant || null"
4427
+ [placeholder]="tx('editor.actions.variant.default', 'Default')"
4296
4428
  (selectionChange)="updateCustomActionField(action, 'variant', $event.value)"
4297
4429
  >
4298
4430
  <mat-option [value]="null">{{ tx('editor.actions.variant.default', 'Default') }}</mat-option>
@@ -4306,6 +4438,7 @@ class ManualFormConfigEditorComponent {
4306
4438
  <mat-label>{{ tx('editor.actions.field.color', 'Color') }}</mat-label>
4307
4439
  <mat-select
4308
4440
  [value]="action.color || null"
4441
+ [placeholder]="tx('editor.actions.color.default', 'Default')"
4309
4442
  (selectionChange)="updateCustomActionField(action, 'color', $event.value)"
4310
4443
  >
4311
4444
  <mat-option [value]="null">{{ tx('editor.actions.color.default', 'Default') }}</mat-option>
@@ -4695,10 +4828,12 @@ class ManualFormConfigEditorComponent {
4695
4828
  <textarea
4696
4829
  matInput
4697
4830
  rows="4"
4698
- [value]="hooksText[stage]"
4699
- (input)="onHookTextChange(stage, $any($event.target).value)"
4700
- [placeholder]="hookPlaceholder()"
4701
- ></textarea>
4831
+ [ngModel]="hooksText[stage]"
4832
+ [ngModelOptions]="{ standalone: true }"
4833
+ [errorStateMatcher]="hookErrorMatchers[stage]"
4834
+ (ngModelChange)="onHookTextChange(stage, $event)"
4835
+ [placeholder]="hookPlaceholder()"
4836
+ ></textarea>
4702
4837
  <button
4703
4838
  praxisIconButton="help_outline"
4704
4839
  size="dense"
@@ -4738,8 +4873,10 @@ class ManualFormConfigEditorComponent {
4738
4873
  <textarea
4739
4874
  matInput
4740
4875
  rows="6"
4741
- [value]="formRulesText"
4742
- (input)="onRulesTextChange($any($event.target).value)"
4876
+ [ngModel]="formRulesText"
4877
+ [ngModelOptions]="{ standalone: true }"
4878
+ [errorStateMatcher]="formRulesErrorMatcher"
4879
+ (ngModelChange)="onRulesTextChange($event)"
4743
4880
  [placeholder]="formRulesPlaceholder()"
4744
4881
  ></textarea>
4745
4882
  <button
@@ -4760,10 +4897,12 @@ class ManualFormConfigEditorComponent {
4760
4897
  <textarea
4761
4898
  matInput
4762
4899
  rows="6"
4763
- [value]="formRulesStateText"
4900
+ [ngModel]="formRulesStateText"
4901
+ [ngModelOptions]="{ standalone: true }"
4902
+ [errorStateMatcher]="formRulesStateErrorMatcher"
4764
4903
  readonly
4765
4904
  aria-readonly="true"
4766
- (input)="onRulesStateTextChange($any($event.target).value)"
4905
+ (ngModelChange)="onRulesStateTextChange($event)"
4767
4906
  placeholder='{"nodes":[],"edges":[]}'
4768
4907
  ></textarea>
4769
4908
  <mat-hint>{{ tx('editor.rules.formRulesStateReadonlyHint', 'Generated by the visual editor for round-trip; edit formRules instead.') }}</mat-hint>
@@ -4808,7 +4947,7 @@ class ManualFormConfigEditorComponent {
4808
4947
  </mat-tab>
4809
4948
  </mat-tab-group>
4810
4949
  </div>
4811
- `, isInline: true, styles: [".mf-editor{display:grid;gap:16px;padding:16px;color:var(--md-sys-color-on-surface)}.mf-editor__toolbar{display:flex;align-items:center;gap:12px;padding:12px 14px;border:1px solid var(--md-sys-color-outline-variant);border-radius:12px;background:var(--md-sys-color-surface-container)}.mf-editor__title{display:flex;flex-direction:column;gap:2px;font-weight:600}.mf-editor__subtitle{font-weight:400;color:var(--md-sys-color-on-surface-variant)}.mf-editor__toolbar .spacer{flex:1}.mf-tabs{min-height:420px}.mf-tab{display:grid;gap:16px;padding:14px 6px 0}.mf-editor__list{display:grid;gap:8px;overflow-x:auto}.mf-editor__row{display:grid;grid-template-columns:2.2fr 2fr 1fr repeat(4,minmax(90px,.8fr));align-items:center;gap:8px;padding:10px 12px;border:1px solid var(--md-sys-color-outline-variant);border-radius:10px;background:var(--md-sys-color-surface);min-width:720px}.mf-editor__row--head{font-weight:600;color:var(--md-sys-color-on-surface);background:var(--md-sys-color-surface-container)}.mf-editor__filter{display:inline-flex;align-items:center;gap:8px;font-weight:500}.mf-editor__filter input{accent-color:var(--md-sys-color-primary)}.col code{display:inline-block;font-family:inherit;font-size:.85rem;padding:2px 8px;border-radius:6px;background:var(--md-sys-color-surface-container-high);color:var(--md-sys-color-on-surface-variant)}.mf-btn{min-height:36px;padding:0 12px;border-radius:8px;border:1px solid var(--md-sys-color-outline);background:var(--md-sys-color-surface);color:var(--md-sys-color-on-surface);cursor:pointer;transition:background-color .18s ease,border-color .18s ease,color .18s ease}.mf-btn:hover{background:var(--md-sys-color-surface-container)}.mf-btn:focus-visible{outline:2px solid var(--md-sys-color-primary);outline-offset:2px}.toggle{display:inline-flex;align-items:center;gap:6px;color:var(--md-sys-color-on-surface-variant)}.mf-section{display:grid;gap:12px;padding:16px;border-radius:12px;border:1px solid var(--md-sys-color-outline-variant);background:var(--md-sys-color-surface)}.mf-section--full{padding:10px}.mf-section__header{display:flex;align-items:flex-start;justify-content:space-between;gap:12px}.mf-section__header h3{margin:0;font-size:1.05rem;font-weight:600}.mf-help{margin:6px 0 0;font-size:.9rem;color:var(--md-sys-color-on-surface-variant)}.mf-note{padding:10px 12px;border-radius:10px;border:1px dashed var(--md-sys-color-outline-variant);background:var(--md-sys-color-surface-container);color:var(--md-sys-color-on-surface-variant);font-size:.9rem}.mf-actions-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(240px,1fr));gap:12px}.mf-action-card,.mf-custom-card,.mf-custom-message-card{display:grid;gap:10px;padding:14px;border-radius:10px;border:1px solid var(--md-sys-color-outline-variant);background:var(--md-sys-color-surface-container-high)}.mf-action-card__title,.mf-custom-card__title{font-weight:600;color:var(--md-sys-color-on-surface)}.mf-actions-layout{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:12px;align-items:center}.mf-toggle-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(260px,1fr));gap:12px;align-items:start}.mf-hooks-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(260px,1fr));gap:12px}.mf-hook-field textarea{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:.85rem}.mf-actions-row{display:inline-flex;gap:8px;align-items:center}.mf-actions-row--end{justify-content:flex-end;width:100%}.mf-custom-actions,.mf-custom-messages{display:grid;gap:12px}.mf-custom-card__header{display:flex;align-items:center;justify-content:space-between;gap:8px}.mf-custom-card__grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:12px;align-items:center}.mf-messages-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(240px,1fr));gap:12px}.mf-empty{margin:0;color:var(--md-sys-color-on-surface-variant)}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: MatTabsModule }, { kind: "component", type: i2.MatTab, selector: "mat-tab", inputs: ["disabled", "label", "aria-label", "aria-labelledby", "labelClass", "bodyClass", "id"], exportAs: ["matTab"] }, { kind: "component", type: i2.MatTabGroup, selector: "mat-tab-group", inputs: ["color", "fitInkBarToContent", "mat-stretch-tabs", "mat-align-tabs", "dynamicHeight", "selectedIndex", "headerPosition", "animationDuration", "contentTabIndex", "disablePagination", "disableRipple", "preserveContent", "backgroundColor", "aria-label", "aria-labelledby"], outputs: ["selectedIndexChange", "focusChange", "animationDone", "selectedTabChange"], exportAs: ["matTabGroup"] }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i3.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i3.MatLabel, selector: "mat-label" }, { kind: "directive", type: i3.MatHint, selector: "mat-hint", inputs: ["align", "id"] }, { kind: "directive", type: i3.MatError, selector: "mat-error, [matError]", inputs: ["id"] }, { kind: "directive", type: i3.MatSuffix, selector: "[matSuffix], [matIconSuffix], [matTextSuffix]", inputs: ["matTextSuffix"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i4.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "ngmodule", type: MatSelectModule }, { kind: "component", type: i5.MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth", "canSelectNullableOptions"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "component", type: i5.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "ngmodule", type: MatSlideToggleModule }, { kind: "component", type: i6.MatSlideToggle, selector: "mat-slide-toggle", inputs: ["name", "id", "labelPosition", "aria-label", "aria-labelledby", "aria-describedby", "required", "color", "disabled", "disableRipple", "tabIndex", "checked", "hideIcon", "disabledInteractive"], outputs: ["change", "toggleChange"], exportAs: ["matSlideToggle"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i7.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i8.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i9.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "component", type: PraxisIconButtonComponent, selector: "button[praxisIconButton]", inputs: ["praxisIconButton", "size", "appearance", "presentation", "pressed", "busy"] }, { kind: "component", type: CascadeManagerTabComponent, selector: "praxis-cascade-manager-tab", inputs: ["fields", "connections"], outputs: ["apply", "cancel"] }] });
4950
+ `, isInline: true, styles: [":host{display:block;container-type:inline-size}.mf-editor{display:grid;gap:16px;padding:4px 0;color:var(--pfx-settings-panel-fg, var(--md-sys-color-on-surface))}.mf-tabs{min-height:420px}.mf-tab{display:grid;gap:16px;padding:14px 2px 0}.mf-editor__list{display:grid;gap:8px;overflow-x:auto}.mf-editor__row{display:grid;grid-template-columns:2.2fr 2fr 1fr repeat(4,minmax(90px,.8fr));align-items:center;gap:8px;padding:10px 12px;border:1px solid var(--pfx-settings-panel-border, var(--md-sys-color-outline-variant));border-radius:10px;background:var(--pfx-settings-panel-surface, var(--md-sys-color-surface));min-width:720px}.mf-editor__row--head{font-weight:600;color:var(--pfx-settings-panel-fg, var(--md-sys-color-on-surface));background:var(--pfx-settings-panel-surface-elevated, var(--md-sys-color-surface-container))}@container (max-width: 680px){.mf-editor__list{overflow-x:visible}.mf-editor__row--head{display:none}.mf-editor__row{min-width:0;grid-template-columns:repeat(2,minmax(0,1fr));gap:10px}.mf-editor__row .col--name,.mf-editor__row .col--label{grid-column:span 1}.mf-editor__row .col--type{grid-column:1 / -1;font-size:.8rem;color:var(--pfx-settings-panel-muted, var(--md-sys-color-on-surface-variant))}.mf-editor__row .col[data-label]{display:flex;align-items:center;gap:6px}.mf-editor__row .col[data-label]:before{content:attr(data-label) \":\";font-size:.78rem;font-weight:600;color:var(--pfx-settings-panel-muted, var(--md-sys-color-on-surface-variant))}}.mf-editor__filter{display:inline-flex;align-items:center;gap:8px;font-weight:500}.mf-editor__filter input,.toggle input{accent-color:var(--md-sys-color-primary)}.col code{display:inline-block;font-family:inherit;font-size:.85rem;padding:2px 8px;border-radius:6px;background:var(--md-sys-color-surface-container-high);color:var(--md-sys-color-on-surface-variant)}.toggle{display:inline-flex;align-items:center;gap:6px;color:var(--md-sys-color-on-surface-variant)}.mf-section{display:grid;gap:12px;padding:16px;border-radius:12px;border:1px solid var(--md-sys-color-outline-variant);background:var(--md-sys-color-surface)}.mf-section--full{padding:10px}.mf-section__header{display:flex;align-items:flex-start;justify-content:space-between;gap:12px}.mf-section__header h3{margin:0;font-size:1.05rem;font-weight:600}.mf-help{margin:6px 0 0;font-size:.9rem;color:var(--md-sys-color-on-surface-variant)}.mf-note{padding:10px 12px;border-radius:10px;border:1px dashed var(--md-sys-color-outline-variant);background:var(--md-sys-color-surface-container);color:var(--md-sys-color-on-surface-variant);font-size:.9rem}.mf-actions-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(240px,1fr));gap:12px}.mf-action-card,.mf-custom-card,.mf-custom-message-card{display:grid;gap:10px;padding:14px;border-radius:10px;border:1px solid var(--md-sys-color-outline-variant);background:var(--md-sys-color-surface-container-high)}.mf-action-card__title,.mf-custom-card__title{font-weight:600;color:var(--md-sys-color-on-surface)}.mf-actions-layout{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:12px;align-items:center}.mf-toggle-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(260px,1fr));gap:12px;align-items:start}.mf-hooks-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(260px,1fr));gap:12px}.mf-hook-field textarea{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:.85rem}.mf-actions-row{display:inline-flex;gap:8px;align-items:center}.mf-actions-row--end{justify-content:flex-end;width:100%}.mf-custom-actions,.mf-custom-messages{display:grid;gap:12px}.mf-custom-card__header{display:flex;align-items:center;justify-content:space-between;gap:8px}.mf-custom-card__grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:12px;align-items:center}.mf-messages-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(240px,1fr));gap:12px}.mf-empty{margin:0;color:var(--md-sys-color-on-surface-variant)}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.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: i1.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: MatTabsModule }, { kind: "component", type: i2.MatTab, selector: "mat-tab", inputs: ["disabled", "label", "aria-label", "aria-labelledby", "labelClass", "bodyClass", "id"], exportAs: ["matTab"] }, { kind: "component", type: i2.MatTabGroup, selector: "mat-tab-group", inputs: ["color", "fitInkBarToContent", "mat-stretch-tabs", "mat-align-tabs", "dynamicHeight", "selectedIndex", "headerPosition", "animationDuration", "contentTabIndex", "disablePagination", "disableRipple", "preserveContent", "backgroundColor", "aria-label", "aria-labelledby"], outputs: ["selectedIndexChange", "focusChange", "animationDone", "selectedTabChange"], exportAs: ["matTabGroup"] }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i3.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i3.MatLabel, selector: "mat-label" }, { kind: "directive", type: i3.MatHint, selector: "mat-hint", inputs: ["align", "id"] }, { kind: "directive", type: i3.MatError, selector: "mat-error, [matError]", inputs: ["id"] }, { kind: "directive", type: i3.MatSuffix, selector: "[matSuffix], [matIconSuffix], [matTextSuffix]", inputs: ["matTextSuffix"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i4.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "ngmodule", type: MatSelectModule }, { kind: "component", type: i5.MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth", "canSelectNullableOptions"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "component", type: i5.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "ngmodule", type: MatSlideToggleModule }, { kind: "component", type: i6.MatSlideToggle, selector: "mat-slide-toggle", inputs: ["name", "id", "labelPosition", "aria-label", "aria-labelledby", "aria-describedby", "required", "color", "disabled", "disableRipple", "tabIndex", "checked", "hideIcon", "disabledInteractive"], outputs: ["change", "toggleChange"], exportAs: ["matSlideToggle"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i7.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i8.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i9.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "component", type: PraxisIconButtonComponent, selector: "button[praxisIconButton]", inputs: ["praxisIconButton", "size", "appearance", "presentation", "pressed", "busy"] }, { kind: "component", type: CascadeManagerTabComponent, selector: "praxis-cascade-manager-tab", inputs: ["fields", "connections"], outputs: ["apply", "cancel"] }] });
4812
4951
  }
4813
4952
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: ManualFormConfigEditorComponent, decorators: [{
4814
4953
  type: Component,
@@ -4826,15 +4965,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
4826
4965
  CascadeManagerTabComponent
4827
4966
  ], providers: [providePraxisManualFormI18n()], template: `
4828
4967
  <div class="mf-editor">
4829
- <div class="mf-editor__toolbar">
4830
- <div class="mf-editor__title">
4831
- <span>{{ tx('editor.title', 'Form settings') }}</span>
4832
- <small class="mf-editor__subtitle">{{ tx('editor.subtitle', 'Fields, actions, messages and behavior') }}</small>
4833
- </div>
4834
- <div class="spacer"></div>
4835
- <button type="button" class="mf-btn" (click)="close()">{{ tx('editor.close', 'Close') }}</button>
4836
- </div>
4837
-
4838
4968
  <mat-tab-group class="mf-tabs">
4839
4969
  <mat-tab [label]="tx('editor.tabs.fields', 'Fields')">
4840
4970
  <div class="mf-tab">
@@ -4858,27 +4988,47 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
4858
4988
  <div class="col col--name"><code>{{ f.name }}</code></div>
4859
4989
  <div class="col col--label">{{ f.label || f.name }}</div>
4860
4990
  <div class="col col--type">{{ f.controlType }}</div>
4861
- <div class="col col--vis">
4991
+ <div class="col col--vis" [attr.data-label]="tx('editor.fields.visible', 'Visible')">
4862
4992
  <label class="toggle">
4863
- <input type="checkbox" [checked]="!f.hidden" (change)="toggleVisibility(f, $any($event.target).checked)" />
4993
+ <input
4994
+ type="checkbox"
4995
+ [checked]="!f.hidden"
4996
+ [attr.aria-label]="(f.label || f.name) + ' - ' + tx('editor.fields.visible', 'Visible')"
4997
+ (change)="toggleVisibility(f, $any($event.target).checked)"
4998
+ />
4864
4999
  <span>{{ f.hidden ? tx('editor.fields.hiddenState', 'Hidden') : tx('editor.fields.visibleState', 'Visible') }}</span>
4865
5000
  </label>
4866
5001
  </div>
4867
- <div class="col col--req">
5002
+ <div class="col col--req" [attr.data-label]="tx('editor.fields.required', 'Required')">
4868
5003
  <label class="toggle">
4869
- <input type="checkbox" [checked]="!!f.required" (change)="toggleRequired(f, $any($event.target).checked)" />
5004
+ <input
5005
+ type="checkbox"
5006
+ [checked]="!!f.required"
5007
+ [attr.aria-label]="(f.label || f.name) + ' - ' + tx('editor.fields.required', 'Required')"
5008
+ (change)="toggleRequired(f, $any($event.target).checked)"
5009
+ />
4870
5010
  <span>{{ f.required ? tx('editor.shared.yes', 'Yes') : tx('editor.shared.no', 'No') }}</span>
4871
5011
  </label>
4872
5012
  </div>
4873
- <div class="col col--ro">
5013
+ <div class="col col--ro" [attr.data-label]="tx('editor.fields.readOnly', 'Read-only')">
4874
5014
  <label class="toggle">
4875
- <input type="checkbox" [checked]="!!f.readOnly" (change)="toggleReadOnly(f, $any($event.target).checked)" />
5015
+ <input
5016
+ type="checkbox"
5017
+ [checked]="!!f.readOnly"
5018
+ [attr.aria-label]="(f.label || f.name) + ' - ' + tx('editor.fields.readOnly', 'Read-only')"
5019
+ (change)="toggleReadOnly(f, $any($event.target).checked)"
5020
+ />
4876
5021
  <span>{{ f.readOnly ? tx('editor.shared.yes', 'Yes') : tx('editor.shared.no', 'No') }}</span>
4877
5022
  </label>
4878
5023
  </div>
4879
- <div class="col col--dis">
5024
+ <div class="col col--dis" [attr.data-label]="tx('editor.fields.disabled', 'Disabled')">
4880
5025
  <label class="toggle">
4881
- <input type="checkbox" [checked]="!!f.disabled" (change)="toggleDisabled(f, $any($event.target).checked)" />
5026
+ <input
5027
+ type="checkbox"
5028
+ [checked]="!!f.disabled"
5029
+ [attr.aria-label]="(f.label || f.name) + ' - ' + tx('editor.fields.disabled', 'Disabled')"
5030
+ (change)="toggleDisabled(f, $any($event.target).checked)"
5031
+ />
4882
5032
  <span>{{ f.disabled ? tx('editor.shared.yes', 'Yes') : tx('editor.shared.no', 'No') }}</span>
4883
5033
  </label>
4884
5034
  </div>
@@ -4927,6 +5077,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
4927
5077
  <mat-label>{{ tx('editor.actions.field.variant', 'Variant') }}</mat-label>
4928
5078
  <mat-select
4929
5079
  [value]="actionsModel[block.key].variant || null"
5080
+ [placeholder]="tx('editor.actions.variant.default', 'Default')"
4930
5081
  (selectionChange)="updateActionField(block.key, 'variant', $event.value)"
4931
5082
  >
4932
5083
  <mat-option [value]="null">{{ tx('editor.actions.variant.default', 'Default') }}</mat-option>
@@ -4940,6 +5091,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
4940
5091
  <mat-label>{{ tx('editor.actions.field.color', 'Color') }}</mat-label>
4941
5092
  <mat-select
4942
5093
  [value]="actionsModel[block.key].color || null"
5094
+ [placeholder]="tx('editor.actions.color.default', 'Default')"
4943
5095
  (selectionChange)="updateActionField(block.key, 'color', $event.value)"
4944
5096
  >
4945
5097
  <mat-option [value]="null">{{ tx('editor.actions.color.default', 'Default') }}</mat-option>
@@ -5092,6 +5244,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
5092
5244
  <mat-label>{{ tx('editor.actions.field.variant', 'Variant') }}</mat-label>
5093
5245
  <mat-select
5094
5246
  [value]="action.variant || null"
5247
+ [placeholder]="tx('editor.actions.variant.default', 'Default')"
5095
5248
  (selectionChange)="updateCustomActionField(action, 'variant', $event.value)"
5096
5249
  >
5097
5250
  <mat-option [value]="null">{{ tx('editor.actions.variant.default', 'Default') }}</mat-option>
@@ -5105,6 +5258,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
5105
5258
  <mat-label>{{ tx('editor.actions.field.color', 'Color') }}</mat-label>
5106
5259
  <mat-select
5107
5260
  [value]="action.color || null"
5261
+ [placeholder]="tx('editor.actions.color.default', 'Default')"
5108
5262
  (selectionChange)="updateCustomActionField(action, 'color', $event.value)"
5109
5263
  >
5110
5264
  <mat-option [value]="null">{{ tx('editor.actions.color.default', 'Default') }}</mat-option>
@@ -5494,10 +5648,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
5494
5648
  <textarea
5495
5649
  matInput
5496
5650
  rows="4"
5497
- [value]="hooksText[stage]"
5498
- (input)="onHookTextChange(stage, $any($event.target).value)"
5499
- [placeholder]="hookPlaceholder()"
5500
- ></textarea>
5651
+ [ngModel]="hooksText[stage]"
5652
+ [ngModelOptions]="{ standalone: true }"
5653
+ [errorStateMatcher]="hookErrorMatchers[stage]"
5654
+ (ngModelChange)="onHookTextChange(stage, $event)"
5655
+ [placeholder]="hookPlaceholder()"
5656
+ ></textarea>
5501
5657
  <button
5502
5658
  praxisIconButton="help_outline"
5503
5659
  size="dense"
@@ -5537,8 +5693,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
5537
5693
  <textarea
5538
5694
  matInput
5539
5695
  rows="6"
5540
- [value]="formRulesText"
5541
- (input)="onRulesTextChange($any($event.target).value)"
5696
+ [ngModel]="formRulesText"
5697
+ [ngModelOptions]="{ standalone: true }"
5698
+ [errorStateMatcher]="formRulesErrorMatcher"
5699
+ (ngModelChange)="onRulesTextChange($event)"
5542
5700
  [placeholder]="formRulesPlaceholder()"
5543
5701
  ></textarea>
5544
5702
  <button
@@ -5559,10 +5717,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
5559
5717
  <textarea
5560
5718
  matInput
5561
5719
  rows="6"
5562
- [value]="formRulesStateText"
5720
+ [ngModel]="formRulesStateText"
5721
+ [ngModelOptions]="{ standalone: true }"
5722
+ [errorStateMatcher]="formRulesStateErrorMatcher"
5563
5723
  readonly
5564
5724
  aria-readonly="true"
5565
- (input)="onRulesStateTextChange($any($event.target).value)"
5725
+ (ngModelChange)="onRulesStateTextChange($event)"
5566
5726
  placeholder='{"nodes":[],"edges":[]}'
5567
5727
  ></textarea>
5568
5728
  <mat-hint>{{ tx('editor.rules.formRulesStateReadonlyHint', 'Generated by the visual editor for round-trip; edit formRules instead.') }}</mat-hint>
@@ -5607,7 +5767,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
5607
5767
  </mat-tab>
5608
5768
  </mat-tab-group>
5609
5769
  </div>
5610
- `, styles: [".mf-editor{display:grid;gap:16px;padding:16px;color:var(--md-sys-color-on-surface)}.mf-editor__toolbar{display:flex;align-items:center;gap:12px;padding:12px 14px;border:1px solid var(--md-sys-color-outline-variant);border-radius:12px;background:var(--md-sys-color-surface-container)}.mf-editor__title{display:flex;flex-direction:column;gap:2px;font-weight:600}.mf-editor__subtitle{font-weight:400;color:var(--md-sys-color-on-surface-variant)}.mf-editor__toolbar .spacer{flex:1}.mf-tabs{min-height:420px}.mf-tab{display:grid;gap:16px;padding:14px 6px 0}.mf-editor__list{display:grid;gap:8px;overflow-x:auto}.mf-editor__row{display:grid;grid-template-columns:2.2fr 2fr 1fr repeat(4,minmax(90px,.8fr));align-items:center;gap:8px;padding:10px 12px;border:1px solid var(--md-sys-color-outline-variant);border-radius:10px;background:var(--md-sys-color-surface);min-width:720px}.mf-editor__row--head{font-weight:600;color:var(--md-sys-color-on-surface);background:var(--md-sys-color-surface-container)}.mf-editor__filter{display:inline-flex;align-items:center;gap:8px;font-weight:500}.mf-editor__filter input{accent-color:var(--md-sys-color-primary)}.col code{display:inline-block;font-family:inherit;font-size:.85rem;padding:2px 8px;border-radius:6px;background:var(--md-sys-color-surface-container-high);color:var(--md-sys-color-on-surface-variant)}.mf-btn{min-height:36px;padding:0 12px;border-radius:8px;border:1px solid var(--md-sys-color-outline);background:var(--md-sys-color-surface);color:var(--md-sys-color-on-surface);cursor:pointer;transition:background-color .18s ease,border-color .18s ease,color .18s ease}.mf-btn:hover{background:var(--md-sys-color-surface-container)}.mf-btn:focus-visible{outline:2px solid var(--md-sys-color-primary);outline-offset:2px}.toggle{display:inline-flex;align-items:center;gap:6px;color:var(--md-sys-color-on-surface-variant)}.mf-section{display:grid;gap:12px;padding:16px;border-radius:12px;border:1px solid var(--md-sys-color-outline-variant);background:var(--md-sys-color-surface)}.mf-section--full{padding:10px}.mf-section__header{display:flex;align-items:flex-start;justify-content:space-between;gap:12px}.mf-section__header h3{margin:0;font-size:1.05rem;font-weight:600}.mf-help{margin:6px 0 0;font-size:.9rem;color:var(--md-sys-color-on-surface-variant)}.mf-note{padding:10px 12px;border-radius:10px;border:1px dashed var(--md-sys-color-outline-variant);background:var(--md-sys-color-surface-container);color:var(--md-sys-color-on-surface-variant);font-size:.9rem}.mf-actions-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(240px,1fr));gap:12px}.mf-action-card,.mf-custom-card,.mf-custom-message-card{display:grid;gap:10px;padding:14px;border-radius:10px;border:1px solid var(--md-sys-color-outline-variant);background:var(--md-sys-color-surface-container-high)}.mf-action-card__title,.mf-custom-card__title{font-weight:600;color:var(--md-sys-color-on-surface)}.mf-actions-layout{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:12px;align-items:center}.mf-toggle-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(260px,1fr));gap:12px;align-items:start}.mf-hooks-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(260px,1fr));gap:12px}.mf-hook-field textarea{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:.85rem}.mf-actions-row{display:inline-flex;gap:8px;align-items:center}.mf-actions-row--end{justify-content:flex-end;width:100%}.mf-custom-actions,.mf-custom-messages{display:grid;gap:12px}.mf-custom-card__header{display:flex;align-items:center;justify-content:space-between;gap:8px}.mf-custom-card__grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:12px;align-items:center}.mf-messages-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(240px,1fr));gap:12px}.mf-empty{margin:0;color:var(--md-sys-color-on-surface-variant)}\n"] }]
5770
+ `, styles: [":host{display:block;container-type:inline-size}.mf-editor{display:grid;gap:16px;padding:4px 0;color:var(--pfx-settings-panel-fg, var(--md-sys-color-on-surface))}.mf-tabs{min-height:420px}.mf-tab{display:grid;gap:16px;padding:14px 2px 0}.mf-editor__list{display:grid;gap:8px;overflow-x:auto}.mf-editor__row{display:grid;grid-template-columns:2.2fr 2fr 1fr repeat(4,minmax(90px,.8fr));align-items:center;gap:8px;padding:10px 12px;border:1px solid var(--pfx-settings-panel-border, var(--md-sys-color-outline-variant));border-radius:10px;background:var(--pfx-settings-panel-surface, var(--md-sys-color-surface));min-width:720px}.mf-editor__row--head{font-weight:600;color:var(--pfx-settings-panel-fg, var(--md-sys-color-on-surface));background:var(--pfx-settings-panel-surface-elevated, var(--md-sys-color-surface-container))}@container (max-width: 680px){.mf-editor__list{overflow-x:visible}.mf-editor__row--head{display:none}.mf-editor__row{min-width:0;grid-template-columns:repeat(2,minmax(0,1fr));gap:10px}.mf-editor__row .col--name,.mf-editor__row .col--label{grid-column:span 1}.mf-editor__row .col--type{grid-column:1 / -1;font-size:.8rem;color:var(--pfx-settings-panel-muted, var(--md-sys-color-on-surface-variant))}.mf-editor__row .col[data-label]{display:flex;align-items:center;gap:6px}.mf-editor__row .col[data-label]:before{content:attr(data-label) \":\";font-size:.78rem;font-weight:600;color:var(--pfx-settings-panel-muted, var(--md-sys-color-on-surface-variant))}}.mf-editor__filter{display:inline-flex;align-items:center;gap:8px;font-weight:500}.mf-editor__filter input,.toggle input{accent-color:var(--md-sys-color-primary)}.col code{display:inline-block;font-family:inherit;font-size:.85rem;padding:2px 8px;border-radius:6px;background:var(--md-sys-color-surface-container-high);color:var(--md-sys-color-on-surface-variant)}.toggle{display:inline-flex;align-items:center;gap:6px;color:var(--md-sys-color-on-surface-variant)}.mf-section{display:grid;gap:12px;padding:16px;border-radius:12px;border:1px solid var(--md-sys-color-outline-variant);background:var(--md-sys-color-surface)}.mf-section--full{padding:10px}.mf-section__header{display:flex;align-items:flex-start;justify-content:space-between;gap:12px}.mf-section__header h3{margin:0;font-size:1.05rem;font-weight:600}.mf-help{margin:6px 0 0;font-size:.9rem;color:var(--md-sys-color-on-surface-variant)}.mf-note{padding:10px 12px;border-radius:10px;border:1px dashed var(--md-sys-color-outline-variant);background:var(--md-sys-color-surface-container);color:var(--md-sys-color-on-surface-variant);font-size:.9rem}.mf-actions-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(240px,1fr));gap:12px}.mf-action-card,.mf-custom-card,.mf-custom-message-card{display:grid;gap:10px;padding:14px;border-radius:10px;border:1px solid var(--md-sys-color-outline-variant);background:var(--md-sys-color-surface-container-high)}.mf-action-card__title,.mf-custom-card__title{font-weight:600;color:var(--md-sys-color-on-surface)}.mf-actions-layout{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:12px;align-items:center}.mf-toggle-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(260px,1fr));gap:12px;align-items:start}.mf-hooks-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(260px,1fr));gap:12px}.mf-hook-field textarea{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:.85rem}.mf-actions-row{display:inline-flex;gap:8px;align-items:center}.mf-actions-row--end{justify-content:flex-end;width:100%}.mf-custom-actions,.mf-custom-messages{display:grid;gap:12px}.mf-custom-card__header{display:flex;align-items:center;justify-content:space-between;gap:8px}.mf-custom-card__grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:12px;align-items:center}.mf-messages-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(240px,1fr));gap:12px}.mf-empty{margin:0;color:var(--md-sys-color-on-surface-variant)}\n"] }]
5611
5771
  }], ctorParameters: () => [{ type: undefined, decorators: [{
5612
5772
  type: Inject,
5613
5773
  args: [SETTINGS_PANEL_DATA]
package/package.json CHANGED
@@ -1,19 +1,19 @@
1
1
  {
2
2
  "name": "@praxisui/manual-form",
3
- "version": "10.0.0-rc.33",
3
+ "version": "10.0.0-rc.35",
4
4
  "description": "Manual form toolkit for Praxis UI: container, instance factory and editor bridge for @praxisui/* fields.",
5
5
  "peerDependencies": {
6
6
  "@angular/common": "^21.0.0",
7
7
  "@angular/core": "^21.0.0",
8
- "@praxisui/core": "^10.0.0-rc.33",
9
- "@praxisui/dynamic-fields": "^10.0.0-rc.33",
10
- "@praxisui/settings-panel": "^10.0.0-rc.33",
11
- "@praxisui/metadata-editor": "^10.0.0-rc.33",
8
+ "@praxisui/core": "^10.0.0-rc.35",
9
+ "@praxisui/dynamic-fields": "^10.0.0-rc.35",
10
+ "@praxisui/settings-panel": "^10.0.0-rc.35",
11
+ "@praxisui/metadata-editor": "^10.0.0-rc.35",
12
12
  "@angular/cdk": "^21.0.0",
13
13
  "@angular/forms": "^21.0.0",
14
14
  "@angular/material": "^21.0.0",
15
15
  "@angular/router": "^21.0.0",
16
- "@praxisui/ai": "^10.0.0-rc.33",
16
+ "@praxisui/ai": "^10.0.0-rc.35",
17
17
  "rxjs": "~7.8.0"
18
18
  },
19
19
  "dependencies": {
@@ -4,6 +4,7 @@ import { Observable, BehaviorSubject } from 'rxjs';
4
4
  import * as _angular_core from '@angular/core';
5
5
  import { AfterContentInit, OnDestroy, OnInit, OnChanges, TemplateRef, ViewContainerRef, SimpleChanges, InjectionToken, Provider } from '@angular/core';
6
6
  import { BaseAiAdapter, PatchResult, PraxisAssistantTurnViewState, PraxisAssistantShellLayout, PraxisAssistantShellLabels, PraxisAssistantSessionSnapshot, PraxisAssistantShellQuickReply, PraxisAssistantShellMessage } from '@praxisui/ai';
7
+ import { ErrorStateMatcher } from '@angular/material/core';
7
8
  import { SettingsPanelRef } from '@praxisui/settings-panel';
8
9
 
9
10
  type ManualFormId = string;
@@ -343,10 +344,14 @@ declare class ManualFormConfigEditorComponent implements OnInit {
343
344
  hintsModel: FormHints;
344
345
  hooksText: Record<FormHookStage, string>;
345
346
  hooksErrors: Partial<Record<FormHookStage, string>>;
347
+ private baselineHooks;
346
348
  formRulesText: string;
347
349
  formRulesStateText: string;
348
350
  formRulesError: string;
349
351
  formRulesStateError: string;
352
+ readonly hookErrorMatchers: Record<FormHookStage, ErrorStateMatcher>;
353
+ readonly formRulesErrorMatcher: ErrorStateMatcher;
354
+ readonly formRulesStateErrorMatcher: ErrorStateMatcher;
350
355
  readonly hookStages: FormHookStage[];
351
356
  readonly actionBlocks: Array<{
352
357
  key: 'submit' | 'cancel' | 'reset';
@@ -389,8 +394,10 @@ declare class ManualFormConfigEditorComponent implements OnInit {
389
394
  get cascadeFields(): Array<FieldDefinition | FieldMetadata>;
390
395
  getSettingsValue(): any;
391
396
  onSave(): any;
397
+ private syncValidationState;
392
398
  private refresh;
393
399
  private normalizeActions;
400
+ private applySilentConfigPatch;
394
401
  private applyConfigPatch;
395
402
  private generateCustomId;
396
403
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<ManualFormConfigEditorComponent, never>;