You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
312 lines
10 KiB
312 lines
10 KiB
/*! |
|
* Bootstrap scrollspy.js v5.2.2 (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.2): 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 <ul> and <nav> markup a parent is the previous sibling of any nav ancestor |
|
for (const item of SelectorEngine__default.default.prev(listGroup, SELECTOR_LINK_ITEMS)) { |
|
item.classList.add(CLASS_NAME_ACTIVE); |
|
} |
|
} |
|
} |
|
|
|
_clearActiveClass(parent) { |
|
parent.classList.remove(CLASS_NAME_ACTIVE); |
|
const activeNodes = SelectorEngine__default.default.find(`${SELECTOR_TARGET_LINKS}.${CLASS_NAME_ACTIVE}`, parent); |
|
|
|
for (const node of activeNodes) { |
|
node.classList.remove(CLASS_NAME_ACTIVE); |
|
} |
|
} // Static |
|
|
|
|
|
static jQueryInterface(config) { |
|
return this.each(function () { |
|
const data = ScrollSpy.getOrCreateInstance(this, config); |
|
|
|
if (typeof config !== 'string') { |
|
return; |
|
} |
|
|
|
if (data[config] === undefined || config.startsWith('_') || config === 'constructor') { |
|
throw new TypeError(`No method named "${config}"`); |
|
} |
|
|
|
data[config](); |
|
}); |
|
} |
|
|
|
} |
|
/** |
|
* Data API implementation |
|
*/ |
|
|
|
|
|
EventHandler__default.default.on(window, EVENT_LOAD_DATA_API, () => { |
|
for (const spy of SelectorEngine__default.default.find(SELECTOR_DATA_SPY)) { |
|
ScrollSpy.getOrCreateInstance(spy); |
|
} |
|
}); |
|
/** |
|
* jQuery |
|
*/ |
|
|
|
index.defineJQueryPlugin(ScrollSpy); |
|
|
|
return ScrollSpy; |
|
|
|
})); |
|
//# sourceMappingURL=scrollspy.js.map
|
|
|