import Util from './util'; /** * -------------------------------------------------------------------------- * Bootstrap (v4.0.0-alpha.6): scrollspy.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) * -------------------------------------------------------------------------- */ const ScrollSpy = ($ => { /** * ------------------------------------------------------------------------ * Constants * ------------------------------------------------------------------------ */ const NAME = 'scrollspy'; const VERSION = '4.0.0-alpha.6'; const DATA_KEY = 'bs.scrollspy'; const EVENT_KEY = `.${ DATA_KEY }`; const DATA_API_KEY = '.data-api'; const JQUERY_NO_CONFLICT = $.fn[NAME]; const Default = { offset: 10, method: 'auto', target: '' }; const DefaultType = { offset: 'number', method: 'string', target: '(string|element)' }; const Event = { ACTIVATE: `activate${ EVENT_KEY }`, SCROLL: `scroll${ EVENT_KEY }`, LOAD_DATA_API: `load${ EVENT_KEY }${ DATA_API_KEY }` }; const ClassName = { DROPDOWN_ITEM: 'dropdown-item', DROPDOWN_MENU: 'dropdown-menu', NAV_LINK: 'nav-link', NAV: 'nav', ACTIVE: 'active' }; const Selector = { DATA_SPY: '[data-spy="scroll"]', ACTIVE: '.active', LIST_ITEM: '.list-item', LI: 'li', LI_DROPDOWN: 'li.dropdown', NAV_LINKS: '.nav-link', DROPDOWN: '.dropdown', DROPDOWN_ITEMS: '.dropdown-item', DROPDOWN_TOGGLE: '.dropdown-toggle' }; const OffsetMethod = { OFFSET: 'offset', POSITION: 'position' }; /** * ------------------------------------------------------------------------ * Class Definition * ------------------------------------------------------------------------ */ class ScrollSpy { constructor(element, config) { this._element = element; this._scrollElement = element.tagName === 'BODY' ? window : element; this._config = this._getConfig(config); this._selector = `${ this._config.target } ${ Selector.NAV_LINKS },` + `${ this._config.target } ${ Selector.DROPDOWN_ITEMS }`; this._offsets = []; this._targets = []; this._activeTarget = null; this._scrollHeight = 0; $(this._scrollElement).on(Event.SCROLL, event => this._process(event)); this.refresh(); this._process(); } // getters static get VERSION() { return VERSION; } static get Default() { return Default; } // public refresh() { const autoMethod = this._scrollElement !== this._scrollElement.window ? OffsetMethod.POSITION : OffsetMethod.OFFSET; const offsetMethod = this._config.method === 'auto' ? autoMethod : this._config.method; const offsetBase = offsetMethod === OffsetMethod.POSITION ? this._getScrollTop() : 0; this._offsets = []; this._targets = []; this._scrollHeight = this._getScrollHeight(); const targets = $.makeArray($(this._selector)); targets.map(element => { let target; const targetSelector = Util.getSelectorFromElement(element); if (targetSelector) { target = $(targetSelector)[0]; } if (target && (target.offsetWidth || target.offsetHeight)) { // todo (fat): remove sketch reliance on jQuery position/offset return [$(target)[offsetMethod]().top + offsetBase, targetSelector]; } return null; }).filter(item => item).sort((a, b) => a[0] - b[0]).forEach(item => { this._offsets.push(item[0]); this._targets.push(item[1]); }); } dispose() { $.removeData(this._element, DATA_KEY); $(this._scrollElement).off(EVENT_KEY); this._element = null; this._scrollElement = null; this._config = null; this._selector = null; this._offsets = null; this._targets = null; this._activeTarget = null; this._scrollHeight = null; } // private _getConfig(config) { config = $.extend({}, Default, config); if (typeof config.target !== 'string') { let id = $(config.target).attr('id'); if (!id) { id = Util.getUID(NAME); $(config.target).attr('id', id); } config.target = `#${ id }`; } Util.typeCheckConfig(NAME, config, DefaultType); return config; } _getScrollTop() { return this._scrollElement === window ? this._scrollElement.pageYOffset : this._scrollElement.scrollTop; } _getScrollHeight() { return this._scrollElement.scrollHeight || Math.max(document.body.scrollHeight, document.documentElement.scrollHeight); } _getOffsetHeight() { return this._scrollElement === window ? window.innerHeight : this._scrollElement.offsetHeight; } _process() { const scrollTop = this._getScrollTop() + this._config.offset; const scrollHeight = this._getScrollHeight(); const maxScroll = this._config.offset + scrollHeight - this._getOffsetHeight(); if (this._scrollHeight !== scrollHeight) { this.refresh(); } if (scrollTop >= maxScroll) { const target = this._targets[this._targets.length - 1]; if (this._activeTarget !== target) { this._activate(target); } return; } if (this._activeTarget && scrollTop < this._offsets[0] && this._offsets[0] > 0) { this._activeTarget = null; this._clear(); return; } for (let i = this._offsets.length; i--;) { const isActiveTarget = this._activeTarget !== this._targets[i] && scrollTop >= this._offsets[i] && (this._offsets[i + 1] === undefined || scrollTop < this._offsets[i + 1]); if (isActiveTarget) { this._activate(this._targets[i]); } } } _activate(target) { this._activeTarget = target; this._clear(); let queries = this._selector.split(','); queries = queries.map(selector => { return `${ selector }[data-target="${ target }"],` + `${ selector }[href="${ target }"]`; }); const $link = $(queries.join(',')); if ($link.hasClass(ClassName.DROPDOWN_ITEM)) { $link.closest(Selector.DROPDOWN).find(Selector.DROPDOWN_TOGGLE).addClass(ClassName.ACTIVE); $link.addClass(ClassName.ACTIVE); } else { // todo (fat) this is kinda sus... // recursively add actives to tested nav-links $link.parents(Selector.LI).find(`> ${ Selector.NAV_LINKS }`).addClass(ClassName.ACTIVE); } $(this._scrollElement).trigger(Event.ACTIVATE, { relatedTarget: target }); } _clear() { $(this._selector).filter(Selector.ACTIVE).removeClass(ClassName.ACTIVE); } // static static _jQueryInterface(config) { return this.each(function () { let data = $(this).data(DATA_KEY); const _config = typeof config === 'object' && config; if (!data) { data = new ScrollSpy(this, _config); $(this).data(DATA_KEY, data); } if (typeof config === 'string') { if (data[config] === undefined) { throw new Error(`No method named "${ config }"`); } data[config](); } }); } } /** * ------------------------------------------------------------------------ * Data Api implementation * ------------------------------------------------------------------------ */ $(window).on(Event.LOAD_DATA_API, () => { const scrollSpys = $.makeArray($(Selector.DATA_SPY)); for (let i = scrollSpys.length; i--;) { const $spy = $(scrollSpys[i]); ScrollSpy._jQueryInterface.call($spy, $spy.data()); } }); /** * ------------------------------------------------------------------------ * jQuery * ------------------------------------------------------------------------ */ $.fn[NAME] = ScrollSpy._jQueryInterface; $.fn[NAME].Constructor = ScrollSpy; $.fn[NAME].noConflict = function () { $.fn[NAME] = JQUERY_NO_CONFLICT; return ScrollSpy._jQueryInterface; }; return ScrollSpy; })(jQuery); export default ScrollSpy; //# sourceMappingURL=scrollspy.js.map