Files

1 line
16 KiB
Plaintext

{"version":3,"file":"scrollspy.mjs","names":["pkg.version"],"sources":["../../../src/strings/activeClass.ts","../../../src/strings/scrollspyString.ts","../../../src/strings/scrollspyComponent.ts","../../../src/util/isDisabled.ts","../../../package.json","../../../src/version.ts","../../../src/components/base-component.ts","../../../src/components/scrollspy.ts"],"sourcesContent":["/**\n * Global namespace for most components active class.\n */\nconst activeClass = \"active\";\nexport default activeClass;\n","/** @type {string} */\nconst scrollspyString = \"scrollspy\";\nexport default scrollspyString;\n","/** @type {string} */\nconst scrollspyComponent = \"ScrollSpy\";\nexport default scrollspyComponent;\n","import { getAttribute, hasClass } from \"@thednp/shorty\";\n\n/**\n * Check if interactive element is disabled.\n * @param target either a `<button>` or an `<a>`\n * @returns whether the target is disabled\n */\nconst isDisabled = (target: Element) => {\n return hasClass(target, \"disabled\") ||\n getAttribute(target, \"disabled\") === \"true\";\n};\n\nexport default isDisabled;\n","","import pkg from \"../package.json\" with { type: \"json\" };\n\nconst Version = pkg.version;\n\nexport default Version;\n","/* Native JavaScript for Bootstrap 5 | Base Component\n----------------------------------------------------- */\nimport {\n Data,\n isElement,\n isString,\n normalizeOptions,\n ObjectKeys,\n querySelector,\n} from \"@thednp/shorty\";\n\nimport type { BaseOptions } from \"~/interface/baseComponent\";\nimport Version from \"~/version\";\n\n/** Returns a new `BaseComponent` instance. */\nexport default class BaseComponent {\n declare element: Element;\n declare options?: BaseOptions;\n\n /**\n * @param target `Element` or selector string\n * @param config component instance options\n */\n constructor(target: Element | string, config?: BaseOptions) {\n let element: Element | null;\n\n try {\n if (isElement(target)) {\n element = target as Element;\n } else if (isString(target)) {\n element = querySelector(target);\n // istanbul ignore else @preserve\n if (!element) throw Error(`\"${target}\" is not a valid selector.`);\n } else {\n throw Error(`your target is not an instance of HTMLElement.`);\n }\n } catch (e) {\n throw Error(`${this.name} Error: ${(e as Error).message}`);\n }\n\n const prevInstance = Data.get<typeof this>(element, this.name);\n /* istanbul ignore else @preserve */\n if (prevInstance) {\n // remove previously attached event listeners\n // to avoid memory leaks\n prevInstance._toggleEventListeners();\n }\n\n this.element = element;\n this.options = this.defaults && ObjectKeys(this.defaults).length\n ? normalizeOptions(element, this.defaults, config || {}, \"bs\")\n : /* istanbul ignore next @preserve */ {};\n\n Data.set(element, this.name, this);\n }\n\n // istanbul ignore next @preserve\n get version() {\n return Version;\n }\n\n // istanbul ignore next @preserve\n get name() {\n return \"BaseComponent\";\n }\n\n // istanbul ignore next @preserve\n get defaults() {\n return {};\n }\n\n /** just to have something to extend from */\n // istanbul ignore next @preserve coverage wise this isn't important\n _toggleEventListeners = () => {\n // do something to please linters\n };\n\n /** Removes component from target element. */\n dispose() {\n Data.remove<typeof this>(this.element, this.name);\n ObjectKeys(this).forEach((prop) => {\n delete this[prop];\n });\n }\n}\n","/* Native JavaScript for Bootstrap 5 | ScrollSpy\n------------------------------------------------ */\nimport {\n addClass,\n closest,\n createCustomEvent,\n dispatchEvent,\n getAttribute,\n getBoundingClientRect,\n getDocument,\n getDocumentElement,\n getElementById,\n getElementsByTagName,\n getInstance,\n hasClass,\n mouseclickEvent,\n MouseEvent,\n querySelector,\n removeClass,\n} from \"@thednp/shorty\";\n\nimport PositionObserver from \"@thednp/position-observer\";\nimport { addListener, removeListener } from \"@thednp/event-listener\";\n\nimport activeClass from \"~/strings/activeClass\";\nimport scrollspyString from \"~/strings/scrollspyString\";\nimport scrollspyComponent from \"~/strings/scrollspyComponent\";\nimport isDisabled from \"~/util/isDisabled\";\nimport BaseComponent from \"./base-component\";\nimport { ScrollSpyEvent, ScrollSpyOptions } from \"~/interface/scrollspy\";\n\n// SCROLLSPY PRIVATE GC\n// ====================\nconst scrollspySelector = '[data-bs-spy=\"scroll\"]';\nconst scrollSpyAnchorSelector = \"[href]\";\n\nconst scrollspyDefaults: Partial<ScrollSpyOptions> = {\n offset: 10,\n target: undefined,\n};\n\ntype ScrollSpyEventProps = {\n relatedTarget: HTMLElement;\n};\n\n/**\n * Static method which returns an existing `ScrollSpy` instance associated\n * to a target `Element`.\n */\nconst getScrollSpyInstance = (element: Element) =>\n getInstance<ScrollSpy>(element, scrollspyComponent);\n\n/**\n * A `ScrollSpy` initialization callback.\n */\nconst scrollspyInitCallback = (element: Element) => new ScrollSpy(element);\n\n// SCROLLSPY CUSTOM EVENT\n// ======================\nconst activateScrollSpy = createCustomEvent<\n ScrollSpyEventProps,\n ScrollSpyEvent\n>(`activate.bs.${scrollspyString}`);\n\n// SCROLLSPY PRIVATE METHODS\n// =========================\n/**\n * Update the state of all items.\n *\n * @param self the `ScrollSpy` instance\n */\nconst updateSpyTargets = (self: ScrollSpy) => {\n const {\n target,\n _itemsLength,\n _observables,\n } = self;\n\n const links = getElementsByTagName<HTMLAnchorElement>(\"A\", target);\n const doc = getDocument(target);\n\n // only update items once or with each mutation\n // istanbul ignore else @preserve\n if (!links.length || _itemsLength === _observables.size) return;\n // reset arrays & update\n _observables.clear();\n\n Array.from(links).forEach((link) => {\n const hash = getAttribute(link, \"href\")?.slice(1);\n const targetItem = hash?.length ? doc.getElementById(hash) : null;\n\n if (targetItem && !isDisabled(link)) {\n self._observables.set(targetItem, link);\n }\n });\n self._itemsLength = self._observables.size;\n};\n\n/**\n * Clear all items of the target.\n *\n * @param target a single item\n */\nconst clear = (target: Element) => {\n Array.from(getElementsByTagName<HTMLAnchorElement>(\"A\", target)).forEach(\n (item) => {\n if (hasClass(item, activeClass)) removeClass(item, activeClass);\n },\n );\n};\n\n/**\n * Activates a new item.\n *\n * @param self the `ScrollSpy` instance\n * @param item a single item\n */\nconst activate = (self: ScrollSpy, item: HTMLElement) => {\n const { target, element } = self;\n\n // istanbul ignore else @preserve\n clear(target);\n\n self._activeItem = item;\n addClass(item, activeClass);\n\n // activate all parents\n let parentItem = item;\n while (parentItem !== target) {\n parentItem = parentItem.parentElement as HTMLElement;\n if (\n [\"nav\", \"dropdown-menu\", \"list-group\"].some((c) =>\n hasClass(parentItem, c)\n )\n ) {\n const parentLink = parentItem.previousElementSibling as\n | HTMLElement\n | null;\n\n // istanbul ignore else @preserve\n if (parentLink && !hasClass(parentLink, activeClass)) {\n addClass(parentLink, activeClass);\n }\n }\n }\n\n // dispatch\n activateScrollSpy.relatedTarget = item;\n dispatchEvent(element, activateScrollSpy);\n};\n\nconst getOffset = (self: ScrollSpy, target: HTMLElement) => {\n const { scrollTarget, element, options } = self;\n\n return (scrollTarget !== element\n ? getBoundingClientRect(target).top + scrollTarget.scrollTop\n : target.offsetTop) - (options.offset as number || 10);\n};\n\n// SCROLLSPY DEFINITION\n// ====================\n/** Returns a new `ScrollSpy` instance. */\nexport default class ScrollSpy extends BaseComponent {\n static selector = scrollspySelector;\n static init = scrollspyInitCallback;\n static getInstance = getScrollSpyInstance;\n declare element: HTMLElement;\n declare options: ScrollSpyOptions;\n declare target: HTMLElement;\n declare scrollTarget: HTMLElement;\n declare _itemsLength: number;\n declare _activeItem: HTMLElement | null;\n declare _observables: Map<HTMLElement, HTMLElement>;\n declare _observer: PositionObserver; //| IntersectionObserver;\n\n /**\n * @param target the target element\n * @param config the instance options\n */\n constructor(\n target: Element | string,\n config?: Partial<ScrollSpyOptions>,\n ) {\n super(target, config);\n\n // initialization element & options\n const { element, options } = this;\n\n // get target\n const spyTarget = querySelector(\n options.target,\n getDocument(element),\n );\n\n // invalidate\n if (!spyTarget) return;\n this.target = spyTarget;\n\n // set initial state\n this.scrollTarget = element.clientHeight < element.scrollHeight\n ? element\n : getDocumentElement(element);\n this._observables = new Map();\n\n // run an initial burst, we need to know the targets\n this.refresh();\n // updateSpyTargets(this);\n\n // create observer\n this._observer = new PositionObserver(() => {\n requestAnimationFrame(() => this.refresh?.());\n }, {\n root: this.scrollTarget,\n });\n\n // add event handlers\n this._toggleEventListeners(true);\n }\n\n /* eslint-disable */\n /**\n * Returns component name string.\n */\n get name() {\n return scrollspyComponent;\n }\n /**\n * Returns component default options.\n */\n get defaults() {\n return scrollspyDefaults;\n }\n /* eslint-enable */\n\n // SCROLLSPY PUBLIC METHODS\n // ========================\n /** Updates all items. */\n refresh = () => {\n const { target, scrollTarget } = this;\n // check if target is visible and invalidate\n // istanbul ignore if @preserve\n if (!target || target.offsetHeight === 0) return;\n\n updateSpyTargets(this);\n const { _itemsLength, _observables, _activeItem } = this;\n\n // istanbul ignore if @preserve\n if (!_itemsLength) return;\n const entries = _observables.entries().toArray();\n\n const { scrollTop, scrollHeight, clientHeight } = scrollTarget;\n\n if (scrollTop >= scrollHeight - clientHeight) {\n const newActiveItem = entries[_itemsLength - 1]?.[1];\n\n // istanbul ignore else @preserve\n if (_activeItem !== newActiveItem) activate(this, newActiveItem);\n return;\n }\n\n /* istanbul ignore next @preserve */\n const firstOffset = entries[0]?.[0] ? getOffset(this, entries[0][0]) : null;\n if (\n firstOffset !== null && scrollTop < firstOffset &&\n firstOffset > 0\n ) {\n this._activeItem = null;\n clear(target);\n return;\n }\n\n for (let i = 0; i < _itemsLength; i += 1) {\n const [targetItem, item] = entries[i];\n const offsetTop = getOffset(this, targetItem);\n const nextTarget = entries[i + 1]?.[0];\n const nextOffsetTop = nextTarget\n ? getOffset(this, nextTarget)\n : /* istanbul ignore next @preserve */ null;\n\n // istanbul ignore else @preserve\n if (\n _activeItem !== item &&\n scrollTop >= offsetTop &&\n (nextOffsetTop === null || scrollTop < nextOffsetTop)\n ) {\n activate(this, item);\n break;\n }\n }\n };\n\n /**\n * This method provides an event handle\n * for scrollspy\n * @param e the event listener object\n */\n _scrollTo = (e: MouseEvent<HTMLAnchorElement>) => {\n const item = closest(e.target, scrollSpyAnchorSelector);\n const hash = item && getAttribute(item, \"href\")?.slice(1);\n const targetItem = hash && getElementById(hash, this.target);\n\n // istanbul ignore else @preserve\n if (targetItem) {\n this.scrollTarget.scrollTo({\n top: targetItem.offsetTop,\n behavior: \"smooth\",\n });\n e.preventDefault();\n }\n };\n\n /**\n * Toggles on/off the component observer.\n *\n * @param self the ScrollSpy instance\n * @param add when `true`, listener is added\n */\n _toggleEventListeners = (\n add?: boolean,\n ) => {\n const { target, _observables, _observer, _scrollTo } = this;\n const action = add ? addListener : removeListener;\n action(target, mouseclickEvent, _scrollTo);\n\n if (add) {\n _observables?.forEach((_, targetItem) => _observer.observe(targetItem));\n } else _observer.disconnect();\n };\n\n /** Removes `ScrollSpy` from the target element. */\n dispose() {\n this._toggleEventListeners();\n super.dispose();\n }\n}\n"],"mappings":"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"}