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';