From 12208de0cafdbaf76e6a99a93dd3289d79f26974 Mon Sep 17 00:00:00 2001 From: Simeon Simeonoff Date: Tue, 26 Nov 2024 13:51:33 +0200 Subject: [PATCH 01/15] fix(themes): wrapping theme mixins don't work with custom palettes --- CHANGELOG.md | 21 +++++++++++++++++++ .../core/styles/themes/generators/_base.scss | 10 +++++---- .../styles/themes/generators/_bootstrap.scss | 2 ++ .../styles/themes/generators/_fluent.scss | 2 ++ .../styles/themes/generators/_indigo.scss | 2 ++ 5 files changed, 33 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2245bc8168e..d4ae00a6247 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,27 @@ All notable changes for each version of this project will be documented in this ### New Features - `IgxColumn` - Introduced the `disabledSummaries` property, allowing users to specify which summaries should be disabled for a given column. This property accepts an array of strings corresponding to the summary keys, enabling selective control over both default summaries (e.g., 'Count', 'Min') and any custom summaries created by the user. +- `Themes` + - **Deprecation** The utility mixins `light-theme`, `dark-theme`, `bootstrap-light-theme`, `bootstrap-dark-theme`, `fluent-light-theme`, `fluent-dark-theme`, `indigo-light-theme`, and `indigo-dark-theme` have been deprecated and will be removed in version 20 of Ignite UI for Angular. Switch to the more generic `theme` mixin instead. + Example: + ```scss + $my-light-palette: palette( + $primary: navy, + $secondary: rebeccapurple, + $surface: white, + ); + + // Before: + @include light-theme( + $palette: $my-light-palette + ); + + // After: + @include theme( + $palette: $my-light-palette, + $schema: $light-material-schema, + ); + ``` ## 18.2.0 ### General diff --git a/projects/igniteui-angular/src/lib/core/styles/themes/generators/_base.scss b/projects/igniteui-angular/src/lib/core/styles/themes/generators/_base.scss index a36f477588f..b7618e3e9c2 100644 --- a/projects/igniteui-angular/src/lib/core/styles/themes/generators/_base.scss +++ b/projects/igniteui-angular/src/lib/core/styles/themes/generators/_base.scss @@ -663,6 +663,7 @@ /// @param {Map} $palette - An palette to be used by the global theme. /// @param {List} $exclude [( )] - A list of ig components to be excluded from the global theme styles. /// @see {mixin} theme +/// @deprecated - Use the theme mixin instead. @mixin light-theme( $palette, $exclude: (), @@ -672,7 +673,7 @@ $gray: color($palette, 'gray'); $surface: color($palette, 'surface'); - $light-palette: palette( + $_light-palette: palette( $primary: color($palette, 'primary'), $secondary: color($palette, 'secondary'), $info: color($palette, 'info'), @@ -684,7 +685,7 @@ ); @include theme( - $palette: $light-material-palette, + $palette: $_light-palette, $schema: $light-material-schema, $exclude: $exclude, $roundness: $roundness, @@ -696,6 +697,7 @@ /// @param {Map} $palette - An palette to be used by the global theme. /// @param {List} $exclude [( )] - A list of igx components to be excluded from the global theme styles. /// @see {mixin} theme +/// @deprecated - Use the theme mixin instead. @mixin dark-theme( $palette, $exclude: (), @@ -705,7 +707,7 @@ $gray: color($palette, 'gray'); $surface: color($palette, 'surface'); - $dark-palette: palette( + $_dark-palette: palette( $primary: color($palette, 'primary'), $secondary: color($palette, 'secondary'), $info: color($palette, 'info'), @@ -717,7 +719,7 @@ ); @include theme( - $palette: $dark-material-palette, + $palette: $_dark-palette, $schema: $dark-material-schema, $exclude: $exclude, $roundness: $roundness, diff --git a/projects/igniteui-angular/src/lib/core/styles/themes/generators/_bootstrap.scss b/projects/igniteui-angular/src/lib/core/styles/themes/generators/_bootstrap.scss index 4b5cb0f34be..0a60109d537 100644 --- a/projects/igniteui-angular/src/lib/core/styles/themes/generators/_bootstrap.scss +++ b/projects/igniteui-angular/src/lib/core/styles/themes/generators/_bootstrap.scss @@ -13,6 +13,7 @@ /// @param {Map} $palette - An palette to be used by the global theme. /// @param {List} $exclude [( )] - A list of ig components to be excluded from the global theme styles. /// @see {mixin} theme +/// @deprecated - Use the theme mixin instead. @mixin bootstrap-light-theme( $palette, $exclude: (), @@ -50,6 +51,7 @@ /// @param {Map} $palette - An palette to be used by the global theme. /// @param {List} $exclude [( )] - A list of ig components to be excluded from the global theme styles. /// @see {mixin} theme +/// @deprecated - Use the theme mixin instead. @mixin bootstrap-dark-theme( $palette, $exclude: (), diff --git a/projects/igniteui-angular/src/lib/core/styles/themes/generators/_fluent.scss b/projects/igniteui-angular/src/lib/core/styles/themes/generators/_fluent.scss index d08a4fb9f8a..865f2e37b6b 100644 --- a/projects/igniteui-angular/src/lib/core/styles/themes/generators/_fluent.scss +++ b/projects/igniteui-angular/src/lib/core/styles/themes/generators/_fluent.scss @@ -13,6 +13,7 @@ /// @param {Map} $palette - An palette to be used by the global theme. /// @param {List} $exclude [( )] - A list of ig components to be excluded from the global theme styles. /// @see {mixin} theme +/// @deprecated - Use the theme mixin instead. @mixin fluent-light-theme( $palette, $exclude: (), @@ -50,6 +51,7 @@ /// @param {Map} $palette - An palette to be used by the global theme. /// @param {List} $exclude [( )] - A list of ig components to be excluded from the global theme styles. /// @see {mixin} theme +/// @deprecated - Use the theme mixin instead. @mixin fluent-dark-theme( $palette, $exclude: (), diff --git a/projects/igniteui-angular/src/lib/core/styles/themes/generators/_indigo.scss b/projects/igniteui-angular/src/lib/core/styles/themes/generators/_indigo.scss index 3213be8a37e..d4b231236f7 100644 --- a/projects/igniteui-angular/src/lib/core/styles/themes/generators/_indigo.scss +++ b/projects/igniteui-angular/src/lib/core/styles/themes/generators/_indigo.scss @@ -14,6 +14,7 @@ /// @param {Map} $palette - An palette to be used by the global theme. /// @param {List} $exclude [( )] - A list of ig components to be excluded from the global theme styles. /// @see {mixin} theme +/// @deprecated - Use the theme mixin instead. @mixin indigo-light-theme( $palette, $exclude: (), @@ -52,6 +53,7 @@ /// @param {Map} $palette - An palette to be used by the global theme. /// @param {List} $exclude [( )] - A list of ig components to be excluded from the global theme styles. /// @see {mixin} theme +/// @deprecated - Use the theme mixin instead. @mixin indigo-dark-theme( $palette, $exclude: (), From c5e5557064cdb9b924f0ada37b49932eb7133c45 Mon Sep 17 00:00:00 2001 From: Konstantin Dinev Date: Wed, 27 Nov 2024 16:00:19 +0200 Subject: [PATCH 02/15] Update codeql-analysis.yml --- .github/workflows/codeql-analysis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 92f683dee7b..1076a0dd971 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -13,10 +13,10 @@ name: "CodeQL" on: push: - branches: [ master, 18.2.x, 17.2.x, 16.1.x, 15.1.x ] + branches: [ master, 19.0.x, 18.2.x, 17.2.x, 16.1.x, 15.1.x ] pull_request: # The branches below must be a subset of the branches above - branches: [ master, 18.2.x, 17.2.x, 16.1.x, 15.1.x ] + branches: [ master, 19.0.x, 18.2.x, 17.2.x, 16.1.x, 15.1.x ] schedule: - cron: '33 4 * * 4' From c16cb7be778978578fe326692ed0085f3e58a3d9 Mon Sep 17 00:00:00 2001 From: didimmova Date: Wed, 27 Nov 2024 16:17:02 +0200 Subject: [PATCH 03/15] fix(tree): update checkbox spacing in tree --- .../src/lib/core/styles/components/tree/_tree-theme.scss | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/projects/igniteui-angular/src/lib/core/styles/components/tree/_tree-theme.scss b/projects/igniteui-angular/src/lib/core/styles/components/tree/_tree-theme.scss index fcd4c05d0d0..7cbd141cfaf 100644 --- a/projects/igniteui-angular/src/lib/core/styles/components/tree/_tree-theme.scss +++ b/projects/igniteui-angular/src/lib/core/styles/components/tree/_tree-theme.scss @@ -148,7 +148,7 @@ ), $variant); $icon-space: map.get(( - 'material': $icon-space-default, + 'material': $icon-space-indigo, 'fluent': $icon-space-default, 'bootstrap': $icon-space-default, 'indigo': $icon-space-indigo @@ -209,6 +209,12 @@ margin-inline-end: $icon-space; } + @if $variant == 'material' { + %node-select { + margin-inline: rem(10px) rem(14px); + } + } + @if $variant == 'indigo' { %node-select { margin-inline-end: rem(8px); From 6c2c6574391172bc4fba9eea3df00ff5d4b39c92 Mon Sep 17 00:00:00 2001 From: igdmdimitrov <49060557+igdmdimitrov@users.noreply.github.com> Date: Thu, 28 Nov 2024 18:16:02 +0200 Subject: [PATCH 04/15] fix(excel-style-filtering): reset filter value (#15090) * fix(esf): show filters based on column name * fix(esf-custom-dialog): filter expressionsList by column name * chore(*): revert changes after branch retarget --------- Co-authored-by: RadoMirchev Co-authored-by: Konstantin Dinev Co-authored-by: Galina Edinakova --- .../excel-style/excel-style-conditional-filter.component.ts | 4 +++- .../excel-style/excel-style-custom-dialog.component.ts | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/projects/igniteui-angular/src/lib/grids/filtering/excel-style/excel-style-conditional-filter.component.ts b/projects/igniteui-angular/src/lib/grids/filtering/excel-style/excel-style-conditional-filter.component.ts index 59b15d00383..4cbd41d7e25 100644 --- a/projects/igniteui-angular/src/lib/grids/filtering/excel-style/excel-style-conditional-filter.component.ts +++ b/projects/igniteui-angular/src/lib/grids/filtering/excel-style/excel-style-conditional-filter.component.ts @@ -40,7 +40,7 @@ export class IgxExcelStyleConditionalFilterComponent implements OnDestroy { public subMenu: IgxDropDownComponent; protected get filterNumber() { - return this.esf.expressionsList.length; + return this.esf.expressionsList.filter(e => e.expression.condition).length; } private shouldOpenSubMenu = true; @@ -145,6 +145,8 @@ export class IgxExcelStyleConditionalFilterComponent implements OnDestroy { if (this.esf.expressionsList && this.esf.expressionsList.length && this.esf.expressionsList[0].expression.condition.name !== 'in') { this.customDialog.expressionsList = this.esf.expressionsList; + } else { + this.customDialog.expressionsList = this.customDialog.expressionsList.filter(e => e.expression.fieldName === this.esf.column.field && e.expression.condition); } this.customDialog.selectedOperator = eventArgs.newSelection.value; diff --git a/projects/igniteui-angular/src/lib/grids/filtering/excel-style/excel-style-custom-dialog.component.ts b/projects/igniteui-angular/src/lib/grids/filtering/excel-style/excel-style-custom-dialog.component.ts index 3f684e5ee44..eb701c26413 100644 --- a/projects/igniteui-angular/src/lib/grids/filtering/excel-style/excel-style-custom-dialog.component.ts +++ b/projects/igniteui-angular/src/lib/grids/filtering/excel-style/excel-style-custom-dialog.component.ts @@ -45,6 +45,7 @@ import { NgClass, NgIf, NgFor } from '@angular/common'; export class IgxExcelStyleCustomDialogComponent implements AfterViewInit { @Input() public expressionsList = new Array(); + @Input() public column: ColumnType; From e753e498addf4dd174bb976d552b9847f676f15e Mon Sep 17 00:00:00 2001 From: Martin Dragnev <37667452+mddragnev@users.noreply.github.com> Date: Mon, 2 Dec 2024 18:02:40 +0200 Subject: [PATCH 05/15] =?UTF-8?q?fix(ssr):=20Change=20all=20occurrences=20?= =?UTF-8?q?of=20pure=20document=20call=20with=20injected=20=E2=80=A6=20(#1?= =?UTF-8?q?5118)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/lib/carousel/carousel.component.ts | 7 ++- .../drag-drop/drag-drop.directive.ts | 55 ++++++++++--------- .../base/grid-filtering-cell.component.ts | 6 +- .../base/grid-filtering-row.component.ts | 4 +- .../src/lib/grids/grid-base.directive.ts | 2 +- .../hierarchical-grid/row-island.component.ts | 4 +- .../lib/grids/moving/moving.drag.directive.ts | 4 +- .../lib/grids/selection/selection.service.ts | 6 +- .../excel/worksheet-data-dictionary.ts | 2 +- .../exporter-common/export-utilities.ts | 8 +-- 10 files changed, 51 insertions(+), 47 deletions(-) diff --git a/projects/igniteui-angular/src/lib/carousel/carousel.component.ts b/projects/igniteui-angular/src/lib/carousel/carousel.component.ts index 9d3281e1b77..196a0202861 100644 --- a/projects/igniteui-angular/src/lib/carousel/carousel.component.ts +++ b/projects/igniteui-angular/src/lib/carousel/carousel.component.ts @@ -1,4 +1,4 @@ -import { NgIf, NgClass, NgFor, NgTemplateOutlet } from '@angular/common'; +import { NgIf, NgClass, NgFor, NgTemplateOutlet, DOCUMENT } from '@angular/common'; import { AfterContentInit, ChangeDetectorRef, @@ -583,7 +583,8 @@ export class IgxCarouselComponent extends IgxCarouselComponentBase implements On private iterableDiffers: IterableDiffers, @Inject(IgxAngularAnimationService) animationService: AnimationService, private platformUtil: PlatformUtil, - private dir: IgxDirectionality + private dir: IgxDirectionality, + @Inject(DOCUMENT) private document: any ) { super(animationService, cdr); this.differ = this.iterableDiffers.find([]).create(null); @@ -1002,7 +1003,7 @@ export class IgxCarouselComponent extends IgxCarouselComponentBase implements On } private focusElement() { - const focusedElement = document.activeElement; + const focusedElement = this.document.activeElement; if (focusedElement.classList.contains('igx-carousel-indicators__indicator')) { this.indicatorsElements[this.current].nativeElement.focus(); diff --git a/projects/igniteui-angular/src/lib/directives/drag-drop/drag-drop.directive.ts b/projects/igniteui-angular/src/lib/directives/drag-drop/drag-drop.directive.ts index 3ab5a759c9f..4c18c7beccf 100644 --- a/projects/igniteui-angular/src/lib/directives/drag-drop/drag-drop.directive.ts +++ b/projects/igniteui-angular/src/lib/directives/drag-drop/drag-drop.directive.ts @@ -18,12 +18,14 @@ import { QueryList, RendererStyleFlags2, booleanAttribute, - EmbeddedViewRef + EmbeddedViewRef, + inject } from '@angular/core'; import { animationFrameScheduler, fromEvent, interval, Subject } from 'rxjs'; import { takeUntil, throttle } from 'rxjs/operators'; import { IBaseEventArgs, PlatformUtil } from '../../core/utils'; import { IDropStrategy, IgxDefaultDropStrategy } from './drag-drop.strategy'; +import { DOCUMENT } from '@angular/common'; enum DragScrollDirection { UP, @@ -551,7 +553,7 @@ export class IgxDragDirective implements AfterContentInit, OnDestroy { protected set ghostLeft(pageX: number) { if (this.ghostElement) { // We need to take into account marginLeft, since top style does not include margin, but pageX includes the margin. - const ghostMarginLeft = parseInt(document.defaultView.getComputedStyle(this.ghostElement)['margin-left'], 10); + const ghostMarginLeft = parseInt(this.document.defaultView.getComputedStyle(this.ghostElement)['margin-left'], 10); // If ghost host is defined it needs to be taken into account. this.ghostElement.style.left = (pageX - ghostMarginLeft - this._ghostHostX) + 'px'; } @@ -566,7 +568,7 @@ export class IgxDragDirective implements AfterContentInit, OnDestroy { protected set ghostTop(pageY: number) { if (this.ghostElement) { // We need to take into account marginTop, since top style does not include margin, but pageY includes the margin. - const ghostMarginTop = parseInt(document.defaultView.getComputedStyle(this.ghostElement)['margin-top'], 10); + const ghostMarginTop = parseInt(this.document.defaultView.getComputedStyle(this.ghostElement)['margin-top'], 10); // If ghost host is defined it needs to be taken into account. this.ghostElement.style.top = (pageY - ghostMarginTop - this._ghostHostY) + 'px'; } @@ -579,19 +581,19 @@ export class IgxDragDirective implements AfterContentInit, OnDestroy { } protected get windowScrollTop() { - return document.documentElement.scrollTop || window.scrollY; + return this.document.documentElement.scrollTop || window.scrollY; } protected get windowScrollLeft() { - return document.documentElement.scrollLeft || window.scrollX; + return this.document.documentElement.scrollLeft || window.scrollX; } protected get windowScrollHeight() { - return document.documentElement.scrollHeight; + return this.document.documentElement.scrollHeight; } protected get windowScrollWidth() { - return document.documentElement.scrollWidth; + return this.document.documentElement.scrollWidth; } /** @@ -641,6 +643,7 @@ export class IgxDragDirective implements AfterContentInit, OnDestroy { protected _scrollContainerStepMs = 10; protected _scrollContainerThreshold = 25; protected _containerScrollIntervalId = null; + private document = inject(DOCUMENT); /** * Sets the offset of the dragged element relative to the mouse in pixels. @@ -690,7 +693,7 @@ export class IgxDragDirective implements AfterContentInit, OnDestroy { public viewContainer: ViewContainerRef, public zone: NgZone, public renderer: Renderer2, - protected platformUtil: PlatformUtil, + protected platformUtil: PlatformUtil ) { } @@ -746,20 +749,20 @@ export class IgxDragDirective implements AfterContentInit, OnDestroy { // We should bind to document events only once when there are no pointer events. if (!this.pointerEventsEnabled && this.touchEventsEnabled) { - fromEvent(document.defaultView, 'touchmove').pipe( + fromEvent(this.document.defaultView, 'touchmove').pipe( throttle(() => interval(0, animationFrameScheduler)), takeUntil(this._destroy) ).subscribe((res) => this.onPointerMove(res)); - fromEvent(document.defaultView, 'touchend').pipe(takeUntil(this._destroy)) + fromEvent(this.document.defaultView, 'touchend').pipe(takeUntil(this._destroy)) .subscribe((res) => this.onPointerUp(res)); } else if (!this.pointerEventsEnabled) { - fromEvent(document.defaultView, 'mousemove').pipe( + fromEvent(this.document.defaultView, 'mousemove').pipe( throttle(() => interval(0, animationFrameScheduler)), takeUntil(this._destroy) ).subscribe((res) => this.onPointerMove(res)); - fromEvent(document.defaultView, 'mouseup').pipe(takeUntil(this._destroy)) + fromEvent(this.document.defaultView, 'mouseup').pipe(takeUntil(this._destroy)) .subscribe((res) => this.onPointerUp(res)); } this.element.nativeElement.addEventListener('transitionend', this.onTransitionEnd.bind(this)); @@ -1140,9 +1143,9 @@ export class IgxDragDirective implements AfterContentInit, OnDestroy { if (this.ghostHost && !Array.from(this.ghostHost.children).includes(this.ghostElement)) { ghostReattached = true; this.ghostHost.appendChild(this.ghostElement); - } else if (!this.ghostHost && !Array.from(document.body.children).includes(this.ghostElement)) { + } else if (!this.ghostHost && !Array.from(this.document.body.children).includes(this.ghostElement)) { ghostReattached = true; - document.body.appendChild(this.ghostElement); + this.document.body.appendChild(this.ghostElement); } if (ghostReattached) { @@ -1293,11 +1296,11 @@ export class IgxDragDirective implements AfterContentInit, OnDestroy { if (this.ghostHost) { this.ghostHost.appendChild(this.ghostElement); } else { - document.body.appendChild(this.ghostElement); + this.document.body.appendChild(this.ghostElement); } - const ghostMarginLeft = parseInt(document.defaultView.getComputedStyle(this.ghostElement)['margin-left'], 10); - const ghostMarginTop = parseInt(document.defaultView.getComputedStyle(this.ghostElement)['margin-top'], 10); + const ghostMarginLeft = parseInt(this.document.defaultView.getComputedStyle(this.ghostElement)['margin-left'], 10); + const ghostMarginTop = parseInt(this.document.defaultView.getComputedStyle(this.ghostElement)['margin-top'], 10); this.ghostElement.style.left = (this._ghostStartX - ghostMarginLeft + totalMovedX - this._ghostHostX) + 'px'; this.ghostElement.style.top = (this._ghostStartY - ghostMarginTop + totalMovedY - this._ghostHostY) + 'px'; @@ -1417,13 +1420,13 @@ export class IgxDragDirective implements AfterContentInit, OnDestroy { // using window.pageXOffset for IE9 compatibility const viewPortX = pageX - window.pageXOffset; const viewPortY = pageY - window.pageYOffset; - if (document['msElementsFromPoint']) { + if (this.document['msElementsFromPoint']) { // Edge and IE special snowflakes - const elements = document['msElementsFromPoint'](viewPortX, viewPortY); + const elements = this.document['msElementsFromPoint'](viewPortX, viewPortY); return elements === null ? [] : elements; } else { // Other browsers like Chrome, Firefox, Opera - return document.elementsFromPoint(viewPortX, viewPortY); + return this.document.elementsFromPoint(viewPortX, viewPortY); } } @@ -1483,8 +1486,8 @@ export class IgxDragDirective implements AfterContentInit, OnDestroy { protected getGhostHostBaseOffsetX() { if (!this.ghostHost) return 0; - const ghostPosition = document.defaultView.getComputedStyle(this.ghostHost).getPropertyValue('position'); - if (ghostPosition === 'static' && this.ghostHost.offsetParent && this.ghostHost.offsetParent === document.body) { + const ghostPosition = this.document.defaultView.getComputedStyle(this.ghostHost).getPropertyValue('position'); + if (ghostPosition === 'static' && this.ghostHost.offsetParent && this.ghostHost.offsetParent === this.document.body) { return 0; } else if (ghostPosition === 'static' && this.ghostHost.offsetParent) { return this.ghostHost.offsetParent.getBoundingClientRect().left + this.windowScrollLeft; @@ -1495,8 +1498,8 @@ export class IgxDragDirective implements AfterContentInit, OnDestroy { protected getGhostHostBaseOffsetY() { if (!this.ghostHost) return 0; - const ghostPosition = document.defaultView.getComputedStyle(this.ghostHost).getPropertyValue('position'); - if (ghostPosition === 'static' && this.ghostHost.offsetParent && this.ghostHost.offsetParent === document.body) { + const ghostPosition = this.document.defaultView.getComputedStyle(this.ghostHost).getPropertyValue('position'); + if (ghostPosition === 'static' && this.ghostHost.offsetParent && this.ghostHost.offsetParent === this.document.body) { return 0; } else if (ghostPosition === 'static' && this.ghostHost.offsetParent) { return this.ghostHost.offsetParent.getBoundingClientRect().top + this.windowScrollTop; @@ -1540,8 +1543,8 @@ export class IgxDragDirective implements AfterContentInit, OnDestroy { let yDir = scrollDir == DragScrollDirection.UP ? -1 : (scrollDir == DragScrollDirection.DOWN ? 1 : 0); if (!this.scrollContainer) { // Cap scrolling so we don't scroll past the window max scroll position. - const maxScrollX = this._originalScrollContainerWidth - document.documentElement.clientWidth; - const maxScrollY = this._originalScrollContainerHeight - document.documentElement.clientHeight; + const maxScrollX = this._originalScrollContainerWidth - this.document.documentElement.clientWidth; + const maxScrollY = this._originalScrollContainerHeight - this.document.documentElement.clientHeight; xDir = (this.windowScrollLeft <= 0 && xDir < 0) || (this.windowScrollLeft >= maxScrollX && xDir > 0) ? 0 : xDir; yDir = (this.windowScrollTop <= 0 && yDir < 0) || (this.windowScrollTop >= maxScrollY && yDir > 0) ? 0 : yDir; } else { diff --git a/projects/igniteui-angular/src/lib/grids/filtering/base/grid-filtering-cell.component.ts b/projects/igniteui-angular/src/lib/grids/filtering/base/grid-filtering-cell.component.ts index d0324650328..620d99b1c5e 100644 --- a/projects/igniteui-angular/src/lib/grids/filtering/base/grid-filtering-cell.component.ts +++ b/projects/igniteui-angular/src/lib/grids/filtering/base/grid-filtering-cell.component.ts @@ -222,7 +222,7 @@ export class IgxGridFilteringCellComponent implements AfterViewInit, OnInit, DoC const chipsAreaElements = this.chipsArea.element.nativeElement.children; let visibleChipsCount = 0; const moreIconWidth = this.moreIcon.nativeElement.offsetWidth - - parseInt(document.defaultView.getComputedStyle(this.moreIcon.nativeElement)['margin-left'], 10); + parseInt(this.column?.grid.document.defaultView.getComputedStyle(this.moreIcon.nativeElement)['margin-left'], 10); for (let index = 0; index < chipsAreaElements.length - 1; index++) { if (viewWidth + chipsAreaElements[index].offsetWidth < areaWidth) { @@ -230,8 +230,8 @@ export class IgxGridFilteringCellComponent implements AfterViewInit, OnInit, DoC if (index % 2 === 0) { visibleChipsCount++; } else { - viewWidth += parseInt(document.defaultView.getComputedStyle(chipsAreaElements[index])['margin-left'], 10); - viewWidth += parseInt(document.defaultView.getComputedStyle(chipsAreaElements[index])['margin-right'], 10); + viewWidth += parseInt(this.column?.grid.document.defaultView.getComputedStyle(chipsAreaElements[index])['margin-left'], 10); + viewWidth += parseInt(this.column?.grid.document.defaultView.getComputedStyle(chipsAreaElements[index])['margin-right'], 10); } } else { if (index % 2 !== 0 && viewWidth + moreIconWidth > areaWidth) { diff --git a/projects/igniteui-angular/src/lib/grids/filtering/base/grid-filtering-row.component.ts b/projects/igniteui-angular/src/lib/grids/filtering/base/grid-filtering-row.component.ts index 3a0858315e4..c816e840053 100644 --- a/projects/igniteui-angular/src/lib/grids/filtering/base/grid-filtering-row.component.ts +++ b/projects/igniteui-angular/src/lib/grids/filtering/base/grid-filtering-row.component.ts @@ -511,7 +511,7 @@ export class IgxGridFilteringRowComponent implements AfterViewInit, OnDestroy { return; } requestAnimationFrame(() => { - const focusedElement = document.activeElement; + const focusedElement = this.column?.grid.document.activeElement; if (focusedElement.classList.contains('igx-chip__remove')) { return; @@ -605,7 +605,7 @@ export class IgxGridFilteringRowComponent implements AfterViewInit, OnDestroy { public onChipPointerdown(args, chip: IgxChipComponent) { - const activeElement = document.activeElement; + const activeElement = this.column?.grid.document.activeElement; this._cancelChipClick = chip.selected && activeElement && this.editorFocused(activeElement); } diff --git a/projects/igniteui-angular/src/lib/grids/grid-base.directive.ts b/projects/igniteui-angular/src/lib/grids/grid-base.directive.ts index 92adb87c83c..b3ce4f4f7b8 100644 --- a/projects/igniteui-angular/src/lib/grids/grid-base.directive.ts +++ b/projects/igniteui-angular/src/lib/grids/grid-base.directive.ts @@ -7524,7 +7524,7 @@ export abstract class IgxGridBaseDirective implements GridType, protected get renderedActualRowHeight() { let border = 1; if (this.rowList.toArray().length > 0) { - const rowStyles = document.defaultView.getComputedStyle(this.rowList.first.nativeElement); + const rowStyles = this.document.defaultView.getComputedStyle(this.rowList.first.nativeElement); border = rowStyles.borderBottomWidth ? Math.ceil(parseFloat(rowStyles.borderBottomWidth)) : border; } return this.rowHeight + border; diff --git a/projects/igniteui-angular/src/lib/grids/hierarchical-grid/row-island.component.ts b/projects/igniteui-angular/src/lib/grids/hierarchical-grid/row-island.component.ts index b6af139b7e9..c1ff8761a48 100644 --- a/projects/igniteui-angular/src/lib/grids/hierarchical-grid/row-island.component.ts +++ b/projects/igniteui-angular/src/lib/grids/hierarchical-grid/row-island.component.ts @@ -305,7 +305,7 @@ export class IgxRowIslandComponent extends IgxHierarchicalGridBaseDirective public set expandChildren(value: boolean) { this._defaultExpandState = value; this.rowIslandAPI.getChildGrids().forEach((grid) => { - if (document.body.contains(grid.nativeElement)) { + if (this.document.body.contains(grid.nativeElement)) { // Detect changes right away if the grid is visible grid.expandChildren = value; grid.cdr.detectChanges(); @@ -549,7 +549,7 @@ export class IgxRowIslandComponent extends IgxHierarchicalGridBaseDirective this.updateColumns(this._childColumns); this.rowIslandAPI.getChildGrids().forEach((grid: GridType) => { grid.createColumnsList(this._childColumns); - if (!document.body.contains(grid.nativeElement)) { + if (!this.document.body.contains(grid.nativeElement)) { grid.updateOnRender = true; } }); diff --git a/projects/igniteui-angular/src/lib/grids/moving/moving.drag.directive.ts b/projects/igniteui-angular/src/lib/grids/moving/moving.drag.directive.ts index 2bb587525fe..99168ef3e89 100644 --- a/projects/igniteui-angular/src/lib/grids/moving/moving.drag.directive.ts +++ b/projects/igniteui-angular/src/lib/grids/moving/moving.drag.directive.ts @@ -120,8 +120,8 @@ export class IgxColumnMovingDragDirective extends IgxDragDirective implements On this.ghostElement.classList.remove(this.columnSelectedClass); - const icon = document.createElement('i'); - const text = document.createTextNode('block'); + const icon = this.column?.grid.document.createElement('i'); + const text = this.column?.grid.document.createTextNode('block'); icon.appendChild(text); icon.classList.add('material-icons'); diff --git a/projects/igniteui-angular/src/lib/grids/selection/selection.service.ts b/projects/igniteui-angular/src/lib/grids/selection/selection.service.ts index a15109e5469..83287824d16 100644 --- a/projects/igniteui-angular/src/lib/grids/selection/selection.service.ts +++ b/projects/igniteui-angular/src/lib/grids/selection/selection.service.ts @@ -248,7 +248,7 @@ export class IgxGridSelectionService { this.pointerState.ctrl = ctrl; this.pointerState.shift = shift; this.pointerEventInGridBody = true; - document.body.addEventListener('pointerup', this.pointerOriginHandler); + this.grid.document.body.addEventListener('pointerup', this.pointerOriginHandler); // No ctrl key pressed - no multiple selection if (!ctrl) { @@ -385,7 +385,7 @@ export class IgxGridSelectionService { public restoreTextSelection(): void { const selection = window.getSelection(); if (!selection.rangeCount) { - selection.addRange(this._selectionRange || document.createRange()); + selection.addRange(this._selectionRange || this.grid.document.createRange()); } } @@ -855,7 +855,7 @@ export class IgxGridSelectionService { private pointerOriginHandler = (event) => { this.pointerEventInGridBody = false; - document.body.removeEventListener('pointerup', this.pointerOriginHandler); + this.grid.document.body.removeEventListener('pointerup', this.pointerOriginHandler); const targetTagName = event.target.tagName.toLowerCase(); if (targetTagName !== 'igx-grid-cell' && targetTagName !== 'igx-tree-grid-cell') { diff --git a/projects/igniteui-angular/src/lib/services/excel/worksheet-data-dictionary.ts b/projects/igniteui-angular/src/lib/services/excel/worksheet-data-dictionary.ts index 84de3089fb7..9a6a86fed39 100644 --- a/projects/igniteui-angular/src/lib/services/excel/worksheet-data-dictionary.ts +++ b/projects/igniteui-angular/src/lib/services/excel/worksheet-data-dictionary.ts @@ -93,7 +93,7 @@ export class WorksheetDataDictionary { private getContext(): any { if (!this._context) { - const canvas = document.createElement('canvas'); + const canvas = globalThis.document?.createElement('canvas'); this._context = canvas.getContext('2d'); this._context.font = WorksheetDataDictionary.DEFAULT_FONT; } diff --git a/projects/igniteui-angular/src/lib/services/exporter-common/export-utilities.ts b/projects/igniteui-angular/src/lib/services/exporter-common/export-utilities.ts index 7561bbfce09..7aebffbed55 100644 --- a/projects/igniteui-angular/src/lib/services/exporter-common/export-utilities.ts +++ b/projects/igniteui-angular/src/lib/services/exporter-common/export-utilities.ts @@ -1,4 +1,3 @@ - /** * @hidden */ @@ -23,14 +22,15 @@ export class ExportUtilities { } public static saveBlobToFile(blob: Blob, fileName) { - const a = document.createElement('a'); + const doc = globalThis.document; + const a = doc.createElement('a'); const url = window.URL.createObjectURL(blob); a.download = fileName; a.href = url; - document.body.appendChild(a); + doc.body.appendChild(a); a.click(); - document.body.removeChild(a); + doc.body.removeChild(a); window.URL.revokeObjectURL(url); } From 9e9ec704284ff0f3002f4ea6cd70cdf2151ec9ad Mon Sep 17 00:00:00 2001 From: Silvia Ivanova <59446295+SisIvanova@users.noreply.github.com> Date: Tue, 3 Dec 2024 20:06:29 +0200 Subject: [PATCH 06/15] fix(avatar): src with null value throws error (#15130) --- README.md | 3 ++- ROADMAP.md | 15 ++++++++++--- SECURITY.md | 5 ++++- .../src/lib/avatar/avatar.component.spec.ts | 22 +++++++++++++++++++ .../igniteui-angular/src/lib/core/utils.ts | 2 +- src/app/avatar/avatar.sample.html | 2 +- 6 files changed, 42 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 58218d1aaa0..6007d3649a5 100644 --- a/README.md +++ b/README.md @@ -160,7 +160,8 @@ Some of the Angular chart types included are: [Polar chart](https://www.infragis |17.2.0|29-Apr-24|[Milestone #31](https://github.com/IgniteUI/igniteui-angular/blob/master/ROADMAP.md#milestone-31-version-172-released-apr-29th-2024)| |18.0.0|07-Jun-24|[Milestone #32](https://github.com/IgniteUI/igniteui-angular/blob/master/ROADMAP.md#milestone-32-version-180-released-jun-07th-2024)| |18.1.0|22-Jul-24|[Milestone #33](https://github.com/IgniteUI/igniteui-angular/blob/master/ROADMAP.md#milestone-33-due-by-jul-2024)| -|18.2.0|25-Oct-24|[Milestone #34]()| +|18.2.0|25-Oct-24|[Milestone #34](https://github.com/IgniteUI/igniteui-angular/blob/master/ROADMAP.md#milestone-34-version-182-released-oct-25th-2024)| +|19.0.0|25-Nov-24|[Milestone #35](https://github.com/IgniteUI/igniteui-angular/blob/master/ROADMAP.md#milestone-35-version-190-released-nov-25th-2024)| ### Components available in [igniteui-angular-charts](https://www.npmjs.com/package/igniteui-angular-charts) diff --git a/ROADMAP.md b/ROADMAP.md index fc6d173cadf..b1175f30047 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -2,17 +2,26 @@ # Current Milestone -## Milestone 35, (Due by Nov, 2024) +## Milestone 35, (Due by Jan, 2025) -1. Angular 19 support +1. Tile Manager - new component [#239](https://github.com/IgniteUI/igniteui-angular/issues/239) 2. Query Builder component update [#14979](https://github.com/IgniteUI/igniteui-angular/issues/14979) +3. IgxBannerComponent - Support collapsed input [#14890](https://github.com/IgniteUI/igniteui-angular/issues/14890) +4. Update of the carousel component [#15025](https://github.com/IgniteUI/igniteui-angular/issues/15025) ## Going down the road -1. Tile Manager - new component [#239](https://github.com/IgniteUI/igniteui-angular/issues/239) +1. Provide an option to modify the default enter edit mode conditions, to e.g. allow edit mode start on a single click [#14658](https://github.com/IgniteUI/igniteui-angular/issues/14658) +2. Provide an excel-like navigation mode for editing where all arrow keys navigate the cell in edit mode. [#14659](https://github.com/IgniteUI/igniteui-angular/issues/14659) +3. Extend the 18.2 editorOptions property to allow modifying numeric editors to not change the value on up/down arrow press [#14660](https://github.com/IgniteUI/igniteui-angular/issues/14660) # Previous Milestone +## Milestone 35, version 19.0 (Released Nov 25th, 2024) + +1. Angular 19 support +2. Simplify hiding Grid summary results through API [#14905](https://github.com/IgniteUI/igniteui-angular/issues/14905) + ## Milestone 34, version 18.2 (Released Oct 25th, 2024) 1. **[DONE]** Indigo Theme Calendar Improvements [#14407](https://github.com/IgniteUI/igniteui-angular/issues/14407) diff --git a/SECURITY.md b/SECURITY.md index a1c952aaf08..5ab0f554a0c 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -4,10 +4,13 @@ | Version | Supported | | -------- | ------------------ | -| 18.1.x | :white_check_mark: | +| 19.0.x | :white_check_mark: | +| 18.2.x | :white_check_mark: | +| 18.1.x | :x: | | 18.0.x | :x: | | 17.2.x | :white_check_mark: | | 17.1.x | :x: | +| 17.0.x | :x: | | 16.1.x | :white_check_mark: | | 16.0.x | :x: | | 15.1.x | :white_check_mark: | diff --git a/projects/igniteui-angular/src/lib/avatar/avatar.component.spec.ts b/projects/igniteui-angular/src/lib/avatar/avatar.component.spec.ts index 71cb975cce3..ca076b9da35 100644 --- a/projects/igniteui-angular/src/lib/avatar/avatar.component.spec.ts +++ b/projects/igniteui-angular/src/lib/avatar/avatar.component.spec.ts @@ -170,6 +170,28 @@ describe('Avatar', () => { expect(instance.src).toEqual("/assets/Test%20-%2017.jpg"); }); + + it('should not throw error if src is null', () => { + const fixture = TestBed.createComponent(InitImageAvatarComponent); + fixture.detectChanges(); + expect(() => { + const instance = fixture.componentInstance.avatar; + instance.src = null; + fixture.detectChanges(); + }).not.toThrow(); + }); + + it('avatar with [src] and fallback [initials] should not throw error if src is null', () => { + const fixture = TestBed.createComponent(AvatarWithAttribsComponent); + fixture.detectChanges(); + const instance = fixture.componentInstance.avatar; + expect(instance.type).toEqual(IgxAvatarType.INITIALS); + expect(instance.initials).toEqual('ZK'); + expect(() => { + instance.src = null; + fixture.detectChanges(); + }).not.toThrow(); + }); }); @Component({ diff --git a/projects/igniteui-angular/src/lib/core/utils.ts b/projects/igniteui-angular/src/lib/core/utils.ts index 509f4a26eda..e0b9c75f8f5 100644 --- a/projects/igniteui-angular/src/lib/core/utils.ts +++ b/projects/igniteui-angular/src/lib/core/utils.ts @@ -656,5 +656,5 @@ export function getComponentCssSizeVar(size: string) { * @returns string encoded using the encodeURI function. */ export function normalizeURI(path: string) { - return path.split('/').map(encodeURI).join('/'); + return path?.split('/').map(encodeURI).join('/'); } diff --git a/src/app/avatar/avatar.sample.html b/src/app/avatar/avatar.sample.html index b2f7286c157..b230295f10a 100644 --- a/src/app/avatar/avatar.sample.html +++ b/src/app/avatar/avatar.sample.html @@ -62,7 +62,7 @@

Square Avatars

- + From 47b2886a06ff07d30e3da8618eae6dbb7eb448b7 Mon Sep 17 00:00:00 2001 From: Dilyana Yarabanova <45598235+didimmova@users.noreply.github.com> Date: Wed, 4 Dec 2024 10:13:51 +0200 Subject: [PATCH 07/15] test(card): create test for icon font-family in card content (#15133) --- .../src/lib/card/card.spec.ts | 21 +++++++++++++++++++ .../styles/components/card/_card-theme.scss | 2 +- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/projects/igniteui-angular/src/lib/card/card.spec.ts b/projects/igniteui-angular/src/lib/card/card.spec.ts index 9d6cd3b5904..35ae76aab43 100644 --- a/projects/igniteui-angular/src/lib/card/card.spec.ts +++ b/projects/igniteui-angular/src/lib/card/card.spec.ts @@ -68,6 +68,7 @@ describe('Card', () => { InitCardComponent, InitOutlinedCardComponent, CardWithHeaderComponent, + CardContentIconComponent, VerticalCardComponent, HorizontalCardComponent ] @@ -182,6 +183,16 @@ describe('Card', () => { expect(actions).not.toHaveClass(classes.actions.vertical); }); + it('Should use Material Icons font-family for igx-icon in card content', () => { + const fixture = TestBed.createComponent(CardContentIconComponent); + fixture.detectChanges(); + + const iconElement = fixture.debugElement.query(By.css('igx-icon')).nativeElement; + const computedStyle = window.getComputedStyle(iconElement); + + expect(computedStyle.fontFamily).toBe('"Material Icons"'); + }); + it('Should automatically align actions vertically when in horizontal layout', () => { const fixture = TestBed.createComponent(HorizontalCardComponent); fixture.detectChanges(); @@ -246,6 +257,16 @@ class InitOutlinedCardComponent { }) class CardWithHeaderComponent { } +@Component({ + template: ` + + face + + `, + imports: [IgxCardComponent, IgxCardContentDirective, IgxIconComponent] +}) +class CardContentIconComponent { } + @Component({ template: ` diff --git a/projects/igniteui-angular/src/lib/core/styles/components/card/_card-theme.scss b/projects/igniteui-angular/src/lib/core/styles/components/card/_card-theme.scss index b75fdbd056e..35828d7dd84 100644 --- a/projects/igniteui-angular/src/lib/core/styles/components/card/_card-theme.scss +++ b/projects/igniteui-angular/src/lib/core/styles/components/card/_card-theme.scss @@ -450,7 +450,7 @@ } } - %igx-card-content > * { + %igx-card-content > *:not(igx-icon) { @include type-style($content) { margin: 0; } From 17ab87ab0921b5bbf744b4980a21662809a12b57 Mon Sep 17 00:00:00 2001 From: lipata Date: Wed, 4 Dec 2024 15:54:06 +0200 Subject: [PATCH 08/15] chore(*): official angular-eslint libs --- package-lock.json | 79 ++++++++++++++++++++++++----------------------- package.json | 10 +++--- 2 files changed, 45 insertions(+), 44 deletions(-) diff --git a/package-lock.json b/package-lock.json index c9e18c1c882..f6239e9cc17 100644 --- a/package-lock.json +++ b/package-lock.json @@ -36,11 +36,11 @@ "devDependencies": { "@angular-devkit/build-angular": "^19.0.0", "@angular-devkit/schematics": "^19.0.0", - "@angular-eslint/builder": "^19.0.0-alpha.1", - "@angular-eslint/eslint-plugin": "^19.0.0-alpha.1", - "@angular-eslint/eslint-plugin-template": "^19.0.0-alpha.1", - "@angular-eslint/schematics": "^19.0.0-alpha.1", - "@angular-eslint/template-parser": "^19.0.0-alpha.1", + "@angular-eslint/builder": "^19.0.0", + "@angular-eslint/eslint-plugin": "^19.0.0", + "@angular-eslint/eslint-plugin-template": "^19.0.0", + "@angular-eslint/schematics": "^19.0.0", + "@angular-eslint/template-parser": "^19.0.0", "@angular/cli": "^19.0.0", "@angular/compiler-cli": "^19.0.0", "@angular/language-service": "^19.0.0", @@ -340,7 +340,6 @@ "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-19.0.1.tgz", "integrity": "sha512-oXIAV3hXqUW3Pmm95pvEmb+24n1cKQG62FzhQSjOIrMeHiCbGLNuc8zHosIi2oMrcCJJxR6KzWjThvbuzDwWlw==", "dev": true, - "peer": true, "dependencies": { "ajv": "8.17.1", "ajv-formats": "3.0.1", @@ -409,29 +408,33 @@ } }, "node_modules/@angular-eslint/builder": { - "version": "19.0.0-alpha.1", - "resolved": "https://registry.npmjs.org/@angular-eslint/builder/-/builder-19.0.0-alpha.1.tgz", - "integrity": "sha512-Qcxk/rU+vIgt9iEizsZcha2DvmnLV/VV7B2Jy6iG3wxWSdNswwlcWCEV1MuENc4Wsj2PNu1iACaocpk10GF5fg==", + "version": "19.0.0", + "resolved": "https://registry.npmjs.org/@angular-eslint/builder/-/builder-19.0.0.tgz", + "integrity": "sha512-vi68ADoEKrg2SB87jwUCaVhOhWPpXyG6X8QJzg8AiYDCQY721x1l6Pdz6WZOPruWALyoIyFGFXqtuysDGqIBhw==", "dev": true, + "dependencies": { + "@angular-devkit/architect": ">= 0.1900.0 < 0.2000.0", + "@angular-devkit/core": ">= 19.0.0 < 20.0.0" + }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": "*" } }, "node_modules/@angular-eslint/bundled-angular-compiler": { - "version": "19.0.0-alpha.1", - "resolved": "https://registry.npmjs.org/@angular-eslint/bundled-angular-compiler/-/bundled-angular-compiler-19.0.0-alpha.1.tgz", - "integrity": "sha512-Nu3rxR8V+ZY4GPMSMlYcoS7EZElCOjtSmCZOSGX+7dL/8s49ABt9nYsMwvxB/ddbJ1/UhS+urCJ6h2e0u1NV7w==", + "version": "19.0.0", + "resolved": "https://registry.npmjs.org/@angular-eslint/bundled-angular-compiler/-/bundled-angular-compiler-19.0.0.tgz", + "integrity": "sha512-q6IaiqKYcmBW/gw55tytDucguo5E48szVCLNLHUFdN98YDDsP+KM3MPWYPyZcXpusmFfIjLdr8d41PlKmyMUpg==", "dev": true }, "node_modules/@angular-eslint/eslint-plugin": { - "version": "19.0.0-alpha.1", - "resolved": "https://registry.npmjs.org/@angular-eslint/eslint-plugin/-/eslint-plugin-19.0.0-alpha.1.tgz", - "integrity": "sha512-DY2u2cLrS/3567L1Gz5JMOQyyAwUE5Wu5KS+kuNd+jTGJWRoScioQkEFzogzd5Y3kpe9F/RekhLwS1sJ/BCMJA==", + "version": "19.0.0", + "resolved": "https://registry.npmjs.org/@angular-eslint/eslint-plugin/-/eslint-plugin-19.0.0.tgz", + "integrity": "sha512-WkUnH5zmvC/cH6f8BGiRK+KebrKdGbQmhtu3IHLEyzG9U4mBiIV8XkSzhdkY3RCN8bKqhmE5C3oNBLNCtvg4QQ==", "dev": true, "dependencies": { - "@angular-eslint/bundled-angular-compiler": "19.0.0-alpha.1", - "@angular-eslint/utils": "19.0.0-alpha.1" + "@angular-eslint/bundled-angular-compiler": "19.0.0", + "@angular-eslint/utils": "19.0.0" }, "peerDependencies": { "@typescript-eslint/utils": "^7.11.0 || ^8.0.0", @@ -440,13 +443,13 @@ } }, "node_modules/@angular-eslint/eslint-plugin-template": { - "version": "19.0.0-alpha.1", - "resolved": "https://registry.npmjs.org/@angular-eslint/eslint-plugin-template/-/eslint-plugin-template-19.0.0-alpha.1.tgz", - "integrity": "sha512-C5KfGpO3++dJl1gZZeMXFMUKu/sXQRHmQvuCU+d2evOV7ut/3wk8C4N13+Y6XpLH2tJJOnJUFWkw30SbylMz7w==", + "version": "19.0.0", + "resolved": "https://registry.npmjs.org/@angular-eslint/eslint-plugin-template/-/eslint-plugin-template-19.0.0.tgz", + "integrity": "sha512-d2NzuAyvFo00QGBv6BLno0KZ3Ptd+UNVHpI9vwU0giaZcjVsdKbcMvMfynkvHAAwVIVw5aSLwabIjnm0rc3x3A==", "dev": true, "dependencies": { - "@angular-eslint/bundled-angular-compiler": "19.0.0-alpha.1", - "@angular-eslint/utils": "19.0.0-alpha.1", + "@angular-eslint/bundled-angular-compiler": "19.0.0", + "@angular-eslint/utils": "19.0.0", "aria-query": "5.3.2", "axobject-query": "4.1.0" }, @@ -458,20 +461,18 @@ } }, "node_modules/@angular-eslint/schematics": { - "version": "19.0.0-alpha.1", - "resolved": "https://registry.npmjs.org/@angular-eslint/schematics/-/schematics-19.0.0-alpha.1.tgz", - "integrity": "sha512-JKPyfBzsYBkYxb498Wedk3kPGAHw6JKI/mK3b9neOf/RqEwloaGHoX23ipRBN/lC0fuYTEKj2hFt+5rQYEW6NQ==", + "version": "19.0.0", + "resolved": "https://registry.npmjs.org/@angular-eslint/schematics/-/schematics-19.0.0.tgz", + "integrity": "sha512-fle4SMxjI+91y5eR6hVG7yhzJHAw87LudHw918hGUVn2INIAW1TTuuQNoah8kNg9I6ICIDat26IenD4nOau6Gg==", "dev": true, "dependencies": { - "@angular-eslint/eslint-plugin": "19.0.0-alpha.1", - "@angular-eslint/eslint-plugin-template": "19.0.0-alpha.1", + "@angular-devkit/core": ">= 19.0.0 < 20.0.0", + "@angular-devkit/schematics": ">= 19.0.0 < 20.0.0", + "@angular-eslint/eslint-plugin": "19.0.0", + "@angular-eslint/eslint-plugin-template": "19.0.0", "ignore": "6.0.2", "semver": "7.6.3", "strip-json-comments": "3.1.1" - }, - "peerDependencies": { - "@angular-devkit/core": ">= 19.0.0 < 20.0.0", - "@angular-devkit/schematics": ">= 19.0.0 < 20.0.0" } }, "node_modules/@angular-eslint/schematics/node_modules/ignore": { @@ -484,12 +485,12 @@ } }, "node_modules/@angular-eslint/template-parser": { - "version": "19.0.0-alpha.1", - "resolved": "https://registry.npmjs.org/@angular-eslint/template-parser/-/template-parser-19.0.0-alpha.1.tgz", - "integrity": "sha512-w+MK9B4Jk7sZpC+wI7oaNlDvHSEONrIYIz7zKyJZwhCTfv4TAOBD1xI+MbIb4wYt7e2uVwrx/jtgr/XaFUT9Rw==", + "version": "19.0.0", + "resolved": "https://registry.npmjs.org/@angular-eslint/template-parser/-/template-parser-19.0.0.tgz", + "integrity": "sha512-bOLMNBQbrLMujGWSda0SF8ka7snQ9Uzxie1dr5LquI104p2J4Wt90DOoaWzhNaBBwedt3WXmhSHmvvR9720kHA==", "dev": true, "dependencies": { - "@angular-eslint/bundled-angular-compiler": "19.0.0-alpha.1", + "@angular-eslint/bundled-angular-compiler": "19.0.0", "eslint-scope": "^8.0.2" }, "peerDependencies": { @@ -498,12 +499,12 @@ } }, "node_modules/@angular-eslint/utils": { - "version": "19.0.0-alpha.1", - "resolved": "https://registry.npmjs.org/@angular-eslint/utils/-/utils-19.0.0-alpha.1.tgz", - "integrity": "sha512-PSnBy1wHUbOCDufl+5quyeynp5PS2yw8nhl9aphLfFKlLTQgLTVAiwxBI5ySeaw0IxW5LuNQm0LFudKXJw60xg==", + "version": "19.0.0", + "resolved": "https://registry.npmjs.org/@angular-eslint/utils/-/utils-19.0.0.tgz", + "integrity": "sha512-PH40BmIcIr5ldr08XYnqJ8cTzJfScJjBym4SECsilBnz5fhCdTD7UEQiW4d0P78Ie8H5PxvOJx9ZE+L4WBNrTA==", "dev": true, "dependencies": { - "@angular-eslint/bundled-angular-compiler": "19.0.0-alpha.1" + "@angular-eslint/bundled-angular-compiler": "19.0.0" }, "peerDependencies": { "@typescript-eslint/utils": "^7.11.0 || ^8.0.0", diff --git a/package.json b/package.json index 33a948eada0..7788d576653 100644 --- a/package.json +++ b/package.json @@ -85,11 +85,11 @@ "devDependencies": { "@angular-devkit/build-angular": "^19.0.0", "@angular-devkit/schematics": "^19.0.0", - "@angular-eslint/builder": "^19.0.0-alpha.1", - "@angular-eslint/eslint-plugin": "^19.0.0-alpha.1", - "@angular-eslint/eslint-plugin-template": "^19.0.0-alpha.1", - "@angular-eslint/schematics": "^19.0.0-alpha.1", - "@angular-eslint/template-parser": "^19.0.0-alpha.1", + "@angular-eslint/builder": "^19.0.0", + "@angular-eslint/eslint-plugin": "^19.0.0", + "@angular-eslint/eslint-plugin-template": "^19.0.0", + "@angular-eslint/schematics": "^19.0.0", + "@angular-eslint/template-parser": "^19.0.0", "@angular/cli": "^19.0.0", "@angular/compiler-cli": "^19.0.0", "@angular/language-service": "^19.0.0", From 13eb7ca4658c2a7ce21aa57acb2bfcb95f25b3d3 Mon Sep 17 00:00:00 2001 From: Silvia Ivanova <59446295+SisIvanova@users.noreply.github.com> Date: Mon, 9 Dec 2024 12:37:20 +0200 Subject: [PATCH 09/15] fix(combo): search input paddings (#15151) --- .../styles/components/combo/_combo-theme.scss | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/projects/igniteui-angular/src/lib/core/styles/components/combo/_combo-theme.scss b/projects/igniteui-angular/src/lib/core/styles/components/combo/_combo-theme.scss index b6686ccc65b..9f6783f9870 100644 --- a/projects/igniteui-angular/src/lib/core/styles/components/combo/_combo-theme.scss +++ b/projects/igniteui-angular/src/lib/core/styles/components/combo/_combo-theme.scss @@ -95,6 +95,20 @@ $variant: map.get($theme, '_meta', 'variant'); + $search-input-inline-padding: map.get(( + 'material': pad-inline(rem(4px), rem(8px), rem(16px)), + 'fluent': pad-inline(rem(2px), rem(4px), rem(8px)), + 'bootstrap': pad-inline(rem(4px), rem(8px), rem(16px)), + 'indigo': pad-inline(rem(12px)) + ), $variant); + + $search-input-block-padding: map.get(( + 'material': pad-inline(rem(8px)), + 'fluent': pad-inline(rem(2px), rem(4px), rem(8px)), + 'bootstrap': pad-inline(rem(8px)), + 'indigo': pad-inline(rem(12px)) + ), $variant); + %igx-combo { position: relative; display: block; @@ -122,8 +136,8 @@ } %igx-combo__search { - padding-inline: if($variant == 'indigo', pad-inline(rem(12px)), pad-inline(rem(16px))); - padding-block: if($variant == 'indigo', pad-block(rem(12px)), pad-block(rem(8px))); + padding-inline: $search-input-inline-padding; + padding-block: $search-input-block-padding; margin: 0 !important; z-index: 26; border-bottom: rem(1px) dashed var-get($theme, 'search-separator-border-color'); From 59a006fd60c7ddeddc889cb5cd75dbc66a9b2493 Mon Sep 17 00:00:00 2001 From: Silvia Ivanova <59446295+SisIvanova@users.noreply.github.com> Date: Mon, 9 Dec 2024 13:23:02 +0200 Subject: [PATCH 10/15] refactor(avatar): remove unused code (#15149) --- .../components/avatar/_avatar-theme.scss | 32 +------------------ 1 file changed, 1 insertion(+), 31 deletions(-) diff --git a/projects/igniteui-angular/src/lib/core/styles/components/avatar/_avatar-theme.scss b/projects/igniteui-angular/src/lib/core/styles/components/avatar/_avatar-theme.scss index 4fa80652ad6..6c462663f50 100644 --- a/projects/igniteui-angular/src/lib/core/styles/components/avatar/_avatar-theme.scss +++ b/projects/igniteui-angular/src/lib/core/styles/components/avatar/_avatar-theme.scss @@ -68,23 +68,7 @@ @include css-vars($theme); $variant: map.get($theme, '_meta', 'variant'); - $bootstrap-theme: $variant == 'bootstrap'; - $box-shadow: map.get(( - 'material': null, - 'fluent': null, - 'bootstrap': 0 0 0 rem(3px) var-get($theme, 'background'), - 'indigo': null, - ), $variant); - - %igx-avatar-outline { - position: absolute; - content: ''; - width: 100%; - height: 100%; - border-radius: inherit; - opacity: .5; - } - + %igx-avatar-display { @include sizable(); @@ -114,20 +98,6 @@ --component-size: 1; } } - - &::after { - box-shadow: none; - - @if $bootstrap-theme { - @extend %igx-avatar-outline; - } - - transition: box-shadow .15s $in-out-quad; - } - - &:focus::after { - box-shadow: $box-shadow; - } } %igx-avatar-image { From ac586e092b5e7afcac0e28b326783de503dd3e2b Mon Sep 17 00:00:00 2001 From: Dilyana Yarabanova <45598235+didimmova@users.noreply.github.com> Date: Mon, 9 Dec 2024 16:36:48 +0200 Subject: [PATCH 11/15] fix(combo): use min-width for search input start and end/update icon size (#15108) --- .../core/styles/components/combo/_combo-theme.scss | 12 ++++++------ .../styles/components/input/_input-group-theme.scss | 7 +++++-- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/projects/igniteui-angular/src/lib/core/styles/components/combo/_combo-theme.scss b/projects/igniteui-angular/src/lib/core/styles/components/combo/_combo-theme.scss index 9f6783f9870..8bfc4c2fab8 100644 --- a/projects/igniteui-angular/src/lib/core/styles/components/combo/_combo-theme.scss +++ b/projects/igniteui-angular/src/lib/core/styles/components/combo/_combo-theme.scss @@ -148,12 +148,12 @@ } .igx-input-group__bundle-main { - padding-inline-start: 0; + padding-inline: 0; } .igx-input-group__bundle-start, .igx-input-group__bundle-end { - display: none; + min-width: 0; } igx-input-group { @@ -164,10 +164,10 @@ %igx-combo__case-icon, %igx-combo__case-icon--active { - .igx-icon { - width: rem(16px); - height: rem(16px); - font-size: rem(16px); + line-height: 0; + + igx-icon { + --size: rem(18px); } } diff --git a/projects/igniteui-angular/src/lib/core/styles/components/input/_input-group-theme.scss b/projects/igniteui-angular/src/lib/core/styles/components/input/_input-group-theme.scss index c1eafa6a211..134807d6e4b 100644 --- a/projects/igniteui-angular/src/lib/core/styles/components/input/_input-group-theme.scss +++ b/projects/igniteui-angular/src/lib/core/styles/components/input/_input-group-theme.scss @@ -422,12 +422,15 @@ } input, - textarea, - span { + textarea { font: inherit; margin: 0; } + span { + font-family: inherit; + } + input[type='file'] { @include hide-default(); } From c8e2490cd3023c5e8af8802416dbb7c857c97bc9 Mon Sep 17 00:00:00 2001 From: Simeon Simeonoff Date: Tue, 10 Dec 2024 13:33:16 +0200 Subject: [PATCH 12/15] fix(*): icon service doesn't work with scoped themes (#15140) --- .../src/lib/checkbox/checkbox.component.ts | 44 +++++---- .../src/lib/combo/combo.component.spec.ts | 93 +++++-------------- .../igniteui-angular/src/lib/core/utils.ts | 8 ++ .../src/lib/icon/icon.component.ts | 3 - .../src/lib/icon/icon.service.spec.ts | 59 +++++++++++- .../src/lib/icon/icon.service.ts | 46 +++++++-- .../igniteui-angular/src/lib/icon/types.ts | 4 +- .../lib/input-group/input-group.component.ts | 49 +++++----- .../src/lib/services/public_api.ts | 1 + .../src/lib/services/theme/theme.service.ts | 57 ------------ .../src/lib/services/theme/theme.token.ts | 52 +++++++++++ .../simple-combo.component.spec.ts | 17 +--- .../themed-icon.component.html | 17 ++++ .../ThemedComponent/themed-icon.component.ts | 35 +++++++ src/app/icon/icon.sample.html | 9 +- src/app/icon/icon.sample.ts | 6 +- 16 files changed, 298 insertions(+), 202 deletions(-) delete mode 100644 projects/igniteui-angular/src/lib/services/theme/theme.service.ts create mode 100644 projects/igniteui-angular/src/lib/services/theme/theme.token.ts create mode 100644 src/app/icon/ThemedComponent/themed-icon.component.html create mode 100644 src/app/icon/ThemedComponent/themed-icon.component.ts diff --git a/projects/igniteui-angular/src/lib/checkbox/checkbox.component.ts b/projects/igniteui-angular/src/lib/checkbox/checkbox.component.ts index f53bb062cbd..9556f9b18ec 100644 --- a/projects/igniteui-angular/src/lib/checkbox/checkbox.component.ts +++ b/projects/igniteui-angular/src/lib/checkbox/checkbox.component.ts @@ -14,15 +14,16 @@ import { Self, booleanAttribute, inject, - DestroyRef + DestroyRef, + Inject } from '@angular/core'; import { ControlValueAccessor, NgControl, Validators } from '@angular/forms'; import { IgxRippleDirective } from '../directives/ripple/ripple.directive'; -import { IBaseEventArgs, mkenum } from '../core/utils'; +import { IBaseEventArgs, getComponentTheme, mkenum } from '../core/utils'; import { EditorProvider, EDITOR_PROVIDER } from '../core/edit-provider'; -import { noop, Subject, Subscription } from 'rxjs'; +import { noop, Subject } from 'rxjs'; import { takeUntil } from 'rxjs/operators'; -import { IgxTheme, ThemeService } from '../services/theme/theme.service'; +import { IgxTheme, THEME_TOKEN, ThemeToken } from '../services/theme/theme.token'; export const LabelPosition = /*@__PURE__*/mkenum({ BEFORE: 'before', @@ -492,28 +493,40 @@ export class IgxCheckboxComponent implements EditorProvider, AfterViewInit, Cont */ private _required = false; private elRef = inject(ElementRef); - private _theme$ = new Subject(); - private _subscription: Subscription; private destroyRef = inject(DestroyRef); constructor( protected cdr: ChangeDetectorRef, protected renderer: Renderer2, - protected themeService: ThemeService, + @Inject(THEME_TOKEN) + protected themeToken: ThemeToken, @Optional() @Self() public ngControl: NgControl, ) { if (this.ngControl !== null) { this.ngControl.valueAccessor = this; } - this.theme = this.themeService.globalTheme; + this.theme = this.themeToken.theme; - this._subscription = this._theme$.asObservable().subscribe(value => { - this.theme = value as IgxTheme; - this.cdr.detectChanges(); + const { unsubscribe } = this.themeToken.onChange((theme) => { + if (this.theme !== theme) { + this.theme = theme; + this.cdr.detectChanges(); + } }); - this.destroyRef.onDestroy(() => this._subscription.unsubscribe()); + this.destroyRef.onDestroy(() => unsubscribe); + } + + private setComponentTheme() { + if(!this.themeToken.preferToken) { + const theme = getComponentTheme(this.elRef.nativeElement); + + if (theme && theme !== this.theme) { + this.theme = theme; + this.cdr.markForCheck(); + } + } } /** @@ -530,12 +543,7 @@ export class IgxCheckboxComponent implements EditorProvider, AfterViewInit, Cont } } - const theme = this.themeService.getComponentTheme(this.elRef); - - if (theme) { - this._theme$.next(theme); - this.cdr.markForCheck(); - } + this.setComponentTheme(); } /** @hidden @internal */ diff --git a/projects/igniteui-angular/src/lib/combo/combo.component.spec.ts b/projects/igniteui-angular/src/lib/combo/combo.component.spec.ts index f8e3ed163df..a19fff8f529 100644 --- a/projects/igniteui-angular/src/lib/combo/combo.component.spec.ts +++ b/projects/igniteui-angular/src/lib/combo/combo.component.spec.ts @@ -13,7 +13,6 @@ import { IBaseCancelableBrowserEventArgs } from '../core/utils'; import { SortingDirection } from '../data-operations/sorting-strategy'; import { IForOfState } from '../directives/for-of/for_of.directive'; import { IgxInputState } from '../directives/input/input.directive'; -import { IgxIconService } from '../icon/public_api'; import { IgxLabelDirective } from '../input-group/public_api'; import { AbsoluteScrollStrategy, ConnectedPositioningStrategy } from '../services/public_api'; import { configureTestSuite } from '../test-utils/configure-suite'; @@ -87,7 +86,6 @@ describe('igxCombo', () => { get: mockNgControl }); mockSelection.get.and.returnValue(new Set([])); - const mockIconService = new IgxIconService(null, null, null, null); const mockDocument = jasmine.createSpyObj('DOCUMENT', [], { 'defaultView': { getComputedStyle: () => null }}); it('should correctly implement interface methods - ControlValueAccessor ', () => { @@ -98,11 +96,9 @@ describe('igxCombo', () => { mockComboService, mockDocument, null, - mockInjector, - mockIconService + mockInjector ); - spyOn(mockIconService, 'addSvgIconFromText').and.returnValue(null); combo.ngOnInit(); expect(mockInjector.get).toHaveBeenCalledWith(NgControl, null); combo.registerOnChange(mockNgControl.registerOnChangeCb); @@ -146,12 +142,10 @@ describe('igxCombo', () => { mockComboService, mockDocument, null, - mockInjector, - mockIconService + mockInjector ); const dropdown = jasmine.createSpyObj('IgxComboDropDownComponent', ['open', 'close', 'toggle']); - spyOn(mockIconService, 'addSvgIconFromText').and.returnValue(null); combo.ngOnInit(); combo.dropdown = dropdown; dropdown.collapsed = true; @@ -179,12 +173,10 @@ describe('igxCombo', () => { mockComboService, mockDocument, null, - mockInjector, - mockIconService + mockInjector ); const dropdownContainer = { nativeElement: { focus: () => { } } }; combo['dropdownContainer'] = dropdownContainer; - spyOn(mockIconService, 'addSvgIconFromText').and.returnValue(null); spyOn(combo, 'focusSearchInput'); combo.autoFocusSearch = false; @@ -207,11 +199,9 @@ describe('igxCombo', () => { mockComboService, mockDocument, null, - mockInjector, - mockIconService + mockInjector ); const dropdown = jasmine.createSpyObj('IgxComboDropDownComponent', ['toggle']); - spyOn(mockIconService, 'addSvgIconFromText').and.returnValue(null); combo.ngOnInit(); combo.dropdown = dropdown; const defaultSettings = (combo as any)._overlaySettings; @@ -234,10 +224,8 @@ describe('igxCombo', () => { mockComboService, mockDocument, null, - mockInjector, - mockIconService + mockInjector ); - spyOn(mockIconService, 'addSvgIconFromText').and.returnValue(null); combo.ngOnInit(); combo.valueKey = 'field'; expect(combo.displayKey).toEqual(combo.valueKey); @@ -253,10 +241,8 @@ describe('igxCombo', () => { mockComboService, mockDocument, null, - mockInjector, - mockIconService + mockInjector ); - spyOn(mockIconService, 'addSvgIconFromText').and.returnValue(null); combo.ngOnInit(); combo.data = data; mockSelection.select_items.calls.reset(); @@ -281,11 +267,9 @@ describe('igxCombo', () => { mockComboService, mockDocument, null, - mockInjector, - mockIconService + mockInjector ); const dropdown = jasmine.createSpyObj('IgxComboDropDownComponent', ['selectItem']); - spyOn(mockIconService, 'addSvgIconFromText').and.returnValue(null); combo.ngOnInit(); combo.data = complexData; combo.valueKey = 'country'; @@ -336,11 +320,9 @@ describe('igxCombo', () => { mockComboService, mockDocument, null, - mockInjector, - mockIconService + mockInjector ); const dropdown = jasmine.createSpyObj('IgxComboDropDownComponent', ['selectItem']); - spyOn(mockIconService, 'addSvgIconFromText').and.returnValue(null); combo.ngOnInit(); combo.data = data; combo.dropdown = dropdown; @@ -375,10 +357,8 @@ describe('igxCombo', () => { mockComboService, mockDocument, null, - mockInjector, - mockIconService + mockInjector ); - spyOn(mockIconService, 'addSvgIconFromText').and.returnValue(null); combo.ngOnInit(); spyOn(combo.opening, 'emit').and.callThrough(); spyOn(combo.closing, 'emit').and.callThrough(); @@ -423,11 +403,9 @@ describe('igxCombo', () => { mockComboService, mockDocument, null, - mockInjector, - mockIconService + mockInjector ); const dropdown = jasmine.createSpyObj('IgxComboDropDownComponent', ['selectItem']); - spyOn(mockIconService, 'addSvgIconFromText').and.returnValue(null); combo.ngOnInit(); combo.data = data; combo.dropdown = dropdown; @@ -524,11 +502,9 @@ describe('igxCombo', () => { mockComboService, mockDocument, null, - mockInjector, - mockIconService + mockInjector ); const dropdown = jasmine.createSpyObj('IgxComboDropDownComponent', ['selectItem']); - spyOn(mockIconService, 'addSvgIconFromText').and.returnValue(null); combo.ngOnInit(); combo.data = complexData; combo.valueKey = 'country'; @@ -570,11 +546,9 @@ describe('igxCombo', () => { mockComboService, mockDocument, null, - mockInjector, - mockIconService + mockInjector ); const dropdown = jasmine.createSpyObj('IgxComboDropDownComponent', ['selectItem']); - spyOn(mockIconService, 'addSvgIconFromText').and.returnValue(null); combo.ngOnInit(); combo.data = complexData; combo.valueKey = 'country'; @@ -637,11 +611,9 @@ describe('igxCombo', () => { mockComboService, mockDocument, null, - mockInjector, - mockIconService + mockInjector ); const dropdown = jasmine.createSpyObj('IgxComboDropDownComponent', ['selectItem']); - spyOn(mockIconService, 'addSvgIconFromText').and.returnValue(null); combo.ngOnInit(); combo.data = data; combo.dropdown = dropdown; @@ -666,11 +638,9 @@ describe('igxCombo', () => { mockComboService, mockDocument, null, - mockInjector, - mockIconService + mockInjector ); const dropdown = jasmine.createSpyObj('IgxComboDropDownComponent', ['selectItem']); - spyOn(mockIconService, 'addSvgIconFromText').and.returnValue(null); combo.ngOnInit(); combo.data = data; combo.dropdown = dropdown; @@ -720,11 +690,9 @@ describe('igxCombo', () => { mockComboService, mockDocument, null, - mockInjector, - mockIconService + mockInjector ); const dropdown = jasmine.createSpyObj('IgxComboDropDownComponent', ['selectItem']); - spyOn(mockIconService, 'addSvgIconFromText').and.returnValue(null); combo.ngOnInit(); combo.data = data; combo.dropdown = dropdown; @@ -745,10 +713,8 @@ describe('igxCombo', () => { mockComboService, mockDocument, null, - mockInjector, - mockIconService + mockInjector ); - spyOn(mockIconService, 'addSvgIconFromText').and.returnValue(null); combo.ngOnInit(); let errorMessage = ''; try { @@ -769,10 +735,8 @@ describe('igxCombo', () => { mockComboService, mockDocument, null, - mockInjector, - mockIconService + mockInjector ); - spyOn(mockIconService, 'addSvgIconFromText').and.returnValue(null); combo.ngOnInit(); let errorMessage = ''; try { @@ -793,11 +757,9 @@ describe('igxCombo', () => { mockComboService, mockDocument, null, - mockInjector, - mockIconService + mockInjector ); const dropdown = jasmine.createSpyObj('IgxComboDropDownComponent', ['selectItem']); - spyOn(mockIconService, 'addSvgIconFromText').and.returnValue(null); combo.ngOnInit(); combo.data = data; combo.dropdown = dropdown; @@ -841,10 +803,8 @@ describe('igxCombo', () => { mockComboService, mockDocument, null, - mockInjector, - mockIconService + mockInjector ); - spyOn(mockIconService, 'addSvgIconFromText').and.returnValue(null); combo.ngOnInit(); combo.data = data; combo.disableFiltering = false; @@ -866,12 +826,10 @@ describe('igxCombo', () => { mockComboService, mockDocument, null, - mockInjector, - mockIconService + mockInjector ); const dropdown = jasmine.createSpyObj('IgxComboDropDownComponent', ['open', 'close', 'toggle']); const spyObj = jasmine.createSpyObj('event', ['stopPropagation', 'preventDefault']); - spyOn(mockIconService, 'addSvgIconFromText').and.returnValue(null); combo.ngOnInit(); combo.dropdown = dropdown; dropdown.collapsed = true; @@ -889,12 +847,10 @@ describe('igxCombo', () => { mockComboService, mockDocument, null, - mockInjector, - mockIconService + mockInjector ); const dropdown = jasmine.createSpyObj('IgxComboDropDownComponent', ['selectItem']); const spyObj = jasmine.createSpyObj('event', ['stopPropagation']); - spyOn(mockIconService, 'addSvgIconFromText').and.returnValue(null); combo.ngOnInit(); combo.data = data; combo.dropdown = dropdown; @@ -916,8 +872,7 @@ describe('igxCombo', () => { mockComboService, mockDocument, null, - mockInjector, - mockIconService + mockInjector ); const dropdown = jasmine.createSpyObj('IgxComboDropDownComponent', ['selectItem']); const mockVirtDir = jasmine.createSpyObj('virtDir', ['scrollTo']); @@ -925,7 +880,6 @@ describe('igxCombo', () => { nativeElement: jasmine.createSpyObj('mockElement', ['focus']) }); spyOn(combo.addition, 'emit').and.callThrough(); - spyOn(mockIconService, 'addSvgIconFromText').and.returnValue(null); const subParams: { cancel: boolean; newValue: string; modify: boolean } = { cancel: false, modify: false, @@ -1023,8 +977,7 @@ describe('igxCombo', () => { mockComboService, mockDocument, null, - mockInjector, - mockIconService + mockInjector ); combo.ngOnDestroy(); expect(mockComboService.clear).toHaveBeenCalled(); diff --git a/projects/igniteui-angular/src/lib/core/utils.ts b/projects/igniteui-angular/src/lib/core/utils.ts index e0b9c75f8f5..487dce78d8d 100644 --- a/projects/igniteui-angular/src/lib/core/utils.ts +++ b/projects/igniteui-angular/src/lib/core/utils.ts @@ -4,6 +4,7 @@ import { mergeWith } from 'lodash-es'; import { NEVER, Observable } from 'rxjs'; import { setImmediate } from './setImmediate'; import { isDevMode } from '@angular/core'; +import { IgxTheme } from '../services/theme/theme.token'; /** @hidden @internal */ export const ELEMENTS_TOKEN = /*@__PURE__*/new InjectionToken('elements environment'); @@ -658,3 +659,10 @@ export function getComponentCssSizeVar(size: string) { export function normalizeURI(path: string) { return path?.split('/').map(encodeURI).join('/'); } + +export function getComponentTheme(el: Element) { + return globalThis.window + ?.getComputedStyle(el) + .getPropertyValue('--theme') + .trim() as IgxTheme; +} diff --git a/projects/igniteui-angular/src/lib/icon/icon.component.ts b/projects/igniteui-angular/src/lib/icon/icon.component.ts index 8c3e2c94714..7f62dfd82e5 100644 --- a/projects/igniteui-angular/src/lib/icon/icon.component.ts +++ b/projects/igniteui-angular/src/lib/icon/icon.component.ts @@ -15,7 +15,6 @@ import { filter, takeUntil } from "rxjs/operators"; import { Subject } from "rxjs"; import { SafeHtml } from "@angular/platform-browser"; import { NgIf, NgTemplateOutlet } from "@angular/common"; -import { ThemeService } from "../services/theme/theme.service"; /** * Icon provides a way to include material icons to markup @@ -135,11 +134,9 @@ export class IgxIconComponent implements OnInit, OnChanges, OnDestroy { constructor( public el: ElementRef, private iconService: IgxIconService, - private themeService: ThemeService, private ref: ChangeDetectorRef, ) { this.family = this.iconService.defaultFamily.name; - this.iconService.setRefsByTheme(this.themeService.globalTheme); this.iconService.iconLoaded .pipe( diff --git a/projects/igniteui-angular/src/lib/icon/icon.service.spec.ts b/projects/igniteui-angular/src/lib/icon/icon.service.spec.ts index 501c71e217b..2474450df60 100644 --- a/projects/igniteui-angular/src/lib/icon/icon.service.spec.ts +++ b/projects/igniteui-angular/src/lib/icon/icon.service.spec.ts @@ -5,9 +5,10 @@ import { IgxIconService } from './icon.service'; import { configureTestSuite } from '../test-utils/configure-suite'; import { first } from 'rxjs/operators'; import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'; -import { Component } from "@angular/core"; +import { Component, inject } from "@angular/core"; import { IgxIconComponent } from "./icon.component"; import { By } from "@angular/platform-browser"; +import { IgxTheme, THEME_TOKEN, ThemeToken } from "igniteui-angular"; describe("Icon Service", () => { configureTestSuite(); @@ -215,6 +216,36 @@ describe("Icon Service", () => { iconService.addSvgIcon(iconName, "test.svg", familyName); }); + + it('should change icon references dynamically when the value of THEME_TOKEN changes', () => { + const fixture = TestBed.createComponent(IconWithThemeTokenComponent); + fixture.detectChanges(); + + let arrow_prev = fixture.debugElement.query(By.css("igx-icon[name='arrow_prev']")); + let expand_more = fixture.debugElement.query(By.css("igx-icon[name='expand_more']")); + + expect(fixture.componentInstance.themeToken.theme).toBe('material'); + expect(arrow_prev).toBeTruthy(); + expect(arrow_prev.classes['material-icons']).toBeTrue(); + expect(expand_more).toBeTruthy(); + expect(expand_more.classes['material-icons']).toBeTrue(); + + fixture.componentInstance.setTheme('indigo'); + fixture.detectChanges(); + + arrow_prev = fixture.debugElement.query(By.css("igx-icon[name='arrow_prev']")); + expand_more = fixture.debugElement.query(By.css("igx-icon[name='expand_more']")); + + expect(fixture.componentInstance.themeToken.theme).toBe('indigo'); + + // The class change should be reflected as the family changes + expect(arrow_prev).toBeTruthy(); + expect(arrow_prev.classes['internal_indigo']).toBeTrue(); + + // The expand_more shouldn't change as its reference is set explicitly + expect(expand_more).toBeTruthy(); + expect(expand_more.classes['material-icons']).toBeTrue(); + }); }); @Component({ @@ -231,3 +262,29 @@ class IconTestComponent { } imports: [IgxIconComponent] }) class IconRefComponent { } + +@Component({ + template: ` + + + `, + providers: [ + { + provide: THEME_TOKEN, + useFactory: () => new ThemeToken() + }, + IgxIconService + ], + imports: [IgxIconComponent] +}) +class IconWithThemeTokenComponent { + public themeToken = inject(THEME_TOKEN); + + constructor(public iconService: IgxIconService) { + this.iconService.setIconRef('expand_more', 'default', { family: 'material', name: 'home' }); + } + + public setTheme(theme: IgxTheme) { + this.themeToken.set(theme); + } +} diff --git a/projects/igniteui-angular/src/lib/icon/icon.service.ts b/projects/igniteui-angular/src/lib/icon/icon.service.ts index 4a5ba94f0bc..4bc94e2e006 100644 --- a/projects/igniteui-angular/src/lib/icon/icon.service.ts +++ b/projects/igniteui-angular/src/lib/icon/icon.service.ts @@ -1,4 +1,4 @@ -import { Injectable, SecurityContext, Inject, Optional } from "@angular/core"; +import { DestroyRef, Inject, Injectable, Optional, SecurityContext } from "@angular/core"; import { DomSanitizer, SafeHtml } from "@angular/platform-browser"; import { DOCUMENT } from "@angular/common"; import { HttpClient } from "@angular/common/http"; @@ -7,7 +7,7 @@ import { PlatformUtil } from "../core/utils"; import { iconReferences } from './icon.references' import { IconFamily, IconMeta, FamilyMeta } from "./types"; import type { IconType, IconReference } from './types'; -import { IgxTheme } from "../services/theme/theme.service"; +import { IgxTheme, THEME_TOKEN, ThemeToken } from "../services/theme/theme.token"; import { IndigoIcons } from "./icons.indigo"; /** @@ -59,17 +59,25 @@ export class IgxIconService { private _cachedIcons = new Map>(); private _iconLoaded = new Subject(); private _domParser: DOMParser; - private theme!: IgxTheme; constructor( @Optional() private _sanitizer: DomSanitizer, @Optional() private _httpClient: HttpClient, @Optional() private _platformUtil: PlatformUtil, + @Optional() @Inject(THEME_TOKEN) private _themeToken: ThemeToken, + @Optional() @Inject(DestroyRef) private _destroyRef: DestroyRef, @Optional() @Inject(DOCUMENT) protected document: Document, ) { + this.iconLoaded = this._iconLoaded.asObservable(); this.setFamily(this._defaultFamily.name, this._defaultFamily.meta); + const { unsubscribe } = this._themeToken?.onChange((theme) => { + this.setRefsByTheme(theme); + }); + + this._destroyRef.onDestroy(() => unsubscribe); + if (this._platformUtil?.isBrowser) { this._domParser = new DOMParser(); @@ -133,13 +141,21 @@ export class IgxIconService { /** @hidden @internal */ public setRefsByTheme(theme: IgxTheme) { - if (this.theme !== theme) { - this.theme = theme; + for (const { alias, target } of iconReferences) { + const external = this._iconRefs.get(alias.family)?.get(alias.name)?.external; - for (const { alias, target } of iconReferences) { - const icon = target.get(theme) ?? target.get('default')!; - this.addIconRef(alias.name, alias.family, icon); - } + const _ref = this._iconRefs.get('default')?.get(alias.name) ?? {}; + const _target = target.get(theme) ?? target.get('default')!; + + const icon = target.get(theme) ?? target.get('default')!; + const overwrite = !external && !(JSON.stringify(_ref) === JSON.stringify(_target)); + + this._setIconRef( + alias.name, + alias.family, + icon, + overwrite + ); } } @@ -169,6 +185,15 @@ export class IgxIconService { } } + private _setIconRef(name: string, family: string, icon: IconMeta, overwrite = false) { + if (overwrite) { + this.setIconRef(name, family, { + ...icon, + external: false + }); + } + } + /** * Similar to addIconRef, but always sets the icon reference meta for an icon in a meta family. * ```typescript @@ -183,8 +208,9 @@ export class IgxIconService { this._iconRefs.set(family, familyRef); } + const external = icon.external ?? true; const familyType = this.familyType(icon?.family); - familyRef.set(name, { ...icon, type: icon.type ?? familyType }); + familyRef.set(name, { ...icon, type: icon.type ?? familyType, external }); this._iconLoaded.next({ name, family }); } diff --git a/projects/igniteui-angular/src/lib/icon/types.ts b/projects/igniteui-angular/src/lib/icon/types.ts index 75154ba3e44..1a22ff1d421 100644 --- a/projects/igniteui-angular/src/lib/icon/types.ts +++ b/projects/igniteui-angular/src/lib/icon/types.ts @@ -1,5 +1,5 @@ /* eslint-disable @typescript-eslint/consistent-type-definitions */ -import { IgxTheme } from "../services/theme/theme.service"; +import { IgxTheme } from "../services/theme/theme.token"; // Exported internal types export type IconThemeKey = IgxTheme | 'default'; @@ -23,6 +23,8 @@ export interface IconMeta { name: string; family: string; type?: IconType; + /** @hidden @internal */ + external?: boolean; } export interface FamilyMeta { diff --git a/projects/igniteui-angular/src/lib/input-group/input-group.component.ts b/projects/igniteui-angular/src/lib/input-group/input-group.component.ts index a9104a90c77..c706f98f0df 100644 --- a/projects/igniteui-angular/src/lib/input-group/input-group.component.ts +++ b/projects/igniteui-angular/src/lib/input-group/input-group.component.ts @@ -1,18 +1,19 @@ import { DOCUMENT, NgIf, NgTemplateOutlet, NgClass, NgSwitch, NgSwitchCase, NgSwitchDefault } from '@angular/common'; import { - AfterViewChecked, + AfterViewInit, ChangeDetectorRef, Component, ContentChild, ContentChildren, + DestroyRef, ElementRef, HostBinding, HostListener, Inject, Input, - OnDestroy, - Optional, QueryList, booleanAttribute + Optional, QueryList, booleanAttribute, + inject } from '@angular/core'; import { IInputResourceStrings, InputResourceStringsEN } from '../core/i18n/input-resources'; -import { PlatformUtil } from '../core/utils'; +import { PlatformUtil, getComponentTheme } from '../core/utils'; import { IgxButtonDirective } from '../directives/button/button.directive'; import { IgxHintDirective } from '../directives/hint/hint.directive'; import { @@ -26,8 +27,7 @@ import { IgxInputGroupBase } from './input-group.common'; import { IgxInputGroupType, IGX_INPUT_GROUP_TYPE } from './inputGroupType'; import { IgxIconComponent } from '../icon/icon.component'; import { getCurrentResourceStrings } from '../core/i18n/resources'; -import { IgxTheme, ThemeService } from '../services/theme/theme.service'; -import { Subject, Subscription } from 'rxjs'; +import { IgxTheme, THEME_TOKEN, ThemeToken } from '../services/theme/theme.token'; @Component({ selector: 'igx-input-group', @@ -35,7 +35,7 @@ import { Subject, Subscription } from 'rxjs'; providers: [{ provide: IgxInputGroupBase, useExisting: IgxInputGroupComponent }], imports: [NgIf, NgTemplateOutlet, IgxPrefixDirective, IgxButtonDirective, NgClass, IgxSuffixDirective, IgxIconComponent, NgSwitch, NgSwitchCase, NgSwitchDefault] }) -export class IgxInputGroupComponent implements IgxInputGroupBase, AfterViewChecked, OnDestroy { +export class IgxInputGroupComponent implements IgxInputGroupBase, AfterViewInit { /** * Sets the resource strings. * By default it uses EN resources. @@ -120,11 +120,10 @@ export class IgxInputGroupComponent implements IgxInputGroupBase, AfterViewCheck @ContentChild(IgxInputDirective, { read: IgxInputDirective, static: true }) protected input: IgxInputDirective; + private _destroyRef = inject(DestroyRef); private _type: IgxInputGroupType = null; private _filled = false; private _theme: IgxTheme; - private _theme$ = new Subject(); - private _subscription: Subscription; private _resourceStrings = getCurrentResourceStrings(InputResourceStringsEN); /** @hidden */ @@ -216,14 +215,19 @@ export class IgxInputGroupComponent implements IgxInputGroupBase, AfterViewCheck private document: any, private platform: PlatformUtil, private cdr: ChangeDetectorRef, - private themeService: ThemeService, + @Inject(THEME_TOKEN) + private themeToken: ThemeToken ) { - this._theme = this.themeService.globalTheme; + this._theme = this.themeToken.theme; - this._subscription = this._theme$.asObservable().subscribe(value => { - this._theme = value as IgxTheme; - this.cdr.detectChanges(); + const { unsubscribe } = this.themeToken.onChange((theme) => { + if (this._theme !== theme) { + this._theme = theme; + this.cdr.detectChanges(); + } }); + + this._destroyRef.onDestroy(() => unsubscribe); } /** @hidden */ @@ -439,18 +443,19 @@ export class IgxInputGroupComponent implements IgxInputGroupBase, AfterViewCheck this._filled = val; } - /** @hidden @internal */ - public ngAfterViewChecked() { - const theme = this.themeService.getComponentTheme(this.element); + private setComponentTheme() { + if (!this.themeToken.preferToken) { + const theme = getComponentTheme(this.element.nativeElement); - if (theme) { - this._theme$.next(theme); - this.cdr.markForCheck(); + if (theme && theme !== this._theme) { + this.theme = theme; + this.cdr.markForCheck(); + } } } /** @hidden @internal */ - public ngOnDestroy() { - this._subscription.unsubscribe(); + public ngAfterViewInit() { + this.setComponentTheme(); } } diff --git a/projects/igniteui-angular/src/lib/services/public_api.ts b/projects/igniteui-angular/src/lib/services/public_api.ts index 2afbfccbe07..a838c7a92fe 100644 --- a/projects/igniteui-angular/src/lib/services/public_api.ts +++ b/projects/igniteui-angular/src/lib/services/public_api.ts @@ -19,4 +19,5 @@ export * from './transaction/igx-hierarchical-transaction'; export * from './transaction/igx-transaction'; export * from './transaction/transaction'; export * from './transaction/transaction-factory.service'; +export * from './theme/theme.token'; diff --git a/projects/igniteui-angular/src/lib/services/theme/theme.service.ts b/projects/igniteui-angular/src/lib/services/theme/theme.service.ts deleted file mode 100644 index e7836e47aca..00000000000 --- a/projects/igniteui-angular/src/lib/services/theme/theme.service.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { ElementRef, Inject, Injectable } from "@angular/core"; -import { mkenum } from "../../core/utils"; -import { BehaviorSubject } from "rxjs"; -import { DOCUMENT } from "@angular/common"; - -const Theme = /*@__PURE__*/ mkenum({ - Material: "material", - Fluent: "fluent", - Bootstrap: "bootstrap", - IndigoDesign: "indigo", -}); - -/** - * Determines the component theme. - */ -export type IgxTheme = (typeof Theme)[keyof typeof Theme]; - -@Injectable({ - providedIn: "root", -}) -export class ThemeService { - /** - * Sets the theme of the component. - * Allowed values of type IgxTheme. - */ - public globalTheme: IgxTheme; - private theme$ = new BehaviorSubject("material"); - - constructor( - @Inject(DOCUMENT) - private document: any, - ) { - this.theme$.asObservable().subscribe((value) => { - this.globalTheme = value as IgxTheme; - }); - - this.init(); - } - - private init() { - const theme = globalThis.window - ?.getComputedStyle(this.document.body) - .getPropertyValue("--ig-theme") - .trim(); - - if (theme !== "") { - this.theme$.next(theme as IgxTheme); - } - } - - public getComponentTheme(el: ElementRef) { - return globalThis.window - ?.getComputedStyle(el.nativeElement) - .getPropertyValue('--theme') - .trim() as IgxTheme; - } -} diff --git a/projects/igniteui-angular/src/lib/services/theme/theme.token.ts b/projects/igniteui-angular/src/lib/services/theme/theme.token.ts new file mode 100644 index 00000000000..1ea02f7aa99 --- /dev/null +++ b/projects/igniteui-angular/src/lib/services/theme/theme.token.ts @@ -0,0 +1,52 @@ +import { inject, InjectionToken } from "@angular/core"; +import { mkenum } from "../../core/utils"; +import { BehaviorSubject } from "rxjs"; +import { DOCUMENT } from "@angular/common"; + +export class ThemeToken { + private document = inject(DOCUMENT); + public subject: BehaviorSubject; + + constructor(private t?: IgxTheme) { + const globalTheme = globalThis.window + ?.getComputedStyle(this.document.body) + .getPropertyValue("--ig-theme") + .trim() || 'material' as IgxTheme; + + const _theme = t ?? globalTheme as IgxTheme; + this.subject = new BehaviorSubject(_theme); + } + + public onChange(callback: (theme: IgxTheme) => void) { + return this.subject.subscribe(callback); + } + + public set(theme: IgxTheme) { + this.subject.next(theme); + } + + public get theme() { + return this.subject.getValue(); + } + + public get preferToken() { + return !!this.t; + } +} + +export const THEME_TOKEN = new InjectionToken('ThemeToken', { + providedIn: 'root', + factory: () => new ThemeToken() +}); + +const Theme = /*@__PURE__*/ mkenum({ + Material: "material", + Fluent: "fluent", + Bootstrap: "bootstrap", + IndigoDesign: "indigo", +}); + +/** + * Determines the component theme. + */ +export type IgxTheme = (typeof Theme)[keyof typeof Theme]; diff --git a/projects/igniteui-angular/src/lib/simple-combo/simple-combo.component.spec.ts b/projects/igniteui-angular/src/lib/simple-combo/simple-combo.component.spec.ts index 0f6f6996616..0b400781774 100644 --- a/projects/igniteui-angular/src/lib/simple-combo/simple-combo.component.spec.ts +++ b/projects/igniteui-angular/src/lib/simple-combo/simple-combo.component.spec.ts @@ -10,7 +10,6 @@ import { IComboSelectionChangingEventArgs, IgxComboFooterDirective, IgxComboHead import { IgxSelectionAPIService } from '../core/selection'; import { IBaseCancelableBrowserEventArgs } from '../core/utils'; import { IgxIconComponent } from '../icon/icon.component'; -import { IgxIconService } from '../icon/icon.service'; import { IgxInputState, IgxLabelDirective } from '../input-group/public_api'; import { AbsoluteScrollStrategy, AutoPositionStrategy, ConnectedPositioningStrategy } from '../services/public_api'; import { configureTestSuite } from '../test-utils/configure-suite'; @@ -76,9 +75,9 @@ describe('IgxSimpleCombo', () => { get: mockNgControl }); mockSelection.get.and.returnValue(new Set([])); - const mockIconService = new IgxIconService(null, null, null, null); const platformUtil = null; const mockDocument = jasmine.createSpyObj('DOCUMENT', [], { 'defaultView': { getComputedStyle: () => null }}); + it('should properly call dropdown methods on toggle', () => { combo = new IgxSimpleComboComponent( elementRef, @@ -90,7 +89,6 @@ describe('IgxSimpleCombo', () => { mockInjector ); const dropdown = jasmine.createSpyObj('IgxComboDropDownComponent', ['open', 'close', 'toggle']); - spyOn(mockIconService, 'addSvgIconFromText').and.returnValue(null); combo.ngOnInit(); combo.dropdown = dropdown; dropdown.collapsed = true; @@ -121,7 +119,6 @@ describe('IgxSimpleCombo', () => { mockInjector ); const dropdown = jasmine.createSpyObj('IgxComboDropDownComponent', ['toggle']); - spyOn(mockIconService, 'addSvgIconFromText').and.returnValue(null); combo.ngOnInit(); combo.dropdown = dropdown; const defaultSettings = (combo as any)._overlaySettings; @@ -146,7 +143,6 @@ describe('IgxSimpleCombo', () => { null, mockInjector ); - spyOn(mockIconService, 'addSvgIconFromText').and.returnValue(null); combo.ngOnInit(); combo.valueKey = 'field'; expect(combo.displayKey).toEqual(combo.valueKey); @@ -168,7 +164,6 @@ describe('IgxSimpleCombo', () => { ); const dropdown = jasmine.createSpyObj('IgxComboDropDownComponent', ['selectItem']); const comboInput = jasmine.createSpyObj('IgxInputDirective', ['value']); - spyOn(mockIconService, 'addSvgIconFromText').and.returnValue(null); combo.ngOnInit(); combo.comboInput = comboInput; combo.data = complexData; @@ -207,7 +202,6 @@ describe('IgxSimpleCombo', () => { null, mockInjector ); - spyOn(mockIconService, 'addSvgIconFromText').and.returnValue(null); combo.ngOnInit(); spyOn(combo.opening, 'emit').and.callThrough(); spyOn(combo.closing, 'emit').and.callThrough(); @@ -257,7 +251,6 @@ describe('IgxSimpleCombo', () => { mockInjector ); const dropdown = jasmine.createSpyObj('IgxComboDropDownComponent', ['selectItem']); - spyOn(mockIconService, 'addSvgIconFromText').and.returnValue(null); combo.ngOnInit(); combo.data = data; combo.dropdown = dropdown; @@ -309,7 +302,6 @@ describe('IgxSimpleCombo', () => { mockInjector ); const dropdown = jasmine.createSpyObj('IgxComboDropDownComponent', ['selectItem']); - spyOn(mockIconService, 'addSvgIconFromText').and.returnValue(null); combo.ngOnInit(); combo.data = complexData; combo.valueKey = 'country'; @@ -353,7 +345,6 @@ describe('IgxSimpleCombo', () => { mockInjector ); const dropdown = jasmine.createSpyObj('IgxComboDropDownComponent', ['selectItem']); - spyOn(mockIconService, 'addSvgIconFromText').and.returnValue(null); combo.ngOnInit(); combo.data = data; combo.dropdown = dropdown; @@ -379,7 +370,6 @@ describe('IgxSimpleCombo', () => { null, mockInjector ); - spyOn(mockIconService, 'addSvgIconFromText').and.returnValue(null); combo.ngOnInit(); let errorMessage = ''; try { @@ -403,7 +393,6 @@ describe('IgxSimpleCombo', () => { null, mockInjector ); - spyOn(mockIconService, 'addSvgIconFromText').and.returnValue(null); combo.ngOnInit(); let errorMessage = ''; try { @@ -428,7 +417,6 @@ describe('IgxSimpleCombo', () => { mockInjector ); const dropdown = jasmine.createSpyObj('IgxComboDropDownComponent', ['selectItem', 'navigateFirst']); - spyOn(mockIconService, 'addSvgIconFromText').and.returnValue(null); combo.ngOnInit(); combo.data = data; combo.dropdown = dropdown; @@ -473,7 +461,6 @@ describe('IgxSimpleCombo', () => { null, mockInjector ); - spyOn(mockIconService, 'addSvgIconFromText').and.returnValue(null); combo.ngOnInit(); combo.data = data; combo.searchInputUpdate.subscribe((e) => { @@ -504,7 +491,6 @@ describe('IgxSimpleCombo', () => { ); const dropdown = jasmine.createSpyObj('IgxComboDropDownComponent', ['open', 'close', 'toggle']); const spyObj = jasmine.createSpyObj('event', ['stopPropagation', 'preventDefault']); - spyOn(mockIconService, 'addSvgIconFromText').and.returnValue(null); const comboInput = jasmine.createSpyObj('IgxInputDirective', ['value']); comboInput.value = 'test'; combo.comboInput = comboInput; @@ -530,7 +516,6 @@ describe('IgxSimpleCombo', () => { ); const dropdown = jasmine.createSpyObj('IgxComboDropDownComponent', ['selectItem', 'focusedItem']); const spyObj = jasmine.createSpyObj('event', ['stopPropagation']); - spyOn(mockIconService, 'addSvgIconFromText').and.returnValue(null); combo.ngOnInit(); combo.data = data; combo.dropdown = dropdown; diff --git a/src/app/icon/ThemedComponent/themed-icon.component.html b/src/app/icon/ThemedComponent/themed-icon.component.html new file mode 100644 index 00000000000..65cd2972a5b --- /dev/null +++ b/src/app/icon/ThemedComponent/themed-icon.component.html @@ -0,0 +1,17 @@ +
+ + + +
+ + + @for (theme of themes; track theme) { + + } + diff --git a/src/app/icon/ThemedComponent/themed-icon.component.ts b/src/app/icon/ThemedComponent/themed-icon.component.ts new file mode 100644 index 00000000000..e63d7a5d22b --- /dev/null +++ b/src/app/icon/ThemedComponent/themed-icon.component.ts @@ -0,0 +1,35 @@ +import { Component, inject } from '@angular/core'; +import { + IgxButtonGroupComponent, + IgxButtonDirective, + IgxIconComponent, + IgxIconService, + type IgxTheme, + THEME_TOKEN, + ThemeToken, +} from 'igniteui-angular'; + +@Component({ + selector: 'app-themed-icon', + templateUrl: 'themed-icon.component.html', + providers: [ + { + provide: THEME_TOKEN, + useFactory: () => new ThemeToken() + }, + IgxIconService, // Create New Icon Service Scoped to this component + ], + imports: [IgxIconComponent, IgxButtonDirective, IgxButtonGroupComponent] +}) +export class ThemedIconComponent { + protected themeToken = inject(THEME_TOKEN); + protected themes: IgxTheme[] = ['material', 'bootstrap', 'indigo', 'fluent']; + + constructor(private iconService: IgxIconService) { + this.iconService.setIconRef('expand_more', 'default', { family: 'material', name: 'home' }); + } + + protected setTheme(theme: IgxTheme) { + this.themeToken.set(theme); + } +} diff --git a/src/app/icon/icon.sample.html b/src/app/icon/icon.sample.html index c65cfbb8e80..178c6b5a625 100644 --- a/src/app/icon/icon.sample.html +++ b/src/app/icon/icon.sample.html @@ -5,8 +5,8 @@

Default

- - + + @@ -94,5 +94,10 @@

Lazy loaded module with cached SVG icons

+
+

Separate Instance of Icon Service w/ Runtime Theme Changes

+ The first two icons (left and rigth chevron) should change to the theme-specific reference when the buttons bellow are pressed. The third icon is set in the component instance by the end user via the Icon Service, thus it shouldn't change. + +
diff --git a/src/app/icon/icon.sample.ts b/src/app/icon/icon.sample.ts index 38b56f095c9..10b2f88c32a 100644 --- a/src/app/icon/icon.sample.ts +++ b/src/app/icon/icon.sample.ts @@ -1,13 +1,15 @@ -import { Component, OnInit } from '@angular/core'; +import { Component, OnInit, ViewEncapsulation } from '@angular/core'; import { Router } from '@angular/router'; import { IgxButtonDirective, IgxIconComponent, IgxIconService } from 'igniteui-angular'; +import { ThemedIconComponent } from './ThemedComponent/themed-icon.component'; @Component({ selector: 'app-icon-sample', styleUrls: ['./icon.sample.scss'], templateUrl: 'icon.sample.html', - imports: [IgxIconComponent, IgxButtonDirective] + encapsulation: ViewEncapsulation.None, + imports: [IgxIconComponent, IgxButtonDirective, ThemedIconComponent] }) export class IconSampleComponent implements OnInit { constructor(private _iconService: IgxIconService, public router: Router) {} From 3caea2b0917696bff9c898f33c73024716662008 Mon Sep 17 00:00:00 2001 From: Simeon Simeonoff Date: Tue, 10 Dec 2024 14:34:43 +0200 Subject: [PATCH 13/15] refactor(icon): remove ariaHidden member Closes #15159 Removing the explicit ariaHidden binding set to true on component instantiation. Users should use [attr.aria-hidden]="true" to control the aria-hidden attribute of the Icon component. --- .../src/lib/icon/icon.component.ts | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/projects/igniteui-angular/src/lib/icon/icon.component.ts b/projects/igniteui-angular/src/lib/icon/icon.component.ts index 7f62dfd82e5..928424fa13a 100644 --- a/projects/igniteui-angular/src/lib/icon/icon.component.ts +++ b/projects/igniteui-angular/src/lib/icon/icon.component.ts @@ -65,22 +65,6 @@ export class IgxIconComponent implements OnInit, OnChanges, OnDestroy { this._iconClasses.clear(); } - /** - * This allows you to disable the `aria-hidden` attribute. By default it's applied. - * - * @example - * ```typescript - * @ViewChild("MyIcon") public icon: IgxIconComponent; - * constructor(private cdRef:ChangeDetectorRef) {} - * ngAfterViewInit() { - * this.icon.ariaHidden = false; - * this.cdRef.detectChanges(); - * } - * ``` - */ - @HostBinding("attr.aria-hidden") - public ariaHidden = true; - /** * An accessor that returns inactive property. * From 280a67739fc3c187129d95a6b8e9f031f78bb1e9 Mon Sep 17 00:00:00 2001 From: Simeon Simeonoff Date: Tue, 10 Dec 2024 14:39:05 +0200 Subject: [PATCH 14/15] spec(icon): don't expect aria-hidden to be set to true --- projects/igniteui-angular/src/lib/icon/icon.component.spec.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/projects/igniteui-angular/src/lib/icon/icon.component.spec.ts b/projects/igniteui-angular/src/lib/icon/icon.component.spec.ts index bcc54384e4a..18cd4bc7b75 100644 --- a/projects/igniteui-angular/src/lib/icon/icon.component.spec.ts +++ b/projects/igniteui-angular/src/lib/icon/icon.component.spec.ts @@ -36,7 +36,6 @@ describe("Icon", () => { fixture.detectChanges(); expect(instance.getFamily).toBe("material"); - expect(instance.ariaHidden).toBe(true); expect(instance.getActive).toBe(true); }); From 57c732b4ee276f066b916f30ef1cba05c3042275 Mon Sep 17 00:00:00 2001 From: Simeon Simeonoff Date: Tue, 10 Dec 2024 15:51:52 +0200 Subject: [PATCH 15/15] refactor(*): add aria-hidden to igx-icon in templates where expected --- .../src/lib/date-range-picker/date-range-picker.component.html | 2 +- .../lib/expansion-panel/expansion-panel-header.component.html | 3 ++- .../src/lib/query-builder/query-builder.component.html | 2 +- projects/igniteui-angular/src/lib/select/select.component.html | 2 +- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/projects/igniteui-angular/src/lib/date-range-picker/date-range-picker.component.html b/projects/igniteui-angular/src/lib/date-range-picker/date-range-picker.component.html index 0dd627fb539..79359e0cd14 100644 --- a/projects/igniteui-angular/src/lib/date-range-picker/date-range-picker.component.html +++ b/projects/igniteui-angular/src/lib/date-range-picker/date-range-picker.component.html @@ -16,7 +16,7 @@ - + {{ dateSeparator }} diff --git a/projects/igniteui-angular/src/lib/expansion-panel/expansion-panel-header.component.html b/projects/igniteui-angular/src/lib/expansion-panel/expansion-panel-header.component.html index aa0ab6f56e0..abdf2d1d6bf 100644 --- a/projects/igniteui-angular/src/lib/expansion-panel/expansion-panel-header.component.html +++ b/projects/igniteui-angular/src/lib/expansion-panel/expansion-panel-header.component.html @@ -10,7 +10,8 @@ + [name]="panel.collapsed ? 'expand' : 'collapse'" + [attr.aria-hidden]="true"> diff --git a/projects/igniteui-angular/src/lib/query-builder/query-builder.component.html b/projects/igniteui-angular/src/lib/query-builder/query-builder.component.html index bf75f31df14..4edbee2f274 100644 --- a/projects/igniteui-angular/src/lib/query-builder/query-builder.component.html +++ b/projects/igniteui-angular/src/lib/query-builder/query-builder.component.html @@ -153,7 +153,7 @@
(keydown)="invokeClick($event)" (click)="enterExpressionEdit(expressionItem)" > - edit + edit