/*! * Bootstrap scrollspy.js v5.2.3 (https://getbootstrap.com/) * Copyright 2011-2022 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors) * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) */ (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? (module.exports = factory( require('./util/index'), require('./dom/event-handler'), require('./dom/selector-engine'), require('./base-component') )) : typeof define === 'function' && define.amd ? define( ['./util/index', './dom/event-handler', './dom/selector-engine', './base-component'], factory ) : ((global = typeof globalThis !== 'undefined' ? globalThis : global || self), (global.Scrollspy = factory( global.Index, global.EventHandler, global.SelectorEngine, global.BaseComponent ))); })(this, function (index, EventHandler, SelectorEngine, BaseComponent) { 'use strict'; const _interopDefaultLegacy = (e) => e && typeof e === 'object' && 'default' in e ? e : { default: e }; const EventHandler__default = /*#__PURE__*/ _interopDefaultLegacy(EventHandler); const SelectorEngine__default = /*#__PURE__*/ _interopDefaultLegacy(SelectorEngine); const BaseComponent__default = /*#__PURE__*/ _interopDefaultLegacy(BaseComponent); /** * -------------------------------------------------------------------------- * Bootstrap (v5.2.3): scrollspy.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ /** * Constants */ const NAME = 'scrollspy'; const DATA_KEY = 'bs.scrollspy'; const EVENT_KEY = `.${DATA_KEY}`; const DATA_API_KEY = '.data-api'; const EVENT_ACTIVATE = `activate${EVENT_KEY}`; const EVENT_CLICK = `click${EVENT_KEY}`; const EVENT_LOAD_DATA_API = `load${EVENT_KEY}${DATA_API_KEY}`; const CLASS_NAME_DROPDOWN_ITEM = 'dropdown-item'; const CLASS_NAME_ACTIVE = 'active'; const SELECTOR_DATA_SPY = '[data-bs-spy="scroll"]'; const SELECTOR_TARGET_LINKS = '[href]'; const SELECTOR_NAV_LIST_GROUP = '.nav, .list-group'; const SELECTOR_NAV_LINKS = '.nav-link'; const SELECTOR_NAV_ITEMS = '.nav-item'; const SELECTOR_LIST_ITEMS = '.list-group-item'; const SELECTOR_LINK_ITEMS = `${SELECTOR_NAV_LINKS}, ${SELECTOR_NAV_ITEMS} > ${SELECTOR_NAV_LINKS}, ${SELECTOR_LIST_ITEMS}`; const SELECTOR_DROPDOWN = '.dropdown'; const SELECTOR_DROPDOWN_TOGGLE = '.dropdown-toggle'; const Default = { offset: null, // TODO: v6 @deprecated, keep it for backwards compatibility reasons rootMargin: '0px 0px -25%', smoothScroll: false, target: null, threshold: [0.1, 0.5, 1], }; const DefaultType = { offset: '(number|null)', // TODO v6 @deprecated, keep it for backwards compatibility reasons rootMargin: 'string', smoothScroll: 'boolean', target: 'element', threshold: 'array', }; /** * Class definition */ class ScrollSpy extends BaseComponent__default.default { constructor(element, config) { super(element, config); // this._element is the observablesContainer and config.target the menu links wrapper this._targetLinks = new Map(); this._observableSections = new Map(); this._rootElement = getComputedStyle(this._element).overflowY === 'visible' ? null : this._element; this._activeTarget = null; this._observer = null; this._previousScrollData = { visibleEntryTop: 0, parentScrollTop: 0, }; this.refresh(); // initialize } // Getters static get Default() { return Default; } static get DefaultType() { return DefaultType; } static get NAME() { return NAME; } // Public refresh() { this._initializeTargetsAndObservables(); this._maybeEnableSmoothScroll(); if (this._observer) { this._observer.disconnect(); } else { this._observer = this._getNewObserver(); } for (const section of this._observableSections.values()) { this._observer.observe(section); } } dispose() { this._observer.disconnect(); super.dispose(); } // Private _configAfterMerge(config) { // TODO: on v6 target should be given explicitly & remove the {target: 'ss-target'} case config.target = index.getElement(config.target) || document.body; // TODO: v6 Only for backwards compatibility reasons. Use rootMargin only config.rootMargin = config.offset ? `${config.offset}px 0px -30%` : config.rootMargin; if (typeof config.threshold === 'string') { config.threshold = config.threshold.split(',').map((value) => Number.parseFloat(value)); } return config; } _maybeEnableSmoothScroll() { if (!this._config.smoothScroll) { return; } // unregister any previous listeners EventHandler__default.default.off(this._config.target, EVENT_CLICK); EventHandler__default.default.on( this._config.target, EVENT_CLICK, SELECTOR_TARGET_LINKS, (event) => { const observableSection = this._observableSections.get(event.target.hash); if (observableSection) { event.preventDefault(); const root = this._rootElement || window; const height = observableSection.offsetTop - this._element.offsetTop; if (root.scrollTo) { root.scrollTo({ top: height, behavior: 'smooth', }); return; } // Chrome 60 doesn't support `scrollTo` root.scrollTop = height; } } ); } _getNewObserver() { const options = { root: this._rootElement, threshold: this._config.threshold, rootMargin: this._config.rootMargin, }; return new IntersectionObserver((entries) => this._observerCallback(entries), options); } // The logic of selection _observerCallback(entries) { const targetElement = (entry) => this._targetLinks.get(`#${entry.target.id}`); const activate = (entry) => { this._previousScrollData.visibleEntryTop = entry.target.offsetTop; this._process(targetElement(entry)); }; const parentScrollTop = (this._rootElement || document.documentElement).scrollTop; const userScrollsDown = parentScrollTop >= this._previousScrollData.parentScrollTop; this._previousScrollData.parentScrollTop = parentScrollTop; for (const entry of entries) { if (!entry.isIntersecting) { this._activeTarget = null; this._clearActiveClass(targetElement(entry)); continue; } const entryIsLowerThanPrevious = entry.target.offsetTop >= this._previousScrollData.visibleEntryTop; // if we are scrolling down, pick the bigger offsetTop if (userScrollsDown && entryIsLowerThanPrevious) { activate(entry); // if parent isn't scrolled, let's keep the first visible item, breaking the iteration if (!parentScrollTop) { return; } continue; } // if we are scrolling up, pick the smallest offsetTop if (!userScrollsDown && !entryIsLowerThanPrevious) { activate(entry); } } } _initializeTargetsAndObservables() { this._targetLinks = new Map(); this._observableSections = new Map(); const targetLinks = SelectorEngine__default.default.find( SELECTOR_TARGET_LINKS, this._config.target ); for (const anchor of targetLinks) { // ensure that the anchor has an id and is not disabled if (!anchor.hash || index.isDisabled(anchor)) { continue; } const observableSection = SelectorEngine__default.default.findOne( anchor.hash, this._element ); // ensure that the observableSection exists & is visible if (index.isVisible(observableSection)) { this._targetLinks.set(anchor.hash, anchor); this._observableSections.set(anchor.hash, observableSection); } } } _process(target) { if (this._activeTarget === target) { return; } this._clearActiveClass(this._config.target); this._activeTarget = target; target.classList.add(CLASS_NAME_ACTIVE); this._activateParents(target); EventHandler__default.default.trigger(this._element, EVENT_ACTIVATE, { relatedTarget: target, }); } _activateParents(target) { // Activate dropdown parents if (target.classList.contains(CLASS_NAME_DROPDOWN_ITEM)) { SelectorEngine__default.default .findOne(SELECTOR_DROPDOWN_TOGGLE, target.closest(SELECTOR_DROPDOWN)) .classList.add(CLASS_NAME_ACTIVE); return; } for (const listGroup of SelectorEngine__default.default.parents( target, SELECTOR_NAV_LIST_GROUP )) { // Set triggered links parents as active // With both