From 52cadb5b8a0fb4e6eb1964beb710d4926c4c95a9 Mon Sep 17 00:00:00 2001 From: Diogo Bastos <50139916+DiogoABastos@users.noreply.github.com> Date: Wed, 17 Dec 2025 13:46:58 +0000 Subject: [PATCH] AAE-40896 Create adf-icon directive (#11467) * AAE-40896 Create adf-icon directive * AAE-40896 Update ligature text method --- lib/core/src/lib/icon/icon.directive.spec.ts | 99 ++++++++++++++++++++ lib/core/src/lib/icon/icon.directive.ts | 62 ++++++++++++ lib/core/src/lib/icon/public-api.ts | 1 + 3 files changed, 162 insertions(+) create mode 100644 lib/core/src/lib/icon/icon.directive.spec.ts create mode 100644 lib/core/src/lib/icon/icon.directive.ts diff --git a/lib/core/src/lib/icon/icon.directive.spec.ts b/lib/core/src/lib/icon/icon.directive.spec.ts new file mode 100644 index 0000000000..2ac74c37ae --- /dev/null +++ b/lib/core/src/lib/icon/icon.directive.spec.ts @@ -0,0 +1,99 @@ +/*! + * @license + * Copyright © 2005-2025 Hyland Software, Inc. and its affiliates. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Component } from '@angular/core'; +import { MatIconModule } from '@angular/material/icon'; +import { IconDirective } from './icon.directive'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed'; +import { IconType, MatIconHarness, MatIconTestingModule } from '@angular/material/icon/testing'; +import { HarnessLoader } from '@angular/cdk/testing'; +import { ICON_ALIAS_MAP_TOKEN } from './icon-alias-map.token'; + +@Component({ + template: ``, + standalone: true, + imports: [MatIconModule, IconDirective] +}) +class TestComponent { + name: string; +} + +describe('IconDirective', () => { + let fixture: ComponentFixture; + let component: TestComponent; + let loader: HarnessLoader; + + describe('alias map NOT provided', () => { + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [TestComponent, MatIconTestingModule] + }); + + fixture = TestBed.createComponent(TestComponent); + component = fixture.componentInstance; + loader = TestbedHarnessEnvironment.loader(fixture); + }); + + it('should append ligature text if no alias map is provided', async () => { + component.name = 'mock_ligature'; + + const iconHarness = await loader.getHarness(MatIconHarness); + + expect(await iconHarness.getType()).toBe(IconType.FONT); + expect(await iconHarness.getName()).toContain('mock_ligature'); + }); + }); + + describe('alias map provided', () => { + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [TestComponent, MatIconTestingModule], + providers: [ + { + provide: ICON_ALIAS_MAP_TOKEN, + useValue: { + mock_icon: 'mock_alias' + } + } + ] + }); + + fixture = TestBed.createComponent(TestComponent); + component = fixture.componentInstance; + loader = TestbedHarnessEnvironment.loader(fixture); + }); + + it('should append ligature text if alias map is provided but name does not match any key', async () => { + component.name = 'mock_ligature'; + + const iconHarness = await loader.getHarness(MatIconHarness); + + expect(await iconHarness.getType()).toBe(IconType.FONT); + expect(await iconHarness.getName()).toContain('mock_ligature'); + }); + + it('should set svg icon if alias map is provided and name matches alias key', async () => { + component.name = 'mock_icon'; + + const iconHarness = await loader.getHarness(MatIconHarness); + + expect(await iconHarness.getType()).toBe(IconType.SVG); + expect(await iconHarness.getName()).toContain('mock_alias'); + }); + }); +}); diff --git a/lib/core/src/lib/icon/icon.directive.ts b/lib/core/src/lib/icon/icon.directive.ts new file mode 100644 index 0000000000..ba21014eb0 --- /dev/null +++ b/lib/core/src/lib/icon/icon.directive.ts @@ -0,0 +1,62 @@ +/*! + * @license + * Copyright © 2005-2025 Hyland Software, Inc. and its affiliates. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Directive, ElementRef, inject, Input, OnChanges } from '@angular/core'; +import { ICON_ALIAS_MAP_TOKEN } from './icon-alias-map.token'; +import { MatIcon } from '@angular/material/icon'; + +@Directive({ + selector: 'mat-icon[adf-icon]' +}) +export class IconDirective implements OnChanges { + private readonly matIcon = inject(MatIcon); + private readonly elementRef: ElementRef = inject(ElementRef); + private readonly aliasMap = inject(ICON_ALIAS_MAP_TOKEN, { optional: true }); + + @Input() name: string; + + ngOnChanges() { + const iconAlias = this.aliasMap?.[this.name]; + + iconAlias ? this.setSvgIcon(iconAlias) : this.appendLigatureText(); + } + + private setSvgIcon(icon: string) { + this.matIcon.svgIcon = icon; + } + + private appendLigatureText() { + this.resetSvg(); + + const element = this.elementRef.nativeElement; + const textNode = this.findTextNode(element); + + if (textNode) { + textNode.nodeValue = this.name; + } else { + this.elementRef.nativeElement.appendChild(document.createTextNode(this.name)); + } + } + + private findTextNode(element: HTMLElement): ChildNode | undefined { + return Array.from(element.childNodes).find((node: ChildNode) => node.nodeType === Node.TEXT_NODE); + } + + private resetSvg() { + this.matIcon.svgIcon = null; + } +} diff --git a/lib/core/src/lib/icon/public-api.ts b/lib/core/src/lib/icon/public-api.ts index 4585d00822..f893d6ff81 100644 --- a/lib/core/src/lib/icon/public-api.ts +++ b/lib/core/src/lib/icon/public-api.ts @@ -18,3 +18,4 @@ export * from './icon.component'; export * from './icon-alias-map.type'; export * from './icon-alias-map.token'; +export * from './icon.directive';