blob: b20936cc35c1f9270faf92c52c76ad42560f86db [file] [log] [blame]
// Copyright 2015 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.
/**
* @fileoverview
* 'settings-animated-pages' is a container for a page and animated subpages.
* It provides a set of common behaviors and animations.
*
* Example:
*
* <settings-animated-pages section="privacy">
* <!-- Insert your section controls here -->
* </settings-animated-pages>
*/
Polymer({
is: 'settings-animated-pages',
behaviors: [settings.RouteObserverBehavior],
properties: {
/**
* Routes with this section activate this element. For instance, if this
* property is 'search', and currentRoute.section is also set to 'search',
* this element will display the subpage in currentRoute.subpage.
*
* The section name must match the name specified in route.js.
*/
section: String,
/**
* A Map specifying which element should be focused when exiting a subpage.
* The key of the map holds a settings.Route path, and the value holds a
* query selector that identifies the desired element.
* @type {?Map<string, string>}
*/
focusConfig: Object,
},
/**
* The last "previous" route reported by the router.
* @private {?settings.Route}
*/
previousRoute_: null,
/** @override */
created: function() {
// Observe the light DOM so we know when it's ready.
this.lightDomObserver_ =
Polymer.dom(this).observeNodes(this.lightDomChanged_.bind(this));
},
/**
* @param {!Event} e
* @private
*/
onIronSelect_: function(e) {
if (!this.focusConfig || !this.previousRoute_)
return;
// Don't attempt to focus any anchor element, unless last navigation was a
// 'pop' (backwards) navigation.
if (!settings.lastRouteChangeWasPopstate())
return;
// Only handle iron-select events from neon-animatable elements and the
// given whitelist of settings-subpage instances.
let whitelist = 'settings-subpage#site-settings';
if (settings.routes.SITE_SETTINGS_COOKIES) {
whitelist += ', settings-subpage[route-path=\"' +
settings.routes.SITE_SETTINGS_COOKIES.path + '\"]';
}
// <if expr="chromeos">
if (settings.routes.INTERNET_NETWORKS) {
whitelist += ', settings-subpage[route-path=\"' +
settings.routes.INTERNET_NETWORKS.path + '\"]';
}
// </if>
if (!e.detail.item.matches('neon-animatable, ' + whitelist))
return;
const selector = this.focusConfig.get(this.previousRoute_.path);
if (selector) {
// neon-animatable has "display: none" until the animation finishes, so
// calling focus() on any of its children has no effect until "display:
// none" is removed. Therefore, don't set focus from within the
// currentRouteChanged callback. Using 'iron-select' listener which fires
// after the animation has finished allows setting focus to work.
cr.ui.focusWithoutInk(assert(this.querySelector(selector)));
}
},
/**
* Called initially once the effective children are ready.
* @private
*/
lightDomChanged_: function() {
if (this.lightDomReady_)
return;
this.lightDomReady_ = true;
Polymer.dom(this).unobserveNodes(this.lightDomObserver_);
this.runQueuedRouteChange_();
},
/**
* Calls currentRouteChanged with the deferred route change info.
* @private
*/
runQueuedRouteChange_: function() {
if (!this.queuedRouteChange_)
return;
this.async(this.currentRouteChanged.bind(
this, this.queuedRouteChange_.newRoute,
this.queuedRouteChange_.oldRoute));
},
/** @protected */
currentRouteChanged: function(newRoute, oldRoute) {
this.previousRoute_ = oldRoute;
if (newRoute.section == this.section && newRoute.isSubpage()) {
this.switchToSubpage_(newRoute, oldRoute);
} else {
this.$.animatedPages.exitAnimation = 'settings-fade-out-animation';
this.$.animatedPages.entryAnimation = 'settings-fade-in-animation';
this.$.animatedPages.selected = 'default';
}
},
/**
* Selects the subpage specified by |newRoute|.
* @param {!settings.Route} newRoute
* @param {!settings.Route} oldRoute
* @private
*/
switchToSubpage_: function(newRoute, oldRoute) {
// Don't manipulate the light DOM until it's ready.
if (!this.lightDomReady_) {
this.queuedRouteChange_ = this.queuedRouteChange_ || {oldRoute: oldRoute};
this.queuedRouteChange_.newRoute = newRoute;
return;
}
this.ensureSubpageInstance_();
if (oldRoute) {
if (oldRoute.isSubpage() && newRoute.depth > oldRoute.depth) {
const isRtl = loadTimeData.getString('textdirection') == 'rtl';
const exit = isRtl ? 'right' : 'left';
const entry = isRtl ? 'left' : 'right';
this.$.animatedPages.exitAnimation = 'slide-' + exit + '-animation';
this.$.animatedPages.entryAnimation =
'slide-from-' + entry + '-animation';
} else if (oldRoute.depth > newRoute.depth) {
const isRtl = loadTimeData.getString('textdirection') == 'rtl';
const exit = isRtl ? 'left' : 'right';
const entry = isRtl ? 'right' : 'left';
this.$.animatedPages.exitAnimation = 'slide-' + exit + '-animation';
this.$.animatedPages.entryAnimation =
'slide-from-' + entry + '-animation';
} else {
// The old route is not a subpage or is at the same level, so just fade.
this.$.animatedPages.exitAnimation = 'settings-fade-out-animation';
this.$.animatedPages.entryAnimation = 'settings-fade-in-animation';
if (!oldRoute.isSubpage()) {
// Set the height the expand animation should start at before
// beginning the transition to the new subpage.
// TODO(michaelpg): Remove MainPageBehavior's dependency on this
// height being set.
this.style.height = this.clientHeight + 'px';
this.async(function() {
this.style.height = '';
});
}
}
}
this.$.animatedPages.selected = newRoute.path;
},
/**
* Ensures that the template enclosing the subpage is stamped.
* @private
*/
ensureSubpageInstance_: function() {
const routePath = settings.getCurrentRoute().path;
const template = Polymer.dom(this).querySelector(
'template[route-path="' + routePath + '"]');
// Nothing to do if the subpage isn't wrapped in a <template> or the
// template is already stamped.
if (!template || template.if)
return;
// Set the subpage's id for use by neon-animated-pages.
const subpage = /** @type {{_content: DocumentFragment}} */ (template)
._content.querySelector('settings-subpage');
subpage.setAttribute('route-path', routePath);
// Carry over the 'no-search' attribute from the template to the stamped
// instance, such that the stamped instance will also be ignored by the
// searching algorithm.
if (template.hasAttribute('no-search'))
subpage.setAttribute('no-search', '');
// Render synchronously so neon-animated-pages can select the subpage.
template.if = true;
template.render();
},
});