From 893e30626cde5f8adcc885732220bb55fc5d5a6f Mon Sep 17 00:00:00 2001 From: Kevin Buhmann Date: Wed, 21 Sep 2022 19:00:25 -0500 Subject: [PATCH] feat: remove deprecated `ClrAriaLiveService` in favor of `@angular/cdk` BREAKING CHANGE: The `ClrAriaLiveService` has been removed. Please use the `LiveAnnouncer` service from `@angular/cdk` instead. --- projects/angular/clarity.api.md | 22 --- .../src/utils/a11y/aria-live.service.spec.ts | 91 ---------- .../src/utils/a11y/aria-live.service.ts | 169 ------------------ projects/angular/src/utils/a11y/index.ts | 7 - projects/angular/src/utils/index.ts | 1 - 5 files changed, 290 deletions(-) delete mode 100644 projects/angular/src/utils/a11y/aria-live.service.spec.ts delete mode 100644 projects/angular/src/utils/a11y/aria-live.service.ts delete mode 100644 projects/angular/src/utils/a11y/index.ts diff --git a/projects/angular/clarity.api.md b/projects/angular/clarity.api.md index 64312a6770..d71dd93e9c 100644 --- a/projects/angular/clarity.api.md +++ b/projects/angular/clarity.api.md @@ -492,28 +492,6 @@ export class ClrAriaCurrentLink implements OnInit, OnDestroy { static ɵfac: i0.ɵɵFactoryDeclaration; } -// @public (undocumented) -export enum ClrAriaLivePoliteness { - // (undocumented) - assertive = "assertive", - // (undocumented) - off = "off", - // (undocumented) - polite = "polite" -} - -// @public @deprecated (undocumented) -export class ClrAriaLiveService implements OnDestroy { - constructor(ngZone: NgZone, _document: any, platformId: any); - announce(message: string | HTMLElement, politeness?: ClrAriaLivePoliteness): void; - get id(): string; - ngOnDestroy(): void; - // (undocumented) - static ɵfac: i0.ɵɵFactoryDeclaration; - // (undocumented) - static ɵprov: i0.ɵɵInjectableDeclaration; -} - // @public (undocumented) export enum ClrAxis { // (undocumented) diff --git a/projects/angular/src/utils/a11y/aria-live.service.spec.ts b/projects/angular/src/utils/a11y/aria-live.service.spec.ts deleted file mode 100644 index 3e8ea7d7bc..0000000000 --- a/projects/angular/src/utils/a11y/aria-live.service.spec.ts +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright (c) 2016-2022 VMware, Inc. All Rights Reserved. - * This software is released under MIT license. - * The full license information can be found in LICENSE in the root directory of this project. - */ - -import { Component } from '@angular/core'; -import { fakeAsync, TestBed, tick } from '@angular/core/testing'; - -import { ClrAriaLivePoliteness, ClrAriaLiveService } from './aria-live.service'; - -// keep in sync with the ClrAriaLiveService manualy -const ARIA_LIVE_TICK = 100; - -@Component({ - selector: 'aria-live-test', - template: '', - providers: [ClrAriaLiveService], -}) -class AriaLiveTest { - constructor(private ariaLiveService: ClrAriaLiveService) {} -} - -interface TestContext { - ariaLiveService: ClrAriaLiveService; -} - -describe('AriaLive service', function () { - let fixture, ariaLiveContent: HTMLElement, ariaLiveService: ClrAriaLiveService; - - beforeEach(function () { - TestBed.configureTestingModule({ - declarations: [AriaLiveTest], - }); - - fixture = TestBed.createComponent(AriaLiveTest); - ariaLiveService = fixture.debugElement.injector.get(ClrAriaLiveService, null); - }); - - it('should set default aria-live value to `polite`', function (this: TestContext) { - ariaLiveService.announce('test'); - ariaLiveContent = document.getElementById(ariaLiveService.id); - expect(ariaLiveContent.getAttribute('aria-live')).toBe('polite'); - }); - - it('should return you the ID of the aria-live DOM', function (this: TestContext) { - expect(ariaLiveService.id).toContain('clr-aria-live-element-'); - }); - - describe('announce', function () { - it('should not create dom element until `announce` is called', () => { - ariaLiveContent = document.getElementById(ariaLiveService.id); - expect(ariaLiveContent).toBe(null); - ariaLiveService.announce('test'); - ariaLiveContent = document.getElementById(ariaLiveService.id); - expect(ariaLiveContent).not.toBe(null); - }); - - it('should add message to aria-live content', fakeAsync(function (this: TestContext) { - ariaLiveService.announce('hello world'); - tick(ARIA_LIVE_TICK); - ariaLiveContent = document.getElementById(ariaLiveService.id); - expect(ariaLiveContent.innerText).toBe('hello world'); - })); - - it('should let you change the aria-live value', fakeAsync(function (this: TestContext) { - ariaLiveService.announce('Must be Assertive', ClrAriaLivePoliteness.assertive); - tick(ARIA_LIVE_TICK); - ariaLiveContent = document.getElementById(ariaLiveService.id); - expect(ariaLiveContent.getAttribute('aria-live')).toBe('assertive'); - })); - - it('should not create aria-live container when politness is off', fakeAsync(function (this: TestContext) { - ariaLiveService.announce('message you never gonna see', ClrAriaLivePoliteness.off); - tick(ARIA_LIVE_TICK); - ariaLiveContent = document.getElementById(ariaLiveService.id); - expect(ariaLiveContent).toBe(null); - })); - }); - - describe('ngOnDestroy', function () { - it('should remove the DOM element from the document', fakeAsync(function (this: TestContext) { - ariaLiveService.announce('hello'); - tick(ARIA_LIVE_TICK); - ariaLiveContent = document.getElementById(ariaLiveService.id); - expect(ariaLiveContent.innerText).toBe('hello'); - ariaLiveService.ngOnDestroy(); - expect(document.getElementById(ariaLiveService.id)).toEqual(null); - })); - }); -}); diff --git a/projects/angular/src/utils/a11y/aria-live.service.ts b/projects/angular/src/utils/a11y/aria-live.service.ts deleted file mode 100644 index f23b6ed736..0000000000 --- a/projects/angular/src/utils/a11y/aria-live.service.ts +++ /dev/null @@ -1,169 +0,0 @@ -/* - * Copyright (c) 2016-2022 VMware, Inc. All Rights Reserved. - * This software is released under MIT license. - * The full license information can be found in LICENSE in the root directory of this project. - */ - -import { DOCUMENT, isPlatformBrowser } from '@angular/common'; -import { PLATFORM_ID } from '@angular/core'; -import { Inject, Injectable, NgZone, OnDestroy } from '@angular/core'; - -import { uniqueIdFactory } from '../id-generator/id-generator.service'; - -export enum ClrAriaLivePoliteness { - off = 'off', - polite = 'polite', - assertive = 'assertive', -} - -/** - * Time in milliseconds before inserting the content into the container - */ -const ARIA_LIVE_TICK = 100; - -/** - * @deprecated - * - * -- ClrAriaLiveService is deprecated in 5.0 to be removed in 6.0 -- - * Please consider using the LiveAnnouncer in the Angular Material CDK - * if your project needs this functionality. - * More info: https://material.angular.io/cdk/a11y/overview#example-1 - * - * This service handle `aria-live` accessibility attribute. The issue is that you need - * to have the DOM Element with attribute `aria-live` before you could insert content - * and SR (Screen Reader) pick the change and announce it. - * - * ```typescript - * import { ClrAriaLiveService } from 'src/clr-angular/utils/a11y/aria-live.service'; - * - * @Component({ - * selector: 'clr-demo-component', - * providers: [ClrAriaLiveService], - * template: ` - * - * `, - * }) - * export class DemoComponent { - * constructor(ariaLiveService: ClrAriaLiveService) {} - * - * public actionThatWillTriggerChange() { - * this.ariaLiveService.announce('message that I want to announce to SR'); - * } - * } - * ``` - * - */ -@Injectable({ - providedIn: 'root', -}) -export class ClrAriaLiveService implements OnDestroy { - private ariaLiveElement: HTMLElement; - private document: Document; - private previousTimeout: ReturnType; - - constructor(private ngZone: NgZone, @Inject(DOCUMENT) _document: any, @Inject(PLATFORM_ID) private platformId: any) { - this.document = _document; - } - - private _id = `clr-aria-live-element-${uniqueIdFactory()}`; - /** - * get access to the internal HTML `id` that gonna be used for the AriaLive container. - * @return ID of the DOM Element as string. - */ - get id(): string { - return this._id; - } - - /** - * Append text content inside the AriaLive Container. This method will check if the - * DOM Element is existing if not it will create one for us and the will apply the text. - * - * ```typescript - * this.ariaLiveService.announce(this.el.nativeElement); - * // or - * this.ariaLiveService.announce('Message to announce to SR'); - * ``` - * - * @remark - * When second argument is `AriaLivePoliteness.off` we won't create aria container or update it. - * The reason for that is that we don't want to do additional work if the SR will ignore it. - * - * @param message - This could be simple string or HTMLElement - * @param politeness - 'polite', 'assertive' or 'off' - */ - announce(message: string | HTMLElement, politeness: ClrAriaLivePoliteness = ClrAriaLivePoliteness.polite): void { - if (politeness === ClrAriaLivePoliteness.off) { - return; - } - - if (!this.ariaLiveElement && isPlatformBrowser(this.platformId)) { - this.ariaLiveElement = this.createContainer(); - } - - message = typeof message !== 'string' && isPlatformBrowser(this.platformId) ? message.textContent : message; - - // when there is no message do NOTHING! - if (!message) { - return; - } - - this.ariaLiveElement.setAttribute('aria-live', politeness); - - // This 100ms timeout is necessary for some browser + screen-reader combinations: - // - Both JAWS and NVDA over IE11 will not announce anything without a non-zero timeout. - // - With Chrome and IE11 with NVDA or JAWS, a repeated (identical) message won't be read a - // second time without clearing and then using a non-zero delay. - // (using JAWS 17 at time of this writing). - this.ngZone.runOutsideAngular(() => { - // This clearTimeout will stop all older messages from announcing - // in the case where the messages are comming too fast we gonna try to append only - // the last one. That's what the SR will try to do anyway. - clearTimeout(this.previousTimeout); - this.previousTimeout = setTimeout(() => { - this.ariaLiveElement.textContent = message as string; - }, ARIA_LIVE_TICK); - }); - } - - /** - * onDestroy life cycle - must stop all active setTimeouts and remove the AriaLive - * container from the document. - */ - ngOnDestroy() { - clearTimeout(this.previousTimeout); - if (isPlatformBrowser(this.platformId) && this.ariaLiveElement) { - this.document.body.removeChild(this.ariaLiveElement); - this.ariaLiveElement = null; - } - } - - /** - * Create AriaLive DOM element as a last child of the document. - * After the element is created, we gonna apply Clarity class to hide it from - * the screen and set the `aria-live` politness. - * - * `clr-sr-only` is the CSS class that is used to hide the element from the screen. - * - * @remark - * Calling this method multiple times will create multiple DOM Elements, that - * won't be tracked and will be GC after the service is destroyed. - * - * @return AriaLive container as HTMLElement - * - */ - private createContainer(): HTMLElement { - const ariaLiveElement = this.document.createElement('div'); - - ariaLiveElement.setAttribute('id', this.id); - // Use clarity screen reader class to hide the dom element - // and fix the scrollbar shake - ariaLiveElement.classList.add('clr-sr-only'); - - ariaLiveElement.setAttribute('aria-atomic', 'true'); - ariaLiveElement.setAttribute('aria-live', ClrAriaLivePoliteness.polite); - - this.document.body.appendChild(ariaLiveElement); - - return ariaLiveElement; - } -} diff --git a/projects/angular/src/utils/a11y/index.ts b/projects/angular/src/utils/a11y/index.ts deleted file mode 100644 index 8b3535bb6e..0000000000 --- a/projects/angular/src/utils/a11y/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -/* - * Copyright (c) 2016-2022 VMware, Inc. All Rights Reserved. - * This software is released under MIT license. - * The full license information can be found in LICENSE in the root directory of this project. - */ - -export * from './aria-live.service'; diff --git a/projects/angular/src/utils/index.ts b/projects/angular/src/utils/index.ts index 2bcba3f3e4..aa702df0ff 100644 --- a/projects/angular/src/utils/index.ts +++ b/projects/angular/src/utils/index.ts @@ -8,7 +8,6 @@ export * from './animations/index'; export * from './loading/index'; export * from './conditional/index'; export * from './i18n/index'; -export * from './a11y/index'; export * from './drag-and-drop/index'; export * from './popover/index'; export * from './focus/focus-on-view-init/index';