chromium/ash/webui/common/resources/cr_elements/cr_view_manager/cr_view_manager.ts

// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// Forked from
// ui/webui/resources/cr_elements/cr_view_manager/cr_view_manager.ts

import {assert} from '//resources/js/assert.js';
import {PolymerElement} from '//resources/polymer/v3_0/polymer/polymer_bundled.min.js';

import {CrLazyRenderElement} from '../cr_lazy_render/cr_lazy_render.js';

import {getTemplate} from './cr_view_manager.html.js';

function getEffectiveView<T extends HTMLElement>(
    element: CrLazyRenderElement<T>|T): HTMLElement {
  return element.matches('cr-lazy-render') ?
      (element as CrLazyRenderElement<T>).get() :
      element;
}

function dispatchCustomEvent(element: Element, eventType: string) {
  element.dispatchEvent(
      new CustomEvent(eventType, {bubbles: true, composed: true}));
}

const viewAnimations: Map<string, (element: Element) => Promise<Animation>> =
    new Map();
viewAnimations.set('fade-in', element => {
  const animation = element.animate([{opacity: 0}, {opacity: 1}], {
    duration: 180,
    easing: 'ease-in-out',
    iterations: 1,
  });

  return animation.finished;
});
viewAnimations.set('fade-out', element => {
  const animation = element.animate([{opacity: 1}, {opacity: 0}], {
    duration: 180,
    easing: 'ease-in-out',
    iterations: 1,
  });

  return animation.finished;
});
viewAnimations.set('slide-in-fade-in-ltr', element => {
  const animation = element.animate(
      [
        {transform: 'translateX(-8px)', opacity: 0},
        {transform: 'translateX(0)', opacity: 1},
      ],
      {
        duration: 300,
        easing: 'cubic-bezier(0.0, 0.0, 0.2, 1)',
        fill: 'forwards',
        iterations: 1,
      });

  return animation.finished;
});
viewAnimations.set('slide-in-fade-in-rtl', element => {
  const animation = element.animate(
      [
        {transform: 'translateX(8px)', opacity: 0},
        {transform: 'translateX(0)', opacity: 1},
      ],
      {
        duration: 300,
        easing: 'cubic-bezier(0.0, 0.0, 0.2, 1)',
        fill: 'forwards',
        iterations: 1,
      });

  return animation.finished;
});

export class CrViewManagerElement extends PolymerElement {
  static get is() {
    return 'cr-view-manager';
  }

  static get template() {
    return getTemplate();
  }

  private exit_(element: HTMLElement, animation: string): Promise<void> {
    const animationFunction = viewAnimations.get(animation);
    element.classList.remove('active');
    element.classList.add('closing');
    dispatchCustomEvent(element, 'view-exit-start');
    if (!animationFunction) {
      // Nothing to animate. Immediately resolve.
      element.classList.remove('closing');
      dispatchCustomEvent(element, 'view-exit-finish');
      return Promise.resolve();
    }
    return animationFunction(element).then(() => {
      element.classList.remove('closing');
      dispatchCustomEvent(element, 'view-exit-finish');
    });
  }

  private enter_(view: HTMLElement, animation: string): Promise<void> {
    const animationFunction = viewAnimations.get(animation);
    const effectiveView = getEffectiveView(view);
    effectiveView.classList.add('active');
    dispatchCustomEvent(effectiveView, 'view-enter-start');
    if (!animationFunction) {
      // Nothing to animate. Immediately resolve.
      dispatchCustomEvent(effectiveView, 'view-enter-finish');
      return Promise.resolve();
    }
    return animationFunction(effectiveView).then(() => {
      dispatchCustomEvent(effectiveView, 'view-enter-finish');
    });
  }

  switchView(
      newViewId: string, enterAnimation?: string,
      exitAnimation?: string): Promise<void> {
    const previousView = this.querySelector<HTMLElement>('.active');
    const newView = this.querySelector<HTMLElement>('#' + newViewId);
    assert(!!newView);

    const promises = [];
    if (previousView) {
      promises.push(this.exit_(previousView, exitAnimation || 'fade-out'));
      promises.push(this.enter_(newView, enterAnimation || 'fade-in'));
    } else {
      promises.push(this.enter_(newView, 'no-animation'));
    }

    return Promise.all(promises).then(() => {});
  }
}

declare global {
  interface HTMLElementTagNameMap {
    'cr-view-manager': CrViewManagerElement;
  }
}

customElements.define(CrViewManagerElement.is, CrViewManagerElement);