igniteui-angular 22.2.0-rc.0 → 22.2.0-rc.1

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.
@@ -65,6 +65,39 @@ export declare class UpdateChanges {
65
65
  protected updateClasses(entryPath: string): void;
66
66
  protected updateBindings(entryPath: string, bindChanges: BindingChanges, type?: BindingType): void;
67
67
  protected updateThemeProps(entryPath: string): void;
68
+ /**
69
+ * Returns the argument list boundaries of every top-level `owner(...)` call in the content.
70
+ * Strings, comments and `url()` tokens are scanned over, so an `owner(` that is only mentioned
71
+ * in one of those is not taken for a call, and a `@mixin` or `@function` that declares the same
72
+ * name is left alone. The brackets are tracked too, so a call nested in another one -
73
+ * `@include scrollbar(scrollbar-theme($sb-size: 6px))` - reports its own closing bracket
74
+ * rather than the one of the call surrounding it.
75
+ */
76
+ private findFunctionCalls;
77
+ /**
78
+ * Returns the index of the bracket closing the one `start` is inside of, or -1 when the
79
+ * content is unbalanced. Brackets in strings, comments and `url()` tokens are ignored.
80
+ */
81
+ private findClosingBracket;
82
+ /**
83
+ * Tells whether the name at `index` is the one a `@mixin` or a `@function` declares rather
84
+ * than a call to it. Rewriting a declaration would strip a parameter its body still reads.
85
+ */
86
+ private isDeclaredName;
87
+ /**
88
+ * When a string, a comment or a `url()` token starts at `index`, returns the index of its last
89
+ * character so that a scan can carry on past it. Returns -1 when `index` is on code.
90
+ */
91
+ private skipNonCode;
92
+ /**
93
+ * Sass parses an unquoted `url()` as a single token, so a `//` inside one is part of the
94
+ * address - `url(//cdn.example.com/bg.png)` - and not the start of a comment.
95
+ */
96
+ private isUrlToken;
97
+ /** Returns the index of the bracket closing a `url(` whose contents start at `start`. */
98
+ private skipUrl;
99
+ /** Returns the index of the quote closing the string opened at `start`. */
100
+ private skipString;
68
101
  protected isNamedArgument(fileContent: string, i: number, occurrences: number[], change: ThemeChange): boolean;
69
102
  protected updateSassVariables(entryPath: string): void;
70
103
  protected updateSassFunctionsAndMixins(entryPath: string): void;
@@ -94,6 +127,9 @@ export declare class UpdateChanges {
94
127
  * prop: inner-func(),
95
128
  * prop2: inner2(inner-param: 3, inner-param: inner-func(..))
96
129
  * ```
130
+ *
131
+ * Strings, comments and `url()` tokens are scanned over the same way the rest of the scanner
132
+ * does it, so a `','` inside one of them is not taken for an argument separator.
97
133
  */
98
134
  private splitFunctionProps;
99
135
  private updateTemplateFiles;
@@ -47,6 +47,7 @@ const tsUtils_1 = require("./tsUtils");
47
47
  const util_1 = require("./util");
48
48
  const project_service_container_1 = require("./project-service-container");
49
49
  const TSCONFIG_PATH = 'tsconfig.json';
50
+ const URL_TOKEN = 'url(';
50
51
  var InputPropertyType;
51
52
  (function (InputPropertyType) {
52
53
  InputPropertyType["EVAL"] = "eval";
@@ -361,52 +362,187 @@ class UpdateChanges {
361
362
  }
362
363
  }
363
364
  updateThemeProps(entryPath) {
364
- var _a;
365
365
  let fileContent = this.host.read(entryPath).toString();
366
366
  let overwrite = false;
367
367
  for (const change of this.themeChanges.changes) {
368
368
  if (change.type !== schema_1.ThemeType.Property) {
369
369
  continue;
370
370
  }
371
- if (fileContent.indexOf(change.owner) !== -1) {
372
- /** owner-func:( * ); */
373
- const searchPattern = String.raw `${change.owner}\([\s\S]+?\);`;
374
- const matches = fileContent.match(new RegExp(searchPattern, 'g'));
375
- if (!matches) {
371
+ if (fileContent.indexOf(change.owner) === -1) {
372
+ continue;
373
+ }
374
+ /** owner-func:( * ) */
375
+ const calls = this.findFunctionCalls(fileContent, change.owner);
376
+ // rewrite back to front so the collected indices stay valid
377
+ for (const call of calls.reverse()) {
378
+ const rawBody = fileContent.substring(call.bodyStart, call.bodyEnd);
379
+ if (rawBody.indexOf(change.name) === -1) {
376
380
  continue;
377
381
  }
378
- for (const match of matches) {
379
- if (match.indexOf(change.name) !== -1) {
380
- const name = change.name.replace('$', '\\$');
381
- const replaceWith = (_a = change.replaceWith) === null || _a === void 0 ? void 0 : _a.replace('$', '\\$');
382
- const reg = new RegExp(String.raw `^\s*${name}:`);
383
- const existing = new RegExp(String.raw `${replaceWith}:`);
384
- const opening = `${change.owner}(`;
385
- const closing = /\s*\);$/.exec(match).pop();
386
- const body = match.substr(opening.length, match.length - opening.length - closing.length);
387
- let params = this.splitFunctionProps(body);
388
- params = params.reduce((arr, param) => {
389
- if (reg.test(param)) {
390
- const duplicate = !!replaceWith && arr.some(p => existing.test(p));
391
- if (!change.remove && !duplicate) {
392
- arr.push(param.replace(change.name, change.replaceWith));
393
- }
394
- }
395
- else {
396
- arr.push(param);
397
- }
398
- return arr;
399
- }, []);
400
- fileContent = fileContent.replace(match, opening + params.join(',') + closing);
401
- overwrite = true;
382
+ const name = (0, util_1.escapeRegExp)(change.name);
383
+ const replaceWith = change.replaceWith ? (0, util_1.escapeRegExp)(change.replaceWith) : undefined;
384
+ const reg = new RegExp(String.raw `^\s*${name}:`);
385
+ const existing = new RegExp(String.raw `${replaceWith}:`);
386
+ // keep whatever sits in front of the closing bracket so the formatting is preserved
387
+ const trailing = /\s*$/.exec(rawBody).pop();
388
+ const body = rawBody.substring(0, rawBody.length - trailing.length);
389
+ let params = this.splitFunctionProps(body);
390
+ params = params.reduce((arr, param) => {
391
+ if (reg.test(param)) {
392
+ const duplicate = !!replaceWith && arr.some(p => existing.test(p));
393
+ if (!change.remove && !duplicate) {
394
+ arr.push(param.replace(change.name, change.replaceWith));
395
+ }
402
396
  }
403
- }
397
+ else {
398
+ arr.push(param);
399
+ }
400
+ return arr;
401
+ }, []);
402
+ fileContent = fileContent.substring(0, call.bodyStart)
403
+ + params.join(',')
404
+ + trailing
405
+ + fileContent.substring(call.bodyEnd);
406
+ overwrite = true;
404
407
  }
405
408
  }
406
409
  if (overwrite) {
407
410
  this.host.overwrite(entryPath, fileContent);
408
411
  }
409
412
  }
413
+ /**
414
+ * Returns the argument list boundaries of every top-level `owner(...)` call in the content.
415
+ * Strings, comments and `url()` tokens are scanned over, so an `owner(` that is only mentioned
416
+ * in one of those is not taken for a call, and a `@mixin` or `@function` that declares the same
417
+ * name is left alone. The brackets are tracked too, so a call nested in another one -
418
+ * `@include scrollbar(scrollbar-theme($sb-size: 6px))` - reports its own closing bracket
419
+ * rather than the one of the call surrounding it.
420
+ */
421
+ findFunctionCalls(content, owner) {
422
+ const calls = [];
423
+ const opening = `${owner}(`;
424
+ for (let i = 0; i < content.length; i++) {
425
+ const nonCodeEnd = this.skipNonCode(content, i);
426
+ if (nonCodeEnd !== -1) {
427
+ i = nonCodeEnd;
428
+ continue;
429
+ }
430
+ if (!content.startsWith(opening, i) || this.isDeclaredName(content, i)) {
431
+ continue;
432
+ }
433
+ const bodyStart = i + opening.length;
434
+ const bodyEnd = this.findClosingBracket(content, bodyStart);
435
+ if (bodyEnd === -1) {
436
+ // unbalanced from here on, leave this call rather than the rest of the file
437
+ continue;
438
+ }
439
+ calls.push({ bodyStart, bodyEnd });
440
+ // a same-owner call nested in this one is already covered by it
441
+ i = bodyEnd;
442
+ }
443
+ return calls;
444
+ }
445
+ /**
446
+ * Returns the index of the bracket closing the one `start` is inside of, or -1 when the
447
+ * content is unbalanced. Brackets in strings, comments and `url()` tokens are ignored.
448
+ */
449
+ findClosingBracket(content, start) {
450
+ let level = 0;
451
+ for (let i = start; i < content.length; i++) {
452
+ const nonCodeEnd = this.skipNonCode(content, i);
453
+ if (nonCodeEnd !== -1) {
454
+ i = nonCodeEnd;
455
+ continue;
456
+ }
457
+ const char = content[i];
458
+ if (char === '(') {
459
+ level++;
460
+ }
461
+ else if (char === ')') {
462
+ if (!level) {
463
+ return i;
464
+ }
465
+ level--;
466
+ }
467
+ }
468
+ return -1;
469
+ }
470
+ /**
471
+ * Tells whether the name at `index` is the one a `@mixin` or a `@function` declares rather
472
+ * than a call to it. Rewriting a declaration would strip a parameter its body still reads.
473
+ */
474
+ isDeclaredName(content, index) {
475
+ let end = index;
476
+ while (end > 0 && /\s/.test(content[end - 1])) {
477
+ end--;
478
+ }
479
+ let start = end;
480
+ while (start > 0 && /[\w@-]/.test(content[start - 1])) {
481
+ start--;
482
+ }
483
+ const keyword = content.substring(start, end);
484
+ return keyword === '@mixin' || keyword === '@function';
485
+ }
486
+ /**
487
+ * When a string, a comment or a `url()` token starts at `index`, returns the index of its last
488
+ * character so that a scan can carry on past it. Returns -1 when `index` is on code.
489
+ */
490
+ skipNonCode(content, index) {
491
+ const char = content[index];
492
+ const next = content[index + 1];
493
+ if (char === '\'' || char === '"') {
494
+ return this.skipString(content, index);
495
+ }
496
+ if (char === '/' && next === '*') {
497
+ const end = content.indexOf('*/', index + 2);
498
+ return end === -1 ? content.length : end + 1;
499
+ }
500
+ if (char === '/' && next === '/') {
501
+ const end = content.indexOf('\n', index + 2);
502
+ return end === -1 ? content.length : end;
503
+ }
504
+ if (this.isUrlToken(content, index)) {
505
+ return this.skipUrl(content, index + URL_TOKEN.length);
506
+ }
507
+ return -1;
508
+ }
509
+ /**
510
+ * Sass parses an unquoted `url()` as a single token, so a `//` inside one is part of the
511
+ * address - `url(//cdn.example.com/bg.png)` - and not the start of a comment.
512
+ */
513
+ isUrlToken(content, index) {
514
+ if (content.substr(index, URL_TOKEN.length).toLowerCase() !== URL_TOKEN) {
515
+ return false;
516
+ }
517
+ const previous = content[index - 1];
518
+ return previous === undefined || !/[\w-]/.test(previous);
519
+ }
520
+ /** Returns the index of the bracket closing a `url(` whose contents start at `start`. */
521
+ skipUrl(content, start) {
522
+ for (let i = start; i < content.length; i++) {
523
+ const char = content[i];
524
+ if (char === '\'' || char === '"') {
525
+ i = this.skipString(content, i);
526
+ }
527
+ else if (char === ')') {
528
+ return i;
529
+ }
530
+ }
531
+ return content.length;
532
+ }
533
+ /** Returns the index of the quote closing the string opened at `start`. */
534
+ skipString(content, start) {
535
+ const quote = content[start];
536
+ for (let i = start + 1; i < content.length; i++) {
537
+ if (content[i] === '\\') {
538
+ i++;
539
+ }
540
+ else if (content[i] === quote) {
541
+ return i;
542
+ }
543
+ }
544
+ return content.length;
545
+ }
410
546
  isNamedArgument(fileContent, i, occurrences, change) {
411
547
  const openingBrackets = [];
412
548
  const closingBrackets = [];
@@ -777,12 +913,20 @@ class UpdateChanges {
777
913
  * prop: inner-func(),
778
914
  * prop2: inner2(inner-param: 3, inner-param: inner-func(..))
779
915
  * ```
916
+ *
917
+ * Strings, comments and `url()` tokens are scanned over the same way the rest of the scanner
918
+ * does it, so a `','` inside one of them is not taken for an argument separator.
780
919
  */
781
920
  splitFunctionProps(body) {
782
921
  const parts = [];
783
922
  let lastIndex = 0;
784
923
  let level = 0;
785
924
  for (let i = 0; i < body.length; i++) {
925
+ const nonCodeEnd = this.skipNonCode(body, i);
926
+ if (nonCodeEnd !== -1) {
927
+ i = nonCodeEnd;
928
+ continue;
929
+ }
786
930
  const char = body[i];
787
931
  switch (char) {
788
932
  case '(':
@@ -567,6 +567,72 @@ $var3: igx-comp-theme(
567
567
  expect(appTree.readContent('test.component.scss')).toEqual(`igx-theme-func($replaced: 10px, $old-prop: 3, $prop3: 2);`);
568
568
  done();
569
569
  });
570
+ it('should keep the brackets balanced for nested theme functions', done => {
571
+ const themeChangesJson = {
572
+ changes: [
573
+ {
574
+ name: '$remove-me', remove: true,
575
+ owner: 'igx-theme-func',
576
+ type: schema_1.ThemeType.Property
577
+ },
578
+ {
579
+ name: '$replace-me', replaceWith: '$replaced',
580
+ owner: 'igx-theme-func',
581
+ type: schema_1.ThemeType.Property
582
+ }
583
+ ]
584
+ };
585
+ const jsonPath = path.join(__dirname, 'changes', 'theme-changes.json');
586
+ spyOn(fs, 'existsSync').and.callFake((filePath) => filePath === jsonPath);
587
+ spyOn(fs, 'readFileSync').and.callFake(() => JSON.stringify(themeChangesJson));
588
+ appTree.create('styles.scss', `@include igx-mixin(igx-theme-func($remove-me: 6px));
589
+ @include igx-mixin(igx-theme-func($prop1: red, $remove-me: 6px));
590
+ @include igx-mixin(igx-theme-func($remove-me: 6px, $prop1: red));
591
+ @include igx-mixin(igx-theme-func($replace-me: 6px));
592
+ $var: igx-theme-func($content: "not a ( bracket", $remove-me: 6px);
593
+ $var2: igx-theme-func($image: url(https://example.com/a.png), $remove-me: 6px);
594
+ $var3: igx-theme-func(
595
+ $remove-me: 6px, // not a ) bracket
596
+ $prop1: red
597
+ );`);
598
+ const update = new UnitUpdateChanges(__dirname, appTree);
599
+ update.applyChanges();
600
+ expect(appTree.readContent('styles.scss')).toEqual(`@include igx-mixin(igx-theme-func());
601
+ @include igx-mixin(igx-theme-func($prop1: red));
602
+ @include igx-mixin(igx-theme-func( $prop1: red));
603
+ @include igx-mixin(igx-theme-func($replaced: 6px));
604
+ $var: igx-theme-func($content: "not a ( bracket");
605
+ $var2: igx-theme-func($image: url(https://example.com/a.png));
606
+ $var3: igx-theme-func( // not a ) bracket
607
+ $prop1: red
608
+ );`);
609
+ done();
610
+ });
611
+ it('should not treat a theme function mentioned in a comment or a string as a call', done => {
612
+ const themeChangesJson = {
613
+ changes: [
614
+ {
615
+ name: '$remove-me', remove: true,
616
+ owner: 'igx-theme-func',
617
+ type: schema_1.ThemeType.Property
618
+ }
619
+ ]
620
+ };
621
+ const jsonPath = path.join(__dirname, 'changes', 'theme-changes.json');
622
+ spyOn(fs, 'existsSync').and.callFake((filePath) => filePath === jsonPath);
623
+ spyOn(fs, 'readFileSync').and.callFake(() => JSON.stringify(themeChangesJson));
624
+ appTree.create('styles.scss', `// igx-theme-func($remove-me: 1px) is gone, use the grid's own borders
625
+ /* igx-theme-func($remove-me: 2px */
626
+ $doc: "igx-theme-func($remove-me: 3px";
627
+ $var: igx-theme-func($remove-me: 4px, $prop1: red);`);
628
+ const update = new UnitUpdateChanges(__dirname, appTree);
629
+ update.applyChanges();
630
+ expect(appTree.readContent('styles.scss')).toEqual(`// igx-theme-func($remove-me: 1px) is gone, use the grid's own borders
631
+ /* igx-theme-func($remove-me: 2px */
632
+ $doc: "igx-theme-func($remove-me: 3px";
633
+ $var: igx-theme-func( $prop1: red);`);
634
+ done();
635
+ });
570
636
  it('should replace imports', done => {
571
637
  const importsJson = {
572
638
  changes: [
@@ -77,6 +77,79 @@ describe(`Update to ${version}`, () => {
77
77
  const tree = yield schematicRunner.runSchematic(migrationName, { shouldInvokeLS: false }, appTree);
78
78
  expect(tree.readContent('/testSrc/appPrefix/component/test.component.scss')).toEqual(content);
79
79
  }));
80
+ it('should keep the brackets balanced when the theme is nested in another call', () => __awaiter(void 0, void 0, void 0, function* () {
81
+ appTree.create(`/testSrc/appPrefix/component/test.component.scss`, `.selection-area {
82
+ @include scrollbar(scrollbar-theme($sb-size: 6px));
83
+ }
84
+
85
+ igx-grid {
86
+ @include scrollbar(scrollbar-theme($sb-thumb-bg-color: blue, $sb-size: 16px));
87
+ }`);
88
+ const tree = yield schematicRunner.runSchematic(migrationName, { shouldInvokeLS: false }, appTree);
89
+ expect(tree.readContent('/testSrc/appPrefix/component/test.component.scss')).toEqual(`.selection-area {
90
+ @include scrollbar(scrollbar-theme());
91
+ }
92
+
93
+ igx-grid {
94
+ @include scrollbar(scrollbar-theme($sb-thumb-bg-color: blue));
95
+ }`);
96
+ }));
97
+ it('should migrate a theme call that is not terminated by a semicolon', () => __awaiter(void 0, void 0, void 0, function* () {
98
+ appTree.create(`/testSrc/appPrefix/component/test.component.scss`, `$my-scrollbar: scrollbar-theme(
99
+ $sb-size: 16px,
100
+ $sb-thumb-bg-color: blue
101
+ )`);
102
+ const tree = yield schematicRunner.runSchematic(migrationName, { shouldInvokeLS: false }, appTree);
103
+ expect(tree.readContent('/testSrc/appPrefix/component/test.component.scss')).toEqual(`$my-scrollbar: scrollbar-theme(
104
+ $sb-thumb-bg-color: blue
105
+ )`);
106
+ }));
107
+ it('should keep a retained property when a comment in the argument list holds an apostrophe', () => __awaiter(void 0, void 0, void 0, function* () {
108
+ appTree.create(`/testSrc/appPrefix/component/test.component.scss`, `$my-scrollbar: scrollbar-theme($sb-size: 6px /* user's size */, $sb-thumb-bg-color: red);`);
109
+ const tree = yield schematicRunner.runSchematic(migrationName, { shouldInvokeLS: false }, appTree);
110
+ expect(tree.readContent('/testSrc/appPrefix/component/test.component.scss')).toEqual(`$my-scrollbar: scrollbar-theme( $sb-thumb-bg-color: red);`);
111
+ }));
112
+ it('should remove the properties that follow a comment holding an apostrophe', () => __awaiter(void 0, void 0, void 0, function* () {
113
+ appTree.create(`/testSrc/appPrefix/component/test.component.scss`, `$my-scrollbar: scrollbar-theme(
114
+ $sb-size: 16px, // don't need this anymore
115
+ $sb-thumb-bg-color: blue,
116
+ $sb-corner-bg: gray
117
+ );`);
118
+ const tree = yield schematicRunner.runSchematic(migrationName, { shouldInvokeLS: false }, appTree);
119
+ expect(tree.readContent('/testSrc/appPrefix/component/test.component.scss')).toEqual(`$my-scrollbar: scrollbar-theme( // don't need this anymore
120
+ $sb-thumb-bg-color: blue
121
+ );`);
122
+ }));
123
+ it('should migrate a theme below a comment glued to a closing brace', () => __awaiter(void 0, void 0, void 0, function* () {
124
+ appTree.create(`/testSrc/appPrefix/component/test.component.scss`, `.selection-area {
125
+ color: red;
126
+ }// don't set $sb-size here
127
+
128
+ $my-scrollbar: scrollbar-theme($sb-size: 16px, $sb-thumb-bg-color: blue);`);
129
+ const tree = yield schematicRunner.runSchematic(migrationName, { shouldInvokeLS: false }, appTree);
130
+ expect(tree.readContent('/testSrc/appPrefix/component/test.component.scss')).toEqual(`.selection-area {
131
+ color: red;
132
+ }// don't set $sb-size here
133
+
134
+ $my-scrollbar: scrollbar-theme( $sb-thumb-bg-color: blue);`);
135
+ }));
136
+ it('should migrate a theme preceded on the same line by a protocol-relative url()', () => __awaiter(void 0, void 0, void 0, function* () {
137
+ appTree.create(`/testSrc/appPrefix/component/test.component.scss`, `.a { background: url(//cdn.example.com/bg.png); $my: scrollbar-theme($sb-size: 16px, $sb-thumb-bg-color: red); }`);
138
+ const tree = yield schematicRunner.runSchematic(migrationName, { shouldInvokeLS: false }, appTree);
139
+ expect(tree.readContent('/testSrc/appPrefix/component/test.component.scss')).toEqual(`.a { background: url(//cdn.example.com/bg.png); $my: scrollbar-theme( $sb-thumb-bg-color: red); }`);
140
+ }));
141
+ it('should not rewrite a mixin or a function declared with the theme name', () => __awaiter(void 0, void 0, void 0, function* () {
142
+ const content = `@mixin scrollbar-theme($sb-size: 16px) {
143
+ width: $sb-size;
144
+ }
145
+
146
+ @function scrollbar-theme($sb-size: 6px) {
147
+ @return ($size: $sb-size);
148
+ }`;
149
+ appTree.create(`/testSrc/appPrefix/component/test.component.scss`, content);
150
+ const tree = yield schematicRunner.runSchematic(migrationName, { shouldInvokeLS: false }, appTree);
151
+ expect(tree.readContent('/testSrc/appPrefix/component/test.component.scss')).toEqual(content);
152
+ }));
80
153
  it('should not touch same-named properties on other themes', () => __awaiter(void 0, void 0, void 0, function* () {
81
154
  const content = `$my-grid: grid-theme(
82
155
  $sb-size: 16px
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "igniteui-angular",
3
- "version": "22.2.0-rc.0",
3
+ "version": "22.2.0-rc.1",
4
4
  "description": "Ignite UI for Angular is a dependency-free Angular toolkit for building modern web apps",
5
5
  "author": "Infragistics",
6
6
  "license": "SEE LICENSE IN LICENSE",
@@ -831,6 +831,8 @@ declare class IgxTouchManager {
831
831
  type NgControlBackend = 'observable' | 'signal';
832
832
  /** Whether a control took a value written through `setValue`. */
833
833
  type ValueWriteResult = 'accepted' | 'ignored';
834
+ /** Validation outcome of a control, mirroring `FormControlStatus`. */
835
+ type ControlStatus = 'valid' | 'invalid' | 'pending' | 'disabled';
834
836
  /**
835
837
  * Uniform access to the `NgControl` bound to a form control.
836
838
  *
@@ -852,11 +854,14 @@ declare class NgControlAdapter {
852
854
  get disabled(): boolean;
853
855
  get valid(): boolean;
854
856
  get invalid(): boolean;
857
+ get pending(): boolean;
858
+ /** Derived, not read from `status`: the Signal Forms interop throws on a status it does not know. */
859
+ get status(): ControlStatus;
855
860
  get touchedOrDirty(): boolean;
856
861
  /**
857
- * Signal Forms expose no rule list. A field that is required, or was ever
858
- * invalid or pending, is known to have rules; a rule satisfied from the
859
- * start stays undetected.
862
+ * Signal Forms expose no rule list. A field that is required, or was invalid or
863
+ * pending since it was last pristine and untouched, is known to have rules;
864
+ * a rule satisfied from that point on stays undetected.
860
865
  */
861
866
  get hasValidators(): boolean;
862
867
  get required(): boolean;
@@ -875,8 +880,12 @@ declare class NgControlAdapter {
875
880
  * (DOM or `ControlValueAccessor`), so the caller must update that instead.
876
881
  */
877
882
  setValue(value: unknown): ValueWriteResult;
878
- /** Remembers that the field had rules. Returns the current invalid or pending state. */
879
- private noteErrors;
883
+ /**
884
+ * Records that the field has rules. A `[formField]` switch reuses the same interop
885
+ * `NgControl`, so an untouched, pristine control drops earlier observations and keeps
886
+ * only its current state, e.g. an empty field failing a custom rule before the first edit.
887
+ */
888
+ private observeErrors;
880
889
  private watch;
881
890
  }
882
891
 
@@ -4392,4 +4401,4 @@ declare global {
4392
4401
  }
4393
4402
 
4394
4403
  export { AbsolutePosition, AbsoluteScrollStrategy, ActionStripResourceStringsEN, AutoPositionStrategy, BannerResourceStringsEN, BaseFilteringStrategy, BaseFitPositionStrategy, BaseFormatter, BlockScrollStrategy, ByLevelTreeGridMergeStrategy, CachedDataCloneStrategy, CalendarDay, CalendarResourceStringsEN, CarouselResourceStringsEN, ChipResourceStringsEN, CloseScrollStrategy, ColumnPinningPosition, ComboResourceStringsEN, ConnectedPositioningStrategy, ContainerPositionStrategy, DEFAULT_LOCALE, DataUtil, DatePart, DatePickerResourceStringsEN, DateRangePickerResourceStringsEN, DateRangeType, DateTimeUtil, DefaultDataCloneStrategy, DefaultMergeStrategy, DefaultSortingStrategy, DefaultTreeGridMergeStrategy, EDITOR_PROVIDER, ELEMENTS_TOKEN, ElasticPositionStrategy, ExpressionsTreeUtil, FilterUtil, FilteringExpressionsTree, FilteringExpressionsTreeType, FilteringLogic, FilteringStrategy, FormattedValuesFilteringStrategy, FormattedValuesSortingStrategy, GlobalPositionStrategy, GridColumnDataType, GridResourceStringsEN, GridSummaryCalculationMode, GroupMemberCountSortingStrategy, GroupedRecords, HorizontalAlignment, I18N_FORMATTER, IgxActionStripActionsToken, IgxActionStripToken, IgxAngularAnimationPlayer, IgxAngularAnimationService, IgxBaseTransactionService, IgxBooleanFilteringOperand, IgxCurrencyFormatterPipe, IgxDataRecordSorting, IgxDateFilteringOperand, IgxDateFormatterPipe, IgxDateSummaryOperand, IgxDateTimeFilteringOperand, IgxFilteringOperand, IgxFlatTransactionFactory, IgxGridTransaction, IgxGrouping, IgxHierarchicalTransactionFactory, IgxHierarchicalTransactionService, IgxNavigationCloseDirective, IgxNavigationService, IgxNavigationToggleDirective, IgxNumberFilteringOperand, IgxNumberFormatterPipe, IgxNumberSummaryOperand, IgxOverlayOutletDirective, IgxOverlayService, IgxPercentFormatterPipe, IgxPickerActionsDirective, IgxPickerClearComponent, IgxPickerToggleComponent, IgxSelectionAPIService, IgxSorting, IgxStringFilteringOperand, IgxStylesRegistrar, IgxSummaryOperand, IgxTimeFilteringOperand, IgxTimeSummaryOperand, IgxTouchManager, IgxTransactionService, InputResourceStringsEN, IntlFormatter, ListResourceStringsEN, NAVIGATION_KEYS, NgControlAdapter, NoOpScrollStrategy, NoopFilteringStrategy, NoopSortingStrategy, OffsetMode, PaginatorResourceStringsEN, PagingError, PickerCalendarOrientation, PickerHeaderOrientation, PickerInteractionMode, PlatformUtil, Point, QueryBuilderResourceStringsEN, RelativePosition, RelativePositionStrategy, ScrollStrategy, SortingDirection, THEME_TOKEN, TRANSACTION_TYPE, ThemeToken, TimePickerResourceStringsEN, TransactionEventOrigin, TransactionType, TreeGridFilteringStrategy, TreeGridFormattedValuesFilteringStrategy, TreeGridMatchingRecordsOnlyFilteringStrategy, TreeResourceStringsEN, Util, VerticalAlignment, WEEKDAYS, areEqualArrays, areSameMonth, calendarRange, changei18n, clamp, cloneArray, cloneHierarchicalArray, cloneValue, cloneValueCached, columnFieldPath, compareMaps, copyDescriptors, daysInWeek, first, flatten, formatToParts, generateMonth, getClosestActiveDate, getComponentCssSizeVar, getComponentSize, getComponentTheme, getCurrentResourceStrings, getHierarchy, getNextActiveDate, getPreviousActiveDate, getResizeObserver, getYearRange, intoChunks, isConstructor, isDate, isDateInRanges, isEqual, isHierarchyMatch, isLeftToRight, isNextMonth, isObject, isPreviousMonth, isTree, last, mergeObjects, modulo, normalizeURI, onResourceChangeHandle, parseDate, provideIgniteIntl, recreateExpression, recreateTree, recreateTreeFromFields, registerI18n, rem, resizeObservable, resolveNestedPath, reverseMapper, runAfterRenderOnce, showMessage, toCalendarDay, trackByIdentity, uniqueDates, Size$1 as ɵSize };
4395
- export type { Action, AnimationPlayer, AnimationService, CalendarRangeParams, CancelableBrowserEventArgs, CancelableEventArgs, ColumnType, ConnectedFit, CustomDateRange, DatePartDeltas, DatePartInfo, DateRange, DateRangeDescriptor, DayInterval, DayParameter, EditorProvider, EntityType, FieldType, GridSelectionRange, GridStyleCSSProperty, GridTypeBase, HierarchicalState, HierarchicalTransaction, HierarchicalTransactionService, IActionStripResourceStrings, IBannerResourceStrings, IBaseCancelableBrowserEventArgs, IBaseCancelableEventArgs, IBaseEventArgs, ICalendarResourceStrings, ICarouselResourceStrings, IChipResourceStrings, IColumnEditorOptions, IColumnPipeArgs, IColumnSelectionState, IComboResourceStrings, IDataCloneStrategy, IDateParts, IDatePickerResourceStrings, IDateRangePickerResourceStrings, IExpressionTree, IFieldEditorOptions, IFieldPipeArgs, IFilteringExpression, IFilteringExpressionsTree, IFilteringOperation, IFilteringState, IFilteringStrategy, IGridGroupingStrategy, IGridMergeStrategy, IGridResourceStrings, IGridSortingStrategy, IGroupByExpandState, IGroupByKey, IGroupByRecord, IGroupByResult, IGroupingExpression, IGroupingState, IInputResourceStrings, IListResourceStrings, IMergeByResult, IMultiRowLayoutNode, IPaginatorResourceStrings, IPagingState, IPathSegment, IPositionStrategy, IQueryBuilderResourceStrings, IResourceStrings, IScrollStrategy, ISelectionKeyboardState, ISelectionNode, ISelectionPointerState, ISortingExpression, ISortingOptions, ISortingStrategy, ISummaryExpression, ISummaryRecord, ITimePickerResourceStrings, IToggleView, ITreeGridRecord, ITreeResourceStrings, IgcCalendarBaseEventMap, IgxFilterItem, IgxGestureEvent, IgxSummaryResult, IgxTheme, IgxTouchManagerCallbacks, IgxTouchManagerOptions, IgxTreeGridAPIService, KeyOfOrString, MRLColumnSizeInfo, MRLResizeColumnInfo, NgControlBackend, OverlayAnimationEventArgs, OverlayCancelableEventArgs, OverlayClosingEventArgs, OverlayCreateSettings, OverlayEventArgs, OverlaySettings, PositionSettings, RenderPhase, SelectionState, Size, State, StateUpdateEvent, Transaction, TransactionService, ValueWriteResult, WeekDays };
4404
+ export type { Action, AnimationPlayer, AnimationService, CalendarRangeParams, CancelableBrowserEventArgs, CancelableEventArgs, ColumnType, ConnectedFit, ControlStatus, CustomDateRange, DatePartDeltas, DatePartInfo, DateRange, DateRangeDescriptor, DayInterval, DayParameter, EditorProvider, EntityType, FieldType, GridSelectionRange, GridStyleCSSProperty, GridTypeBase, HierarchicalState, HierarchicalTransaction, HierarchicalTransactionService, IActionStripResourceStrings, IBannerResourceStrings, IBaseCancelableBrowserEventArgs, IBaseCancelableEventArgs, IBaseEventArgs, ICalendarResourceStrings, ICarouselResourceStrings, IChipResourceStrings, IColumnEditorOptions, IColumnPipeArgs, IColumnSelectionState, IComboResourceStrings, IDataCloneStrategy, IDateParts, IDatePickerResourceStrings, IDateRangePickerResourceStrings, IExpressionTree, IFieldEditorOptions, IFieldPipeArgs, IFilteringExpression, IFilteringExpressionsTree, IFilteringOperation, IFilteringState, IFilteringStrategy, IGridGroupingStrategy, IGridMergeStrategy, IGridResourceStrings, IGridSortingStrategy, IGroupByExpandState, IGroupByKey, IGroupByRecord, IGroupByResult, IGroupingExpression, IGroupingState, IInputResourceStrings, IListResourceStrings, IMergeByResult, IMultiRowLayoutNode, IPaginatorResourceStrings, IPagingState, IPathSegment, IPositionStrategy, IQueryBuilderResourceStrings, IResourceStrings, IScrollStrategy, ISelectionKeyboardState, ISelectionNode, ISelectionPointerState, ISortingExpression, ISortingOptions, ISortingStrategy, ISummaryExpression, ISummaryRecord, ITimePickerResourceStrings, IToggleView, ITreeGridRecord, ITreeResourceStrings, IgcCalendarBaseEventMap, IgxFilterItem, IgxGestureEvent, IgxSummaryResult, IgxTheme, IgxTouchManagerCallbacks, IgxTouchManagerOptions, IgxTreeGridAPIService, KeyOfOrString, MRLColumnSizeInfo, MRLResizeColumnInfo, NgControlBackend, OverlayAnimationEventArgs, OverlayCancelableEventArgs, OverlayClosingEventArgs, OverlayCreateSettings, OverlayEventArgs, OverlaySettings, PositionSettings, RenderPhase, SelectionState, Size, State, StateUpdateEvent, Transaction, TransactionService, ValueWriteResult, WeekDays };
@@ -1,7 +1,7 @@
1
1
  import * as i0 from '@angular/core';
2
2
  import { OnInit, AfterViewInit, OnDestroy, ElementRef, ChangeDetectorRef, Renderer2, InjectionToken, AfterContentChecked, QueryList } from '@angular/core';
3
3
  import { NgModel, NgControl } from '@angular/forms';
4
- import { IInputResourceStrings, IgxTheme } from 'igniteui-angular/core';
4
+ import { ControlStatus, IInputResourceStrings, IgxTheme } from 'igniteui-angular/core';
5
5
 
6
6
  declare class IgxHintDirective implements OnInit {
7
7
  /**
@@ -80,6 +80,19 @@ declare enum IgxInputState {
80
80
  VALID = 1,
81
81
  INVALID = 2
82
82
  }
83
+ /**
84
+ * Whether an editor may paint the success state.
85
+ *
86
+ * @hidden @internal
87
+ */
88
+ type SuccessState = 'allowed' | 'suppressed';
89
+ /**
90
+ * Maps a control status to an editor state. A pending async rule has not answered
91
+ * yet, so it reads as initial rather than as an error.
92
+ *
93
+ * @hidden @internal
94
+ */
95
+ declare function toInputState(status: ControlStatus, success: SuccessState): IgxInputState;
83
96
  /**
84
97
  * The `igxInput` directive creates single- or multiline text elements, covering common scenarios when dealing with form inputs.
85
98
  *
@@ -261,6 +274,7 @@ declare class IgxInputDirective implements AfterViewInit, OnDestroy {
261
274
  * @internal
262
275
  */
263
276
  protected updateValidityState(): void;
277
+ private updateAriaRequired;
264
278
  /**
265
279
  * Gets whether the igxInput has a placeholder.
266
280
  *
@@ -695,5 +709,5 @@ declare class IgxInputGroupModule {
695
709
  static ɵinj: i0.ɵɵInjectorDeclaration<IgxInputGroupModule>;
696
710
  }
697
711
 
698
- export { IGX_INPUT_GROUP_DIRECTIVES, IGX_INPUT_GROUP_TYPE, IgxHintDirective, IgxInputDirective, IgxInputGroupBase, IgxInputGroupComponent, IgxInputGroupEnum, IgxInputGroupModule, IgxInputState, IgxLabelDirective, IgxPrefixDirective, IgxReadOnlyInputDirective, IgxSuffixDirective };
699
- export type { IgxInputGroupType };
712
+ export { IGX_INPUT_GROUP_DIRECTIVES, IGX_INPUT_GROUP_TYPE, IgxHintDirective, IgxInputDirective, IgxInputGroupBase, IgxInputGroupComponent, IgxInputGroupEnum, IgxInputGroupModule, IgxInputState, IgxLabelDirective, IgxPrefixDirective, IgxReadOnlyInputDirective, IgxSuffixDirective, toInputState };
713
+ export type { IgxInputGroupType, SuccessState };
@@ -252,7 +252,6 @@ declare class IgxTimePickerComponent extends PickerBaseDirective implements IgxT
252
252
  private _cancelButtonLabel;
253
253
  private _itemsDelta;
254
254
  private _statusChanges$;
255
- private _ngControl;
256
255
  private _control;
257
256
  private _onChangeCallback;
258
257
  private _onTouchedCallback;