Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
1527 lines
55 KiB
JavaScript
1527 lines
55 KiB
JavaScript
"use strict";
|
|
|
|
(function (f) {
|
|
if (typeof exports === "object" && typeof module !== "undefined") {
|
|
module.exports = f();
|
|
} else if (typeof define === "function" && define.amd) {
|
|
define([], f);
|
|
} else {
|
|
var g;if (typeof window !== "undefined") {
|
|
g = window;
|
|
} else if (typeof global !== "undefined") {
|
|
g = global;
|
|
} else if (typeof self !== "undefined") {
|
|
g = self;
|
|
} else {
|
|
g = this;
|
|
}g.GaiaHeader = f();
|
|
}
|
|
})(function () {
|
|
var define, module, exports;return (function e(t, n, r) {
|
|
function s(o, u) {
|
|
if (!n[o]) {
|
|
if (!t[o]) {
|
|
var a = typeof require == "function" && require;if (!u && a) return a(o, !0);if (i) return i(o, !0);var f = new Error("Cannot find module '" + o + "'");throw (f.code = "MODULE_NOT_FOUND", f);
|
|
}var l = n[o] = { exports: {} };t[o][0].call(l.exports, function (e) {
|
|
var n = t[o][1][e];return s(n ? n : e);
|
|
}, l, l.exports, e, t, n, r);
|
|
}return n[o].exports;
|
|
}var i = typeof require == "function" && require;for (var o = 0; o < r.length; o++) s(r[o]);return s;
|
|
})({ 1: [function (require, module, exports) {
|
|
;(function (define) {
|
|
define(function (require, exports, module) {
|
|
'use strict';
|
|
|
|
/**
|
|
* Simple logger.
|
|
*
|
|
* @return {Function}
|
|
*/
|
|
var debug = 0 ? console.log.bind(console) : function () {};
|
|
|
|
/**
|
|
* Global canvas cache.
|
|
*
|
|
* @type {Object}
|
|
*/
|
|
var cache = {};
|
|
|
|
/**
|
|
* Default min/max font-size.
|
|
*
|
|
* @type {Number}
|
|
*/
|
|
var MIN = 16;
|
|
var MAX = 24;
|
|
|
|
/**
|
|
* The number of pixels to subtract from
|
|
* the given `config.space` to ensure
|
|
* HTML text doesn't overflow container.
|
|
*
|
|
* Ideally we would use 1px, but in some
|
|
* cases italicised text in canvas is ~2px
|
|
* longer than the same text in HTML.
|
|
*
|
|
* http://bugzil.la/1126391
|
|
*
|
|
* @type {Number}
|
|
*/
|
|
var BUFFER = 3;
|
|
|
|
/**
|
|
* Get the font-size that closest fits
|
|
* the given space with the given font.
|
|
*
|
|
* Config:
|
|
*
|
|
* - {String} `text` The text string
|
|
* - {String} `font` Font shorthand string
|
|
* - {Number} `space` Width (px) to fit the text into
|
|
* - {Number} `min` Min font-size (px) (optional)
|
|
* - {Number} `max` Max font-size (px) (optional)
|
|
*
|
|
* @param {Object} config
|
|
* @return {Object} {fontSize,overflowing,textWidth}
|
|
*/
|
|
module.exports = function (config) {
|
|
debug('font fit', config);
|
|
var space = config.space - BUFFER;
|
|
var min = config.min || MIN;
|
|
var max = config.max || MAX;
|
|
var text = trim(config.text);
|
|
var fontSize = max;
|
|
var textWidth;
|
|
var font;
|
|
|
|
do {
|
|
font = config.font.replace(/\d+px/, fontSize + 'px');
|
|
textWidth = getTextWidth(text, font);
|
|
} while (textWidth > space && fontSize !== min && fontSize--);
|
|
|
|
return {
|
|
textWidth: textWidth,
|
|
fontSize: fontSize,
|
|
overflowing: textWidth > space
|
|
};
|
|
};
|
|
|
|
/**
|
|
* Get the width of the given text
|
|
* with the given font style.
|
|
*
|
|
* @param {String} text
|
|
* @param {String} font (CSS shorthand)
|
|
* @return {Number} (px)
|
|
*/
|
|
function getTextWidth(text, font) {
|
|
var ctx = getCanvasContext(font);
|
|
var width = ctx.measureText(text).width;
|
|
debug('got text width', width);
|
|
return width;
|
|
}
|
|
|
|
/**
|
|
* Get a canvas context configured
|
|
* to the given font style.
|
|
*
|
|
* @param {String} font
|
|
* @return {CanvasRenderingContext2D}
|
|
*/
|
|
function getCanvasContext(font) {
|
|
debug('get canvas context', font);
|
|
|
|
var cached = cache[font];
|
|
if (cached) {
|
|
return cached;
|
|
}
|
|
|
|
var canvas = document.createElement('canvas');
|
|
canvas.setAttribute('moz-opaque', 'true');
|
|
canvas.setAttribute('width', '1px');
|
|
canvas.setAttribute('height', '1px');
|
|
debug('created canvas', canvas);
|
|
|
|
var ctx = canvas.getContext('2d', { willReadFrequently: true });
|
|
ctx.font = font;
|
|
|
|
return cache[font] = ctx;
|
|
}
|
|
|
|
/**
|
|
* Trim leading, trailing
|
|
* and excess whitespace.
|
|
*
|
|
* @param {String} text
|
|
* @return {String}
|
|
*/
|
|
function trim(text) {
|
|
return text.replace(/\s+/g, ' ').trim();
|
|
}
|
|
});
|
|
})(typeof define == 'function' && define.amd ? define : (function (n, w) {
|
|
'use strict';return typeof module == 'object' ? function (c) {
|
|
c(require, exports, module);
|
|
} : function (c) {
|
|
var m = { exports: {} };c(function (n) {
|
|
return w[n];
|
|
}, m.exports, m);w[n] = m.exports;
|
|
};
|
|
})('font-fit', this));
|
|
}, {}], 2: [function (require, module, exports) {
|
|
/* globals define */
|
|
;(function (define) {
|
|
'use strict';define(function (require, exports, module) {
|
|
/**
|
|
* Locals
|
|
*/
|
|
var textContent = Object.getOwnPropertyDescriptor(Node.prototype, 'textContent');
|
|
var innerHTML = Object.getOwnPropertyDescriptor(Element.prototype, 'innerHTML');
|
|
var removeAttribute = Element.prototype.removeAttribute;
|
|
var setAttribute = Element.prototype.setAttribute;
|
|
var noop = function noop() {};
|
|
|
|
/**
|
|
* Register a new component.
|
|
*
|
|
* @param {String} name
|
|
* @param {Object} props
|
|
* @return {constructor}
|
|
* @public
|
|
*/
|
|
exports.register = function (name, props) {
|
|
var baseProto = getBaseProto(props["extends"]);
|
|
var template = props.template || baseProto.templateString;
|
|
|
|
// Components are extensible by default but can be declared
|
|
// as non extensible as an optimization to avoid
|
|
// storing the template strings
|
|
var extensible = props.extensible = props.hasOwnProperty('extensible') ? props.extensible : true;
|
|
|
|
// Clean up
|
|
delete props["extends"];
|
|
|
|
// Pull out CSS that needs to be in the light-dom
|
|
if (template) {
|
|
// Stores the string to be reprocessed when
|
|
// a new component extends this one
|
|
if (extensible && props.template) {
|
|
props.templateString = props.template;
|
|
}
|
|
|
|
var output = processCss(template, name);
|
|
|
|
props.template = document.createElement('template');
|
|
props.template.innerHTML = output.template;
|
|
props.lightCss = output.lightCss;
|
|
|
|
props.globalCss = props.globalCss || '';
|
|
props.globalCss += output.globalCss;
|
|
}
|
|
|
|
// Inject global CSS into the document,
|
|
// and delete as no longer needed
|
|
injectGlobalCss(props.globalCss);
|
|
delete props.globalCss;
|
|
|
|
// Merge base getter/setter attributes with the user's,
|
|
// then define the property descriptors on the prototype.
|
|
var descriptors = mixin(props.attrs || {}, base.descriptors);
|
|
|
|
// Store the orginal descriptors somewhere
|
|
// a little more private and delete the original
|
|
props._attrs = props.attrs;
|
|
delete props.attrs;
|
|
|
|
// Create the prototype, extended from base and
|
|
// define the descriptors directly on the prototype
|
|
var proto = createProto(baseProto, props);
|
|
Object.defineProperties(proto, descriptors);
|
|
|
|
// Register the custom-element and return the constructor
|
|
try {
|
|
return document.registerElement(name, { prototype: proto });
|
|
} catch (e) {
|
|
if (e.name !== 'NotSupportedError') {
|
|
throw e;
|
|
}
|
|
}
|
|
};
|
|
|
|
var base = {
|
|
properties: {
|
|
GaiaComponent: true,
|
|
attributeChanged: noop,
|
|
attached: noop,
|
|
detached: noop,
|
|
created: noop,
|
|
|
|
createdCallback: function createdCallback() {
|
|
if (this.dirObserver) {
|
|
addDirObserver();
|
|
}
|
|
injectLightCss(this);
|
|
this.created();
|
|
},
|
|
|
|
/**
|
|
* It is very common to want to keep object
|
|
* properties in-sync with attributes,
|
|
* for example:
|
|
*
|
|
* el.value = 'foo';
|
|
* el.setAttribute('value', 'foo');
|
|
*
|
|
* So we support an object on the prototype
|
|
* named 'attrs' to provide a consistent
|
|
* way for component authors to define
|
|
* these properties. When an attribute
|
|
* changes we keep the attr[name]
|
|
* up-to-date.
|
|
*
|
|
* @param {String} name
|
|
* @param {String||null} from
|
|
* @param {String||null} to
|
|
*/
|
|
attributeChangedCallback: function attributeChangedCallback(name, from, to) {
|
|
var prop = toCamelCase(name);
|
|
if (this._attrs && this._attrs[prop]) {
|
|
this[prop] = to;
|
|
}
|
|
this.attributeChanged(name, from, to);
|
|
},
|
|
|
|
attachedCallback: function attachedCallback() {
|
|
if (this.dirObserver) {
|
|
this.setInnerDirAttributes = setInnerDirAttributes.bind(null, this);
|
|
document.addEventListener('dirchanged', this.setInnerDirAttributes);
|
|
}
|
|
this.attached();
|
|
},
|
|
|
|
detachedCallback: function detachedCallback() {
|
|
if (this.dirObserver) {
|
|
document.removeEventListener('dirchanged', this.setInnerDirAttributes);
|
|
}
|
|
this.detached();
|
|
},
|
|
|
|
/**
|
|
* A convenient method for setting up
|
|
* a shadow-root using the defined template.
|
|
*
|
|
* @return {ShadowRoot}
|
|
*/
|
|
setupShadowRoot: function setupShadowRoot() {
|
|
if (!this.template) {
|
|
return;
|
|
}
|
|
var node = document.importNode(this.template.content, true);
|
|
this.createShadowRoot().appendChild(node);
|
|
if (this.dirObserver) {
|
|
setInnerDirAttributes(this);
|
|
}
|
|
return this.shadowRoot;
|
|
},
|
|
|
|
/**
|
|
* Sets an attribute internally
|
|
* and externally. This is so that
|
|
* we can style internal shadow-dom
|
|
* content.
|
|
*
|
|
* @param {String} name
|
|
* @param {String} value
|
|
*/
|
|
setAttr: function setAttr(name, value) {
|
|
var internal = this.shadowRoot.firstElementChild;
|
|
setAttribute.call(internal, name, value);
|
|
setAttribute.call(this, name, value);
|
|
},
|
|
|
|
/**
|
|
* Removes an attribute internally
|
|
* and externally. This is so that
|
|
* we can style internal shadow-dom
|
|
* content.
|
|
*
|
|
* @param {String} name
|
|
* @param {String} value
|
|
*/
|
|
removeAttr: function removeAttr(name) {
|
|
var internal = this.shadowRoot.firstElementChild;
|
|
removeAttribute.call(internal, name);
|
|
removeAttribute.call(this, name);
|
|
}
|
|
},
|
|
|
|
descriptors: {
|
|
textContent: {
|
|
set: function set(value) {
|
|
textContent.set.call(this, value);
|
|
if (this.lightStyle) {
|
|
this.appendChild(this.lightStyle);
|
|
}
|
|
},
|
|
|
|
get: function get() {
|
|
return textContent.get();
|
|
}
|
|
},
|
|
|
|
innerHTML: {
|
|
set: function set(value) {
|
|
innerHTML.set.call(this, value);
|
|
if (this.lightStyle) {
|
|
this.appendChild(this.lightStyle);
|
|
}
|
|
},
|
|
|
|
get: innerHTML.get
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* The default base prototype to use
|
|
* when `extends` is undefined.
|
|
*
|
|
* @type {Object}
|
|
*/
|
|
var defaultPrototype = createProto(HTMLElement.prototype, base.properties);
|
|
|
|
/**
|
|
* Returns a suitable prototype based
|
|
* on the object passed.
|
|
*
|
|
* @private
|
|
* @param {HTMLElementPrototype|undefined} proto
|
|
* @return {HTMLElementPrototype}
|
|
*/
|
|
function getBaseProto(proto) {
|
|
if (!proto) {
|
|
return defaultPrototype;
|
|
}
|
|
proto = proto.prototype || proto;
|
|
return !proto.GaiaComponent ? createProto(proto, base.properties) : proto;
|
|
}
|
|
|
|
/**
|
|
* Extends the given proto and mixes
|
|
* in the given properties.
|
|
*
|
|
* @private
|
|
* @param {Object} proto
|
|
* @param {Object} props
|
|
* @return {Object}
|
|
*/
|
|
function createProto(proto, props) {
|
|
return mixin(Object.create(proto), props);
|
|
}
|
|
|
|
/**
|
|
* Detects presence of shadow-dom
|
|
* CSS selectors.
|
|
*
|
|
* @private
|
|
* @return {Boolean}
|
|
*/
|
|
var hasShadowCSS = (function () {
|
|
var div = document.createElement('div');
|
|
try {
|
|
div.querySelector(':host');div.querySelector('::content');return true;
|
|
} catch (e) {
|
|
return false;
|
|
}
|
|
})();
|
|
|
|
/**
|
|
* Regexs used to extract shadow-css
|
|
*
|
|
* @type {Object}
|
|
*/
|
|
var regex = {
|
|
shadowCss: /(?:\:host|\:\:content)[^{]*\{[^}]*\}/g,
|
|
':host': /(?:\:host)/g,
|
|
':host()': /\:host\((.+)\)(?: \:\:content)?/g,
|
|
':host-context': /\:host-context\((.+)\)([^{,]+)?/g,
|
|
'::content': /(?:\:\:content)/g
|
|
};
|
|
|
|
/**
|
|
* Extracts the :host and ::content rules
|
|
* from the shadow-dom CSS and rewrites
|
|
* them to work from the <style scoped>
|
|
* injected at the root of the component.
|
|
*
|
|
* @private
|
|
* @return {String}
|
|
*/
|
|
function processCss(template, name) {
|
|
var globalCss = '';
|
|
var lightCss = '';
|
|
|
|
if (!hasShadowCSS) {
|
|
template = template.replace(regex.shadowCss, function (match) {
|
|
var hostContext = regex[':host-context'].exec(match);
|
|
|
|
if (hostContext) {
|
|
globalCss += match.replace(regex['::content'], '').replace(regex[':host-context'], '$1 ' + name + '$2').replace(/ +/g, ' '); // excess whitespace
|
|
} else {
|
|
lightCss += match.replace(regex[':host()'], name + '$1').replace(regex[':host'], name).replace(regex['::content'], name);
|
|
}
|
|
|
|
return '';
|
|
});
|
|
}
|
|
|
|
return {
|
|
template: template,
|
|
lightCss: lightCss,
|
|
globalCss: globalCss
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Some CSS rules, such as @keyframes
|
|
* and @font-face don't work inside
|
|
* scoped or shadow <style>. So we
|
|
* have to put them into 'global'
|
|
* <style> in the head of the
|
|
* document.
|
|
*
|
|
* @private
|
|
* @param {String} css
|
|
*/
|
|
function injectGlobalCss(css) {
|
|
if (!css) {
|
|
return;
|
|
}
|
|
var style = document.createElement('style');
|
|
style.innerHTML = css.trim();
|
|
headReady().then(function () {
|
|
document.head.appendChild(style);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Resolves a promise once document.head is ready.
|
|
*
|
|
* @private
|
|
*/
|
|
function headReady() {
|
|
return new Promise(function (resolve) {
|
|
if (document.head) {
|
|
return resolve();
|
|
}
|
|
window.addEventListener('load', function fn() {
|
|
window.removeEventListener('load', fn);
|
|
resolve();
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
* The Gecko platform doesn't yet have
|
|
* `::content` or `:host`, selectors,
|
|
* without these we are unable to style
|
|
* user-content in the light-dom from
|
|
* within our shadow-dom style-sheet.
|
|
*
|
|
* To workaround this, we clone the <style>
|
|
* node into the root of the component,
|
|
* so our selectors are able to target
|
|
* light-dom content.
|
|
*
|
|
* @private
|
|
*/
|
|
function injectLightCss(el) {
|
|
if (hasShadowCSS) {
|
|
return;
|
|
}
|
|
var stylesheet = el.querySelector('style');
|
|
|
|
if (!stylesheet) {
|
|
stylesheet = document.createElement('style');
|
|
stylesheet.setAttribute('scoped', '');
|
|
stylesheet.appendChild(document.createTextNode(el.lightCss));
|
|
el.appendChild(stylesheet);
|
|
}
|
|
|
|
el.lightStyle = stylesheet;
|
|
}
|
|
|
|
/**
|
|
* Convert hyphen separated
|
|
* string to camel-case.
|
|
*
|
|
* Example:
|
|
*
|
|
* toCamelCase('foo-bar'); //=> 'fooBar'
|
|
*
|
|
* @private
|
|
* @param {String} string
|
|
* @return {String}
|
|
*/
|
|
function toCamelCase(string) {
|
|
return string.replace(/-(.)/g, function replacer(string, p1) {
|
|
return p1.toUpperCase();
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Observer (singleton)
|
|
*
|
|
* @type {MutationObserver|undefined}
|
|
*/
|
|
var dirObserver;
|
|
|
|
/**
|
|
* Workaround for bug 1100912: applies a `dir` attribute to all shadowRoot
|
|
* children so that :-moz-dir() selectors work on shadow DOM elements.
|
|
*
|
|
* In order to keep decent performances, the `dir` is the component dir if
|
|
* defined, or the document dir otherwise. This won't work if the component's
|
|
* direction is defined by CSS or inherited from a parent container.
|
|
*
|
|
* This method should be removed when bug 1100912 is fixed.
|
|
*
|
|
* @private
|
|
* @param {WebComponent}
|
|
*/
|
|
function setInnerDirAttributes(component) {
|
|
var dir = component.dir || document.dir;
|
|
Array.from(component.shadowRoot.children).forEach(function (element) {
|
|
if (element.nodeName !== 'STYLE') {
|
|
element.dir = dir;
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Observes the document `dir` (direction) attribute and when it changes:
|
|
* - dispatches a global `dirchanged` event;
|
|
* - forces the `dir` attribute of all shadowRoot children.
|
|
*
|
|
* Components can listen to this event and make internal changes if needed.
|
|
*
|
|
* @private
|
|
*/
|
|
function addDirObserver() {
|
|
if (dirObserver) {
|
|
return;
|
|
}
|
|
|
|
dirObserver = new MutationObserver(onChanged);
|
|
dirObserver.observe(document.documentElement, {
|
|
attributeFilter: ['dir'],
|
|
attributes: true
|
|
});
|
|
|
|
function onChanged(mutations) {
|
|
document.dispatchEvent(new Event('dirchanged'));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Copy the values of all properties from
|
|
* source object `target` to a target object `source`.
|
|
* It will return the target object.
|
|
*
|
|
* @private
|
|
* @param {Object} target
|
|
* @param {Object} source
|
|
* @returns {Object}
|
|
*/
|
|
function mixin(target, source) {
|
|
for (var key in source) {
|
|
target[key] = source[key];
|
|
}
|
|
return target;
|
|
}
|
|
});
|
|
})(typeof define == 'function' && define.amd ? define : (function (n, w) {
|
|
'use strict';return typeof module == 'object' ? function (c) {
|
|
c(require, exports, module);
|
|
} : function (c) {
|
|
var m = { exports: {} };c(function (n) {
|
|
return w[n];
|
|
}, m.exports, m);w[n] = m.exports;
|
|
};
|
|
})('gaia-component', this));
|
|
}, {}], 3: [function (require, module, exports) {
|
|
/* global define */
|
|
(function (define) {
|
|
'use strict';define(function (require, exports, module) {
|
|
|
|
/**
|
|
* Exports
|
|
*/
|
|
|
|
var base = window.GAIA_ICONS_BASE_URL || window.COMPONENTS_BASE_URL || 'bower_components/';
|
|
|
|
if (!document.documentElement) {
|
|
window.addEventListener('load', load);
|
|
} else {
|
|
load();
|
|
}
|
|
|
|
function load() {
|
|
if (isLoaded()) {
|
|
return;
|
|
}
|
|
|
|
var link = document.createElement('link');
|
|
link.rel = 'stylesheet';
|
|
link.type = 'text/css';
|
|
link.href = base + 'gaia-icons/gaia-icons.css';
|
|
document.head.appendChild(link);
|
|
exports.loaded = true;
|
|
}
|
|
|
|
function isLoaded() {
|
|
return exports.loaded || document.querySelector('link[href*=gaia-icons]') || document.documentElement.classList.contains('gaia-icons-loaded');
|
|
}
|
|
});
|
|
})(typeof define == 'function' && define.amd ? define : (function (n, w) {
|
|
'use strict';return typeof module == 'object' ? function (c) {
|
|
c(require, exports, module);
|
|
} : function (c) {
|
|
var m = { exports: {} };c(function (n) {
|
|
return w[n];
|
|
}, m.exports, m);w[n] = m.exports;
|
|
};
|
|
})('gaia-icons', this));
|
|
}, {}], 4: [function (require, module, exports) {
|
|
/* globals define */
|
|
;(function (define) {
|
|
'use strict';define(function (require, exports, module) {
|
|
|
|
/**
|
|
* Dependencies
|
|
*/
|
|
var component = require('gaia-component');
|
|
var _fontFit = require('font-fit');
|
|
|
|
/**
|
|
* Load 'gaia-icons' font-family
|
|
*/
|
|
require('gaia-icons');
|
|
|
|
/**
|
|
* Simple logger (toggle 0)
|
|
*
|
|
* @type {Function}
|
|
*/
|
|
var debug = 0 ? console.log.bind(console) : function () {};
|
|
|
|
/**
|
|
* Supported action types
|
|
*
|
|
* @type {Object}
|
|
*/
|
|
var KNOWN_ACTIONS = {
|
|
menu: 'menu',
|
|
back: 'back',
|
|
close: 'close'
|
|
};
|
|
|
|
/**
|
|
* The default title font.
|
|
*
|
|
* @type {String}
|
|
*/
|
|
var TITLE_FONT = 'italic 300 24px FiraSans';
|
|
|
|
/**
|
|
* The padding (start/end) used if
|
|
* the title needs padding away from
|
|
* the edge of the header.
|
|
*
|
|
* @type {Number}
|
|
*/
|
|
var TITLE_PADDING = 10;
|
|
|
|
/**
|
|
* This is the minimum font size that we can take
|
|
* when the header title is centered in the window.
|
|
*/
|
|
var MINIMUM_FONT_SIZE_CENTERED = 20;
|
|
|
|
/**
|
|
* This is the minimum font size that we can take
|
|
* when the header title is not centered in the window.
|
|
*/
|
|
var MINIMUM_FONT_SIZE_UNCENTERED = 16;
|
|
|
|
/**
|
|
* This is the maximum font-size
|
|
* for the header title.
|
|
*/
|
|
var MAXIMUM_FONT_SIZE = 23;
|
|
|
|
/**
|
|
* Register the element.
|
|
*
|
|
* @return {Element} constructor
|
|
*/
|
|
module.exports = component.register('gaia-header', {
|
|
extensible: false, // discards some strings
|
|
dirObserver: true, // triggers a workaround for bug 1100912 in gaia-component
|
|
|
|
/**
|
|
* Called when the element is first created.
|
|
*
|
|
* Here we create the shadow-root and
|
|
* inject our template into it.
|
|
*
|
|
* @private
|
|
*/
|
|
created: function created() {
|
|
var _this = this;
|
|
|
|
debug('created');
|
|
this.setupShadowRoot();
|
|
|
|
// Elements
|
|
this.els = {
|
|
actionButton: this.shadowRoot.querySelector('.action-button'),
|
|
titles: this.getElementsByTagName('h1')
|
|
};
|
|
|
|
// Events
|
|
this.els.actionButton.addEventListener('click', function (e) {
|
|
return _this.onActionButtonClick(e);
|
|
});
|
|
this.observer = new MutationObserver(this.onMutation.bind(this));
|
|
|
|
// Properties
|
|
this.ignoreDir = this.hasAttribute('ignore-dir');
|
|
this.titleEnd = this.getAttribute('title-end');
|
|
this.titleStart = this.getAttribute('title-start');
|
|
this.noFontFit = this.getAttribute('no-font-fit');
|
|
this.notFlush = this.hasAttribute('not-flush');
|
|
this.action = this.getAttribute('action');
|
|
|
|
this.unresolved = {};
|
|
this.pending = {};
|
|
this._resizeThrottlingId = null;
|
|
|
|
// bind the listener in advance so that we can remove it when detaching.
|
|
this.onResize = this.onResize.bind(this);
|
|
},
|
|
|
|
/**
|
|
* Called when the element is
|
|
* attached to the DOM.
|
|
*
|
|
* Run fontFit when we have DOM
|
|
* context and start listening
|
|
* for DOM mutations.
|
|
*
|
|
* We run font-fit on next tick to
|
|
* avoid reading from the DOM when
|
|
* it may not be loaded yet.
|
|
*
|
|
* @private
|
|
*/
|
|
attached: function attached() {
|
|
debug('attached');
|
|
this.runFontFitSoon();
|
|
this.observerStart();
|
|
window.addEventListener('resize', this.onResize);
|
|
},
|
|
|
|
/**
|
|
* Called when the element is
|
|
* detached from the DOM.
|
|
*
|
|
* @private
|
|
*/
|
|
detached: function detached() {
|
|
debug('detached');
|
|
window.removeEventListener('resize', this.onResize);
|
|
this.observerStop();
|
|
this.clearPending();
|
|
},
|
|
|
|
/**
|
|
* Clears pending `.nextTick()`s and requestAnimationFrame's.
|
|
*
|
|
* @private
|
|
*/
|
|
clearPending: function clearPending() {
|
|
for (var key in this.pending) {
|
|
this.pending[key].clear();
|
|
delete this.pending[key];
|
|
}
|
|
|
|
window.cancelAnimationFrame(this._resizeThrottlingId);
|
|
this._resizeThrottlingId = null;
|
|
},
|
|
|
|
/**
|
|
* Run the font-fit logic and
|
|
* center the title.
|
|
*
|
|
* The styles are calculated synchronously,
|
|
* but then set asynchronously. This means
|
|
* this function can be hammered in one turn,
|
|
* and the title styles will only be written
|
|
* once, with the latest styles.
|
|
*
|
|
* @return {Promise}
|
|
* @public
|
|
*/
|
|
runFontFit: function runFontFit() {
|
|
var _this2 = this;
|
|
|
|
debug('run font-fit');
|
|
|
|
// Nothing is run if `no-font-fit` attribute
|
|
// is present. We don't `reject()` as this
|
|
// isn't technically an error state.
|
|
if (this.noFontFit) {
|
|
return Promise.resolve();
|
|
}
|
|
|
|
var titles = this.els.titles;
|
|
var space = this.getTitleSpace();
|
|
var styles = [].map.call(titles, function (el) {
|
|
return _this2.getTitleStyle(el, space);
|
|
});
|
|
|
|
// Update the title styles using the latest
|
|
// styles. This function can be called many
|
|
// times but will only run once in each tick.
|
|
return this.setTitleStylesSoon(styles);
|
|
},
|
|
|
|
/**
|
|
* Microtask debounced `runFontFit`
|
|
*
|
|
* @private
|
|
*/
|
|
runFontFitSoon: function runFontFitSoon() {
|
|
var _this3 = this;
|
|
|
|
debug('run font-fit soon');
|
|
if (this.pending.runFontFitSoon) {
|
|
return;
|
|
}
|
|
this.pending.runFontFitSoon = this.nextTick(function () {
|
|
delete _this3.pending.runFontFitSoon;
|
|
_this3.runFontFit();
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Get the style properties required
|
|
* to fit and position the title text.
|
|
*
|
|
* @param {HTMLH1Element} el
|
|
* @param {Number} space
|
|
* @return {Object} {id, fontSize, marginStart, overflowing, padding}
|
|
* @private
|
|
*/
|
|
getTitleStyle: function getTitleStyle(el, space) {
|
|
debug('get el style', el, space);
|
|
var text = el.textContent;
|
|
var styleId = space.start + text + space.end + '#' + space.value;
|
|
|
|
// Bail when there's no text (or just whitespace)
|
|
if (!text || !text.trim()) {
|
|
return debug('exit: no text');
|
|
}
|
|
|
|
// If neither the text or the titleSpace
|
|
// changed, there's no reason to continue.
|
|
if (getStyleId(el) === styleId) {
|
|
return debug('exit: no change');
|
|
}
|
|
|
|
var marginStart = this.getTitleMarginStart();
|
|
var textSpace = space.value - Math.abs(marginStart);
|
|
var fontFitResult = this.fontFit(text, textSpace, {
|
|
min: MINIMUM_FONT_SIZE_CENTERED
|
|
});
|
|
|
|
var overflowing = fontFitResult.overflowing;
|
|
var padding = { start: 0, end: 0 };
|
|
|
|
// If the text is overflowing in the
|
|
// centered title, we remove centering
|
|
// to free up space, rerun fontFit to
|
|
// get a fontSize which fits this space.
|
|
if (overflowing) {
|
|
debug('title overflowing');
|
|
padding.start = !space.start ? TITLE_PADDING : 0;
|
|
padding.end = !space.end ? TITLE_PADDING : 0;
|
|
textSpace = space.value - padding.start - padding.end;
|
|
fontFitResult = this.fontFit(text, textSpace);
|
|
marginStart = 0;
|
|
}
|
|
|
|
return {
|
|
id: styleId,
|
|
fontSize: fontFitResult.fontSize,
|
|
marginStart: marginStart,
|
|
overflowing: overflowing,
|
|
padding: padding
|
|
};
|
|
},
|
|
|
|
/**
|
|
* Sets styles on the title elements.
|
|
*
|
|
* If there is already an unresolved Promise
|
|
* we return it instead of scheduling another.
|
|
* This means that the function can be hammered
|
|
* in the same sync-turn and will only run
|
|
* once (like a debounce).
|
|
*
|
|
* @param {Array} styles
|
|
* @return {Promise}
|
|
*/
|
|
setTitleStylesSoon: function setTitleStylesSoon(styles) {
|
|
var _this4 = this;
|
|
|
|
debug('set title styles soon', styles);
|
|
var key = 'setStyleTitlesSoon';
|
|
|
|
// Always update styles
|
|
this._titleStyles = styles;
|
|
|
|
// Return existing unresolved
|
|
// promise, or make a new one
|
|
if (this.unresolved[key]) {
|
|
return this.unresolved[key];
|
|
}
|
|
this.unresolved[key] = new Promise(function (resolve) {
|
|
_this4.pending[key] = _this4.nextTick(function () {
|
|
var styles = _this4._titleStyles;
|
|
var els = _this4.els.titles;
|
|
|
|
[].forEach.call(els, function (el, i) {
|
|
if (!styles[i]) {
|
|
return debug('exit');
|
|
}
|
|
_this4.setTitleStyle(el, styles[i]);
|
|
});
|
|
|
|
// Clean up
|
|
delete _this4._titleStyles;
|
|
delete _this4.unresolved[key];
|
|
delete _this4.pending[key];
|
|
|
|
resolve();
|
|
});
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Fit text and center a title between the buttons before and after.
|
|
*
|
|
* @param {HTMLH1Element} el
|
|
* @param {Object} style
|
|
* @private
|
|
*/
|
|
setTitleStyle: function setTitleStyle(el, style) {
|
|
debug('set title style', style);
|
|
this.observerStop();
|
|
if (this.ignoreDir) {
|
|
el.style.marginLeft = style.marginStart + 'px';
|
|
el.style.paddingLeft = style.padding.start + 'px';
|
|
el.style.paddingRight = style.padding.end + 'px';
|
|
} else {
|
|
el.style.marginInlineStart = style.marginStart + 'px';
|
|
el.style.paddingInlineStart = style.padding.start + 'px';
|
|
el.style.paddingInlineEnd = style.padding.end + 'px';
|
|
}
|
|
el.style.fontSize = style.fontSize + 'px';
|
|
setStyleId(el, style.id);
|
|
this.observerStart();
|
|
},
|
|
|
|
/**
|
|
* Run font-fit on a title with
|
|
* the given amount of content space.
|
|
*
|
|
* @param {String} text
|
|
* @param {Number} space
|
|
* @param {Object} optional {[min]}
|
|
* @return {Object} {fontSize, textWidth}
|
|
* @private
|
|
*/
|
|
fontFit: function fontFit(text, space) {
|
|
var opts = arguments.length <= 2 || arguments[2] === undefined ? {} : arguments[2];
|
|
|
|
debug('font fit:', text, space, opts);
|
|
|
|
var fontFitArgs = {
|
|
font: TITLE_FONT,
|
|
min: opts.min || MINIMUM_FONT_SIZE_UNCENTERED,
|
|
max: MAXIMUM_FONT_SIZE,
|
|
text: text,
|
|
space: space
|
|
};
|
|
|
|
return _fontFit(fontFitArgs);
|
|
},
|
|
|
|
/**
|
|
* Start the observer listening
|
|
* for DOM mutations.
|
|
* Start the listener for 'resize' event.
|
|
*
|
|
* @private
|
|
*/
|
|
observerStart: function observerStart() {
|
|
if (this.observing) {
|
|
return;
|
|
}
|
|
|
|
this.observer.observe(this, {
|
|
childList: true,
|
|
attributes: true,
|
|
subtree: true
|
|
});
|
|
|
|
this.observing = true;
|
|
debug('observer started');
|
|
},
|
|
|
|
/**
|
|
* Stop the observer listening
|
|
* for DOM mutations.
|
|
*
|
|
* @private
|
|
*/
|
|
observerStop: function observerStop() {
|
|
if (!this.observing) {
|
|
return;
|
|
}
|
|
this.observer.disconnect();
|
|
|
|
this.observing = false;
|
|
debug('observer stopped');
|
|
},
|
|
|
|
/**
|
|
* Handle 'resize' events.
|
|
* @param {Event} The DOM Event that's being handled.
|
|
*
|
|
* @private
|
|
*/
|
|
onResize: function onResize(e) {
|
|
var _this5 = this;
|
|
|
|
debug('onResize', this._resizeThrottlingId);
|
|
|
|
if (this._resizeThrottlingId !== null) {
|
|
return;
|
|
}
|
|
|
|
/* Resize events can arrive at a very high rate, so we're trying to
|
|
* reasonably throttle these events. */
|
|
this._resizeThrottlingId = window.requestAnimationFrame(function () {
|
|
_this5._resizeThrottlingId = null;
|
|
_this5.runFontFitSoon();
|
|
});
|
|
},
|
|
|
|
/**
|
|
* When the components DOM changes we
|
|
* call `.runFontFit()` (sync).
|
|
*
|
|
* If the `textContent` is changed in a
|
|
* mutation observer just after attaching,
|
|
* we end up running twice.
|
|
*
|
|
* If there is a pending async .runFontFit(),
|
|
* then we don't want to run it now.
|
|
*
|
|
* @param {Array} mutations
|
|
* @private
|
|
*/
|
|
onMutation: function onMutation(mutations) {
|
|
debug('on mutation', mutations);
|
|
if (!this.pending.runFontFitSoon) {
|
|
this.runFontFit();
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Get the title width.
|
|
*
|
|
* Returns the space available for <h1>.
|
|
*
|
|
* @return {Number}
|
|
* @private
|
|
*/
|
|
getTitleSpace: function getTitleSpace() {
|
|
var start = this.titleStart;
|
|
var end = this.titleEnd;
|
|
var space = this.getWidth() - start - end;
|
|
var result = {
|
|
value: space,
|
|
start: start,
|
|
end: end
|
|
};
|
|
|
|
debug('get title space', result);
|
|
return result;
|
|
},
|
|
|
|
/**
|
|
* Get the width of the component.
|
|
*
|
|
* For performance reasons we make the
|
|
* assumption that the width is the same
|
|
* as `window.innerWidth` unless the
|
|
* `not-flush` attribute is used.
|
|
*
|
|
* @return {Number}
|
|
* @private
|
|
*/
|
|
getWidth: function getWidth() {
|
|
var value = this.notFlush ? this.clientWidth : window.innerWidth;
|
|
|
|
debug('get width', value);
|
|
return value;
|
|
},
|
|
|
|
/**
|
|
* Triggers the 'action' button
|
|
* (used in Gaia integration testing).
|
|
*
|
|
* @public
|
|
*/
|
|
triggerAction: function triggerAction() {
|
|
if (this.action) {
|
|
this.els.actionButton.click();
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Handle clicks on the action button.
|
|
*
|
|
* Fired pending to allow the 'click' event
|
|
* to finish its event path before
|
|
* dispatching the 'action' event.
|
|
*
|
|
* @param {Event} e
|
|
* @private
|
|
*/
|
|
onActionButtonClick: function onActionButtonClick() {
|
|
var _this6 = this;
|
|
|
|
debug('action button click');
|
|
var config = { detail: { type: this.action } };
|
|
var e = new CustomEvent('action', config);
|
|
setTimeout(function () {
|
|
return _this6.dispatchEvent(e);
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Get the margin-start value required
|
|
* to center the title between
|
|
* surrounding buttons.
|
|
*
|
|
* @param {Object} title
|
|
* @return {Object}
|
|
* @private
|
|
*/
|
|
getTitleMarginStart: function getTitleMarginStart() {
|
|
var start = this.titleStart;
|
|
var end = this.titleEnd;
|
|
var marginStart = end - start;
|
|
debug('get title margin start', marginStart);
|
|
return marginStart;
|
|
},
|
|
|
|
/**
|
|
* Get all the buttons (<a> & <button>)
|
|
* before the first <h1>.
|
|
*
|
|
* @return {Array}
|
|
* @private
|
|
*/
|
|
getButtonsBeforeTitle: function getButtonsBeforeTitle() {
|
|
var children = this.children;
|
|
var l = children.length;
|
|
var els = [];
|
|
|
|
for (var i = 0; i < l; i++) {
|
|
var el = children[i];
|
|
if (el.tagName === 'H1') {
|
|
break;
|
|
}
|
|
if (!contributesToLayout(el)) {
|
|
continue;
|
|
}
|
|
|
|
els.push(el);
|
|
}
|
|
|
|
// Don't forget the action button
|
|
if (this.action) {
|
|
els.push(this.els.actionButton);
|
|
}
|
|
return els;
|
|
},
|
|
|
|
/**
|
|
* Get all the buttons (<a> & <button>)
|
|
* after the last <h1>.
|
|
*
|
|
* @return {Array}
|
|
* @private
|
|
*/
|
|
getButtonsAfterTitle: function getButtonsAfterTitle() {
|
|
var children = this.children;
|
|
var els = [];
|
|
|
|
for (var i = children.length - 1; i >= 0; i--) {
|
|
var el = children[i];
|
|
if (el.tagName === 'H1') {
|
|
break;
|
|
}
|
|
if (!contributesToLayout(el)) {
|
|
continue;
|
|
}
|
|
|
|
els.push(el);
|
|
}
|
|
|
|
return els;
|
|
},
|
|
|
|
/**
|
|
* Get the sum of the width of
|
|
* the given buttons.
|
|
*
|
|
* This function is optimized to avoid reading
|
|
* `element.clientWidth` when possible.
|
|
*
|
|
* If a button is `display: none` it will
|
|
* have a `.clientWidth` of 0, therefore won't
|
|
* contribute anything to the overall sum.
|
|
*
|
|
* @param {Array} buttons
|
|
* @return {Number}
|
|
* @private
|
|
*/
|
|
sumButtonWidths: function sumButtonWidths(buttons) {
|
|
var _this7 = this;
|
|
|
|
var defaultWidth = 50;
|
|
var sum = buttons.reduce(function (prev, button) {
|
|
var isStandardButton = button === _this7.els.actionButton;
|
|
var width = isStandardButton ? defaultWidth : button.clientWidth;
|
|
return prev + width;
|
|
}, 0);
|
|
|
|
debug('sum button widths', buttons, sum);
|
|
return sum;
|
|
},
|
|
|
|
/**
|
|
* Known attribute property
|
|
* descriptors.
|
|
*
|
|
* These setters get called when matching
|
|
* attributes change on the element.
|
|
*
|
|
* @type {Object}
|
|
*/
|
|
attrs: {
|
|
action: {
|
|
get: function get() {
|
|
return this._action;
|
|
},
|
|
set: function set(value) {
|
|
var action = KNOWN_ACTIONS[value];
|
|
if (action === this._action) {
|
|
return;
|
|
}
|
|
this.setAttr('action', action);
|
|
this._action = action;
|
|
}
|
|
},
|
|
|
|
titleStart: {
|
|
get: function get() {
|
|
debug('get title-start');
|
|
if ('_titleStart' in this) {
|
|
return this._titleStart;
|
|
}
|
|
var buttons = this.getButtonsBeforeTitle();
|
|
var value = this.sumButtonWidths(buttons);
|
|
debug('get title-start', buttons, value);
|
|
return value;
|
|
},
|
|
|
|
set: function set(value) {
|
|
debug('set title-start', value);
|
|
value = parseInt(value, 10);
|
|
if (value === this._titleStart || isNaN(value)) {
|
|
return;
|
|
}
|
|
this.setAttr('title-start', value);
|
|
this._titleStart = value;
|
|
debug('set');
|
|
}
|
|
},
|
|
|
|
titleEnd: {
|
|
get: function get() {
|
|
debug('get title-end');
|
|
if ('_titleEnd' in this) {
|
|
return this._titleEnd;
|
|
}
|
|
var buttons = this.getButtonsAfterTitle();
|
|
return this.sumButtonWidths(buttons);
|
|
},
|
|
|
|
set: function set(value) {
|
|
debug('set title-end', value);
|
|
value = parseInt(value, 10);
|
|
if (value === this._titleEnd || isNaN(value)) {
|
|
return;
|
|
}
|
|
this.setAttr('title-end', value);
|
|
this._titleEnd = value;
|
|
}
|
|
},
|
|
|
|
noFontFit: {
|
|
get: function get() {
|
|
return this._noFontFit || false;
|
|
},
|
|
set: function set(value) {
|
|
debug('set no-font-fit', value);
|
|
value = !!(value || value === '');
|
|
|
|
if (value === this.noFontFit) {
|
|
return;
|
|
}
|
|
this._noFontFit = value;
|
|
|
|
if (value) {
|
|
this.setAttr('no-font-fit', '');
|
|
} else {
|
|
this.removeAttr('no-font-fit');
|
|
}
|
|
}
|
|
},
|
|
|
|
// The [ignore-dir] attribute can be used to force the older behavior of
|
|
// gaia-header, where the whole header is always displayed in LTR mode.
|
|
ignoreDir: {
|
|
get: function get() {
|
|
return this._ignoreDir || false;
|
|
},
|
|
set: function set(value) {
|
|
debug('set ignore-dir', value);
|
|
value = !!(value || value === '');
|
|
|
|
if (value === this.ignoreDir) {
|
|
return;
|
|
}
|
|
this._ignoreDir = value;
|
|
this.dirObserver = !value;
|
|
|
|
if (value) {
|
|
this.setAttr('ignore-dir', '');
|
|
} else {
|
|
this.removeAttr('ignore-dir');
|
|
}
|
|
}
|
|
}
|
|
},
|
|
|
|
template: "<div class=\"inner\">\n <button class=\"action-button\">\n <content select=\"[l10n-action]\"></content>\n </button>\n <content></content>\n </div>\n\n <style>\n\n :host {\n display: block;\n -moz-user-select: none;\n\n --gaia-header-button-color:\n var(--header-button-color,\n var(--header-color,\n var(--link-color,\n inherit)));\n }\n\n /**\n * [hidden]\n */\n\n :host[hidden] {\n display: none;\n }\n\n /** Reset\n ---------------------------------------------------------*/\n\n ::-moz-focus-inner { border: 0; }\n\n /** Inner\n ---------------------------------------------------------*/\n\n .inner {\n display: flex;\n min-height: 50px;\n -moz-user-select: none;\n\n background:\n var(--header-background,\n var(--background,\n #fff));\n }\n\n /** Action Button\n ---------------------------------------------------------*/\n\n /**\n * 1. Hidden by default\n */\n\n .action-button {\n position: relative;\n\n display: none; /* 1 */\n width: 50px;\n font-size: 30px;\n margin: 0;\n padding: 0;\n border: 0;\n outline: 0;\n\n align-items: center;\n background: none;\n cursor: pointer;\n transition: opacity 200ms 280ms;\n color:\n var(--header-action-button-color,\n var(--header-icon-color,\n var(--gaia-header-button-color)));\n }\n\n /**\n * [action=back]\n * [action=menu]\n * [action=close]\n *\n * 1. For icon vertical-alignment\n */\n\n [action=back] .action-button,\n [action=menu] .action-button,\n [action=close] .action-button {\n display: flex; /* 1 */\n }\n\n /**\n * :active\n */\n\n .action-button:active {\n transition: none;\n opacity: 0.2;\n }\n\n /** Action Button Icon\n ---------------------------------------------------------*/\n\n .action-button:before {\n font-family: 'gaia-icons';\n font-style: normal;\n text-rendering: optimizeLegibility;\n font-weight: 500;\n }\n\n [action=close] .action-button:before { content: 'close' }\n [action=menu] .action-button:before { content: 'menu' }\n\n [action=back]:-moz-dir(ltr) .action-button:before { content: 'left' }\n [action=back]:-moz-dir(rtl) .action-button:before { content: 'right' }\n\n /** Action Button Icon\n ---------------------------------------------------------*/\n\n /**\n * 1. To enable vertical alignment.\n */\n\n .action-button:before {\n display: block;\n }\n\n /** Action Button Text\n ---------------------------------------------------------*/\n\n /**\n * To provide custom localized content for\n * the action-button, we allow the user\n * to provide an element with the class\n * .l10n-action. This node is then\n * pulled inside the real action-button.\n *\n * Example:\n *\n * <gaia-header action=\"back\">\n * <span l10n-action aria-label=\"Back\">Localized text</span>\n * <h1>title</h1>\n * </gaia-header>\n */\n\n ::content [l10n-action] {\n position: absolute;\n left: 0;\n top: 0;\n width: 100%;\n height: 100%;\n font-size: 0;\n }\n\n /** Title\n ---------------------------------------------------------*/\n\n /**\n * 1. Vertically center text. We can't use flexbox\n * here as it breaks text-overflow ellipsis\n * without an inner div.\n */\n\n ::content h1 {\n flex: 1;\n margin: 0;\n padding: 0;\n overflow: hidden;\n\n white-space: nowrap;\n text-overflow: ellipsis;\n text-align: center;\n line-height: 50px; /* 1 */\n font-weight: 300;\n font-style: italic;\n font-size: 24px;\n\n color:\n var(--header-title-color,\n var(--header-color,\n var(--title-color,\n var(--text-color,\n inherit))));\n }\n\n /**\n * [ignore-dir]\n *\n * When the <gaia-header> component has an [ignore-dir] attribute, header\n * direction is forced to LTR but we still want the <h1> text to be reversed\n * so that strings like '1 selected' become 'selected 1'.\n *\n * When we're happy for <gaia-header> to be fully RTL responsive we won't need\n * these rules anymore, but this depends on all Gaia apps being ready.\n *\n * This should be safe to remove when bug 1179459 lands.\n */\n\n :host[ignore-dir] {\n direction: ltr;\n }\n\n :host[ignore-dir]:-moz-dir(rtl) h1 {\n direction: rtl;\n }\n\n /** Buttons\n ---------------------------------------------------------*/\n\n ::content a,\n ::content button {\n position: relative;\n z-index: 1;\n box-sizing: border-box;\n display: flex;\n width: auto;\n height: auto;\n min-width: 50px;\n margin: 0;\n padding: 0 10px;\n outline: 0;\n border: 0;\n\n font-size: 14px;\n line-height: 1;\n align-items: center;\n justify-content: center;\n text-decoration: none;\n text-align: center;\n background: none;\n border-radius: 0;\n font-style: italic;\n cursor: pointer;\n transition: opacity 200ms 280ms;\n color: var(--gaia-header-button-color);\n }\n\n /**\n * :active\n */\n\n ::content a:active,\n ::content button:active {\n transition: none;\n opacity: 0.2;\n }\n\n /**\n * [hidden]\n */\n\n ::content a[hidden],\n ::content button[hidden] {\n display: none;\n }\n\n /**\n * [disabled]\n */\n\n ::content a[disabled],\n ::content button[disabled] {\n pointer-events: none;\n color: var(--header-disabled-button-color);\n }\n\n /** Icon Buttons\n ---------------------------------------------------------*/\n\n /**\n * Icons are a different color to text\n */\n\n ::content .icon,\n ::content [data-icon] {\n color:\n var(--header-icon-color,\n var(--gaia-header-button-color));\n }\n\n /**\n * If users want their action button\n * to be in the component's light-dom\n * they can add an .action class\n * to make it look like the\n * shadow action button.\n */\n\n ::content .action {\n color:\n var(--header-action-button-color,\n var(--header-icon-color,\n var(--gaia-header-button-color)));\n }\n\n /**\n * [data-icon]:empty\n *\n * Icon buttons with no textContent,\n * should always be 50px.\n *\n * This is to prevent buttons being\n * larger than they should be before\n * icon-font has loaded.\n */\n\n ::content [data-icon]:empty {\n width: 50px;\n }\n\n </style>",
|
|
|
|
// Test hook
|
|
nextTick: nextTick
|
|
});
|
|
|
|
/**
|
|
* Utils
|
|
*/
|
|
|
|
/**
|
|
* Determines whether passed element
|
|
* contributes to the layout in gaia-header.
|
|
*
|
|
* Children with `[l10n-action]` get distributed
|
|
* inside the action-button so don't occupy
|
|
* space before the `<h1>`.
|
|
*
|
|
* @param {Element} el
|
|
* @return {Boolean}
|
|
*/
|
|
function contributesToLayout(el) {
|
|
return el.localName !== 'style' && !el.hasAttribute('l10n-action');
|
|
}
|
|
|
|
/**
|
|
* Set a 'style id' property that
|
|
* can be retrieved later.
|
|
*
|
|
* Used to determine whether a title's
|
|
* `style` needs to be updated or not.
|
|
*
|
|
* @param {Element} el
|
|
* @param {String} id
|
|
*/
|
|
function setStyleId(el, id) {
|
|
el._styleId = id;
|
|
}
|
|
|
|
/**
|
|
* Get a 'style id' property.
|
|
*
|
|
* Used to determine whether a title's
|
|
* `style` needs to be updated or not.
|
|
*
|
|
* @param {Element} el
|
|
* @param {String} id
|
|
*/
|
|
function getStyleId(el) {
|
|
return el._styleId;
|
|
}
|
|
|
|
/**
|
|
* Calls callback at next 'microtask'.
|
|
* Returns an object that has
|
|
* a `.clear()` method.
|
|
*
|
|
* @param {Function} fn
|
|
* @return {Object} { clear }
|
|
*/
|
|
function nextTick(fn) {
|
|
var cleared;
|
|
Promise.resolve().then(function () {
|
|
if (!cleared) {
|
|
fn();
|
|
}
|
|
});
|
|
return { clear: function clear() {
|
|
cleared = true;
|
|
} };
|
|
}
|
|
});
|
|
})(typeof define == 'function' && define.amd ? define : (function (n, w) {
|
|
'use strict';return typeof module == 'object' ? function (c) {
|
|
c(require, exports, module);
|
|
} : function (c) {
|
|
var m = { exports: {} };c(function (n) {
|
|
return w[n];
|
|
}, m.exports, m);w[n] = m.exports;
|
|
};
|
|
})('gaia-header', this));
|
|
}, { "font-fit": 1, "gaia-component": 2, "gaia-icons": 3 }] }, {}, [4])(4);
|
|
});
|