chromium/ui/webui/resources/cr_elements/cr_toast/cr_toast.ts

// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
 * @fileoverview A lightweight toast.
 */
import {CrLitElement} from '//resources/lit/v3_0/lit.rollup.js';
import type {PropertyValues} from '//resources/lit/v3_0/lit.rollup.js';

import {getCss} from './cr_toast.css.js';
import {getHtml} from './cr_toast.html.js';

export class CrToastElement extends CrLitElement {
  static get is() {
    return 'cr-toast';
  }

  static override get styles() {
    return getCss();
  }

  override render() {
    return getHtml.bind(this)();
  }

  static override get properties() {
    return {
      duration: {
        type: Number,
      },

      open: {
        type: Boolean,
        reflect: true,
      },
    };
  }

  duration: number = 0;
  open: boolean = false;
  private hideTimeoutId_: number|null = null;

  override willUpdate(changedProperties: PropertyValues<this>) {
    super.willUpdate(changedProperties);

    if (changedProperties.has('duration') || changedProperties.has('open')) {
      this.resetAutoHide_();
    }
  }

  /**
   * Cancels existing auto-hide, and sets up new auto-hide.
   */
  private resetAutoHide_() {
    if (this.hideTimeoutId_ !== null) {
      window.clearTimeout(this.hideTimeoutId_);
      this.hideTimeoutId_ = null;
    }

    if (this.open && this.duration !== 0) {
      this.hideTimeoutId_ = window.setTimeout(() => {
        this.hide();
      }, this.duration);
    }
  }

  /**
   * Shows the toast and auto-hides after |this.duration| milliseconds has
   * passed. If the toast is currently being shown, any preexisting auto-hide
   * is cancelled and replaced with a new auto-hide.
   */
  async show() {
    // Force autohide to reset if calling show on an already shown toast.
    const shouldResetAutohide = this.open;

    // The role attribute is removed first so that screen readers to better
    // ensure that screen readers will read out the content inside the toast.
    // If the role is not removed and re-added back in, certain screen readers
    // do not read out the contents, especially if the text remains exactly
    // the same as a previous toast.
    this.removeAttribute('role');

    // Reset the aria-hidden attribute as screen readers need to access the
    // contents of an opened toast.
    this.removeAttribute('aria-hidden');

    this.open = true;
    await this.updateComplete;
    this.setAttribute('role', 'alert');

    if (shouldResetAutohide) {
      this.resetAutoHide_();
    }
  }

  /**
   * Hides the toast and ensures that screen readers cannot its contents while
   * hidden.
   */
  async hide() {
    this.setAttribute('aria-hidden', 'true');
    this.open = false;
    await this.updateComplete;
  }
}

declare global {
  interface HTMLElementTagNameMap {
    'cr-toast': CrToastElement;
  }
}

customElements.define(CrToastElement.is, CrToastElement);