From e95ff8a08c229567c8744d21e7e3ca6b4f3be691 Mon Sep 17 00:00:00 2001 From: "Andrew C. Dvorak" Date: Fri, 8 Feb 2019 16:16:54 -0800 Subject: [PATCH] feat(snackbar): Convert JS to TypeScript (#4363) Refs #4225 --- packages/mdc-list/adapter.ts | 3 +- packages/mdc-snackbar/README.md | 16 +- .../mdc-snackbar/{adapter.js => adapter.ts} | 43 ++--- .../{constants.js => constants.ts} | 25 ++- .../{foundation.js => foundation.ts} | 116 ++++-------- packages/mdc-snackbar/{index.js => index.ts} | 165 ++++++------------ packages/mdc-snackbar/types.ts | 25 +++ packages/mdc-snackbar/{util.js => util.ts} | 13 +- scripts/webpack/js-bundle-factory.js | 2 +- test/unit/mdc-snackbar/util.test.js | 23 ++- typings/dom.ie.d.ts | 2 +- 11 files changed, 169 insertions(+), 264 deletions(-) rename packages/mdc-snackbar/{adapter.js => adapter.ts} (68%) rename packages/mdc-snackbar/{constants.js => constants.ts} (99%) rename packages/mdc-snackbar/{foundation.js => foundation.ts} (69%) rename packages/mdc-snackbar/{index.js => index.ts} (55%) create mode 100644 packages/mdc-snackbar/types.ts rename packages/mdc-snackbar/{util.js => util.ts} (93%) diff --git a/packages/mdc-list/adapter.ts b/packages/mdc-list/adapter.ts index 048902210a8..ceb63a6fe0b 100644 --- a/packages/mdc-list/adapter.ts +++ b/packages/mdc-list/adapter.ts @@ -22,8 +22,7 @@ */ /** - * Adapter for MDC List. Provides an interface for managing focus. - * + * Defines the shape of the adapter expected by the foundation. * Implement this adapter for your framework of choice to delegate updates to * the component in your framework of choice. See architecture documentation * for more details. diff --git a/packages/mdc-snackbar/README.md b/packages/mdc-snackbar/README.md index d9bb1bc5ed1..e0abf37f82b 100644 --- a/packages/mdc-snackbar/README.md +++ b/packages/mdc-snackbar/README.md @@ -186,8 +186,8 @@ Event Name | `event.detail` | Description --- | --- | --- `MDCSnackbar:opening` | `{}` | Indicates when the snackbar begins its opening animation. `MDCSnackbar:opened` | `{}` | Indicates when the snackbar finishes its opening animation. -`MDCSnackbar:closing` | `{reason: ?string}` | Indicates when the snackbar begins its closing animation. `reason` contains the reason why the snackbar closed (`dismiss` or `action`). -`MDCSnackbar:closed` | `{reason: ?string}` | Indicates when the snackbar finishes its closing animation. `reason` contains the reason why the snackbar closed (`dismiss` or `action`). +`MDCSnackbar:closing` | `{reason?: string}` | Indicates when the snackbar begins its closing animation. `reason` contains the reason why the snackbar closed (`'dismiss'`, `'action'`, or `undefined`). +`MDCSnackbar:closed` | `{reason?: string}` | Indicates when the snackbar finishes its closing animation. `reason` contains the reason why the snackbar closed (`'dismiss'`, `'action'`, or `undefined`). ### Usage Within Frameworks @@ -202,8 +202,8 @@ Method Signature | Description `announce() => void` | Announces the snackbar's label text to screen reader users. `notifyOpening() => void` | Broadcasts an event denoting that the snackbar has just started opening. `notifyOpened() => void` | Broadcasts an event denoting that the snackbar has finished opening. -`notifyClosing(reason: string) {}` | Broadcasts an event denoting that the snackbar has just started closing. If a non-empty `reason` is passed, the event's `detail` object should include its value in the `reason` property. -`notifyClosed(reason: string) {}` | Broadcasts an event denoting that the snackbar has finished closing. If a non-empty `reason` is passed, the event's `detail` object should include its value in the `reason` property. +`notifyClosing(reason: string) => void` | Broadcasts an event denoting that the snackbar has just started closing. If a non-empty `reason` is passed, the event's `detail` object should include its value in the `reason` property. +`notifyClosed(reason: string) => void` | Broadcasts an event denoting that the snackbar has finished closing. If a non-empty `reason` is passed, the event's `detail` object should include its value in the `reason` property. #### `MDCSnackbarFoundation` Methods @@ -216,9 +216,9 @@ Method Signature | Description `setTimeoutMs(timeoutMs: number)` | Sets the automatic dismiss timeout in milliseconds. Value must be between `4000` and `10000` or an error will be thrown. `getCloseOnEscape() => boolean` | Returns whether the snackbar closes when it is focused and the user presses the ESC key. `setCloseOnEscape(closeOnEscape: boolean) => void` | Sets whether the snackbar closes when it is focused and the user presses the ESC key. -`handleKeyDown(event: !KeyEvent)` | Handles `keydown` events on or within the snackbar's root element. -`handleActionButtonClick(event: !MouseEvent)` | Handles `click` events on or within the action button. -`handleActionIconClick(event: !MouseEvent)` | Handles `click` events on or within the dismiss icon. +`handleKeyDown(event: KeyEvent)` | Handles `keydown` events on or within the snackbar's root element. +`handleActionButtonClick(event: MouseEvent)` | Handles `click` events on or within the action button. +`handleActionIconClick(event: MouseEvent)` | Handles `click` events on or within the dismiss icon. #### Event Handlers @@ -236,7 +236,7 @@ External frameworks and libraries can use the following utility methods from the Method Signature | Description --- | --- -`announce(ariaEl: !HTMLElement, labelEl: !HTMLElement=) => void` | Announces the label text to screen reader users. +`announce(ariaEl: Element, labelEl?: Element) => void` | Announces the label text to screen reader users. > Alternatively, frameworks can use [Closure Library's `goog.a11y.aria.Announcer#say()` method](https://github.com/google/closure-library/blob/bee9ced776b4700e8076a3466bd9d3f9ade2fb54/closure/goog/a11y/aria/announcer.js#L80). diff --git a/packages/mdc-snackbar/adapter.js b/packages/mdc-snackbar/adapter.ts similarity index 68% rename from packages/mdc-snackbar/adapter.js rename to packages/mdc-snackbar/adapter.ts index 3fa8e867d2f..e450d031154 100644 --- a/packages/mdc-snackbar/adapter.js +++ b/packages/mdc-snackbar/adapter.ts @@ -21,44 +21,21 @@ * THE SOFTWARE. */ -/* eslint no-unused-vars: [2, {"args": "none"}] */ - /** - * Adapter for MDC Snackbar. Provides an interface for managing: - * - CSS classes - * - Event handlers - * - * Additionally, provides type information for the adapter to the Closure - * compiler. - * + * Defines the shape of the adapter expected by the foundation. * Implement this adapter for your framework of choice to delegate updates to * the component in your framework of choice. See architecture documentation * for more details. * https://github.com/material-components/material-components-web/blob/master/docs/code/architecture.md - * - * @record */ -class MDCSnackbarAdapter { - /** @param {string} className */ - addClass(className) {} - - /** @param {string} className */ - removeClass(className) {} - - announce() {} - - notifyOpening() {} - notifyOpened() {} - - /** - * @param {string} reason - */ - notifyClosing(reason) {} - - /** - * @param {string} reason - */ - notifyClosed(reason) {} +interface MDCSnackbarAdapter { + addClass(className: string): void; + announce(): void; + notifyClosed(reason: string): void; + notifyClosing(reason: string): void; + notifyOpened(): void; + notifyOpening(): void; + removeClass(className: string): void; } -export default MDCSnackbarAdapter; +export {MDCSnackbarAdapter as default, MDCSnackbarAdapter}; diff --git a/packages/mdc-snackbar/constants.js b/packages/mdc-snackbar/constants.ts similarity index 99% rename from packages/mdc-snackbar/constants.js rename to packages/mdc-snackbar/constants.ts index 79c0681bd6e..65b95d3856b 100644 --- a/packages/mdc-snackbar/constants.js +++ b/packages/mdc-snackbar/constants.ts @@ -22,36 +22,33 @@ */ const cssClasses = { - OPENING: 'mdc-snackbar--opening', - OPEN: 'mdc-snackbar--open', CLOSING: 'mdc-snackbar--closing', + OPEN: 'mdc-snackbar--open', + OPENING: 'mdc-snackbar--opening', }; const strings = { - SURFACE_SELECTOR: '.mdc-snackbar__surface', - LABEL_SELECTOR: '.mdc-snackbar__label', ACTION_SELECTOR: '.mdc-snackbar__action', + ARIA_LIVE_LABEL_TEXT_ATTR: 'data-mdc-snackbar-label-text', + CLOSED_EVENT: 'MDCSnackbar:closed', + CLOSING_EVENT: 'MDCSnackbar:closing', DISMISS_SELECTOR: '.mdc-snackbar__dismiss', - - OPENING_EVENT: 'MDCSnackbar:opening', + LABEL_SELECTOR: '.mdc-snackbar__label', OPENED_EVENT: 'MDCSnackbar:opened', - CLOSING_EVENT: 'MDCSnackbar:closing', - CLOSED_EVENT: 'MDCSnackbar:closed', - + OPENING_EVENT: 'MDCSnackbar:opening', REASON_ACTION: 'action', REASON_DISMISS: 'dismiss', - - ARIA_LIVE_LABEL_TEXT_ATTR: 'data-mdc-snackbar-label-text', + SURFACE_SELECTOR: '.mdc-snackbar__surface', }; const numbers = { - MIN_AUTO_DISMISS_TIMEOUT_MS: 4000, - MAX_AUTO_DISMISS_TIMEOUT_MS: 10000, DEFAULT_AUTO_DISMISS_TIMEOUT_MS: 5000, + MAX_AUTO_DISMISS_TIMEOUT_MS: 10000, + MIN_AUTO_DISMISS_TIMEOUT_MS: 4000, // These variables need to be kept in sync with the values in _variables.scss. - SNACKBAR_ANIMATION_OPEN_TIME_MS: 150, SNACKBAR_ANIMATION_CLOSE_TIME_MS: 75, + SNACKBAR_ANIMATION_OPEN_TIME_MS: 150, /** * Number of milliseconds to wait between temporarily clearing the label text diff --git a/packages/mdc-snackbar/foundation.js b/packages/mdc-snackbar/foundation.ts similarity index 69% rename from packages/mdc-snackbar/foundation.js rename to packages/mdc-snackbar/foundation.ts index f32da6a171d..adf3c7cff12 100644 --- a/packages/mdc-snackbar/foundation.js +++ b/packages/mdc-snackbar/foundation.ts @@ -21,16 +21,14 @@ * THE SOFTWARE. */ -/* eslint no-unused-vars: ["error", {"argsIgnorePattern": "evt", "varsIgnorePattern": "Adapter$"}] */ - import {MDCFoundation} from '@material/base/foundation'; -import MDCSnackbarAdapter from './adapter'; +import {MDCSnackbarAdapter} from './adapter'; import {cssClasses, numbers, strings} from './constants'; const {OPENING, OPEN, CLOSING} = cssClasses; const {REASON_ACTION, REASON_DISMISS} = strings; -class MDCSnackbarFoundation extends MDCFoundation { +class MDCSnackbarFoundation extends MDCFoundation { static get cssClasses() { return cssClasses; } @@ -43,44 +41,27 @@ class MDCSnackbarFoundation extends MDCFoundation { return numbers; } - /** - * @return {!MDCSnackbarAdapter} - */ - static get defaultAdapter() { - return /** @type {!MDCSnackbarAdapter} */ ({ - addClass: (/* className: string */) => {}, - removeClass: (/* className: string */) => {}, - announce: () => {}, - notifyOpening: () => {}, - notifyOpened: () => {}, - notifyClosing: (/* reason: string */) => {}, - notifyClosed: (/* reason: string */) => {}, - }); + static get defaultAdapter(): MDCSnackbarAdapter { + return { + addClass: () => undefined, + announce: () => undefined, + notifyClosed: () => undefined, + notifyClosing: () => undefined, + notifyOpened: () => undefined, + notifyOpening: () => undefined, + removeClass: () => undefined, + }; } - /** - * @param {!MDCSnackbarAdapter=} adapter - */ - constructor(adapter) { - super(Object.assign(MDCSnackbarFoundation.defaultAdapter, adapter)); - - /** @private {boolean} */ - this.isOpen_ = false; + private isOpen_ = false; + private animationFrame_ = 0; + private animationTimer_ = 0; + private autoDismissTimer_ = 0; + private autoDismissTimeoutMs_ = numbers.DEFAULT_AUTO_DISMISS_TIMEOUT_MS; + private closeOnEscape_ = true; - /** @private {number} */ - this.animationFrame_ = 0; - - /** @private {number} */ - this.animationTimer_ = 0; - - /** @private {number} */ - this.autoDismissTimer_ = 0; - - /** @private {number} */ - this.autoDismissTimeoutMs_ = numbers.DEFAULT_AUTO_DISMISS_TIMEOUT_MS; - - /** @private {boolean} */ - this.closeOnEscape_ = true; + constructor(adapter?: MDCSnackbarAdapter) { + super(Object.assign(MDCSnackbarFoundation.defaultAdapter, adapter)); } destroy() { @@ -117,7 +98,7 @@ class MDCSnackbarFoundation extends MDCFoundation { } /** - * @param {string=} reason Why the snackbar was closed. Value will be passed to CLOSING_EVENT and CLOSED_EVENT via the + * @param reason Why the snackbar was closed. Value will be passed to CLOSING_EVENT and CLOSED_EVENT via the * `event.detail.reason` property. Standard values are REASON_ACTION and REASON_DISMISS, but custom * client-specific values may also be used if desired. */ @@ -144,24 +125,15 @@ class MDCSnackbarFoundation extends MDCFoundation { }, numbers.SNACKBAR_ANIMATION_CLOSE_TIME_MS); } - /** - * @return {boolean} - */ - isOpen() { + isOpen(): boolean { return this.isOpen_; } - /** - * @return {number} - */ - getTimeoutMs() { + getTimeoutMs(): number { return this.autoDismissTimeoutMs_; } - /** - * @param {number} timeoutMs - */ - setTimeoutMs(timeoutMs) { + setTimeoutMs(timeoutMs: number) { // Use shorter variable names to make the code more readable const minValue = numbers.MIN_AUTO_DISMISS_TIMEOUT_MS; const maxValue = numbers.MAX_AUTO_DISMISS_TIMEOUT_MS; @@ -173,51 +145,35 @@ class MDCSnackbarFoundation extends MDCFoundation { } } - /** - * @return {boolean} - */ - getCloseOnEscape() { + getCloseOnEscape(): boolean { return this.closeOnEscape_; } - /** - * @param {boolean} closeOnEscape - */ - setCloseOnEscape(closeOnEscape) { + setCloseOnEscape(closeOnEscape: boolean) { this.closeOnEscape_ = closeOnEscape; } - /** - * @param {!KeyboardEvent} evt - */ - handleKeyDown(evt) { - if (this.getCloseOnEscape() && (evt.key === 'Escape' || evt.keyCode === 27)) { + handleKeyDown(evt: KeyboardEvent) { + const isEscapeKey = evt.key === 'Escape' || evt.keyCode === 27; + if (isEscapeKey && this.getCloseOnEscape()) { this.close(REASON_DISMISS); } } - /** - * @param {!MouseEvent} evt - */ - handleActionButtonClick(evt) { + handleActionButtonClick(_evt: MouseEvent) { this.close(REASON_ACTION); } - /** - * @param {!MouseEvent} evt - */ - handleActionIconClick(evt) { + handleActionIconClick(_evt: MouseEvent) { this.close(REASON_DISMISS); } - /** @private */ - clearAutoDismissTimer_() { + private clearAutoDismissTimer_() { clearTimeout(this.autoDismissTimer_); this.autoDismissTimer_ = 0; } - /** @private */ - handleAnimationTimerEnd_() { + private handleAnimationTimerEnd_() { this.animationTimer_ = 0; this.adapter_.removeClass(cssClasses.OPENING); this.adapter_.removeClass(cssClasses.CLOSING); @@ -225,10 +181,8 @@ class MDCSnackbarFoundation extends MDCFoundation { /** * Runs the given logic on the next animation frame, using setTimeout to factor in Firefox reflow behavior. - * @param {Function} callback - * @private */ - runNextAnimationFrame_(callback) { + private runNextAnimationFrame_(callback: () => void) { cancelAnimationFrame(this.animationFrame_); this.animationFrame_ = requestAnimationFrame(() => { this.animationFrame_ = 0; @@ -238,4 +192,4 @@ class MDCSnackbarFoundation extends MDCFoundation { } } -export default MDCSnackbarFoundation; +export {MDCSnackbarFoundation as default, MDCSnackbarFoundation}; diff --git a/packages/mdc-snackbar/index.js b/packages/mdc-snackbar/index.ts similarity index 55% rename from packages/mdc-snackbar/index.js rename to packages/mdc-snackbar/index.ts index e0ff9b7bcf2..204ab5b9041 100644 --- a/packages/mdc-snackbar/index.js +++ b/packages/mdc-snackbar/index.ts @@ -21,61 +21,48 @@ * THE SOFTWARE. */ +import {SpecificEventListener} from '@material/base'; import {MDCComponent} from '@material/base/component'; -import MDCSnackbarFoundation from './foundation'; +import * as ponyfill from '@material/dom/ponyfill'; import {strings} from './constants'; +import {MDCSnackbarFoundation} from './foundation'; +import {Announcer, AnnouncerFactory} from './types'; import * as util from './util'; -import * as ponyfill from '@material/dom/ponyfill'; const { SURFACE_SELECTOR, LABEL_SELECTOR, ACTION_SELECTOR, DISMISS_SELECTOR, OPENING_EVENT, OPENED_EVENT, CLOSING_EVENT, CLOSED_EVENT, } = strings; -class MDCSnackbar extends MDCComponent { - static attachTo(root) { +class MDCSnackbar extends MDCComponent { + static attachTo(root: Element) { return new MDCSnackbar(root); } - constructor(...args) { - super(...args); - - /** @type {!HTMLElement} */ - this.surfaceEl_; - - /** @type {!HTMLElement} */ - this.labelEl_; + private announce_!: Announcer; // assigned in initialize() - /** @type {!HTMLElement} */ - this.actionEl_; + private actionEl_!: Element; // assigned in initialSyncWithDOM() + private labelEl_!: Element; // assigned in initialSyncWithDOM() + private surfaceEl_!: Element; // assigned in initialSyncWithDOM() - /** @type {function(!HTMLElement, !HTMLElement=): void} */ - this.announce_; + private handleKeyDown_!: SpecificEventListener<'keydown'>; // assigned in initialSyncWithDOM() + private handleSurfaceClick_!: SpecificEventListener<'click'>; // assigned in initialSyncWithDOM() - /** @private {!Function} */ - this.handleKeyDown_; - - /** @private {!Function} */ - this.handleSurfaceClick_; - } - - /** - * @param {function(): function(!HTMLElement, !HTMLElement=):void} announceFactory - */ - initialize(announceFactory = () => util.announce) { - this.announce_ = announceFactory(); + initialize(announcerFactory: AnnouncerFactory = () => util.announce) { + this.announce_ = announcerFactory(); } initialSyncWithDOM() { - this.surfaceEl_ = /** @type {!HTMLElement} */ (this.root_.querySelector(SURFACE_SELECTOR)); - this.labelEl_ = /** @type {!HTMLElement} */ (this.root_.querySelector(LABEL_SELECTOR)); - this.actionEl_ = /** @type {!HTMLElement} */ (this.root_.querySelector(ACTION_SELECTOR)); + this.surfaceEl_ = this.root_.querySelector(SURFACE_SELECTOR)!; + this.labelEl_ = this.root_.querySelector(LABEL_SELECTOR)!; + this.actionEl_ = this.root_.querySelector(ACTION_SELECTOR)!; this.handleKeyDown_ = (evt) => this.foundation_.handleKeyDown(evt); this.handleSurfaceClick_ = (evt) => { - if (this.isActionButton_(evt.target)) { + const target = evt.target as Element; + if (this.isActionButton_(target)) { this.foundation_.handleActionButtonClick(evt); - } else if (this.isActionIcon_(evt.target)) { + } else if (this.isActionIcon_(target)) { this.foundation_.handleActionIconClick(evt); } }; @@ -95,7 +82,7 @@ class MDCSnackbar extends MDCComponent { } /** - * @param {string=} reason Why the snackbar was closed. Value will be passed to CLOSING_EVENT and CLOSED_EVENT via the + * @param reason Why the snackbar was closed. Value will be passed to CLOSING_EVENT and CLOSED_EVENT via the * `event.detail.reason` property. Standard values are REASON_ACTION and REASON_DISMISS, but custom * client-specific values may also be used if desired. */ @@ -103,134 +90,82 @@ class MDCSnackbar extends MDCComponent { this.foundation_.close(reason); } - /** - * @return {!MDCSnackbarFoundation} - */ getDefaultFoundation() { - /* eslint brace-style: "off" */ return new MDCSnackbarFoundation({ addClass: (className) => this.root_.classList.add(className), - removeClass: (className) => this.root_.classList.remove(className), announce: () => this.announce_(this.labelEl_), - notifyOpening: () => this.emit(OPENING_EVENT, {}), - notifyOpened: () => this.emit(OPENED_EVENT, {}), - notifyClosing: (reason) => this.emit(CLOSING_EVENT, reason ? {reason} : {}), notifyClosed: (reason) => this.emit(CLOSED_EVENT, reason ? {reason} : {}), + notifyClosing: (reason) => this.emit(CLOSING_EVENT, reason ? {reason} : {}), + notifyOpened: () => this.emit(OPENED_EVENT, {}), + notifyOpening: () => this.emit(OPENING_EVENT, {}), + removeClass: (className) => this.root_.classList.remove(className), }); } - /** - * @return {number} - */ - get timeoutMs() { + get timeoutMs(): number { return this.foundation_.getTimeoutMs(); } - /** - * @param {number} timeoutMs - */ - set timeoutMs(timeoutMs) { + set timeoutMs(timeoutMs: number) { this.foundation_.setTimeoutMs(timeoutMs); } - /** - * @return {boolean} - */ - get closeOnEscape() { + get closeOnEscape(): boolean { return this.foundation_.getCloseOnEscape(); } - /** - * @param {boolean} closeOnEscape - */ - set closeOnEscape(closeOnEscape) { + set closeOnEscape(closeOnEscape: boolean) { this.foundation_.setCloseOnEscape(closeOnEscape); } - /** - * @return {boolean} - */ - get isOpen() { + get isOpen(): boolean { return this.foundation_.isOpen(); } - /** - * @return {string} - */ - get labelText() { - return this.labelEl_.textContent; + get labelText(): string { + // This property only returns null if the node is a document, DOCTYPE, or notation. + // On Element nodes, it always returns a string. + return this.labelEl_.textContent!; } - /** - * @param {string} labelText - */ - set labelText(labelText) { + set labelText(labelText: string) { this.labelEl_.textContent = labelText; } - /** - * @return {string} - */ - get actionButtonText() { - return this.actionEl_.textContent; + get actionButtonText(): string { + return this.actionEl_.textContent!; } - /** - * @param {string} actionButtonText - */ - set actionButtonText(actionButtonText) { + set actionButtonText(actionButtonText: string) { this.actionEl_.textContent = actionButtonText; } - /** - * @param {!Function} handler - * @private - */ - registerKeyDownHandler_(handler) { + private registerKeyDownHandler_(handler: SpecificEventListener<'keydown'>) { this.listen('keydown', handler); } - /** - * @param {!Function} handler - * @private - */ - deregisterKeyDownHandler_(handler) { + private deregisterKeyDownHandler_(handler: SpecificEventListener<'keydown'>) { this.unlisten('keydown', handler); } - /** - * @param {!Function} handler - * @private - */ - registerSurfaceClickHandler_(handler) { - this.surfaceEl_.addEventListener('click', handler); + private registerSurfaceClickHandler_(handler: SpecificEventListener<'click'>) { + this.surfaceEl_.addEventListener('click', handler as EventListener); } - /** - * @param {!Function} handler - * @private - */ - deregisterSurfaceClickHandler_(handler) { - this.surfaceEl_.removeEventListener('click', handler); + private deregisterSurfaceClickHandler_(handler: SpecificEventListener<'click'>) { + this.surfaceEl_.removeEventListener('click', handler as EventListener); } - /** - * @param {!Element} target - * @return {boolean} - * @private - */ - isActionButton_(target) { + private isActionButton_(target: Element): boolean { return Boolean(ponyfill.closest(target, ACTION_SELECTOR)); } - /** - * @param {!Element} target - * @return {boolean} - * @private - */ - isActionIcon_(target) { + private isActionIcon_(target: Element): boolean { return Boolean(ponyfill.closest(target, DISMISS_SELECTOR)); } } -export {MDCSnackbar, MDCSnackbarFoundation, util}; +export {MDCSnackbar as default, MDCSnackbar, util}; +export * from './adapter'; +export * from './foundation'; +export * from './types'; diff --git a/packages/mdc-snackbar/types.ts b/packages/mdc-snackbar/types.ts new file mode 100644 index 00000000000..e5ed7555b44 --- /dev/null +++ b/packages/mdc-snackbar/types.ts @@ -0,0 +1,25 @@ +/** + * @license + * Copyright 2019 Google Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +export type Announcer = (ariaEl: Element, labelEl?: Element) => void; +export type AnnouncerFactory = () => Announcer; diff --git a/packages/mdc-snackbar/util.js b/packages/mdc-snackbar/util.ts similarity index 93% rename from packages/mdc-snackbar/util.js rename to packages/mdc-snackbar/util.ts index addbe60b293..8a2e0815286 100644 --- a/packages/mdc-snackbar/util.js +++ b/packages/mdc-snackbar/util.ts @@ -26,14 +26,13 @@ import {numbers, strings} from './constants'; const {ARIA_LIVE_DELAY_MS} = numbers; const {ARIA_LIVE_LABEL_TEXT_ATTR} = strings; -/** - * @param {!HTMLElement} ariaEl - * @param {!HTMLElement=} labelEl - */ -function announce(ariaEl, labelEl = ariaEl) { +function announce(ariaEl: Element, labelEl: Element = ariaEl) { const priority = ariaEl.getAttribute('aria-live'); - const labelText = labelEl.textContent.trim(); // Ignore ` ` (see below) - if (!labelText) { + + // Trim text to ignore ` ` (see below). + // textContent is only null if the node is a document, DOCTYPE, or notation. + const labelText = labelEl.textContent!.trim(); + if (!labelText || !priority) { return; } diff --git a/scripts/webpack/js-bundle-factory.js b/scripts/webpack/js-bundle-factory.js index 3cbfe2e2263..f91236916ed 100644 --- a/scripts/webpack/js-bundle-factory.js +++ b/scripts/webpack/js-bundle-factory.js @@ -176,7 +176,7 @@ class JsBundleFactory { select: getAbsolutePath('/packages/mdc-select/index.js'), selectionControl: getAbsolutePath('/packages/mdc-selection-control/index.ts'), slider: getAbsolutePath('/packages/mdc-slider/index.ts'), - snackbar: getAbsolutePath('/packages/mdc-snackbar/index.js'), + snackbar: getAbsolutePath('/packages/mdc-snackbar/index.ts'), switch: getAbsolutePath('/packages/mdc-switch/index.ts'), tab: getAbsolutePath('/packages/mdc-tab/index.js'), tabBar: getAbsolutePath('/packages/mdc-tab-bar/index.js'), diff --git a/test/unit/mdc-snackbar/util.test.js b/test/unit/mdc-snackbar/util.test.js index 06e2ed4df7e..e5d26155ff0 100644 --- a/test/unit/mdc-snackbar/util.test.js +++ b/test/unit/mdc-snackbar/util.test.js @@ -105,7 +105,7 @@ test('#announce second argument is optional', () => { clock.tick(ARIA_LIVE_DELAY_MS); assert.equal(ariaEl.textContent, labelText); - assert.equal(ariaEl.getAttribute(ARIA_LIVE_LABEL_TEXT_ATTR), null); + assert.isNull(ariaEl.getAttribute(ARIA_LIVE_LABEL_TEXT_ATTR)); assert.equal(ariaEl.getAttribute('aria-live'), 'polite'); }); @@ -123,6 +123,25 @@ test('#announce does nothing if textContent is empty', () => { util.announce(ariaEl, labelEl); assert.equal(labelEl.textContent.trim(), ''); - assert.equal(labelEl.getAttribute(ARIA_LIVE_LABEL_TEXT_ATTR), null); + assert.isNull(labelEl.getAttribute(ARIA_LIVE_LABEL_TEXT_ATTR)); assert.equal(ariaEl.getAttribute('aria-live'), 'polite'); }); + +test('#announce does nothing if aria-live was not present', () => { + const fixture = bel` +
+
Foo
+
`; + const clock = installClock(); + const ariaEl = fixture.querySelector('.aria'); + + util.announce(ariaEl); + + assert.isNull(ariaEl.getAttribute('aria-live')); + assert.isNull(ariaEl.getAttribute(ARIA_LIVE_LABEL_TEXT_ATTR)); + + clock.tick(ARIA_LIVE_DELAY_MS); + + assert.isNull(ariaEl.getAttribute('aria-live')); + assert.isNull(ariaEl.getAttribute(ARIA_LIVE_LABEL_TEXT_ATTR)); +}); diff --git a/typings/dom.ie.d.ts b/typings/dom.ie.d.ts index 3ea2ebc26b9..0ff50d00190 100644 --- a/typings/dom.ie.d.ts +++ b/typings/dom.ie.d.ts @@ -23,4 +23,4 @@ declare interface Element { msMatchesSelector?: (selector: string) => boolean; -} \ No newline at end of file +}