UNPKG

material-design-kit

Version:

Interactive web components inspired from Material Design, using vanilla CSS, JavaScript and HTML.

1,675 lines (1,442 loc) 77.9 kB
'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); var domFactory = require('dom-factory'); var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {}; function createCommonjsModule(fn, basedir, module) { return module = { path: basedir, exports: {}, require: function (path, base) { return commonjsRequire(path, (base === undefined || base === null) ? module.path : base); } }, fn(module, module.exports), module.exports; } function commonjsRequire () { throw new Error('Dynamic requires are not currently supported by @rollup/plugin-commonjs'); } var watchObject = createCommonjsModule(function (module, exports) { !function(t,e){module.exports=e();}(commonjsGlobal,function(){return function(t){function e(r){if(n[r])return n[r].exports;var o=n[r]={exports:{},id:r,loaded:!1};return t[r].call(o.exports,o,o.exports,e),o.loaded=!0,o.exports}var n={};return e.m=t,e.c=n,e.p="",e(0)}([function(t,e,n){function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0}),e.unwatch=e.watch=void 0;var o=n(4),i=r(o),c=n(3),a=r(c),f=(e.watch=function(){for(var t=arguments.length,e=Array(t),n=0;t>n;n++)e[n]=arguments[n];var r=e[1];_(r)?g.apply(void 0,e):f(r)?b.apply(void 0,e):w.apply(void 0,e);},e.unwatch=function(){for(var t=arguments.length,e=Array(t),n=0;t>n;n++)e[n]=arguments[n];var r=e[1];_(r)||void 0===r?m.apply(void 0,e):f(r)?O.apply(void 0,e):x.apply(void 0,e);},function(t){return "[object Array]"==={}.toString.call(t)}),u=function(t){return "[object Object]"==={}.toString.call(t)},_=function(t){return "[object Function]"==={}.toString.call(t)},s=function(t,e,n){(0, a["default"])(t,e,{enumerable:!1,configurable:!0,writable:!1,value:n});},l=function(t,e,n,r){(0, a["default"])(t,e,{get:n,set:function(t){r.call(this,t);},enumerable:!0,configurable:!0});},p=function(t,e,n,r,o){var i=void 0,c=t.__watchers__[e];(i=t.__watchers__.__watchall__)&&(c=c?c.concat(i):i);for(var a=c?c.length:0,f=0;a>f;f++)c[f].call(t,n,r,e,o);},v=["pop","push","reverse","shift","sort","unshift","splice"],h=function(t,e,n,r){s(t,n,function(){for(var o=0,i=void 0,c=void 0,a=arguments.length,f=Array(a),u=0;a>u;u++)f[u]=arguments[u];if("splice"===n){var _=f[0],s=_+f[1];i=t.slice(_,s),c=[];for(var l=2;l<f.length;l++)c[l-2]=f[l];o=_;}else c="push"===n||"unshift"===n?f.length>0?f:void 0:f.length>0?f[0]:void 0;var p=e.apply(t,f);return "pop"===n?(i=p,o=t.length):"push"===n?o=t.length-1:"shift"===n?i=p:"unshift"!==n&&void 0===c&&(c=p),r.call(t,o,n,c,i),p});},d=function(t,e){if(_(e)&&t&&!(t instanceof String)&&f(t))for(var n=v.length;n>0;n--){var r=v[n-1];h(t,t[r],r,e);}},y=function(t,e,n,r){var o=!1,c=f(t);void 0===t.__watchers__&&(s(t,"__watchers__",{}),c&&d(t,function(n,o,i,c){if(p(t,n,i,c,o),0!==r&&i&&(u(i)||f(i))){var a=void 0,_=t.__watchers__[e];(a=t.__watchers__.__watchall__)&&(_=_?_.concat(a):a);for(var s=_?_.length:0,l=0;s>l;l++)if("splice"!==o)g(i,_[l],void 0===r?r:r-1);else for(var v=0;v<i.length;v++)g(i[v],_[l],void 0===r?r:r-1);}})),void 0===t.__proxy__&&s(t,"__proxy__",{}),void 0===t.__watchers__[e]&&(t.__watchers__[e]=[],c||(o=!0));for(var _=0;_<t.__watchers__[e].length;_++)if(t.__watchers__[e][_]===n)return;t.__watchers__[e].push(n),o&&!function(){var n=(0, i["default"])(t,e);void 0!==n?!function(){var r={enumerable:n.enumerable,configurable:n.configurable},o=["get","set"];o.forEach(function(e){void 0!==n[e]&&(r[e]=function(){for(var r=arguments.length,o=Array(r),i=0;r>i;i++)o[i]=arguments[i];return n[e].apply(t,o)});});var i=["writable","value"];i.forEach(function(t){void 0!==n[t]&&(r[t]=n[t]);}),(0, a["default"])(t.__proxy__,e,r);}():t.__proxy__[e]=t[e];var o=function(){return t.__proxy__[e]},c=function(n){var o=t.__proxy__[e];if(0!==r&&t[e]&&(u(t[e])||f(t[e]))&&!t[e].__watchers__)for(var i=0;i<t.__watchers__[e].length;i++)g(t[e],t.__watchers__[e][i],void 0===r?r:r-1);o!==n&&(t.__proxy__[e]=n,p(t,e,n,o,"set"));};l(t,e,o,c);}();},g=function P(t,e,n){if("string"!=typeof t&&(t instanceof Object||f(t)))if(f(t)){if(y(t,"__watchall__",e,n),void 0===n||n>0)for(var r=0;r<t.length;r++)P(t[r],e,n);}else {var o=[];for(var i in t)({}).hasOwnProperty.call(t,i)&&o.push(i);b(t,o,e,n);}},w=function(t,e,n,r){"string"!=typeof t&&(t instanceof Object||f(t))&&(_(t[e])||(null!==t[e]&&(void 0===r||r>0)&&g(t[e],n,void 0!==r?r-1:r),y(t,e,n,r)));},b=function(t,e,n,r){if("string"!=typeof t&&(t instanceof Object||f(t)))for(var o=0;o<e.length;o++){var i=e[o];w(t,i,n,r);}},x=function(t,e,n){if(void 0!==t.__watchers__&&void 0!==t.__watchers__[e])if(void 0===n)delete t.__watchers__[e];else for(var r=0;r<t.__watchers__[e].length;r++)t.__watchers__[e][r]===n&&t.__watchers__[e].splice(r,1);},O=function(t,e,n){for(var r in e)e.hasOwnProperty(r)&&x(t,e[r],n);},j=function S(t,e){var n=[];for(var r in t)t.hasOwnProperty(r)&&(t[r]instanceof Object&&S(t[r],e),n.push(r));O(t,n,e);},m=function(t,e){if(!(t instanceof String||!t instanceof Object&&!f(t)))if(f(t)){for(var n=["__watchall__"],r=0;r<t.length;r++)n.push(r);O(t,n,e);}else j(t,e);};},function(t,e){var n=t.exports={version:"1.2.6"};"number"==typeof __e&&(__e=n);},function(t,e){var n=Object;t.exports={create:n.create,getProto:n.getPrototypeOf,isEnum:{}.propertyIsEnumerable,getDesc:n.getOwnPropertyDescriptor,setDesc:n.defineProperty,setDescs:n.defineProperties,getKeys:n.keys,getNames:n.getOwnPropertyNames,getSymbols:n.getOwnPropertySymbols,each:[].forEach};},function(t,e,n){t.exports={"default":n(5),__esModule:!0};},function(t,e,n){t.exports={"default":n(6),__esModule:!0};},function(t,e,n){var r=n(2);t.exports=function(t,e,n){return r.setDesc(t,e,n)};},function(t,e,n){var r=n(2);n(17),t.exports=function(t,e){return r.getDesc(t,e)};},function(t,e){t.exports=function(t){if("function"!=typeof t)throw TypeError(t+" is not a function!");return t};},function(t,e){var n={}.toString;t.exports=function(t){return n.call(t).slice(8,-1)};},function(t,e,n){var r=n(7);t.exports=function(t,e,n){if(r(t),void 0===e)return t;switch(n){case 1:return function(n){return t.call(e,n)};case 2:return function(n,r){return t.call(e,n,r)};case 3:return function(n,r,o){return t.call(e,n,r,o)}}return function(){return t.apply(e,arguments)}};},function(t,e){t.exports=function(t){if(void 0==t)throw TypeError("Can't call method on "+t);return t};},function(t,e,n){var r=n(13),o=n(1),i=n(9),c="prototype",a=function(t,e,n){var f,u,_,s=t&a.F,l=t&a.G,p=t&a.S,v=t&a.P,h=t&a.B,d=t&a.W,y=l?o:o[e]||(o[e]={}),g=l?r:p?r[e]:(r[e]||{})[c];l&&(n=e);for(f in n)u=!s&&g&&f in g,u&&f in y||(_=u?g[f]:n[f],y[f]=l&&"function"!=typeof g[f]?n[f]:h&&u?i(_,r):d&&g[f]==_?function(t){var e=function(e){return this instanceof t?new t(e):t(e)};return e[c]=t[c],e}(_):v&&"function"==typeof _?i(Function.call,_):_,v&&((y[c]||(y[c]={}))[f]=_));};a.F=1,a.G=2,a.S=4,a.P=8,a.B=16,a.W=32,t.exports=a;},function(t,e){t.exports=function(t){try{return !!t()}catch(e){return !0}};},function(t,e){var n=t.exports="undefined"!=typeof window&&window.Math==Math?window:"undefined"!=typeof self&&self.Math==Math?self:Function("return this")();"number"==typeof __g&&(__g=n);},function(t,e,n){var r=n(8);t.exports=Object("z").propertyIsEnumerable(0)?Object:function(t){return "String"==r(t)?t.split(""):Object(t)};},function(t,e,n){var r=n(11),o=n(1),i=n(12);t.exports=function(t,e){var n=(o.Object||{})[t]||Object[t],c={};c[t]=e(n),r(r.S+r.F*i(function(){n(1);}),"Object",c);};},function(t,e,n){var r=n(14),o=n(10);t.exports=function(t){return r(o(t))};},function(t,e,n){var r=n(16);n(15)("getOwnPropertyDescriptor",function(t){return function(e,n){return t(r(e),n)}});}])}); }); /** * Allows an element to respond to scroll events from a designated scroll target. * Use from a consumer via dom-factory mixins i.e. * * import { scrollTargetBehavior } from 'material-design-kit' * const anotherComponent = () => ({ * mixins: [ scrollTargetBehavior ] * }) * * @param {HTMLElement} element The element which should respond to scroll events * @return {Object} */ const scrollTargetBehavior = () => ({ // The scroll target selector _scrollTargetSelector: null, // The scroll target HTMLElement _scrollTarget: null, /** * Get the HTMLELement of the scroll target * @return {HTMLElement} */ get scrollTarget () { if (this._scrollTarget) { return this._scrollTarget } return this._defaultScrollTarget }, /** * Set the HTMLElement of the scroll target * @param {HTMLElement} value */ set scrollTarget (value) { this._scrollTarget = value; }, /** * Get the scroll target selector * @return {String|HTMLElement} */ get scrollTargetSelector () { if (this._scrollTargetSelector) { return this._scrollTargetSelector } if (this.element.hasAttribute('data-scroll-target')) { return this.element.getAttribute('data-scroll-target') } }, /** * Set the scroll target selector * @param {String|HTMLElement} value */ set scrollTargetSelector (value) { this._scrollTargetSelector = value; }, /** * Get the default scroll target * @return {HTMLElement} */ get _defaultScrollTarget () { return this._doc }, /** * Get the ownerDocument * @return {HTMLElement} */ get _owner () { return this.element.ownerDocument }, /** * Get the document element * @return {HTMLElement} */ get _doc () { return this._owner.documentElement }, /** * Gets the number of pixels that the content of an element is scrolled upward. * @return {number} */ get _scrollTop () { if (this._isValidScrollTarget()) { return this.scrollTarget === this._doc ? window.pageYOffset : this.scrollTarget.scrollTop } return 0 }, /** * Sets the number of pixels that the content of an element is scrolled upward. * @param {number} top */ set _scrollTop (top) { if (this.scrollTarget === this._doc) { window.scrollTo(window.pageXOffset, top); } else if (this._isValidScrollTarget()) { this.scrollTarget.scrollTop = top; } }, /** * Gets the number of pixels that the content of an element is scrolled to the left. * @return {number} */ get _scrollLeft () { if (this._isValidScrollTarget()) { return this.scrollTarget === this._doc ? window.pageXOffset : this.scrollTarget.scrollLeft } return 0 }, /** * Sets the number of pixels that the content of an element is scrolled to the left. * @param {number} left */ set _scrollLeft (left) { if (this.scrollTarget === this._doc) { window.scrollTo(left, window.pageYOffset); } else if (this._isValidScrollTarget()) { this.scrollTarget.scrollLeft = left; } }, /** * Gets the width of the scroll target. * @return {number} */ get _scrollTargetWidth () { if (this._isValidScrollTarget()) { return this.scrollTarget === this._doc ? window.innerWidth : this.scrollTarget.offsetWidth } return 0 }, /** * Gets the height of the scroll target. * @return {number} */ get _scrollTargetHeight () { if (this._isValidScrollTarget()) { return this.scrollTarget === this._doc ? window.innerHeight : this.scrollTarget.offsetHeight } return 0 }, get _isPositionedFixed () { if (this.element instanceof HTMLElement) { return window.getComputedStyle(this.element).position === 'fixed' } return false }, /** * Attach the scroll event listener to the scroll target * @param {string|HTMLElement} scrollTarget The scroll target (optional) */ attachToScrollTarget () { this.detachFromScrollTarget(); watchObject.watch(this, 'scrollTargetSelector', this.attachToScrollTarget); if (this.scrollTargetSelector === 'document') { this.scrollTarget = this._doc; } else if (typeof this.scrollTargetSelector === 'string') { this.scrollTarget = document.querySelector(`${ this.scrollTargetSelector }`); } else if (this.scrollTargetSelector instanceof HTMLElement) { this.scrollTarget = this.scrollTargetSelector; } if (!this._doc.style.overflow) { this._doc.style.overflow = this.scrollTarget !== this._doc ? 'hidden' : ''; } if (this.scrollTarget) { this.eventTarget = this.scrollTarget === this._doc ? window : this.scrollTarget; this._boundScrollHandler = this._boundScrollHandler || this._scrollHandler.bind(this); this._loop(); // this.eventTarget.addEventListener('scroll', this._boundScrollHandler) } }, _loop () { requestAnimationFrame(this._boundScrollHandler); }, /** * Detach the scroll event listener from the scroll target * @return {[type]} [description] */ detachFromScrollTarget () { watchObject.unwatch(this, 'scrollTargetSelector', this.attachToScrollTarget); // if (this.eventTarget) { // this.eventTarget.removeEventListener('scroll', this._boundScrollHandler) // } }, /** * Scrolls the content to a particular place. * @param {number} left The left position * @param {number} top The top position */ scroll (left = 0, top = 0) { if (this.scrollTarget === this._doc) { window.scrollTo(left, top); } else if (this._isValidScrollTarget()) { this.scrollTarget.scrollLeft = left; this.scrollTarget.scrollTop = top; } }, /** * Scrolls the content to a particular place using a behavior. * @param {Number} left The left position * @param {Number} top The top position * @param {String} behavior The behavior name * @param {Function} scrollFn Custom scroll timing function used with `behavior` (optional) */ scrollWithBehavior (left = 0, top = 0, behavior, scrollFn) { // Scroll timing function used with `behavior` scrollFn = typeof scrollFn === 'function' ? scrollFn : function easeOutQuad (t, b, c, d) { t /= d; return -c * t * (t - 2) + b }; // Smooth if (behavior === 'smooth') { let startTime = Date.now(); let currentScrollTop = this._scrollTop; let currentScrollLeft = this._scrollLeft; let dScrollTop = top - currentScrollTop; let dScrollLeft = left - currentScrollLeft; let duration = 300; (function updateFrame () { let now = Date.now(); let elapsedTime = now - startTime; if (elapsedTime < duration) { this.scroll( scrollFn(elapsedTime, currentScrollLeft, dScrollLeft, duration), scrollFn(elapsedTime, currentScrollTop, dScrollTop, duration) ); requestAnimationFrame(updateFrame.bind(this)); } }).call(this); } // Default else { this.scroll(left, top); } }, /** * Returns true if the scroll target is a valid HTMLElement. * @return {Boolean} */ _isValidScrollTarget () { return this.scrollTarget instanceof HTMLElement }, /** * Scroll event handler (runs on every scroll event) */ _scrollHandler () {} }); /** * Allows a scrollTargetBehavior consumer to use scroll effects. * Use from a consumer via dom-factory mixins i.e. * * import { * scrollTargetBehavior, * scrollEffectBehavior * } from 'material-design-kit' * * const anotherComponent = () => ({ * mixins: [ * scrollTargetBehavior, * scrollEffectBehavior * ] * }) * * @param {HTMLElement} element The element which should respond to scroll events * @return {Object} */ const scrollEffectBehavior = () => ({ // List of registered scroll effects _scrollEffects: {}, // List of effects handlers that will take place during scroll _effectsRunFn: [], // List of the effects definitions _effects: [], // Effects config _effectsConfig: null, /** * Get the list of effect names to run * @return {Array} */ get effects () { if (!this.element.dataset.effects) { return [] } return this.element.dataset.effects.split(' ') }, /** * Get the effects config object * @return {Object} */ get effectsConfig () { if (this._effectsConfig) { return this._effectsConfig } if (this.element.hasAttribute('data-effects-config')) { try { return JSON.parse(this.element.getAttribute('data-effects-config')) } catch (e) {} } return {} }, /** * Set the effects config object * @param {Object} value */ set effectsConfig (value) { this._effectsConfig = value; }, /** * The clamped value of `_scrollTop`. * @return {number} */ get _clampedScrollTop () { return Math.max(0, this._scrollTop) }, /** * Registers a scroll effect * @param {string} effectName The effect name * @param {Object} effectDef The effect definition */ registerEffect (effectName, effectDef) { if (this._scrollEffects[effectName] !== undefined) { throw new Error(`effect ${ effectName } is already registered.`) } this._scrollEffects[effectName] = effectDef; }, /** * Returns true if the element is visible in the current viewport. * This method should be overridden by the consumer of this behavior. * @return {Boolean} */ isOnScreen () { return false }, /** * Returns true if there's content below the element. * This method should be overridden by the consumer of this behavior. * @return {Boolean} */ isContentBelow () { return false }, /** * Creates an effect object from an effect's name that can be used to run * effects programmatically. * @param {string} effectName The effect name * @param {Object} effectConfig The effect config (optional) * @return {Object} An effect object with the following functions: * * `effect.setUp()`, Sets up the requirements for the effect * `effect.run(progress, top)`, Runs the effect given a `progress` * `effect.tearDown()`, Clean up */ createEffect (effectName, effectConfig = {}) { const effectDef = this._scrollEffects[effectName]; if (typeof effectDef === undefined) { throw new ReferenceError(`Scroll effect ${ effectName } was not registered`) } const prop = this._boundEffect(effectDef, effectConfig); prop.setUp(); return prop }, /** * Returns an effect object bound to the current context. * @param {Object} effectDef The effect definition * @param {Object} effectConfig The effect config (optional) * @return {Object} */ _boundEffect (effectDef, effectConfig = {}) { let startsAt = parseFloat(effectConfig.startsAt || 0); let endsAt = parseFloat(effectConfig.endsAt || 1); let deltaS = endsAt - startsAt; let noop = Function(); let runFn = (startsAt === 0 && endsAt === 1) ? effectDef.run : function (progress, top) { effectDef.run.call(this, Math.max(0, (progress - startsAt) / deltaS), top); }; return { setUp: effectDef.setUp ? effectDef.setUp.bind(this, effectConfig) : noop, run: effectDef.run ? runFn.bind(this) : noop, tearDown: effectDef.tearDown ? effectDef.tearDown.bind(this) : noop } }, /** * Sets up the effects. */ _setUpEffects () { this._tearDownEffects(); this.effects.forEach((effectName) => { let effectDef; if ((effectDef = this._scrollEffects[effectName])) { this._effects.push(this._boundEffect(effectDef, this.effectsConfig[effectName])); } }); this._effects.forEach((effectDef) => { if (effectDef.setUp() !== false) { this._effectsRunFn.push(effectDef.run); } }); }, /** * Tears down the effects. */ _tearDownEffects () { this._effects.forEach((effectDef) => { effectDef.tearDown(); }); this._effectsRunFn = []; this._effects = []; }, /** * Runs the effects. * @param {number} progress The progress * @param {number} top The top position of the current element relative to the viewport */ _runEffects (progress, top) { this._effectsRunFn.forEach(run => run(progress, top)); }, /** * Overrides `scrollTargetBehavior._scrollHandler` */ _scrollHandler () { this._updateScrollState(this._clampedScrollTop); this._loop(); }, /** * Updates the scroll state. * Should be overriden from the consumer of the behavior. * @param {number} scrollTop */ _updateScrollState (scrollTop) {}, /** * Transform style * @param {String} value The transform value * @param {HTMLElement} element The element to apply transforms to (optional) */ _transform (value, element) { element = element || this.element; domFactory.util.transform(value, element); } }); const mouseScrollEvents = ['DOMMouseScroll', 'mousewheel']; const handleScroll = (evt, target, preventDefault, scrollMultiplier) => { if (preventDefault) { if (evt.preventDefault) { evt.preventDefault(); } else { event.returnValue = false; } } var scrollAmount = evt.detail || (-evt.wheelDelta / 40); // convert wheelData to lines scrollAmount *= 19; // convert lines to pixels if (typeof scrollMultiplier === 'number' && !isNaN(scrollMultiplier)) scrollAmount *= scrollMultiplier; if (evt.wheelDeltaX || ('axis' in evt && 'HORIZONTAL_AXIS' in evt && evt.axis == evt.HORIZONTAL_AXIS)) // horizontal scroll if (target.scrollBy) target.scrollBy(scrollAmount, 0); else target.scrollLeft += scrollAmount; else // vertical scroll if (target.scrollBy) target.scrollBy(0, scrollAmount); else target.scrollTop += scrollAmount; }; const RetargetMouseScroll = (elem, target, preventDefault, scrollMultiplier, preventRetarget) => { if (!elem) elem = document; if (!target) target = window; if (typeof preventDefault !== 'boolean') preventDefault = true; if (typeof preventRetarget !== 'function') preventRetarget = null; var addListener, removeListener, restoreFn, handler = function (evt) { evt = evt || window.event; if(preventRetarget && preventRetarget.call(this, evt)) return; handleScroll(evt, target, preventDefault, scrollMultiplier); }; if (addListener = elem.addEventListener) { addListener.call(elem, mouseScrollEvents[0], handler, false); addListener.call(elem, mouseScrollEvents[1], handler, false); } else if (addListener = elem.attachEvent) addListener.call(elem, 'on'+mouseScrollEvents[1], handler); if (removeListener = elem.removeEventListener) restoreFn = function () { removeListener.call(elem, mouseScrollEvents[0], handler, false); removeListener.call(elem, mouseScrollEvents[1], handler, false); }; else if (removeListener = elem.detachEvent) restoreFn = function () { removeListener.call(elem, 'on'+mouseScrollEvents[1], handler); }; return { restore: restoreFn } }; const FRONT_LAYER = '[class*="__bg-front"]'; const REAR_LAYER = '[class*="__bg-rear"]'; /** * blend-background effect */ const SCROLL_EFFECT_BLEND_BACKGROUND = { name: 'blend-background', setUp () { let frontLayer = this.element.querySelector(FRONT_LAYER); let rearLayer = this.element.querySelector(REAR_LAYER); const layers = [ frontLayer, rearLayer ]; layers.map(layer => { if (layer) { if (layer.style.transform === '') { this._transform('translateZ(0)', layer); layer.style.willChange = 'opacity'; } } }); rearLayer.style.opacity = 0; }, run (progress, top) { let frontLayer = this.element.querySelector(FRONT_LAYER); let rearLayer = this.element.querySelector(REAR_LAYER); frontLayer.style.opacity = (1 - progress).toFixed(5); rearLayer.style.opacity = progress.toFixed(5); } }; const FRONT_LAYER$1 = '[class*="__bg-front"]'; const REAR_LAYER$1 = '[class*="__bg-rear"]'; /** * fade-background effect */ const SCROLL_EFFECT_FADE_BACKGROUND = { name: 'fade-background', setUp (config) { const duration = config.duration || '0.5s'; const threshold = config.threshold || (this._isPositionedFixed ? 1 : 0.3); let frontLayer = this.element.querySelector(FRONT_LAYER$1); let rearLayer = this.element.querySelector(REAR_LAYER$1); const layers = [ frontLayer, rearLayer ]; layers.map(layer => { if (layer) { let willChange = layer.style.willChange.split(',').map(c => c.trim()).filter(c => c.length); willChange.push('opacity', 'transform'); layer.style.willChange = [...new Set(willChange)].join(', '); if (layer.style.transform === '') { this._transform('translateZ(0)', layer); } layer.style.transitionProperty = 'opacity'; layer.style.transitionDuration = duration; } }); this._fadeBackgroundThreshold = !this._isPositionedFixed ? threshold + (this._progress * threshold) : threshold; }, tearDown () { delete this._fadeBackgroundThreshold; }, run (progress, top) { let frontLayer = this.element.querySelector(FRONT_LAYER$1); let rearLayer = this.element.querySelector(REAR_LAYER$1); if (progress >= this._fadeBackgroundThreshold) { frontLayer.style.opacity = 0; rearLayer.style.opacity = 1; } else { frontLayer.style.opacity = 1; rearLayer.style.opacity = 0; } } }; const FRONT_LAYER$2 = '[class*="__bg-front"]'; const REAR_LAYER$2 = '[class*="__bg-rear"]'; const BG = '[class$="__bg"]'; /** * parallax-background effect */ const SCROLL_EFFECT_PARALLAX_BACKGROUND = { name: 'parallax-background', setUp () {}, tearDown () { let layers = [ this.element.querySelector(FRONT_LAYER$2), this.element.querySelector(REAR_LAYER$2) ]; let props = ['marginTop', 'marginBottom']; layers.map(layer => { if (layer) { this._transform('translate3d(0, 0, 0)', layer); props.forEach((prop) => layer.style[prop] = ''); } }); }, run (progress, top) { let unscrolledPercent = (this.scrollTarget.scrollHeight - this._scrollTargetHeight) / this.scrollTarget.scrollHeight; let distance = this.element.offsetHeight * unscrolledPercent; if (this._dHeight !== undefined) { unscrolledPercent = this._dHeight / this.element.offsetHeight; distance = this._dHeight * unscrolledPercent; } let scalar = 0.5; let delta = Math.abs(distance * scalar).toFixed(5); let max = this._isPositionedFixedEmulated ? 1000000 : distance; let deltaProgress = delta * progress; let transform = (Math.min(deltaProgress, max)).toFixed(5); let layers = [ this.element.querySelector(FRONT_LAYER$2), this.element.querySelector(REAR_LAYER$2) ]; layers.map(layer => { if (layer) { layer.style['marginTop'] = `${ -1 * delta }px`; this._transform(`translate3d(0, ${ transform }px, 0)`, layer); } }); let bgNode = this.element.querySelector(BG); if (!bgNode.style.visibility) { bgNode.style.visibility = 'visible'; } } }; // import effects // export list const SCROLL_EFFECTS = [ SCROLL_EFFECT_BLEND_BACKGROUND, SCROLL_EFFECT_FADE_BACKGROUND, SCROLL_EFFECT_PARALLAX_BACKGROUND ]; /** * waterfall effect */ const HEADER_SCROLL_EFFECT_WATERFALL = { name: 'waterfall', setUp () { this._primary.classList.add('mdk-header--shadow'); }, run (progress, top) { this._primary.classList[this.isOnScreen() && this.isContentBelow() ? 'add' : 'remove']('mdk-header--shadow-show'); }, tearDown () { this._primary.classList.remove('mdk-header--shadow'); } }; const interpolate = (progress, points, fn, ctx) => { fn.apply(ctx, points.map(function (point) { return point[0] + (point[1] - point[0]) * progress })); }; /** * fx-condenses effect * * Transform properties of one or more designated header elements * between two values based on the scroll position. */ const HEADER_SCROLL_EFFECT_FX_CONDENSES = { name: 'fx-condenses', setUp () { const elements = [ ...this.element.querySelectorAll('[data-fx-condenses]') ]; const targets = [ ...this.element.querySelectorAll('[data-fx-id]') ]; let bounds = {}; elements.forEach(element => { if (element) { this._transform('translateZ(0)', element); if (window.getComputedStyle(element).display === 'inline') { element.style.display = 'inline-block'; } let id = element.getAttribute('id'); if (!element.hasAttribute('id')) { id = 'rt' + (0 | Math.random() * 9e6).toString(36); element.setAttribute('id', id); } const bound = element.getBoundingClientRect(); bounds[id] = bound; } }); targets.forEach(target => { if (target) { let id = target.getAttribute('id'); let fxId = target.getAttribute('data-fx-id'); let fxEl = this.element.querySelector(`#${ fxId }`); let targetBounds = bounds[id]; let fxBounds = bounds[fxId]; const hasTextContent = target.textContent.trim().length > 0; let scale = 1; if (fxBounds !== undefined) { bounds[id].dx = targetBounds.left - fxBounds.left; bounds[id].dy = targetBounds.top - fxBounds.top; if (hasTextContent) { scale = parseInt(window.getComputedStyle(fxEl)['font-size'], 10) / parseInt(window.getComputedStyle(target)['font-size'], 10); } else { scale = fxBounds.height / targetBounds.height; } bounds[id].scale = scale; } } }); this._fxCondenses = { elements, targets, bounds }; }, run (progress, top) { let fx = this._fxCondenses; if (!this.condenses) { top = 0; } if (progress >= 1) { fx.elements.forEach(el => { if (el) { el.style.opacity = fx.targets.indexOf(el) !== -1 ? 0 : 1; } }); } else { fx.elements.forEach(el => { if (el) { el.style.opacity = fx.targets.indexOf(el) !== -1 ? 1 : 0; } }); } fx.targets.forEach(target => { if (target) { let id = target.getAttribute('id'); interpolate( Math.min(1, progress), [ [1, fx.bounds[id].scale], [0, -fx.bounds[id].dx], [top, top - fx.bounds[id].dy] ], (scale, translateX, translateY) => { translateX = translateX.toFixed(5); translateY = translateY.toFixed(5); scale = scale.toFixed(5); this._transform(`translate(${ translateX }px, ${ translateY }px) scale3d(${ scale }, ${ scale }, 1)`, target); }); } }); }, tearDown () { delete this._fxCondenses; } }; // import effects // export list const HEADER_SCROLL_EFFECTS = [ HEADER_SCROLL_EFFECT_WATERFALL, HEADER_SCROLL_EFFECT_FX_CONDENSES ]; const MODULE = 'mdk-header'; const CONTENT = `.${ MODULE }__content`; const BG$1 = `.${ MODULE }__bg`; const FRONT_LAYER$3 = `${ BG$1 }-front`; const REAR_LAYER$3 = `${ BG$1 }-rear`; const MODIFIER_FIXED = `${ MODULE }--fixed`; /** * A container element for navigation and other content at the top * of the screen with visual effects based on scroll position * * @param {HTMLElement} element * @return {Object} */ const headerComponent = (element) => ({ /** * Public properties. * @type {Object} */ properties: { /** * Collapse the header when scrolling down, leaving only the `[primary]` element visible. * If there is no `[primary]` element, the first child remains visibile. * @type {Object} */ condenses: { type: Boolean, reflectToAttribute: true }, /** * Slides back the header when scrolling back up. * @type {Object} */ reveals: { type: Boolean, reflectToAttribute: true }, /** * Mantains the header fixed at the top. * @type {Object} */ fixed: { type: Boolean, reflectToAttribute: true }, /** * Disables all scroll effects. * @type {Object} */ disabled: { type: Boolean, reflectToAttribute: true }, retargetMouseScroll: { type: Boolean, reflectToAttribute: true, value: true } }, /** * Property change observers. * @type {Array} */ observers: [ '_handleFixedPositionedScroll(scrollTarget)', '_reset(condenses, reveals, fixed)' ], /** * Event listeners * @type {Array} */ listeners: [ 'window._debounceResize(resize)' ], /** * Compose mixins * @type {Array} */ mixins: [ scrollTargetBehavior(), scrollEffectBehavior() ], // A cached offsetHeight of the element _height: 0, // The distance in pixels the header will be translated to when scrolling _dHeight: 0, // The offsetTop of `_primary` _primaryTop: 0, // The element that remains visibile when the header condenses _primary: null, // The header's top value used for the `transformY` _top: 0, // The current scroll progress _progress: 0, _wasScrollingDown: false, _initScrollTop: 0, _initTimestamp: 0, _lastTimestamp: 0, _lastScrollTop: 0, /** * Disables transform effects * @return {Boolean} */ get transformDisabled () { return this.disabled || this.element.dataset.transformDisabled || ! this._isPositionedFixedEmulated || this.condenses && ! this.willCondense() }, /** * Update `transform-disabled` attribute on `element` * @param {Boolean} value */ set transformDisabled (value) { this.element[value ? 'setAttribute' : 'removeAttribute']('data-transform-disabled', 'data-transform-disabled'); }, /** * The distance the header is allowed to move away. * @return {number} */ get _maxHeaderTop () { return this.fixed ? this._dHeight : this._height + 5 }, get _isPositionedFixedEmulated () { return this.fixed || this.condenses || this.reveals }, get _isPositionedAbsolute () { return window.getComputedStyle(this.element).position === 'absolute' }, get _primaryisPositionedFixed () { return window.getComputedStyle(this._primary).position === 'fixed' }, /** * Returns true if the header will condense based on the size of the header * @return {Boolean} */ willCondense () { return this._dHeight > 0 && this.condenses }, /** * Returns true if the element is visible in the current viewport. * @return {Boolean} */ isOnScreen () { return this._height !== 0 && this._top < this._height }, /** * Returns true if there's content below the element. * @return {Boolean} */ isContentBelow () { if (this._top === 0) { return this._clampedScrollTop > 0 } return this._clampedScrollTop - this._maxHeaderTop >= 0 }, /** * Returns an object containing the progress value of the scroll * and the top position of the header. * @return {Object} */ getScrollState () { return { progress: this._progress, top: this._top } }, _setupBackgrounds () { let bgNode = this.element.querySelector(BG$1); if (!bgNode) { bgNode = document.createElement('DIV'); this.element.insertBefore(bgNode, this.element.childNodes[0]); bgNode.classList.add(BG$1.substr(1)); } [FRONT_LAYER$3, REAR_LAYER$3].map(className => { let bgNodeLayer = bgNode.querySelector(className); if (!bgNodeLayer) { bgNodeLayer = document.createElement('DIV'); bgNode.appendChild(bgNodeLayer); bgNodeLayer.classList.add(className.substr(1)); } }); }, _reset () { this._primary.classList[(this.fixed || this.condenses || this.reveals) ? 'add' : 'remove'](MODIFIER_FIXED); if (this.element.offsetWidth === 0 && this.element.offsetHeight === 0) { return } if (this._primaryisPositionedFixed) { this.element.style.paddingTop = this._primary.offsetHeight + 'px'; } let scrollTop = this._clampedScrollTop; let firstSetup = this._height === 0 || scrollTop === 0; this._height = this.element.offsetHeight; this._primaryTop = this._primary ? this._primary.offsetTop : 0; this._dHeight = 0; if (this._mayMove()) { this._dHeight = this._primary ? this._height - this._primary.offsetHeight : 0; } this._setUpEffects(); this._updateScrollState(firstSetup ? scrollTop : this._lastScrollTop, true); }, /** * Pass MouseWheel events from the scroll target * when the header is fixed and the scroll target is not the document */ _handleFixedPositionedScroll () { if (this._fixedPositionedScrollHandler !== undefined) { this._fixedPositionedScrollHandler.restore(); } if (this._isValidScrollTarget() && this._isPositionedFixedEmulated && this.scrollTarget !== this._doc && this.retargetMouseScroll) { this._fixedPositionedScrollHandler = RetargetMouseScroll(this.element, this.scrollTarget); } }, /** * Returns a reference to the element that remains visible when the header condenses. * @return {HTMLElement} */ get _primary () { if (this._primaryElement) { return this._primaryElement } let primary; let nodes = this.element.querySelector(CONTENT).children; for (let i = 0; i < nodes.length; i++) { if (nodes[i].nodeType === Node.ELEMENT_NODE) { let node = nodes[i]; if (node.dataset.primary !== undefined) { primary = node; break } else if (!primary) { primary = node; } } } this._primaryElement = primary; return this._primaryElement }, /** * Updates the scroll state. * @param {number} scrollTop * @param {Boolean} forceUpdate */ _updateScrollState (scrollTop, forceUpdate) { if (this._height === 0 || this.disabled) { return } if (!forceUpdate && scrollTop === this._lastScrollTop) { return } let progress = 0; let top = 0; let lastTop = this._top; let maxHeaderTop = this._maxHeaderTop; let dScrollTop = scrollTop - this._lastScrollTop; let absDScrollTop = Math.abs(dScrollTop); let isScrollingDown = scrollTop > this._lastScrollTop; let now = Date.now(); if (this._mayMove()) { top = this._clamp(this.reveals ? lastTop + dScrollTop : scrollTop, 0, maxHeaderTop); } if (scrollTop >= this._dHeight) { top = this.condenses ? Math.max(this._dHeight, top) : top; } if (this.reveals && absDScrollTop < 100) { if (now - this._initTimestamp > 300 || this._wasScrollingDown !== isScrollingDown) { this._initScrollTop = scrollTop; this._initTimestamp = now; } if (scrollTop >= maxHeaderTop) { if (Math.abs(this._initScrollTop - scrollTop) > 30 || absDScrollTop > 10) { if (isScrollingDown && scrollTop >= maxHeaderTop) { top = maxHeaderTop; } else if (!isScrollingDown && scrollTop >= this._dHeight) { top = this.condenses ? this._dHeight : 0; } let scrollVelocity = dScrollTop / (now - this._lastTimestamp); this._revealTransitionDuration = this._clamp((top - lastTop) / scrollVelocity, 0, 300); } else { top = this._top; } } } if (this._dHeight === 0) { progress = scrollTop > 0 ? 1 : 0; } else { progress = top / this._dHeight; } if (!forceUpdate) { this._lastScrollTop = scrollTop; this._top = top; this._wasScrollingDown = isScrollingDown; this._lastTimestamp = now; } if (forceUpdate || progress !== this._progress || lastTop !== top || scrollTop === 0) { this._progress = progress; this._runEffects(progress, top); this._transformHeader(top); } }, /** * Transforms the header. * @param {number} top */ _transformHeader (top) { if (this.transformDisabled) { return } if (this._isPositionedAbsolute) { let transform = top; if (this.scrollTarget === this._doc) { transform = 0; } if (top === transform) { this._transform(`translate3d(0, ${ transform * -1 }px, 0)`); } if (top >= this._primaryTop) { this._transform(`translate3d(0, ${ Math.min(top, this._dHeight) - this._primaryTop }px, 0)`, this._primary); } return } if (this.fixed && this._isPositionedFixed) { let transform = top; this._transform(`translate3d(0, ${ transform * -1 }px, 0)`); if (top >= this._primaryTop) { this._transform(`translate3d(0, ${ Math.min(top, this._dHeight) - this._primaryTop }px, 0)`, this._primary); } return } let transform = 0; let duration = `${ this._revealTransitionDuration }ms`; if (top > this._dHeight) { transform = -1 * (top - this._dHeight); if (this.reveals) { duration = '0ms'; } } if (this.reveals && top <= this._dHeight) { this._primary.style.transitionDuration = duration; } this._transform(`translate3d(0, ${ transform }px, 0)`, this._primary); if (this.reveals && top > this._dHeight) { setTimeout(() => this._primary.style.transitionDuration = duration); } }, _clamp (v, min, max) { return Math.min(max, Math.max(min, v)) }, /** * Returns true if the current header is allowed to move as the user scrolls. * @return {Boolean} */ _mayMove () { return this.condenses || !this.fixed }, /** * Handle the resize event every 50ms */ _debounceResize () { clearTimeout(this._onResizeTimeout); if (this._resizeWidth !== window.innerWidth) { this._onResizeTimeout = setTimeout(() => { this._resizeWidth = window.innerWidth; this._reset(); }, 50); } }, /** * Initialize component */ init () { this._resizeWidth = window.innerWidth; this.attachToScrollTarget(); this._handleFixedPositionedScroll(); this._setupBackgrounds(); this._primary.setAttribute('data-primary', 'data-primary'); SCROLL_EFFECTS.concat(HEADER_SCROLL_EFFECTS).map(effect => this.registerEffect(effect.name, effect)); }, /** * Destroy component */ destroy () { clearTimeout(this._onResizeTimeout); this.detachFromScrollTarget(); } }); domFactory.handler.register(MODULE, headerComponent); /** * A wrapper element that positions a Header and other content. * @param {HTMLElement} element * @return {Object} */ const headerLayoutComponent = () => ({ /** * Public properties. * @type {Object} */ properties: { /** * If true, defines it's own scrolling region, otherwise uses the document scroll. * @type {Object} */ hasScrollingRegion: { type: Boolean, reflectToAttribute: true }, fullbleed: { type: Boolean, reflectToAttribute: true } }, /** * Property change observers. * @type {Array} */ observers: [ '_updateScroller(hasScrollingRegion)', '_updateContentPosition(hasScrollingRegion, header.fixed, header.condenses)', '_updateDocument(fullbleed)' ], /** * Event listeners. * @type {Array} */ listeners: [ 'window._debounceResize(resize)' ], /** * The header layout content wrapper HTMLElement * @return {HTMLElement} */ get contentContainer () { return this.element.querySelector('.mdk-header-layout__content') }, /** * A reference to the header component * @return {Object} */ get header () { const headerNode = this.element.querySelector('.mdk-header'); if (headerNode) { return headerNode.mdkHeader } }, _updateScroller () { this.header.scrollTargetSelector = this.hasScrollingRegion ? this.contentContainer : null; }, _updateContentPosition () { const headerHeight = this.header.element.offsetHeight; const gutter = parseInt(window.getComputedStyle(this.header.element).marginBottom, 10); const containerStyle = this.contentContainer.style; if (this.header.fixed || this.header.willCondense()) { containerStyle.paddingTop = `${ headerHeight + gutter }px`; containerStyle.marginTop = ''; } }, /** * Handle the resize event every 50ms */ _debounceResize () { clearTimeout(this._onResizeTimeout); if (this._resizeWidth !== window.innerWidth) { this._onResizeTimeout = setTimeout(() => { this._resizeWidth = window.innerWidth; this._reset(); }, 50); } }, _updateDocument () { const docElements = [...document.querySelectorAll('html, body')]; if (this.fullbleed) { docElements.forEach(el => { el.style.height = '100%'; }); } }, _reset () { this._updateContentPosition(); }, /** * Initialize component */ init () { this._resizeWidth = window.innerWidth; this._updateDocument(); this._updateScroller(); }, /** * Destroy component */ destroy () { clearTimeout(this._onResizeTimeout); const docElements = [...document.querySelectorAll('html, body')]; docElements.forEach(el => { // fullbleed el.style.height = ''; }); } }); domFactory.handler.register('mdk-header-layout', headerLayoutComponent); const MODULE$1 = 'mdk-box'; const BG$2 = `.${ MODULE$1 }__bg`; const FRONT_LAYER$4 = `${ BG$2 }-front`; const REAR_LAYER$4 = `${ BG$2 }-rear`; /** * A container element for generic content with visual effects based on scroll position * @param {HTMLElement} element * @return {Object} */ const boxComponent = (element) => ({ /** * Public properties. * @type {Object} */ properties: { /** * Disables effects */ disabled: { type: Boolean, reflectToAttribute: true } }, /** * Event listeners. * @type {Array} */ listeners: [ 'window._debounceResize(resize)' ], /** * Compose mixins * @type {Array} */ mixins: [ scrollTargetBehavior(), scrollEffectBehavior() ], // The current scroll progress _progress: 0, /** * Returns true if the element is visible in the current viewport. * @return {Boolean} */ isOnScreen () { return this._elementTop < this._scrollTop + this._scrollTargetHeight && this._elementTop + this.element.offsetHeight > this._scrollTop }, isVisible () { return this.element.offsetWidth > 0 && this.element.offsetHeight > 0 }, /** * Returns an object containing the progress value of the scroll effects. * @return {Object} */ getScrollState () { return { progress: this._progress } }, _setupBackgrounds () { let bgNode = this.element.querySelector(BG$2); if (!bgNode) { bgNode = document.createElement('DIV'); this.element.insertBefore(bgNode, this.element.childNodes[0]); bgNode.classList.add(BG$2.substr(1)); } [FRONT_LAYER$4, REAR_LAYER$4].map(className => { let bgNodeLayer = bgNode.querySelector(className); if (!bgNodeLayer) { bgNodeLayer = document.createElement('DIV'); bgNode.appendChild(bgNodeLayer); bgNodeLayer.classList.add(className.substr(1)); } }); }, _getElementTop () { let currentNode = this.element; let top = 0; while (currentNode && currentNode !== this.scrollTarget) { top += currentNode.offsetTop; currentNode = currentNode.offsetParent; } return top }, /** * Updates the scroll state. * @param {number} scrollTop */ _updateScrollState (scrollTop) { if (this.disabled) { return } if (this.isOnScreen()) { let target = Math.min(this._scrollTargetHeight, this._elementTop + this.element.offsetHeight); let viewportTop = this._elementTop - scrollTop; let progress = 1 - (viewportTop + this.element.offsetHeight) / target; this._progress = progress; this._runEffects(this._progress, scrollTop); } }, /** * Handle the resize event every 50ms */ _debounceResize () { clearTimeout(this._onResizeTimeout); if (this._resizeWidth !== window.innerWidth) { this._onResizeTimeout = setTimeout(() => { this._resizeWidth = window.innerWidth; this._reset(); }, 50); } }, /** * Initialize component */ init () { this._resizeWidth = window.innerWidth; this.attachToScrollTarget(); this._setupBackgrounds(); SCROLL_EFFECTS.map(effect => this.registerEffect(effect.name, effect)); }, _reset () { this._elementTop = this._getElementTop(); this._setUpEffects(); this._updateScrollState(this._clampedScrollTop); }, /** * Destroy component */ destroy () { clearTimeout(this._onResizeTimeout); this.detachFromScrollTarget(); } }); domFactory.handler.register(MODULE$1, boxComponent); let isRTLIntv; /** * A navigation drawer that can slide in from the left or right * @param {HTMLElement} element * @return {Object} */ const drawerComponent = () => ({ /** * Public properties. * @type {Object} */ properties: { /** * The opened state of the drawer. * @type {Object} */ opened: { type: Boolean, reflectToAttribute: true }, /** * The drawer does not have a scrim. * @type {Object} */ persistent: { type: Boolean, reflectToAttribute: true }, /** * The alignment of the drawer on the screen ('left', 'right', 'start' or 'end'). * 'start' computes to left and 'end' to right in LTR and RTL layouts. * @type {Object} */ align: { reflectToAttribute