Commit 9bde8d25 authored by Roman Aleksandrov's avatar Roman Aleksandrov Committed by Commit Bot

OOBE components: Add oobe-carousel.

Add new polymer class oobe-carousel. Oobe-carousel is responsible for
showing slides in a carousel.

- Animation is done as all slides stand in a circle.
- Navigation bar consists of left and right arrows and non-accessible
  dots which indicate an active slide.
- Autotransition is supported only if ChromeVox is not enabled.

Fixed: 1104887
Change-Id: I5c2332d6cde470479c13d2297458213deb0faa4c
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2294664
Commit-Queue: Roman Aleksandrov <raleksandrov@google.com>
Reviewed-by: default avatarDenis Kuznetsov [CET] <antrim@chromium.org>
Cr-Commit-Position: refs/heads/master@{#799210}
parent 2809634b
......@@ -13,6 +13,7 @@ js_type_check("closure_compile") {
":login_screen_behavior",
":multi_step_behavior",
":oobe_buttons",
":oobe_carousel",
":oobe_dialog",
":oobe_dialog_host_behavior",
":oobe_help_dialog",
......@@ -72,6 +73,9 @@ js_library("oobe_help_dialog") {
deps = [ ":oobe_i18n_behavior" ]
}
js_library("oobe_carousel") {
}
js_library("hd-iron-icon") {
}
......
<!-- Copyright 2020 The Chromium Authors. All rights reserved.
Use of this source code is governed by a BSD-style license that can be
found in the LICENSE file. -->
<link rel="import" href="chrome://resources/html/polymer.html">
<link rel="import" href="chrome://resources/cr_elements/cr_icon_button/cr_icon_button.html">
<link rel="import" href="chrome://resources/cr_elements/icons.html">
<!--
Simple OOBE polymer element which is used for displaying slides in a carousel.
It has fixed height and width which match general width and height oobe-dialog
restrictions.
Example (each slide must be within a oobe-slide tag):
<oobe-carousel slide-duration="2">
<div slot="slides">Slide 1</div>
<div slot="slides">Slide 2</div>
<div slot="slides">Slide 3</div>
</oobe-carousel>
-->
<dom-module id="oobe-carousel">
<template>
<style>
:host {
width: 100%;
}
#container {
height: 350px;
overflow: hidden;
position: relative;
white-space: nowrap;
width: 100%;
}
#slidesContainer {
background: white;
height: 350px;
position: relative;
width: 100%;
will-change: transform;
}
#slidesContainer ::slotted(*) {
height: 350px;
overflow-x: hidden;
position: absolute;
width: 100%;
}
#navigationContainer {
bottom: 8px;
display: flex;
left: 50%;
position: absolute;
transform: translateX(-50%);
}
#dotsContainer {
display: flex;
}
::slotted(.animated) {
/* cubic-bezier(.51, .92, .24, 1) makes animation of transition similar
to basic slide rotation. */
transition: transform 1.0s cubic-bezier(.51, .92, .24, 1);
}
::slotted(.backward) {
transform: var(--oobe-backward-slide-animation);
}
::slotted(.forward) {
transform: var(--oobe-forward-slide-animation);
}
.buttons {
height: 20px;
margin-inline-end: 0;
margin-inline-start: 0;
width: 20px;
}
.slider-dot {
background: var(--google-grey-200);
border-radius: 5px;
height: 10px;
margin: 5px;
pointer-events: none;
transition: background 1s;
width: 10px;
}
.slider-dot[active] {
background: var(--google-blue-600);
}
</style>
<div id="container">
<div id="slidesContainer">
<slot id="slot" name="slides"></slot>
</div>
<div id="navigationContainer">
<cr-icon-button id="left" class="buttons" iron-icon="cr:chevron-left"
on-click="movePrev"></cr-icon-button>
<div id="dotsContainer">
<template is="dom-repeat" items="[[dots]]">
<span class="slider-dot" aria-hidden="true"
tabindex="-1" active$="[[isActive_(index, slideIndex)]]"></span>
</template>
</div>
<cr-icon-button id="right" class="buttons" iron-icon="cr:chevron-right"
on-click="moveNext"></cr-icon-button>
</div>
</div>
</template>
</dom-module>
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
Polymer({
is: 'oobe-carousel',
properties: {
/**
* Current slide index.
*/
slideIndex: {
type: Number,
value: 0,
observer: 'onSlideIndexChanged_',
},
/**
* Controls if slides will be rotated automatically.
* Note: This feature is not a11y friendly and will cause bugs when
* ChromeVox is turned on. There is a need to observe ChromeVox and stop
* auto transition when ChromeVox is turned on.
*/
autoTransition: {
type: Boolean,
value: false,
observer: 'restartAutoTransition_',
},
/**
* Number of seconds each slide should remain for.
*/
slideDurationInSeconds: {
type: Number,
value: 10,
observer: 'restartAutoTransition_',
},
},
/**
* Array for storing number leading up to totalSlides
* @type {Array<number>}
*/
dots: [],
/**
* Array of slotted slides.
* @type {Array<HTMLElement>}
*/
slides: [],
/**
* Total number of slides.
* @type {number}
*/
totalSlides: 0,
/**
* ID of the timer which rotates slides.
* @type {number|null}
*/
timerID: null,
/** @type {function(Event)} */
removeAnimateToHandler: Function,
/** @type {function(Event)} */
removeAnimateFromHandler: Function,
attached() {
this.removeAnimateToHandler = this.removeAnimateTo_.bind(this);
this.removeAnimateFromHandler = this.removeAnimateFrom_.bind(this);
this.countSlides_();
this.restartAutoTransition_();
this.hideNonActiveSlides_();
},
/**
* @private
* Count slides and create dots.
*/
countSlides_() {
this.slides = this.$.slot.assignedElements();
this.totalSlides = this.slides.length;
let array = [];
for (let i = 0; i < this.totalSlides; ++i) {
array.push(i);
}
this.dots = array;
},
/**
* @private
*/
hideNonActiveSlides_() {
for (let idx = 0; idx < this.totalSlides; ++idx) {
if (idx != this.slideIndex)
this.hideSlide(this.slides[idx]);
}
},
/**
* @private
* Re-inits timer which rotates slides if |autorotation| is set.
*/
restartAutoTransition_() {
this.stopAutoTransition_();
this.startAutoTransition_();
},
/**
* @private
* Inits timer which rotates slides if |autoTransition| is set.
*/
startAutoTransition_() {
if (this.autoTransition && this.slideDurationInSeconds != null) {
this.timerID = setInterval(
this.moveNext.bind(this), (this.slideDurationInSeconds * 1000));
}
},
/**
* @private
* Stops timer which rotates slides.
*/
stopAutoTransition_() {
if (this.timerID != null) {
clearInterval(this.timerID);
this.timerID = null;
}
},
/**
* @private
* @param {number} toIndex Index of slide which should be shown.
* @param {number|undefined} fromIndex Index of slide which should be hidden.
* Method which moves slides to show active one.
*/
animateSlides_(toIndex, fromIndex) {
if (fromIndex == 0 && toIndex == this.totalSlides - 1) {
this.animateInternal_(toIndex, fromIndex, false);
return;
}
if (fromIndex == this.totalSlides - 1 && toIndex == 0) {
this.animateInternal_(toIndex, fromIndex, true);
return;
}
if (toIndex < fromIndex) {
this.animateInternal_(toIndex, fromIndex, false);
}
if (toIndex > fromIndex) {
this.animateInternal_(toIndex, fromIndex, true);
}
},
/**
* @private
* @param {number} toIndex Index of slide which should be shown.
* @param {number|undefined} fromIndex Index of slide which should be hidden.
* @param {boolean} forward Show forward animation or backward.
* Method which moves slides to show active one using set direction.
*/
animateInternal_(toIndex, fromIndex, forward) {
if (forward) {
this.animateInternalWithStyles_(
toIndex, fromIndex, 'forward', 'backward');
} else {
this.animateInternalWithStyles_(
toIndex, fromIndex, 'backward', 'forward');
}
},
/**
* @private
* @param {EventTarget|null} slide
*/
hideSlide(slide) {
slide.setAttribute('aria-hidden', 'true');
slide.hidden = true;
},
/**
* @private
* @param {EventTarget|null} slide
*/
showSlide(slide) {
slide.removeAttribute('aria-hidden');
slide.hidden = false;
},
/**
* @private
* @param {EventTarget|null} slide
*/
cleanStyles(slide) {
slide.classList.remove('animated');
slide.classList.remove('forward');
slide.classList.remove('backward');
},
/**
* @private
* @param {Event} event transitionend event.
*/
removeAnimateTo_(event) {
const toElement = event.target;
this.cleanStyles(toElement);
toElement.removeEventListener('transitionend', this.removeAnimateToHandler);
},
/**
* @private
* @param {Event} event transitionend event.
*/
removeAnimateFrom_(event) {
const fromElement = event.target;
this.hideSlide(fromElement);
this.cleanStyles(fromElement);
fromElement.removeEventListener(
'transitionend', this.removeAnimateFromHandler);
},
/**
* @private
* @param {number} fromIndex Index of slide which should be hidden.
* Clean the state the could be uncertain due to several transitions started
* one after another without waiting for their end.
*/
cleanUpState_(fromIndex) {
for (let idx = 0; idx < this.totalSlides; ++idx) {
const slide = this.slides[idx];
slide.removeEventListener('transitionend', this.removeAnimateFromHandler);
slide.removeEventListener('transitionend', this.removeAnimateToHandler);
if (idx != fromIndex) {
this.hideSlide(slide);
}
this.cleanStyles(slide);
}
this.showSlide(this.slides[fromIndex]);
},
/**
* @private
* @param {number} toIndex Index of slide which should be shown.
* @param {number|undefined} fromIndex Index of slide which should be hidden.
* @param {string} toStyle CSS class to apply on the slide to be shown.
* @param {string} fromStyle CSS class to apply on the slide to be hidden.
* @suppress {uselessCode} To avoid error on calling void operator.
* Animates slides using given CSS class animation.
*/
animateInternalWithStyles_(toIndex, fromIndex, toStyle, fromStyle) {
if (fromIndex == null) {
return;
}
this.cleanUpState_(fromIndex);
const toElement = this.slides[toIndex];
const fromElement = this.slides[fromIndex];
toElement.removeAttribute('aria-hidden');
toElement.removeAttribute('hidden');
toElement.classList.add(toStyle);
// Call offsetWidth to apply |toStyle| and render |toElement|.
void toElement.offsetWidth;
toElement.classList.add('animated');
fromElement.classList.add('animated');
toElement.classList.remove(toStyle);
fromElement.classList.add(fromStyle);
toElement.addEventListener('transitionend', this.removeAnimateToHandler);
fromElement.addEventListener(
'transitionend', this.removeAnimateFromHandler);
},
/**
* @private
* @param {number} index Index of dot.
* Returns whether a dot is active.
*/
isActive_(index) {
return index == this.slideIndex;
},
/**
* @private
* @param {number} toIndex Index of slide which should be shown.
* @param {number|undefined} fromIndex Index of slide which should be hidden.
* Observe index change and activates all animation and slides attributes
* changes.
*/
onSlideIndexChanged_(toIndex, fromIndex) {
this.restartAutoTransition_();
this.animateSlides_(toIndex, fromIndex);
},
moveNext() {
this.slideIndex = (this.slideIndex + 1) % this.totalSlides;
},
movePrev() {
this.slideIndex =
(this.slideIndex + this.totalSlides - 1) % this.totalSlides;
},
});
......@@ -11,6 +11,7 @@
<include src="../components/oobe_help_dialog.html">
<include src="../components/html-echo.html">
<include src="../components/progress_list_item.html">
<include src="../components/oobe_carousel.html">
<include src="../throbber_notice.html">
<include src="../notification_card.html">
......
......@@ -15,6 +15,7 @@
// <include src="../components/oobe_help_dialog.js">
// <include src="../components/html-echo.js">
// <include src="../components/progress_list_item.js">
// <include src="../components/oobe_carousel.js">
// <include src="../throbber_notice.js">
// <include src="../notification_card.js">
......
......@@ -60,6 +60,14 @@ html {
--oobe-dialog-side-margin: 48px;
--oobe-dialog-adaptable-flex-direction: column;
--oobe-forward-slide-animation: translateX(+100%);
--oobe-backward-slide-animation: translateX(-100%);
}
html[dir=rtl] {
--oobe-forward-slide-animation: translateX(-100%);
--oobe-backward-slide-animation: translateX(+100%);
}
html[screen=gaia-signin] {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment