iss-ctrl 0.0.19 → 0.0.21

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.
Files changed (26) hide show
  1. package/fesm2022/{iss-ctrl-iss-ctrl-Cy9p_PEj.mjs → iss-ctrl-iss-ctrl-BKu6a5HF.mjs} +976 -766
  2. package/fesm2022/iss-ctrl-iss-ctrl-BKu6a5HF.mjs.map +1 -0
  3. package/fesm2022/{iss-ctrl-load-event-DbAc-Wqq.mjs → iss-ctrl-load-event-bOt6IlIo.mjs} +3 -3
  4. package/fesm2022/{iss-ctrl-load-event-DbAc-Wqq.mjs.map → iss-ctrl-load-event-bOt6IlIo.mjs.map} +1 -1
  5. package/fesm2022/{iss-ctrl-load-options-event-CmXkuQ5O.mjs → iss-ctrl-load-options-event-CxyAyNMe.mjs} +3 -3
  6. package/fesm2022/{iss-ctrl-load-options-event-CmXkuQ5O.mjs.map → iss-ctrl-load-options-event-CxyAyNMe.mjs.map} +1 -1
  7. package/fesm2022/{iss-ctrl-load-value-event-DmX9Pkzf.mjs → iss-ctrl-load-value-event-DJKJ5S3h.mjs} +3 -3
  8. package/fesm2022/{iss-ctrl-load-value-event-DmX9Pkzf.mjs.map → iss-ctrl-load-value-event-DJKJ5S3h.mjs.map} +1 -1
  9. package/fesm2022/{iss-ctrl-note.field-CbLlparb.mjs → iss-ctrl-note.field-B_Lbp28X.mjs} +2 -2
  10. package/fesm2022/{iss-ctrl-note.field-CbLlparb.mjs.map → iss-ctrl-note.field-B_Lbp28X.mjs.map} +1 -1
  11. package/fesm2022/{iss-ctrl-replace-api-keys-C_mYQdtP.mjs → iss-ctrl-replace-api-keys-C0XYQW4i.mjs} +2 -2
  12. package/fesm2022/{iss-ctrl-replace-api-keys-C_mYQdtP.mjs.map → iss-ctrl-replace-api-keys-C0XYQW4i.mjs.map} +1 -1
  13. package/fesm2022/{iss-ctrl-set-value-event-vKIrSRgu.mjs → iss-ctrl-set-value-event-CgXhi0Bv.mjs} +2 -2
  14. package/fesm2022/{iss-ctrl-set-value-event-vKIrSRgu.mjs.map → iss-ctrl-set-value-event-CgXhi0Bv.mjs.map} +1 -1
  15. package/fesm2022/{iss-ctrl-table.field-CvqXIE4q.mjs → iss-ctrl-table.field-B5FxF6hn.mjs} +4 -4
  16. package/fesm2022/{iss-ctrl-table.field-CvqXIE4q.mjs.map → iss-ctrl-table.field-B5FxF6hn.mjs.map} +1 -1
  17. package/fesm2022/{iss-ctrl-trigger-based-on-json-value-wyn6AXWA.mjs → iss-ctrl-trigger-based-on-json-value-2v1kLVHZ.mjs} +2 -2
  18. package/fesm2022/{iss-ctrl-trigger-based-on-json-value-wyn6AXWA.mjs.map → iss-ctrl-trigger-based-on-json-value-2v1kLVHZ.mjs.map} +1 -1
  19. package/fesm2022/{iss-ctrl-user-picker.field-BhjVNeqN.mjs → iss-ctrl-user-picker.field-BH63sZ-X.mjs} +13 -6
  20. package/fesm2022/iss-ctrl-user-picker.field-BH63sZ-X.mjs.map +1 -0
  21. package/fesm2022/iss-ctrl.mjs +1 -1
  22. package/package.json +1 -1
  23. package/types/iss-ctrl.d.ts +54 -12
  24. package/upgrade.scss +30 -18
  25. package/fesm2022/iss-ctrl-iss-ctrl-Cy9p_PEj.mjs.map +0 -1
  26. package/fesm2022/iss-ctrl-user-picker.field-BhjVNeqN.mjs.map +0 -1
@@ -1,2 +1,2 @@
1
- export { aj as ATTACHMENT_DOWNLOAD_URL, ai as ATTACHMENT_UPLOAD_RESPONSE_MAPPER, ah as ATTACHMENT_UPLOAD_URL, an as ArabicNumberPipe, a0 as AttachmentViewer, ab as COMPONENTS, a6 as COUNTRIES, d as CheckboxControl, v as CheckboxField, Y as CheckboxViewer, e as ChipsControl, q as ChipsField, Q as ChipsViewer, C as ControlBase, a8 as DATETIME_FORMAT, a7 as DATE_FORMAT_ADAPTER, a5 as DEFAULT_COUNTRY, D as DatePickerControl, B as DatePickerField, a3 as DatePickerTimeViewer, a2 as DatePickerViewer, f as DateRangePickerControl, J as DateRangePickerField, a4 as DateRangePickerViewer, h as DatetimePickerControl, G as DatetimePickerField, am as Droppable, E as Employee, af as FIELD_DATA, F as FieldBase, ao as FieldDataInjector, as as FileHandler, i as FilePickerControl, z as FilePickerField, aI as FileValidators, c as FormEngine, ay as HIJRI_LOCALE, az as HIJRI_MONTH_NAMES, H as HOST, al as HintsComponent, j as InputControl, o as InputField, K as InputViewer, at as IssDateAdapter, aH as IssValidators, L as Loader, M as MobileControl, u as MobileField, X as MobileViewer, O as Outlet, P as PLACEHOLDER_REGEX, a9 as PLATE_LETTERS, av as Phone, k as PlateControl, y as PlateField, ap as PlateLetterPipe, $ as PlateViewer, R as RadioControl, s as RadioField, U as RadioViewer, ac as SERVICES, S as SelectControl, t as SelectField, aq as SelectStatusPipe, W as SelectViewer, l as SlideToggleControl, x as SlideToggleField, _ as SlideToggleViewer, T as TextareaControl, p as TextareaField, N as TextareaViewer, m as TimePickerControl, A as TimePickerField, a1 as TimePickerViewer, aJ as TimeValidator, n as ToggleButtonControl, w as ToggleButtonField, Z as ToggleButtonViewer, ar as TranslatePipe, ag as UI_LANGUAGE, ak as USER_SEARCH_API, aa as VALIDATION_MESSAGES, ad as VALIDATORS, V as VIEW_PROVIDER, a as ValidationMessageComponent, aw as arrayFrom, aD as createHijriDate, ax as formatFileSize, aE as formatHijriDate, aC as getHijriDaysInMonth, aA as getHijriParts, g as getPropertyByPath, ae as isActiveValidationRule, aF as parseHijriDate, au as provideIssDateAdapter, r as replacePlaceholders, aB as startOfHijriMonth, aG as toHijriDisplay } from './iss-ctrl-iss-ctrl-Cy9p_PEj.mjs';
1
+ export { ak as ATTACHMENT_DOWNLOAD_URL, aj as ATTACHMENT_UPLOAD_RESPONSE_MAPPER, ai as ATTACHMENT_UPLOAD_URL, ao as ArabicNumberPipe, a1 as AttachmentViewer, ac as COMPONENTS, a7 as COUNTRIES, e as CheckboxControl, w as CheckboxField, Z as CheckboxViewer, f as ChipsControl, s as ChipsField, U as ChipsViewer, C as ControlBase, a9 as DATETIME_FORMAT, a8 as DATE_FORMAT_ADAPTER, a6 as DEFAULT_COUNTRY, D as DatePickerControl, G as DatePickerField, a4 as DatePickerTimeViewer, a3 as DatePickerViewer, h as DateRangePickerControl, K as DateRangePickerField, a5 as DateRangePickerViewer, i as DatetimePickerControl, J as DatetimePickerField, an as Droppable, E as Employee, ag as FIELD_DATA, F as FieldBase, ap as FieldDataInjector, b as FieldViewer, at as FileHandler, j as FilePickerControl, A as FilePickerField, aJ as FileValidators, d as FormEngine, az as HIJRI_LOCALE, aA as HIJRI_MONTH_NAMES, H as HOST, am as Hints, k as InputControl, p as InputField, N as InputViewer, au as IssDateAdapter, aI as IssValidators, L as Loader, M as MobileControl, v as MobileField, Y as MobileViewer, O as Outlet, P as PLACEHOLDER_REGEX, aa as PLATE_LETTERS, aw as Phone, l as PlateControl, z as PlateField, aq as PlateLetterPipe, a0 as PlateViewer, R as RadioControl, t as RadioField, W as RadioViewer, ad as SERVICES, S as SelectControl, u as SelectField, ar as SelectStatusPipe, X as SelectViewer, m as SlideToggleControl, y as SlideToggleField, $ as SlideToggleViewer, T as TextareaControl, q as TextareaField, Q as TextareaViewer, n as TimePickerControl, B as TimePickerField, a2 as TimePickerViewer, aK as TimeValidator, o as ToggleButtonControl, x as ToggleButtonField, _ as ToggleButtonViewer, as as TranslatePipe, ah as UI_LANGUAGE, al as USER_SEARCH_API, ab as VALIDATION_MESSAGES, ae as VALIDATORS, V as VIEW_PROVIDER, a as ValidationMessage, ax as arrayFrom, aE as createHijriDate, ay as formatFileSize, aF as formatHijriDate, aD as getHijriDaysInMonth, aB as getHijriParts, g as getPropertyByPath, af as isActiveValidationRule, aG as parseHijriDate, av as provideIssDateAdapter, r as replacePlaceholders, aC as startOfHijriMonth, aH as toHijriDisplay } from './iss-ctrl-iss-ctrl-BKu6a5HF.mjs';
2
2
  //# sourceMappingURL=iss-ctrl.mjs.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "iss-ctrl",
3
- "version": "0.0.19",
3
+ "version": "0.0.21",
4
4
  "peerDependencies": {
5
5
  "@angular/common": "^21.0.6",
6
6
  "@angular/core": "^21.0.6",
@@ -1,11 +1,11 @@
1
1
  import * as i0 from '@angular/core';
2
2
  import { InputSignal, WritableSignal, OutputEmitterRef, OnInit, OnDestroy, Signal, Type, DestroyRef, StaticProvider, InjectionToken, PipeTransform, Injector, Provider } from '@angular/core';
3
- import { ControlValueAccessor, Validator, FormControl, AbstractControl, ValidationErrors, FormGroup, ValidatorFn, AsyncValidatorFn, ControlContainer } from '@angular/forms';
3
+ import { ControlValueAccessor, Validator, FormControl, AbstractControl, ValidationErrors, NgControl, FormGroup, ValidatorFn, AsyncValidatorFn, ControlContainer } from '@angular/forms';
4
+ import { ErrorStateMatcher, MatDateFormats, NativeDateAdapter } from '@angular/material/core';
4
5
  import { MatChipInputEvent } from '@angular/material/chips';
5
6
  import { MatDatepickerInputEvent } from '@angular/material/datepicker';
6
7
  import { PhoneNumber } from 'libphonenumber-js/mobile';
7
8
  import { Observable, BehaviorSubject } from 'rxjs';
8
- import { MatDateFormats, NativeDateAdapter } from '@angular/material/core';
9
9
 
10
10
  interface Option {
11
11
  value: string;
@@ -64,6 +64,7 @@ interface Hint {
64
64
  * ```
65
65
  */
66
66
  declare class ControlBase implements ControlValueAccessor, Validator {
67
+ #private;
67
68
  /**
68
69
  * Name of this control inside the surrounding `FormGroup`.
69
70
  */
@@ -118,12 +119,28 @@ declare class ControlBase implements ControlValueAccessor, Validator {
118
119
  */
119
120
  onChange: OutputEmitterRef<any>;
120
121
  protected ctrl: FormControl;
122
+ /**
123
+ * Error state for the inner `mat-form-field`, derived from the **host** control.
124
+ *
125
+ * Material reads `errorState` off the `NgControl` bound to its own `matInput` — here that is
126
+ * {@link ControlBase.ctrl}, which carries no validators and is therefore always valid. Every
127
+ * error a consumer cares about (the field config's validators plus the control's own
128
+ * {@link ControlBase.validate}) lives on the host control instead, so without this matcher an
129
+ * invalid `iss-ctrl-*` would keep rendering the valid outline. Bind it on the `matInput` as
130
+ * `[errorStateMatcher]="errorStateMatcher"`.
131
+ */
132
+ protected errorStateMatcher: ErrorStateMatcher;
121
133
  registerOnChange(fn: () => void): void;
122
134
  registerOnTouched(fn: () => void): void;
123
135
  setDisabledState(isDisabled: boolean): void;
124
136
  writeValue(value: string | FormData | Date | null): void;
125
137
  markAsTouched(): void;
126
138
  validate(control: AbstractControl): ValidationErrors | null;
139
+ /**
140
+ * The `NgControl` this component is bound to from the outside (`formControlName`,
141
+ * `[formControl]` or `[ngModel]`), or `null` when it is used without one.
142
+ */
143
+ protected hostControl(): NgControl | null;
127
144
  /**
128
145
  * Whether the control currently holds no value.
129
146
  *
@@ -813,6 +830,7 @@ declare class SelectControl extends ControlBase implements OnDestroy {
813
830
  clearSelection(): void;
814
831
  toggleAllSelection(): void;
815
832
  ngOnDestroy(): void;
833
+ protected isEmpty(): boolean;
816
834
  static ɵfac: i0.ɵɵFactoryDeclaration<SelectControl, never>;
817
835
  static ɵcmp: i0.ɵɵComponentDeclaration<SelectControl, "iss-ctrl-select", never, { "options": { "alias": "options"; "required": true; "isSignal": true; }; "height": { "alias": "height"; "required": false; "isSignal": true; }; "loading": { "alias": "loading"; "required": false; "isSignal": true; }; }, { "onQueryChange": "onQueryChange"; }, ["optionTemplate", "cardViewerTemplate"], ["label"], true, never>;
818
836
  }
@@ -1925,11 +1943,11 @@ interface I18n {
1925
1943
 
1926
1944
  type ValidationMessageFn<T> = (args: T) => I18n;
1927
1945
 
1928
- interface ValidationMessage {
1946
+ interface IssValidator {
1929
1947
  [key: PropertyKey]: ValidationMessageFn<any>;
1930
1948
  }
1931
1949
 
1932
- declare const VALIDATION_MESSAGES: ValidationMessage;
1950
+ declare const VALIDATION_MESSAGES: IssValidator;
1933
1951
 
1934
1952
  interface DynamicImport<T> {
1935
1953
  [key: PropertyKey]: () => Promise<T>;
@@ -1990,22 +2008,46 @@ declare const ATTACHMENT_DOWNLOAD_URL: InjectionToken<string>;
1990
2008
 
1991
2009
  declare const USER_SEARCH_API: InjectionToken<string>;
1992
2010
 
1993
- declare class HintsComponent {
2011
+ declare class Hints {
1994
2012
  hints: InputSignal<Hint[]>;
1995
2013
  touched: InputSignal<boolean>;
1996
- static ɵfac: i0.ɵɵFactoryDeclaration<HintsComponent, never>;
1997
- static ɵcmp: i0.ɵɵComponentDeclaration<HintsComponent, "iss-ctrl-hints", never, { "hints": { "alias": "hints"; "required": false; "isSignal": true; }; "touched": { "alias": "touched"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
2014
+ static ɵfac: i0.ɵɵFactoryDeclaration<Hints, never>;
2015
+ static ɵcmp: i0.ɵɵComponentDeclaration<Hints, "iss-ctrl-hints", never, { "hints": { "alias": "hints"; "required": false; "isSignal": true; }; "touched": { "alias": "touched"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
1998
2016
  }
1999
2017
 
2000
- declare class ValidationMessageComponent {
2018
+ declare class ValidationMessage {
2001
2019
  errors: InputSignal<ValidationErrors | null>;
2002
2020
  showMessage: InputSignal<boolean>;
2003
2021
  getValidationMessage(error: {
2004
2022
  key: string;
2005
2023
  value: any;
2006
2024
  }): any;
2007
- static ɵfac: i0.ɵɵFactoryDeclaration<ValidationMessageComponent, never>;
2008
- static ɵcmp: i0.ɵɵComponentDeclaration<ValidationMessageComponent, "iss-validation-message-viewer", never, { "errors": { "alias": "errors"; "required": true; "isSignal": true; }; "showMessage": { "alias": "showMessage"; "required": true; "isSignal": true; }; }, {}, never, never, true, never>;
2025
+ static ɵfac: i0.ɵɵFactoryDeclaration<ValidationMessage, never>;
2026
+ static ɵcmp: i0.ɵɵComponentDeclaration<ValidationMessage, "iss-validation-message-viewer", never, { "errors": { "alias": "errors"; "required": true; "isSignal": true; }; "showMessage": { "alias": "showMessage"; "required": true; "isSignal": true; }; }, {}, never, never, true, never>;
2027
+ }
2028
+
2029
+ /**
2030
+ * `<iss-field-viewer>` — read-only presentation of a single field as a
2031
+ * label / value row.
2032
+ *
2033
+ * Project the caption into the `label` slot and the rendered value into any
2034
+ * element carrying a `value` attribute:
2035
+ *
2036
+ * ```html
2037
+ * <iss-field-viewer>
2038
+ * <label>Request Type</label>
2039
+ * <span>value</span>
2040
+ * </iss-field-viewer>
2041
+ * ```
2042
+ *
2043
+ * The label sits in a fixed-width column, so every value in a stack of viewers
2044
+ * starts on the same vertical line. Override that column with the
2045
+ * `--field-viewer-label-width` custom property on any ancestor.
2046
+ * When no label is projected, the value spans the full width of the row.
2047
+ */
2048
+ declare class FieldViewer {
2049
+ static ɵfac: i0.ɵɵFactoryDeclaration<FieldViewer, never>;
2050
+ static ɵcmp: i0.ɵɵComponentDeclaration<FieldViewer, "iss-field-viewer", never, {}, {}, never, ["label", "*"], true, never>;
2009
2051
  }
2010
2052
 
2011
2053
  declare class Droppable {
@@ -2219,5 +2261,5 @@ declare abstract class TimeValidator {
2219
2261
  static afterDate(otherFieldName: string): ValidatorFn;
2220
2262
  }
2221
2263
 
2222
- export { ATTACHMENT_DOWNLOAD_URL, ATTACHMENT_UPLOAD_RESPONSE_MAPPER, ATTACHMENT_UPLOAD_URL, ArabicNumberPipe, AttachmentViewer, COMPONENTS, COUNTRIES, CheckboxControl, CheckboxField, CheckboxViewer, ChipsControl, ChipsField, ChipsViewer, ControlBase, DATETIME_FORMAT, DATE_FORMAT_ADAPTER, DEFAULT_COUNTRY, DatePickerControl, DatePickerField, DatePickerTimeViewer, DatePickerViewer, DateRangePickerControl, DateRangePickerField, DateRangePickerViewer, DatetimePickerControl, DatetimePickerField, Droppable, Employee, FIELD_DATA, FieldBase, FieldDataInjector, FileHandler, FilePickerControl, FilePickerField, FileValidators, FormEngine, HIJRI_LOCALE, HIJRI_MONTH_NAMES, HOST, HintsComponent, InputControl, InputField, InputViewer, IssDateAdapter, IssValidators, Loader, MobileControl, MobileField, MobileViewer, Outlet, PLACEHOLDER_REGEX, PLATE_LETTERS, Phone, PlateControl, PlateField, PlateLetterPipe, PlateViewer, RadioControl, RadioField, RadioViewer, SERVICES, SelectControl, SelectField, SelectStatusPipe, SelectViewer, SlideToggleControl, SlideToggleField, SlideToggleViewer, TextareaControl, TextareaField, TextareaViewer, TimePickerControl, TimePickerField, TimePickerViewer, TimeValidator, ToggleButtonControl, ToggleButtonField, ToggleButtonViewer, TranslatePipe, UI_LANGUAGE, USER_SEARCH_API, VALIDATION_MESSAGES, VALIDATORS, VIEW_PROVIDER, ValidationMessageComponent, arrayFrom, createHijriDate, formatFileSize, formatHijriDate, getHijriDaysInMonth, getHijriParts, getPropertyByPath, isActiveValidationRule, parseHijriDate, provideIssDateAdapter, replacePlaceholders, startOfHijriMonth, toHijriDisplay };
2223
- export type { ActionTriggerFn, ComponentOutlet, Country, DateRangeEmission, DynamicImport, EventArgument, EventConfigFn, EventStrategy, FieldChange, HijriParts, Hint, HintAlignment, I18n, ISO2, IssAttachment, IssFile, Mobile, Option, PlateLetter, ValidationMessage, ValidationMessageFn };
2264
+ export { ATTACHMENT_DOWNLOAD_URL, ATTACHMENT_UPLOAD_RESPONSE_MAPPER, ATTACHMENT_UPLOAD_URL, ArabicNumberPipe, AttachmentViewer, COMPONENTS, COUNTRIES, CheckboxControl, CheckboxField, CheckboxViewer, ChipsControl, ChipsField, ChipsViewer, ControlBase, DATETIME_FORMAT, DATE_FORMAT_ADAPTER, DEFAULT_COUNTRY, DatePickerControl, DatePickerField, DatePickerTimeViewer, DatePickerViewer, DateRangePickerControl, DateRangePickerField, DateRangePickerViewer, DatetimePickerControl, DatetimePickerField, Droppable, Employee, FIELD_DATA, FieldBase, FieldDataInjector, FieldViewer, FileHandler, FilePickerControl, FilePickerField, FileValidators, FormEngine, HIJRI_LOCALE, HIJRI_MONTH_NAMES, HOST, Hints, InputControl, InputField, InputViewer, IssDateAdapter, IssValidators, Loader, MobileControl, MobileField, MobileViewer, Outlet, PLACEHOLDER_REGEX, PLATE_LETTERS, Phone, PlateControl, PlateField, PlateLetterPipe, PlateViewer, RadioControl, RadioField, RadioViewer, SERVICES, SelectControl, SelectField, SelectStatusPipe, SelectViewer, SlideToggleControl, SlideToggleField, SlideToggleViewer, TextareaControl, TextareaField, TextareaViewer, TimePickerControl, TimePickerField, TimePickerViewer, TimeValidator, ToggleButtonControl, ToggleButtonField, ToggleButtonViewer, TranslatePipe, UI_LANGUAGE, USER_SEARCH_API, VALIDATION_MESSAGES, VALIDATORS, VIEW_PROVIDER, ValidationMessage, arrayFrom, createHijriDate, formatFileSize, formatHijriDate, getHijriDaysInMonth, getHijriParts, getPropertyByPath, isActiveValidationRule, parseHijriDate, provideIssDateAdapter, replacePlaceholders, startOfHijriMonth, toHijriDisplay };
2265
+ export type { ActionTriggerFn, ComponentOutlet, Country, DateRangeEmission, DynamicImport, EventArgument, EventConfigFn, EventStrategy, FieldChange, HijriParts, Hint, HintAlignment, I18n, ISO2, IssAttachment, IssFile, IssValidator, Mobile, Option, PlateLetter, ValidationMessageFn };
package/upgrade.scss CHANGED
@@ -74,35 +74,47 @@ mat-form-field.mat-mdc-form-field.mat-focused
74
74
  --placeholder-fc: var(--dark-gray) !important;
75
75
  }
76
76
 
77
-
78
- // Outline states. Focus is not a state: while the user is in a field the outline keeps the
79
- // colour it had before the caret arrived, so the only thing the outline reports is validity.
77
+ // Outline states.
80
78
  //
81
- // --input-border-idle snapshots the field's resting border on the mat-form-field element, which
82
- // is also where per-instance overrides such as .filter-input redefine it so a field with a
83
- // custom resting border keeps that one while focused instead of falling back to the default.
79
+ // Two audiences share this stylesheet: the iss-ctrl controls, whose outline reports validity,
80
+ // and every other mat-form-field in the consuming app, which keeps the basic-ui behaviour it
81
+ // had before. Only the .iss-ctrl block below may be unscoped-looking everything outside it
82
+ // lands on app markup too, so it stays as it was.
83
+
84
+ // Global. Focus is not a state: a focused field keeps the outline it had before the caret
85
+ // arrived rather than turning Material's purple.
84
86
  mat-form-field {
85
87
  --input-border-idle: var(--input-border-active, 1px solid var(--black));
88
+ --input-border-valid: 1px solid var(--black);
86
89
  }
87
90
 
88
91
  mat-form-field.mat-mdc-form-field .mdc-text-field--focused {
89
92
  --input-border-active: var(--input-border-idle);
90
93
  }
91
94
 
92
- // An empty field rests on the plain border, and stays there while the caret is in it. The base
93
- // stylesheet keys that off Material's mat-form-field-hide-placeholder, which Material drops as
94
- // soon as the field is focused — so the emptiness comes from the control itself instead, through
95
- // the iss-ctrl-empty host class that ControlBase.isEmpty() reflects.
96
- .iss-ctrl-empty > mat-form-field.mat-mdc-form-field .mdc-text-field {
95
+ // The child combinator keeps an invalid *form* ancestor from painting every field inside it red.
96
+ .ng-touched.ng-invalid > mat-form-field.mat-mdc-form-field .mdc-text-field {
97
+ --input-border-active: var(--input-border-invalid);
98
+ }
99
+
100
+ // iss-ctrl controls only, via the class ControlBase puts on every control host. Here the outline
101
+ // reports validity and nothing else: light while the value is not yet good, dark once it is,
102
+ // red once the user has left an invalid field behind.
103
+ .iss-ctrl > mat-form-field.mat-mdc-form-field .mdc-text-field,
104
+ .iss-ctrl > mat-form-field.mat-mdc-form-field .mdc-text-field--focused {
97
105
  --input-border-active: var(--input-border);
98
106
  }
99
107
 
100
- // Red belongs to a field the user has actually left in a bad state. Angular puts ng-touched and
101
- // ng-invalid on the control host (iss-ctrl-*) the same condition <iss-validation-message-viewer>
102
- // uses — and that is the only reliable signal here: the mat control is bound to the control's
103
- // internal FormControl, which carries no validators, so Material's own --invalid state stays off
104
- // for everything but the errors it raises itself (a datepicker parse/min/max failure, say).
105
- // The child combinator keeps an invalid *form* ancestor from painting every field inside it red.
106
- .ng-touched.ng-invalid > mat-form-field.mat-mdc-form-field .mdc-text-field {
108
+ // :not(.iss-ctrl-empty) is what keeps an untouched optional field out of the dark state: empty
109
+ // and optional is ng-valid too, and should still read as resting rather than as complete.
110
+ .iss-ctrl.ng-valid:not(.iss-ctrl-empty) > mat-form-field.mat-mdc-form-field .mdc-text-field {
111
+ --input-border-active: var(--input-border-valid);
112
+ }
113
+
114
+ // Angular puts ng-touched and ng-invalid on the control host — the same condition
115
+ // <iss-validation-message-viewer> uses. It stays necessary alongside Material's own --invalid
116
+ // state: ControlBase.errorStateMatcher reports the host control to the mat control, but a
117
+ // control used without a host NgControl still has only these classes to go on.
118
+ .iss-ctrl.ng-touched.ng-invalid > mat-form-field.mat-mdc-form-field .mdc-text-field {
107
119
  --input-border-active: var(--input-border-invalid);
108
120
  }