diff --git a/goldens/cdk/a11y/index.api.md b/goldens/cdk/a11y/index.api.md index ea7039d26cd3..0a68560adbda 100644 --- a/goldens/cdk/a11y/index.api.md +++ b/goldens/cdk/a11y/index.api.md @@ -295,7 +295,7 @@ export interface Highlightable extends ListKeyManagerOption { // @public export class _IdGenerator { - getId(prefix: string): string; + getId(prefix: string, randomize?: boolean): string; // (undocumented) static ɵfac: i0.ɵɵFactoryDeclaration<_IdGenerator, never>; // (undocumented) diff --git a/src/aria/accordion/accordion.ts b/src/aria/accordion/accordion.ts index 8a47e36c68ec..7dff19c0b928 100644 --- a/src/aria/accordion/accordion.ts +++ b/src/aria/accordion/accordion.ts @@ -54,7 +54,7 @@ export class AccordionPanel { private readonly _deferredContentAware = inject(DeferredContentAware); /** A global unique identifier for the panel. */ - private readonly _id = inject(_IdGenerator).getId('accordion-trigger-'); + private readonly _id = inject(_IdGenerator).getId('accordion-trigger-', true); /** A local unique identifier for the panel, used to match with its trigger's value. */ value = input.required(); @@ -102,7 +102,7 @@ export class AccordionPanel { }) export class AccordionTrigger { /** A global unique identifier for the trigger. */ - private readonly _id = inject(_IdGenerator).getId('ng-accordion-trigger-'); + private readonly _id = inject(_IdGenerator).getId('ng-accordion-trigger-', true); /** A reference to the trigger element. */ private readonly _elementRef = inject(ElementRef); diff --git a/src/aria/grid/grid.ts b/src/aria/grid/grid.ts index 3bbc4c7d0152..3bdfb6bb6185 100644 --- a/src/aria/grid/grid.ts +++ b/src/aria/grid/grid.ts @@ -184,7 +184,7 @@ export class GridCell { private readonly _row = inject(GridRow); /** A unique identifier for the cell. */ - private readonly _id = inject(_IdGenerator).getId('ng-grid-cell-'); + private readonly _id = inject(_IdGenerator).getId('ng-grid-cell-', true); /** The host native element. */ readonly element = computed(() => this._elementRef.nativeElement); diff --git a/src/aria/listbox/listbox.ts b/src/aria/listbox/listbox.ts index c30817b9be86..197ab52287e0 100644 --- a/src/aria/listbox/listbox.ts +++ b/src/aria/listbox/listbox.ts @@ -60,7 +60,7 @@ import {ComboboxPopup} from '../combobox'; }) export class Listbox { /** A unique identifier for the listbox. */ - private readonly _generatedId = inject(_IdGenerator).getId('ng-listbox-'); + private readonly _generatedId = inject(_IdGenerator).getId('ng-listbox-', true); // TODO(wagnermaciel): https://github.com/angular/components/pull/30495#discussion_r1972601144. /** A unique identifier for the listbox. */ @@ -207,7 +207,7 @@ export class Option { private readonly _listbox = inject(Listbox); /** A unique identifier for the option. */ - private readonly _generatedId = inject(_IdGenerator).getId('ng-option-'); + private readonly _generatedId = inject(_IdGenerator).getId('ng-option-', true); // TODO(wagnermaciel): https://github.com/angular/components/pull/30495#discussion_r1972601144. /** A unique identifier for the option. */ diff --git a/src/aria/menu/menu.ts b/src/aria/menu/menu.ts index 32dc29c0654b..c5c5edb5bfe3 100644 --- a/src/aria/menu/menu.ts +++ b/src/aria/menu/menu.ts @@ -27,6 +27,7 @@ import { MenuPattern, MenuTriggerPattern, } from '@angular/aria/private'; +import {_IdGenerator} from '@angular/cdk/a11y'; import {toSignal} from '@angular/core/rxjs-interop'; import {Directionality} from '@angular/cdk/bidi'; @@ -132,7 +133,7 @@ export class Menu { readonly submenu = input | undefined>(undefined); /** The unique ID of the menu. */ - readonly id = input(Math.random().toString(36).substring(2, 10)); + readonly id = input(inject(_IdGenerator).getId('ng-menu-', true)); /** Whether the menu should wrap its items. */ readonly wrap = input(true); @@ -325,7 +326,7 @@ export class MenuItem { readonly element: HTMLElement = this._elementRef.nativeElement; /** The unique ID of the menu item. */ - readonly id = input(Math.random().toString(36).substring(2, 10)); + readonly id = input(inject(_IdGenerator).getId('ng-menu-item-', true)); /** The value of the menu item. */ readonly value = input.required(); diff --git a/src/aria/radio-group/radio-group.ts b/src/aria/radio-group/radio-group.ts index 0bedacb19674..deae9d1933f6 100644 --- a/src/aria/radio-group/radio-group.ts +++ b/src/aria/radio-group/radio-group.ts @@ -221,7 +221,7 @@ export class RadioButton { private readonly _radioGroup = inject(RadioGroup); /** A unique identifier for the radio button. */ - private readonly _generatedId = inject(_IdGenerator).getId('ng-radio-button-'); + private readonly _generatedId = inject(_IdGenerator).getId('ng-radio-button-', true); /** A unique identifier for the radio button. */ readonly id = computed(() => this._generatedId); diff --git a/src/aria/tabs/tabs.ts b/src/aria/tabs/tabs.ts index 4bdfbb55da60..62552e3b28cc 100644 --- a/src/aria/tabs/tabs.ts +++ b/src/aria/tabs/tabs.ts @@ -315,7 +315,7 @@ export class TabPanel implements OnInit, OnDestroy { private readonly _Tabs = inject(Tabs); /** A global unique identifier for the tab. */ - private readonly _id = inject(_IdGenerator).getId('ng-tabpanel-'); + private readonly _id = inject(_IdGenerator).getId('ng-tabpanel-', true); /** The Tab UIPattern associated with the tabpanel */ readonly tab = computed(() => this._Tabs.tabs()?.find(tab => tab.value() === this.value())); diff --git a/src/aria/toolbar/toolbar.ts b/src/aria/toolbar/toolbar.ts index a3f5a8ec75d3..9376eae3807f 100644 --- a/src/aria/toolbar/toolbar.ts +++ b/src/aria/toolbar/toolbar.ts @@ -185,7 +185,7 @@ export class ToolbarWidget implements OnInit, OnDestroy { private readonly _toolbar = inject(Toolbar); /** A unique identifier for the widget. */ - private readonly _generatedId = inject(_IdGenerator).getId('ng-toolbar-widget-'); + private readonly _generatedId = inject(_IdGenerator).getId('ng-toolbar-widget-', true); /** A unique identifier for the widget. */ readonly id = computed(() => this._generatedId); @@ -236,7 +236,7 @@ export class ToolbarWidgetGroup implements OnInit, OnDestroy { private readonly _toolbar = inject(Toolbar, {optional: true}); /** A unique identifier for the widget. */ - private readonly _generatedId = inject(_IdGenerator).getId('ng-toolbar-widget-group-'); + private readonly _generatedId = inject(_IdGenerator).getId('ng-toolbar-widget-group-', true); /** A unique identifier for the widget. */ readonly id = computed(() => this._generatedId); diff --git a/src/aria/tree/tree.ts b/src/aria/tree/tree.ts index fda200442dca..f925143eec56 100644 --- a/src/aria/tree/tree.ts +++ b/src/aria/tree/tree.ts @@ -82,7 +82,7 @@ function sortDirectives(a: HasElement, b: HasElement) { }) export class Tree { /** A unique identifier for the tree. */ - private readonly _generatedId = inject(_IdGenerator).getId('ng-tree-'); + private readonly _generatedId = inject(_IdGenerator).getId('ng-tree-', true); // TODO(wagnermaciel): https://github.com/angular/components/pull/30495#discussion_r1972601144. /** A unique identifier for the tree. */ @@ -229,7 +229,7 @@ export class TreeItem extends DeferredContentAware implements OnInit, OnDestr private readonly _elementRef = inject(ElementRef); /** A unique identifier for the tree item. */ - private readonly _id = inject(_IdGenerator).getId('ng-tree-item-'); + private readonly _id = inject(_IdGenerator).getId('ng-tree-item-', true); /** The owned tree item group. */ private readonly _group = signal | undefined>(undefined); diff --git a/src/cdk/a11y/id-generator.ts b/src/cdk/a11y/id-generator.ts index 55bae7a0a551..c125fba10ac8 100644 --- a/src/cdk/a11y/id-generator.ts +++ b/src/cdk/a11y/id-generator.ts @@ -19,12 +19,14 @@ const counters: Record = {}; @Injectable({providedIn: 'root'}) export class _IdGenerator { private _appId = inject(APP_ID); + private _infix = `a${Math.floor(Math.random() * 100000).toString()}`; /** * Generates a unique ID with a specific prefix. * @param prefix Prefix to add to the ID. + * @param randomize Add a randomized infix string. */ - getId(prefix: string): string { + getId(prefix: string, randomize: boolean = false): string { // Omit the app ID if it's the default `ng`. Since the vast majority of pages have one // Angular app on them, we can reduce the amount of breakages by not adding it. if (this._appId !== 'ng') { @@ -35,6 +37,6 @@ export class _IdGenerator { counters[prefix] = 0; } - return `${prefix}${counters[prefix]++}`; + return `${prefix}${randomize ? this._infix + '-' : ''}${counters[prefix]++}`; } }