igniteui-angular 22.2.0-rc.1 → 22.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (107) hide show
  1. package/README.md +1 -1
  2. package/button-group/README.md +42 -9
  3. package/calendar/README.md +30 -20
  4. package/card/README.md +1 -1
  5. package/fesm2022/igniteui-angular-accordion.mjs +7 -7
  6. package/fesm2022/igniteui-angular-action-strip.mjs +11 -22
  7. package/fesm2022/igniteui-angular-action-strip.mjs.map +1 -1
  8. package/fesm2022/igniteui-angular-avatar.mjs +7 -7
  9. package/fesm2022/igniteui-angular-badge.mjs +7 -7
  10. package/fesm2022/igniteui-angular-banner.mjs +10 -10
  11. package/fesm2022/igniteui-angular-bottom-nav.mjs +22 -22
  12. package/fesm2022/igniteui-angular-button-group.mjs +32 -35
  13. package/fesm2022/igniteui-angular-button-group.mjs.map +1 -1
  14. package/fesm2022/igniteui-angular-calendar.mjs +132 -180
  15. package/fesm2022/igniteui-angular-calendar.mjs.map +1 -1
  16. package/fesm2022/igniteui-angular-card.mjs +47 -58
  17. package/fesm2022/igniteui-angular-card.mjs.map +1 -1
  18. package/fesm2022/igniteui-angular-carousel.mjs +22 -22
  19. package/fesm2022/igniteui-angular-chat-extras.mjs +6 -6
  20. package/fesm2022/igniteui-angular-chat.mjs +12 -12
  21. package/fesm2022/igniteui-angular-checkbox.mjs +7 -7
  22. package/fesm2022/igniteui-angular-chips.mjs +10 -10
  23. package/fesm2022/igniteui-angular-combo.mjs +66 -60
  24. package/fesm2022/igniteui-angular-combo.mjs.map +1 -1
  25. package/fesm2022/igniteui-angular-core.mjs +107 -79
  26. package/fesm2022/igniteui-angular-core.mjs.map +1 -1
  27. package/fesm2022/igniteui-angular-date-picker.mjs +38 -38
  28. package/fesm2022/igniteui-angular-dialog.mjs +13 -13
  29. package/fesm2022/igniteui-angular-directives.mjs +194 -194
  30. package/fesm2022/igniteui-angular-drop-down.mjs +29 -29
  31. package/fesm2022/igniteui-angular-expansion-panel.mjs +28 -28
  32. package/fesm2022/igniteui-angular-grids-core.mjs +737 -661
  33. package/fesm2022/igniteui-angular-grids-core.mjs.map +1 -1
  34. package/fesm2022/igniteui-angular-grids-grid.mjs +69 -58
  35. package/fesm2022/igniteui-angular-grids-grid.mjs.map +1 -1
  36. package/fesm2022/igniteui-angular-grids-hierarchical-grid.mjs +37 -37
  37. package/fesm2022/igniteui-angular-grids-lite.mjs +25 -17
  38. package/fesm2022/igniteui-angular-grids-lite.mjs.map +1 -1
  39. package/fesm2022/igniteui-angular-grids-pivot-grid.mjs +92 -81
  40. package/fesm2022/igniteui-angular-grids-pivot-grid.mjs.map +1 -1
  41. package/fesm2022/igniteui-angular-grids-tree-grid.mjs +55 -55
  42. package/fesm2022/igniteui-angular-icon.mjs +10 -10
  43. package/fesm2022/igniteui-angular-input-group.mjs +25 -25
  44. package/fesm2022/igniteui-angular-list.mjs +40 -40
  45. package/fesm2022/igniteui-angular-navbar.mjs +13 -13
  46. package/fesm2022/igniteui-angular-navigation-drawer.mjs +43 -38
  47. package/fesm2022/igniteui-angular-navigation-drawer.mjs.map +1 -1
  48. package/fesm2022/igniteui-angular-paginator.mjs +19 -19
  49. package/fesm2022/igniteui-angular-progressbar.mjs +19 -19
  50. package/fesm2022/igniteui-angular-query-builder.mjs +22 -22
  51. package/fesm2022/igniteui-angular-radio.mjs +25 -21
  52. package/fesm2022/igniteui-angular-radio.mjs.map +1 -1
  53. package/fesm2022/igniteui-angular-select.mjs +25 -25
  54. package/fesm2022/igniteui-angular-simple-combo.mjs +9 -9
  55. package/fesm2022/igniteui-angular-simple-combo.mjs.map +1 -1
  56. package/fesm2022/igniteui-angular-slider.mjs +28 -28
  57. package/fesm2022/igniteui-angular-snackbar.mjs +7 -7
  58. package/fesm2022/igniteui-angular-splitter.mjs +13 -13
  59. package/fesm2022/igniteui-angular-stepper.mjs +34 -34
  60. package/fesm2022/igniteui-angular-switch.mjs +7 -7
  61. package/fesm2022/igniteui-angular-tabs.mjs +34 -34
  62. package/fesm2022/igniteui-angular-time-picker.mjs +19 -19
  63. package/fesm2022/igniteui-angular-toast.mjs +7 -7
  64. package/fesm2022/igniteui-angular-tree.mjs +28 -28
  65. package/fesm2022/igniteui-angular-virtual-scroll.mjs +497 -145
  66. package/fesm2022/igniteui-angular-virtual-scroll.mjs.map +1 -1
  67. package/migrations/common/UpdateChanges.d.ts +25 -5
  68. package/migrations/common/UpdateChanges.js +218 -34
  69. package/migrations/common/UpdateChanges.spec.js +763 -0
  70. package/migrations/migration-collection.json +1 -1
  71. package/migrations/update-22_2_0/index.js +145 -0
  72. package/migrations/update-22_2_0/index.spec.js +133 -0
  73. package/navigation-drawer/README.md +1 -1
  74. package/package.json +4 -4
  75. package/schematics/tsconfig.tsbuildinfo +1 -1
  76. package/skills/igniteui-angular-components/SKILL.md +9 -5
  77. package/skills/igniteui-angular-components/references/form-controls.md +1 -1
  78. package/skills/igniteui-angular-components/references/mcp-setup.md +14 -2
  79. package/skills/igniteui-angular-figma-to-app/SKILL.md +112 -525
  80. package/skills/igniteui-angular-figma-to-app/references/asset-extraction.md +49 -75
  81. package/skills/igniteui-angular-figma-to-app/references/design-provenance.md +201 -0
  82. package/skills/igniteui-angular-figma-to-app/references/design-token-bridge.md +175 -52
  83. package/skills/igniteui-angular-figma-to-app/references/figma-component-map.md +153 -99
  84. package/skills/igniteui-angular-figma-to-app/references/figma-exploration.md +226 -0
  85. package/skills/igniteui-angular-figma-to-app/references/mcp-setup.md +71 -105
  86. package/skills/igniteui-angular-figma-to-app/references/project-setup.md +63 -0
  87. package/skills/igniteui-angular-figma-to-app/references/theme-generation.md +120 -0
  88. package/skills/igniteui-angular-figma-to-app/references/validation-patterns.md +48 -50
  89. package/skills/igniteui-angular-generate-from-image-design/SKILL.md +11 -7
  90. package/skills/igniteui-angular-grids/SKILL.md +7 -3
  91. package/skills/igniteui-angular-grids/references/editing.md +1 -2
  92. package/skills/igniteui-angular-grids/references/grid-migration.md +1 -1
  93. package/skills/igniteui-angular-theming/SKILL.md +9 -5
  94. package/skills/igniteui-angular-theming/references/mcp-setup.md +12 -2
  95. package/types/igniteui-angular-button-group.d.ts +49 -34
  96. package/types/igniteui-angular-calendar.d.ts +33 -50
  97. package/types/igniteui-angular-card.d.ts +12 -17
  98. package/types/igniteui-angular-combo.d.ts +6 -0
  99. package/types/igniteui-angular-core.d.ts +10 -2
  100. package/types/igniteui-angular-grids-core.d.ts +48 -4
  101. package/types/igniteui-angular-grids-grid.d.ts +4 -0
  102. package/types/igniteui-angular-grids-lite.d.ts +5 -1
  103. package/types/igniteui-angular-grids-pivot-grid.d.ts +5 -1
  104. package/types/igniteui-angular-navigation-drawer.d.ts +1 -0
  105. package/types/igniteui-angular-radio.d.ts +5 -0
  106. package/types/igniteui-angular-virtual-scroll.d.ts +41 -13
  107. package/virtual-scroll/README.md +32 -1
@@ -48,6 +48,8 @@ const util_1 = require("./util");
48
48
  const project_service_container_1 = require("./project-service-container");
49
49
  const TSCONFIG_PATH = 'tsconfig.json';
50
50
  const URL_TOKEN = 'url(';
51
+ /** Dependency and build output folders never searched for stylesheets. */
52
+ const SKIPPED_STYLE_DIRS = ['node_modules', 'dist', '.angular', '.git'];
51
53
  var InputPropertyType;
52
54
  (function (InputPropertyType) {
53
55
  InputPropertyType["EVAL"] = "eval";
@@ -101,17 +103,25 @@ class UpdateChanges {
101
103
  /** Sass (both .scss and .sass) files in the project being updated. */
102
104
  get sassFiles() {
103
105
  if (!this._sassFiles.length) {
104
- // files can be outside the app prefix, so start from sourceRoot
106
+ // stylesheets can sit anywhere in the workspace, not only under sourceRoot
105
107
  // also ignore schematics `styleext` as Sass can be used regardless
106
- const sourceDirs = (0, util_1.getProjects)(this.workspace).map(x => x.sourceRoot).filter(x => x);
107
- this.sourceDirsVisitor((fulPath, entry) => {
108
- if (fulPath.endsWith('.scss') || fulPath.endsWith('.sass')) {
109
- this._sassFiles.push(entry.path);
110
- }
111
- }, sourceDirs);
108
+ this.collectSassFiles(this.host.getDir('/'), this._sassFiles);
112
109
  }
113
110
  return this._sassFiles;
114
111
  }
112
+ /** Collects the Sass files under a folder, skipping dependency and build output folders. */
113
+ collectSassFiles(dir, files) {
114
+ for (const name of dir.subfiles) {
115
+ if (name.endsWith('.scss') || name.endsWith('.sass')) {
116
+ files.push(dir.file(name).path);
117
+ }
118
+ }
119
+ for (const name of dir.subdirs) {
120
+ if (!SKIPPED_STYLE_DIRS.includes(name)) {
121
+ this.collectSassFiles(dir.dir(name), files);
122
+ }
123
+ }
124
+ }
115
125
  get service() {
116
126
  if (!this._service) {
117
127
  this._service = (0, tsUtils_1.getLanguageService)(this.tsFiles, this.host);
@@ -372,7 +382,7 @@ class UpdateChanges {
372
382
  continue;
373
383
  }
374
384
  /** owner-func:( * ) */
375
- const calls = this.findFunctionCalls(fileContent, change.owner);
385
+ const calls = this.findFunctionCalls(fileContent, change.owner, entryPath.endsWith('.sass'));
376
386
  // rewrite back to front so the collected indices stay valid
377
387
  for (const call of calls.reverse()) {
378
388
  const rawBody = fileContent.substring(call.bodyStart, call.bodyEnd);
@@ -381,26 +391,40 @@ class UpdateChanges {
381
391
  }
382
392
  const name = (0, util_1.escapeRegExp)(change.name);
383
393
  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}:`);
394
+ // both are matched at the argument's code start; Sass allows whitespace before the colon
395
+ const reg = new RegExp(String.raw `^${name}\s*:`);
396
+ const existing = new RegExp(String.raw `^${replaceWith}\s*:`);
386
397
  // keep whatever sits in front of the closing bracket so the formatting is preserved
387
398
  const trailing = /\s*$/.exec(rawBody).pop();
388
399
  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));
400
+ const eol = fileContent.includes('\r\n') ? '\r\n' : '\n';
401
+ const params = this.splitFunctionProps(body);
402
+ const codeStarts = params.map(param => this.leadingCommentsEnd(param));
403
+ // renaming onto a name the call already passes would make it a duplicate argument
404
+ const duplicate = !!replaceWith && params.some((param, i) => existing.test(param.substring(codeStarts[i])));
405
+ // comments in front of a removed argument usually describe the previous one, so they are kept
406
+ let carried = '';
407
+ const kept = params.reduce((arr, param, i) => {
408
+ const codeStart = codeStarts[i];
409
+ if (reg.test(param.substring(codeStart))) {
393
410
  if (!change.remove && !duplicate) {
394
- arr.push(param.replace(change.name, change.replaceWith));
411
+ // the name sits at the code start, so renaming must not search the comments
412
+ arr.push(carried + this.commentBreak(carried, param, eol) + param.substring(0, codeStart)
413
+ + change.replaceWith + param.substring(codeStart + change.name.length));
414
+ carried = '';
415
+ }
416
+ else {
417
+ carried += param.substring(0, codeStart).trimEnd();
395
418
  }
396
419
  }
397
420
  else {
398
- arr.push(param);
421
+ arr.push(carried + this.commentBreak(carried, param, eol) + param);
422
+ carried = '';
399
423
  }
400
424
  return arr;
401
425
  }, []);
402
426
  fileContent = fileContent.substring(0, call.bodyStart)
403
- + params.join(',')
427
+ + kept.join(',') + carried + this.commentBreak(carried, trailing, eol)
404
428
  + trailing
405
429
  + fileContent.substring(call.bodyEnd);
406
430
  overwrite = true;
@@ -413,21 +437,29 @@ class UpdateChanges {
413
437
  /**
414
438
  * Returns the argument list boundaries of every top-level `owner(...)` call in the content.
415
439
  * 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 -
440
+ * in one of those is not taken for a call, and anything the stylesheet declares itself is left
441
+ * alone. The brackets are tracked too, so a call nested in another one -
418
442
  * `@include scrollbar(scrollbar-theme($sb-size: 6px))` - reports its own closing bracket
419
443
  * rather than the one of the call surrounding it.
444
+ *
445
+ * An owner is matched anywhere in a name, because migrations rely on it: `circular-theme` is
446
+ * how `progress-circular-theme` is addressed, and `theme` covers every `*-theme` mixin.
420
447
  */
421
- findFunctionCalls(content, owner) {
448
+ findFunctionCalls(content, owner, indented) {
422
449
  const calls = [];
423
450
  const opening = `${owner}(`;
451
+ const code = this.sassCode(content);
452
+ // Earlier changes can move both calls and scope boundaries, so collect them together.
453
+ const scopes = this.declarationScopes(content, code, indented);
454
+ let brackets = 0;
424
455
  for (let i = 0; i < content.length; i++) {
425
- const nonCodeEnd = this.skipNonCode(content, i);
426
- if (nonCodeEnd !== -1) {
427
- i = nonCodeEnd;
428
- continue;
456
+ if (code[i] === '(' || code[i] === '[') {
457
+ brackets++;
458
+ }
459
+ else if (code[i] === ')' || code[i] === ']') {
460
+ brackets--;
429
461
  }
430
- if (!content.startsWith(opening, i) || this.isDeclaredName(content, i)) {
462
+ if (!code.startsWith(opening, i) || this.isOwnMember(code, i, owner, scopes, indented && !brackets)) {
431
463
  continue;
432
464
  }
433
465
  const bodyStart = i + opening.length;
@@ -467,21 +499,173 @@ class UpdateChanges {
467
499
  }
468
500
  return -1;
469
501
  }
502
+ /** Collects declarations within their brace or indentation scopes in code with non-code masked. */
503
+ declarationScopes(content, code, indented) {
504
+ const scopes = [];
505
+ const stack = [];
506
+ let brackets = 0;
507
+ let pendingDeclaration;
508
+ const enterScope = (start, indentation = 0) => {
509
+ const scope = {
510
+ start, end: code.length, mixins: new Map(), functions: new Map(), declarations: []
511
+ };
512
+ scopes.push(scope);
513
+ // Braces in an interpolated default argument do not start the declaration's body.
514
+ const declaration = brackets ? undefined : pendingDeclaration;
515
+ stack.push({ scope, indentation, declaration });
516
+ if (declaration) {
517
+ pendingDeclaration = undefined;
518
+ }
519
+ };
520
+ const leaveScope = (end) => {
521
+ const { scope, declaration } = stack.pop();
522
+ scope.end = end;
523
+ if (declaration) {
524
+ declaration.end = end;
525
+ }
526
+ };
527
+ enterScope(0);
528
+ const declaration = /(?:@(mixin|function)\s+|=\s*)([\w-]+)/y;
529
+ for (let i = 0; i < code.length; i++) {
530
+ // Indentation inside a multiline argument/list is not a new Sass scope.
531
+ if (indented && !brackets && (i === 0 || code[i - 1] === '\n')) {
532
+ const lineEnd = code.indexOf('\n', i);
533
+ const line = code.substring(i, lineEnd === -1 ? code.length : lineEnd);
534
+ if (line.trim()) {
535
+ // Masked comments are whitespace too, but are not part of the source indentation.
536
+ const indentation = /^[ \t]*/.exec(content.substring(i))[0].length;
537
+ // An indented mixin may have defaults but no body.
538
+ if (pendingDeclaration && indentation <= stack[stack.length - 1].indentation) {
539
+ pendingDeclaration.end = i;
540
+ pendingDeclaration = undefined;
541
+ }
542
+ while (stack.length > 1 && indentation < stack[stack.length - 1].indentation) {
543
+ leaveScope(i);
544
+ }
545
+ if (indentation > stack[stack.length - 1].indentation) {
546
+ enterScope(i, indentation);
547
+ }
548
+ }
549
+ }
550
+ const char = code[i];
551
+ if (!indented && char === '{') {
552
+ enterScope(i);
553
+ }
554
+ else if (!indented && char === '}' && stack.length > 1) {
555
+ leaveScope(i);
556
+ }
557
+ else if (char === '(' || char === '[') {
558
+ brackets++;
559
+ }
560
+ else if (char === ')' || char === ']') {
561
+ brackets--;
562
+ }
563
+ else if (char === '@' || (indented && !brackets && char === '='
564
+ && this.keywordBefore(code, i + 1, true) === '@mixin')) {
565
+ declaration.lastIndex = i;
566
+ const match = declaration.exec(code);
567
+ if (match) {
568
+ const scope = stack[stack.length - 1].scope;
569
+ // Sass treats underscores and hyphens as the same identifier character.
570
+ const members = scope[match[1] === 'function' ? 'functions' : 'mixins'];
571
+ const name = match[2].replace(/_/g, '-');
572
+ if (!members.has(name)) {
573
+ members.set(name, i);
574
+ }
575
+ pendingDeclaration = { start: i, end: code.length };
576
+ scope.declarations.push(pendingDeclaration);
577
+ i = declaration.lastIndex - 1;
578
+ }
579
+ }
580
+ }
581
+ return scopes;
582
+ }
470
583
  /**
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.
584
+ * Tells whether the `owner` at `index` is part of a member the stylesheet declares itself. A
585
+ * user's own `app-grid-summary-theme` ends with a theme function name, so without this its
586
+ * declaration would lose a parameter its body reads.
587
+ *
588
+ * Only this stylesheet is read. Telling which member a call loaded from another one refers to
589
+ * needs the module rules of `@use` and `@forward` - re-exports, `hide` and `as prefix-*` - so a
590
+ * member of the app that another stylesheet declares is still taken for the Ignite UI one.
473
591
  */
474
- isDeclaredName(content, index) {
475
- let end = index;
592
+ isOwnMember(content, index, owner, scopes, indented) {
593
+ let start = index;
594
+ while (start > 0 && /[\w-]/.test(content[start - 1])) {
595
+ start--;
596
+ }
597
+ // a namespace holds the members of another stylesheet, never the ones declared here
598
+ if (content[start - 1] === '.') {
599
+ return false;
600
+ }
601
+ const keyword = this.keywordBefore(content, start, indented);
602
+ if (keyword === '@mixin' || keyword === '@function') {
603
+ // a declaration, whose body reads the parameters it lists
604
+ return true;
605
+ }
606
+ const name = content.substring(start, index + owner.length).replace(/_/g, '-');
607
+ // Bodies and defaults can run after a later declaration becomes available. Keep their
608
+ // existing protection; declaration order is conclusive only for immediate calls.
609
+ const deferred = scopes.some(scope => scope.declarations.some(member => member.start <= index && index < member.end));
610
+ // a mixin and a function of one name are separate members, so only the called one shadows
611
+ return scopes.some(scope => {
612
+ const declaredAt = (keyword === '@include' ? scope.mixins : scope.functions).get(name);
613
+ return scope.start <= index && index < scope.end && declaredAt !== undefined
614
+ && (deferred || declaredAt <= index);
615
+ });
616
+ }
617
+ /** The at-rule before a member name, or indented shorthand when outside an expression. */
618
+ keywordBefore(content, start, indented) {
619
+ let end = start;
476
620
  while (end > 0 && /\s/.test(content[end - 1])) {
477
621
  end--;
478
622
  }
479
- let start = end;
480
- while (start > 0 && /[\w@-]/.test(content[start - 1])) {
481
- start--;
623
+ // Shorthand starts a statement. Unlike '=', '+' must be adjacent to the mixin name.
624
+ const shorthand = content[end - 1];
625
+ if (indented && (shorthand === '=' || (shorthand === '+' && end === start))
626
+ && !content.substring(content.lastIndexOf('\n', end - 1) + 1, end - 1).trim()) {
627
+ return shorthand === '=' ? '@mixin' : '@include';
482
628
  }
483
- const keyword = content.substring(start, end);
484
- return keyword === '@mixin' || keyword === '@function';
629
+ let keywordStart = end;
630
+ while (keywordStart > 0 && /[\w@-]/.test(content[keywordStart - 1])) {
631
+ keywordStart--;
632
+ }
633
+ return content.substring(keywordStart, end);
634
+ }
635
+ /** Masks non-code with whitespace, preserving offsets, line endings and the original stylesheet. */
636
+ sassCode(content) {
637
+ const parts = [];
638
+ let start = 0;
639
+ for (let i = 0; i < content.length; i++) {
640
+ const nonCodeEnd = this.skipNonCode(content, i);
641
+ if (nonCodeEnd !== -1) {
642
+ parts.push(content.substring(start, i), content.substring(i, nonCodeEnd + 1).replace(/[^\r\n]/g, ' '));
643
+ start = nonCodeEnd + 1;
644
+ i = nonCodeEnd;
645
+ }
646
+ }
647
+ parts.push(content.substring(start));
648
+ return parts.join('');
649
+ }
650
+ /** Index where an argument's code starts, after its leading whitespace and comments. */
651
+ leadingCommentsEnd(param) {
652
+ let i = 0;
653
+ while (i < param.length) {
654
+ if (/\s/.test(param[i])) {
655
+ i++;
656
+ }
657
+ else if (param[i] === '/' && (param[i + 1] === '/' || param[i + 1] === '*')) {
658
+ i = this.skipNonCode(param, i) + 1;
659
+ }
660
+ else {
661
+ break;
662
+ }
663
+ }
664
+ return i;
665
+ }
666
+ /** The line break a carried `//` comment needs so that it does not swallow what follows it. */
667
+ commentBreak(carried, next, eol) {
668
+ return /\/\/[^\n]*$/.test(carried) && !/^[ \t]*\r?\n/.test(next) ? eol : '';
485
669
  }
486
670
  /**
487
671
  * When a string, a comment or a `url()` token starts at `index`, returns the index of its last