Skip to content

Commit 99eb64d

Browse files
simeonoffCopilotrkaraivanov
authored
feat(icon): add stripMeta option to registerIcon and registerIconFromText (#2286)
Adds a RegisterIconOptions object accepted as the third argument of both registration functions (backward-compatible with the existing string form). When stripMeta is true, the parser removes <title> and <desc> from the stored SVG, preventing browser-native tooltips on hover, and cleans up any aria-labelledby/aria-describedby references on the root <svg> that pointed to the stripped elements' IDs. The title text is still captured into SvgIcon.title so the host <igc-icon> continues to expose it as aria-label. Closes #1822 --------- Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Co-authored-by: Radoslav Karaivanov <rkaraivanov@infragistics.com>
1 parent 9dffa42 commit 99eb64d

7 files changed

Lines changed: 364 additions & 54 deletions

File tree

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,11 @@ All notable changes to this project will be documented in this file.
44
The format is based on [Keep a Changelog](http://keepachangelog.com/)
55
and this project adheres to [Semantic Versioning](http://semver.org/).
66

7+
## [Unreleased]
8+
### Added
9+
- #### Icon
10+
- `registerIcon` and `registerIconFromText` now accept a `RegisterIconOptions` object as their third argument in addition to the existing plain collection string. Setting `stripMeta: true` removes `<title>` and `<desc>` elements from the stored SVG, preventing the browser from displaying a native tooltip on hover. The title text is still captured and exposed as the `aria-label` of the host `<igc-icon>` element. Any `aria-labelledby` / `aria-describedby` references on the root `<svg>` that pointed to the stripped elements' IDs are cleaned up automatically. [#1822](https://github.com/IgniteUI/igniteui-webcomponents/issues/1822)
11+
712
## [7.2.4] - 2026-06-29
813
### Added
914
- #### Form associated custom elements with external labels

src/components/icon/icon.registry.ts

Lines changed: 99 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,33 @@ import type {
88
IconCallback,
99
IconMeta,
1010
IconReferencePair,
11+
RegisterIconOptions,
1112
SvgIcon,
1213
} from './registry/types.js';
1314
import { ActionType } from './registry/types.js';
1415

16+
/**
17+
* Resolves the stripMeta argument of `registerIcon` / `registerIconFromText`.
18+
*
19+
* Accepts either the plain-string collection name **or** the
20+
* {@link RegisterIconOptions} object, and always returns a fully-resolved
21+
* options record so call-sites do not need to branch.
22+
*
23+
* @internal
24+
*/
25+
function resolveIconOptions(
26+
collectionOrOptions?: string | RegisterIconOptions
27+
): Required<RegisterIconOptions> {
28+
if (typeof collectionOrOptions === 'string') {
29+
return { collection: collectionOrOptions, stripMeta: false };
30+
}
31+
32+
return {
33+
collection: collectionOrOptions?.collection ?? 'default',
34+
stripMeta: collectionOrOptions?.stripMeta ?? false,
35+
};
36+
}
37+
1538
/**
1639
* Global singleton registry for managing SVG icons and their references.
1740
*
@@ -62,6 +85,8 @@ class IconsRegistry {
6285
* @param name - The unique name for the icon within its collection
6386
* @param iconText - The SVG markup as a string
6487
* @param collection - The collection to register the icon in (default: 'default')
88+
* @param stripMeta - When `true`, strips `<title>` and `<desc>` elements from
89+
* the SVG before storing it (see {@link RegisterIconOptions.stripMeta}).
6590
*
6691
* @remarks
6792
* This method:
@@ -75,9 +100,10 @@ class IconsRegistry {
75100
public register(
76101
name: string,
77102
iconText: string,
78-
collection = 'default'
103+
collection = 'default',
104+
stripMeta = false
79105
): void {
80-
const svgIcon = this._svgIconParser.parse(iconText);
106+
const svgIcon = this._svgIconParser.parse(iconText, stripMeta);
81107
this._collections.getOrCreate(collection).set(name, svgIcon);
82108

83109
const icons = createIconDefaultMap<string, SvgIcon>();
@@ -292,36 +318,61 @@ export function getIconRegistry() {
292318
*
293319
* @param name - The unique name for the icon
294320
* @param url - The URL to fetch the SVG icon from
295-
* @param collection - The collection to register the icon in (default: 'default')
321+
* @param collection - The collection to register the icon in (default: `'default'`)
322+
*
323+
* @returns A promise that resolves when the icon is registered
324+
*
325+
* @throws If the HTTP request fails or returns a non-OK status
326+
*/
327+
export async function registerIcon(
328+
name: string,
329+
url: string,
330+
collection?: string
331+
): Promise<void>;
332+
333+
/**
334+
* Registers an icon by fetching it from a URL.
335+
*
336+
* @param name - The unique name for the icon
337+
* @param url - The URL to fetch the SVG icon from
338+
* @param options - Registration options: target collection and/or `stripMeta`
296339
*
297340
* @returns A promise that resolves when the icon is registered
298341
*
299342
* @throws If the HTTP request fails or returns a non-OK status
300343
*
301344
* @remarks
302-
* This function fetches SVG content from the provided URL and registers it
303-
* in the icon registry. The icon becomes immediately available to all icon
304-
* components in the application.
345+
* This overload accepts a {@link RegisterIconOptions} object so you can control
346+
* the target collection **and** opt into SVG meta stripping in one call:
305347
*
306-
* @example
307348
* ```typescript
308-
* // Register an icon from a URL
309-
* await registerIcon('custom-icon', '/assets/icons/custom.svg');
349+
* // Strip <title>/<desc> to prevent browser-native tooltips on hover
350+
* await registerIcon('home', '/icons/home.svg', { stripMeta: true });
310351
*
311-
* // Use in HTML
312-
* // <igc-icon name="custom-icon"></igc-icon>
352+
* // Or with a custom collection:
353+
* await registerIcon('home', '/icons/home.svg', {
354+
* collection: 'my-lib',
355+
* stripMeta: true,
356+
* });
313357
* ```
314358
*/
315359
export async function registerIcon(
316360
name: string,
317361
url: string,
318-
collection = 'default'
319-
) {
362+
options?: RegisterIconOptions
363+
): Promise<void>;
364+
365+
export async function registerIcon(
366+
name: string,
367+
url: string,
368+
collectionOrOptions?: string | RegisterIconOptions
369+
): Promise<void> {
370+
const { collection, stripMeta } = resolveIconOptions(collectionOrOptions);
320371
const response = await fetch(url);
321372

322373
if (response.ok) {
323374
const value = await response.text();
324-
getIconRegistry().register(name, value, collection);
375+
getIconRegistry().register(name, value, collection, stripMeta);
325376
} else {
326377
throw new Error(`Icon request failed. Status: ${response.status}.`);
327378
}
@@ -332,32 +383,52 @@ export async function registerIcon(
332383
*
333384
* @param name - The unique name for the icon
334385
* @param iconText - The SVG markup as a string
335-
* @param collection - The collection to register the icon in (default: 'default')
386+
* @param collection - The collection to register the icon in (default: `'default'`)
336387
*
337388
* @throws If the SVG text is malformed or doesn't contain an SVG element
389+
*/
390+
export function registerIconFromText(
391+
name: string,
392+
iconText: string,
393+
collection?: string
394+
): void;
395+
396+
/**
397+
* Registers an icon from SVG text content.
338398
*
339-
* @remarks
340-
* This is the preferred method for registering icons when you have the SVG
341-
* content directly (e.g., from a bundled asset or inline string). The icon
342-
* becomes immediately available to all icon components.
399+
* @param name - The unique name for the icon
400+
* @param iconText - The SVG markup as a string
401+
* @param options - Registration options: target collection and/or `stripMeta`
402+
*
403+
* @throws If the SVG text is malformed or doesn't contain an SVG element
343404
*
344-
* The SVG text is parsed to extract viewBox, title, and other metadata.
405+
* @remarks
406+
* This overload accepts a {@link RegisterIconOptions} object so you can control
407+
* the target collection **and** opt into SVG meta stripping in one call:
345408
*
346-
* @example
347409
* ```typescript
348-
* const iconSvg = '<svg viewBox="0 0 24 24"><path d="..."/></svg>';
349-
* registerIconFromText('my-icon', iconSvg, 'custom');
410+
* const iconSvg = '<svg viewBox="0 0 24 24"><title>Home</title><path d="..."/></svg>';
350411
*
351-
* // Use in HTML
352-
* // <igc-icon name="my-icon" collection="custom"></igc-icon>
412+
* // Strip <title>/<desc> to prevent browser-native tooltips on hover
413+
* registerIconFromText('home', iconSvg, { stripMeta: true });
414+
*
415+
* // Or with a custom collection:
416+
* registerIconFromText('home', iconSvg, { collection: 'my-lib', stripMeta: true });
353417
* ```
354418
*/
355419
export function registerIconFromText(
356420
name: string,
357421
iconText: string,
358-
collection = 'default'
359-
) {
360-
getIconRegistry().register(name, iconText, collection);
422+
options?: RegisterIconOptions
423+
): void;
424+
425+
export function registerIconFromText(
426+
name: string,
427+
iconText: string,
428+
collectionOrOptions?: string | RegisterIconOptions
429+
): void {
430+
const { collection, stripMeta } = resolveIconOptions(collectionOrOptions);
431+
getIconRegistry().register(name, iconText, collection, stripMeta);
361432
}
362433

363434
/**

0 commit comments

Comments
 (0)