; (function (define) { 'use strict'; define([ 'require', 'exports', 'module', 'gaia-component', 'font-fit', 'gaia-icons' ], function (require, exports, module) { var component = require('gaia-component'); var fontFit = require('font-fit'); require('gaia-icons'); var debug = 0 ? console.log.bind(console) : function () { }; const KNOWN_ACTIONS = { menu: 'menu', back: 'back', close: 'close' }; const TITLE_FONT = 'italic 300 24px FiraSans'; const TITLE_PADDING = 10; const MINIMUM_FONT_SIZE_CENTERED = 20; const MINIMUM_FONT_SIZE_UNCENTERED = 16; const MAXIMUM_FONT_SIZE = 23; module.exports = component.register('gaia-header', { extensible: false, dirObserver: true, created: function () { debug('created'); this.setupShadowRoot(); this.els = { actionButton: this.shadowRoot.querySelector('.action-button'), titles: this.getElementsByTagName('h1') }; this.els.actionButton.addEventListener('click', e => this.onActionButtonClick(e)); this.observer = new MutationObserver(this.onMutation.bind(this)); 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; this.onResize = this.onResize.bind(this); }, attached: function () { debug('attached'); this.runFontFitSoon(); this.observerStart(); window.addEventListener('resize', this.onResize); }, detached: function () { debug('detached'); window.removeEventListener('resize', this.onResize); this.observerStop(); this.clearPending(); }, clearPending: function () { for (var key in this.pending) { this.pending[key].clear(); delete this.pending[key]; } window.cancelAnimationFrame(this._resizeThrottlingId); this._resizeThrottlingId = null; }, runFontFit: function () { debug('run font-fit'); if (this.noFontFit) { return Promise.resolve(); } var titles = this.els.titles; var space = this.getTitleSpace(); var styles = [].map.call(titles, el => this.getTitleStyle(el, space)); return this.setTitleStylesSoon(styles); }, runFontFitSoon: function () { debug('run font-fit soon'); if (this.pending.runFontFitSoon) { return; } this.pending.runFontFitSoon = this.nextTick(() => { delete this.pending.runFontFitSoon; this.runFontFit(); }); }, getTitleStyle: function (el, space) { debug('get el style', el, space); var text = el.textContent; var styleId = space.start + text + space.end + '#' + space.value; if (!text || !text.trim()) { return debug('exit: no text'); } 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 (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 }; }, setTitleStylesSoon: function (styles) { debug('set title styles soon', styles); var key = 'setStyleTitlesSoon'; this._titleStyles = styles; if (this.unresolved[key]) { return this.unresolved[key]; } this.unresolved[key] = new Promise(resolve => { this.pending[key] = this.nextTick(() => { var styles = this._titleStyles; var els = this.els.titles; [].forEach.call(els, (el, i) => { if (!styles[i]) { return debug('exit'); } this.setTitleStyle(el, styles[i]); }); delete this._titleStyles; delete this.unresolved[key]; delete this.pending[key]; resolve(); }); }); }, setTitleStyle: function (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(); }, fontFit: function (text, space, opts = {}) { 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); }, observerStart: function () { if (this.observing) { return; } this.observer.observe(this, { childList: true, attributes: true, subtree: true }); this.observing = true; debug('observer started'); }, observerStop: function () { if (!this.observing) { return; } this.observer.disconnect(); this.observing = false; debug('observer stopped'); }, onResize: function (e) { debug('onResize', this._resizeThrottlingId); if (this._resizeThrottlingId !== null) { return; } this._resizeThrottlingId = window.requestAnimationFrame(() => { this._resizeThrottlingId = null; this.runFontFitSoon(); }); }, onMutation: function (mutations) { debug('on mutation', mutations); if (!this.pending.runFontFitSoon) { this.runFontFit(); } }, getTitleSpace: function () { 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; }, getWidth: function () { var value = this.notFlush ? this.clientWidth : window.innerWidth; debug('get width', value); return value; }, triggerAction: function () { if (this.action) { this.els.actionButton.click(); } }, onActionButtonClick: function () { debug('action button click'); var config = { detail: { type: this.action } }; var e = new CustomEvent('action', config); setTimeout(() => this.dispatchEvent(e)); }, getTitleMarginStart: function () { var start = this.titleStart; var end = this.titleEnd; var marginStart = end - start; debug('get title margin start', marginStart); return marginStart; }, getButtonsBeforeTitle: function () { 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); } if (this.action) { els.push(this.els.actionButton); } return els; }, getButtonsAfterTitle: function () { 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; }, sumButtonWidths: function (buttons) { var defaultWidth = 50; var sum = buttons.reduce((prev, button) => { var isStandardButton = button === this.els.actionButton; var width = isStandardButton ? defaultWidth : button.clientWidth; return prev + width; }, 0); debug('sum button widths', buttons, sum); return sum; }, attrs: { action: { get: function () { return this._action; }, set: function (value) { var action = KNOWN_ACTIONS[value]; if (action === this._action) { return; } this.setAttr('action', action); this._action = action; } }, titleStart: { get: function () { 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 (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 () { debug('get title-end'); if ('_titleEnd' in this) { return this._titleEnd; } var buttons = this.getButtonsAfterTitle(); return this.sumButtonWidths(buttons); }, set: function (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 () { return this._noFontFit || false; }, set: function (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'); } } }, ignoreDir: { get: function () { return this._ignoreDir || false; }, set: function (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: `
`, nextTick: nextTick }); function contributesToLayout(el) { return el.localName !== 'style' && !el.hasAttribute('l10n-action'); } function setStyleId(el, id) { el._styleId = id; } function getStyleId(el) { return el._styleId; } function nextTick(fn) { var cleared; Promise.resolve().then(() => { if (!cleared) { fn(); } }); return { clear: function () { 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)));