From b10daefa7dba9c88005c00f1c42a0294e8ae4222 Mon Sep 17 00:00:00 2001 From: mr-ema Date: Sat, 23 Nov 2024 13:48:53 -0300 Subject: [PATCH] chore(build): update dist files --- dist/onepage.esm.js | 2634 +++++++++++++++++++++++++++++++++++++++ dist/onepage.esm.min.js | 111 ++ dist/onepage.js | 462 ++++--- dist/onepage.min.js | 62 +- 4 files changed, 3092 insertions(+), 177 deletions(-) create mode 100644 dist/onepage.esm.js create mode 100644 dist/onepage.esm.min.js diff --git a/dist/onepage.esm.js b/dist/onepage.esm.js new file mode 100644 index 0000000..3efe6b3 --- /dev/null +++ b/dist/onepage.esm.js @@ -0,0 +1,2634 @@ +// Zero-Clause BSD +// +// Permission to use, copy, modify, and/or distribute this software for +// any purpose with or without fee is hereby granted. +// +// THE SOFTWARE IS PROVIDED “AS IS” AND THE AUTHOR DISCLAIMS ALL +// WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES +// OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE +// FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY +// DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN +// AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +// OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + +const constants = { + CLASS_NAME_PREFIX: "op", + SECTION_CLASS_NAME: "section", + ROOT_ID_NAME: "onepage", + SLIDER_WRAPPER_CLASS_NAME: "slider-ctn", + SINGLE_SLIDE_CLASS_NAME: "slide", + TOUCH_THRESHOLD: 30, // Minimum distance in pixels to consider it a swipe + + /** + * Specifies the mouse buttons to ignore during swipe interactions. + * This list is used to discard specific buttons when swiping with a mouse, + * allowing the swipe action to proceed only when other buttons are used. + * + * Possible values for mouse buttons: + * 0: Main button pressed, usually the left button or the un-initialized state. + * 1: Auxiliary button pressed, usually the wheel button or the middle button (if present). + * 2: Secondary button pressed, usually the right button. + * 3: Fourth button, typically the Browser Back button. + * 4: Fifth button, typically the Browser Forward button. + * + * @type {Array} + */ + MOUSE_SWIPE_DISCARDED_BUTTONS: [2], +}; + +// Zero-Clause BSD +// +// Permission to use, copy, modify, and/or distribute this software for +// any purpose with or without fee is hereby granted. +// +// THE SOFTWARE IS PROVIDED “AS IS” AND THE AUTHOR DISCLAIMS ALL +// WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES +// OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE +// FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY +// DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN +// AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +// OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + +/** + * Custom error type for never errors (indicating unreachable or "impossible" code). + */ +class NeverError extends Error { + /** @param message {string} */ + constructor(message) { + super(message); + this.name = 'NeverError'; + } +} + +/** + * @param msg {string} + * @param data {any[]} + * + * @throws {NeverError} Throws an error with a custom message. + * @returns {never} + */ +function never(msg, ...data) { + console.error("%c[FATAL ERROR]", "color: red;", msg, ...data); + throw new NeverError(msg); +} + +// Zero-Clause BSD +// +// Permission to use, copy, modify, and/or distribute this software for +// any purpose with or without fee is hereby granted. +// +// THE SOFTWARE IS PROVIDED “AS IS” AND THE AUTHOR DISCLAIMS ALL +// WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES +// OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE +// FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY +// DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN +// AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +// OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + + +/** + * @param property {string} + * @returns {string} + */ +function toKebabCase(property) { + return property + .replace(/_/g, "-") // Replace underscores with hyphens + .replace(/([a-z])([A-Z])/g, "$1-$2") // Convert camelCase to kebab-case + .toLowerCase(); +} + +/** + * @param elem {Element} + * @param name {string} + * @returns {void} + */ +function addClassName(elem, name) { + elem.classList.add(name); +} + +/** + * @param elem {Element} + * @param name {string} + * @returns {void} + */ +function removeClassName(elem, name) { + elem.classList.remove(name); +} + +/** + * @param element {Element} + * @param depth {number|-1} [depth=0] - If `depth` is -1, the function will traverse the DOM tree indefinitely until it finds a scrollable parent or reaches the top of the DOM tree. + * @returns {boolean} + */ +function isParentElementScrollable(element, depth) { + let parent = element.parentElement; + + for (let level = depth; level <= depth;) { + if (parent == null) break; + + const hasVerticalScroll = parent.scrollHeight > parent.clientHeight; + const hasHorizontalScroll = parent.scrollWidth > parent.clientWidth; + if (hasVerticalScroll || hasHorizontalScroll) { + return true; + } + + parent = parent?.parentElement; + + // Keep Iterating Until There Is No More Parents Elements + if (depth !== -1) { + level += 1; + } + } + + return false; +} + +/** + * @param element {Element} + * @returns {boolean} + */ +function isElementScrollable(element) { + const hasVerticalScroll = element.scrollHeight > element.clientHeight; + const hasHorizontalScroll = element.scrollWidth > element.clientWidth; + + if (hasVerticalScroll || hasHorizontalScroll) { + return true; + } + + return false; +} + + +/** + * @param element {Element} + * @param depth {number|-1} [depth=0] - If `depth` is -1, the function will traverse the DOM tree indefinitely until it finds a scrollable parent or reaches the top of the DOM tree. + * @returns {Element|null} + */ +function tryToGetScrollableParentElement(element, depth = 0) { + let parent = element.parentElement; + + for (let level = depth; level <= depth;) { + if (parent == null) break; + + const hasVerticalScroll = parent.scrollHeight > parent.clientHeight; + const hasHorizontalScroll = parent.scrollWidth > parent.clientWidth; + if (hasVerticalScroll || hasHorizontalScroll) { + return parent; + } + + parent = parent?.parentElement; + + // Keep Iterating Until There Is No More Parents Elements + if (depth !== -1) { + level += 1; + } + } + + return null; +} + +/** + * @param slide {Element} + * @param depth {number|-1} [depth=0] - If `depth` is -1, the function will traverse the DOM tree indefinitely until it finds a parent or reaches the top of the DOM tree. + * @returns {Element|null} + */ +function slideParentCtnOrNull(slide, depth = 0) { + let parent = slide.parentElement; + + for (let level = depth; level <= depth;) { + if (parent == null) break; + + if (parent.classList.contains(constants.SLIDER_WRAPPER_CLASS_NAME)) { + return parent; + } + + parent = parent?.parentElement; + + // Keep Iterating Until There Is No More Parents Elements + if (depth !== -1) { + level += 1; + } + } + + return null; +} + +/** + * @param element {Element} + * @param depth {number|-1} [depth=0] - If `depth` is -1, the function will traverse the DOM tree indefinitely until it finds a parent or reaches the top of the DOM tree. + * @returns {Element|null} + */ +function sectionParentOrNull(element, depth = 0) { + let parent = element.parentElement; + + for (let level = depth; level <= depth;) { + if (parent == null) break; + + if (parent.classList.contains(constants.SECTION_CLASS_NAME)) { + return parent; + } + + parent = parent?.parentElement; + + // Keep Iterating Until There Is No More Parents Elements + if (depth !== -1) { + level += 1; + } + + } + + return null; +} + +/** + * @param element {Element} + * @param direction {"vertical"|"horizontal"} [direction="vertical"] + * @returns {boolean} + */ +function hasReachedEndOfScroll(element, direction = "vertical") { + const tolerance = 1; // Allow a small margin for rounding errors + + switch (direction.toLowerCase()) { + case "vertical": + return ((element.scrollTop + element.clientHeight + tolerance) >= element.scrollHeight); + case "horizontal": + return ((element.scrollLeft + element.clientWidth + tolerance) >= element.scrollWidth); + } + + return false; +} + +/** + * @param element {Element} + * @param direction {"vertical"|"horizontal"} [direction="vertical"] + * @returns {boolean} + */ +function hasReachedStartOfScroll(element, direction = "vertical") { + const tolerance = 1; // Allow a small margin for rounding errors + + switch (direction.toLowerCase()) { + case "vertical": + return (element.scrollTop <= tolerance); + case "horizontal": + return (element.scrollLeft <= tolerance); + } + + return false; +} + +/** + * Wraps all child nodes of the specified element in a new wrapper element. + * + * @param parentElement {Element} - The parent element whose child nodes will be wrapped. + * @param wrapperTag {string} [wrapperTag="div"] - The tag name of the wrapper element to create. + * + * @returns {Element} - The newly created wrapper element containing all original child nodes. + * + * @note This function assumes any string as a valid tag name, allowing support + * for custom web components. However, it does not validate the tag name before use. + */ +function wrapAllChildrenOf(parentElement, wrapperTag = "div") { + const wrapper = document.createElement(wrapperTag); + while (parentElement.firstChild) { + wrapper.appendChild(parentElement.firstChild); + } + + parentElement.appendChild(wrapper); + + return wrapper; +} + +/** + * Wraps the provided child elements inside a new wrapper element with the specified tag name. + * + * @param children {Array|NodeListOf} - The child elements to be wrapped. + * @param wrapperTag {string} [wrapperTag="div"] - The tag name of the wrapper element to create. + * + * @returns {Element} - The newly created wrapper element containing all the provided child elements. + * + * @note This function assumes any string as a valid tag name, allowing support + * for custom web components. It does not validate the tag name before use. + */ +function wrapChildrenInTag(children, wrapperTag = "div") { + const wrapper = document.createElement(wrapperTag); + + children.forEach(child => { + wrapper.appendChild(child); + }); + + return wrapper; +} + +/** + * @param element {Element} + * @returns {NodeListOf} + */ +function getSliderListInElement(element) { + const slider = element.querySelectorAll("." + constants.SLIDER_WRAPPER_CLASS_NAME); + + return slider; +} + +/** + * @param element {Element} + * @returns {NodeListOf|null} + */ +function getSingleSlidesInElementOrNull(element) { + const slides = element.querySelectorAll("." + constants.SINGLE_SLIDE_CLASS_NAME); + if (slides.length >= 1) return slides; + + return null; +} + +/** + * Retrieves the root element by its ID or throws an error if it doesn't exist. + * + * @throws {NeverError} + * @returns {Element} + */ +function getRootNodeOrThrow() { + const root = document.getElementById("#" + constants.ROOT_ID_NAME); + if (root == null) { + never(`${constants.ROOT_ID_NAME} element not founded in DOOM`); + } + + return root; +} + +/** + * Merges the source object into the target object recursively. + * + * @param target {Object.} + * @param source {Object.} + * + * @returns {Object} The merged target object. + */ +function deepMerge(target, source) { + for (const key in source) { + if (source.hasOwnProperty(key)) { + if (source[key] instanceof Object && target[key] instanceof Object) { + target[key] = deepMerge(target[key], source[key]); + } else { + target[key] = source[key]; + } + } + } + + return target; +} + +/** + * Generates a random string by combining letters and numbers. + * + * @param length {number} [length=16] - specified how long should the generated string be + * @returns {string} A randomly generated string + */ +function generateRandomString(length = 16) { + // Character set to be used in the random class name + const chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; + + let randomString = ""; + for (let i = 0; i < length; i++) { + // Append a random character from `chars` to `className` + randomString += chars.charAt(Math.floor(Math.random() * chars.length)); + } + + // Return the generated class name with a prefix for readability + return randomString; +} + +/** + * Removes white space and new line + * + * @param raw {string} + * @returns {string} + */ +function removeSpace(raw) { + const regex = /\r?\n|\r|\s+/g; + + return raw.replace(regex, ""); +} + +/** + * Inlines a string by removing excessive line breaks and spaces, while + * ensuring no more than a specified number of consecutive spaces are retained. + * + * @param string {string} + * @param maxWhitespace {number} [maxWhitespace=1] + * @returns {string} + */ +function inlineString(string, maxWhitespace = 1) { + const noLineBreaksText = string.replace(/[\r\n]+/g, " "); + + // Replace multiple spaces with exactly maxWhitespace spaces + const regex = new RegExp(` {${maxWhitespace + 1},}`, "g"); + const inlinedText = noLineBreaksText.replace(regex, " ".repeat(maxWhitespace)); + + return inlinedText.trim(); +} + +/** + * Extracts class names from a CSS-like string. + * Matches strings that start with a period (.) followed by the class name, + * and stops at the first whitespace or `{` character. + * + * @param raw {string} - The raw CSS string to search for class names. + * @returns {Array} - An array of matched class names (without the dot). + */ +function extractClassName(raw) { + const regex = /^\s*\.([\w-]+)\s*\{/g; + + const matches = raw.match(regex); + if (matches) { + // Clean up matches to remove the leading dot and trailing `{` + return matches.map(match => match.replace(/^\.\s*|\s*{\s*$/g, "")); + } + + return []; +} + +/** + * Adds a prefix to each CSS class name in a string. + * Matches patterns that start with a dot (.) followed by the class name. + * + * @param raw {string} - The raw CSS string to modify. + * @param prefix {string} - The prefix to add to each class name. + * @returns {string} - The modified CSS string with prefixed class names. + */ +function addPrefixToClassNames(raw, prefix) { + const classRegex = /\.([a-zA-Z0-9_-]+)(?=\s*{|\s*:)/g; + return raw.replace(classRegex, (_match, className) => `.${prefix}-${className}`); +} + +/** + * @param elem {Element} + * @returns {boolean} + */ +function isSection(elem) { + return elem.classList.contains(constants.SECTION_CLASS_NAME); +} + +/** + * @param elem {Element} + * @returns {boolean} + */ +function isSlider(elem) { + return elem.classList.contains(constants.SLIDER_WRAPPER_CLASS_NAME); +} + +/** + * @param elem {Element} + * @returns {boolean} + */ +function isSlide(elem) { + return elem.classList.contains(constants.SINGLE_SLIDE_CLASS_NAME); +} + +/** + * Determines whether an element comes before or after its sibling using compareDocumentPosition. + * + * @param element {Element} + * @param sibling {Element} + * @returns {"before"|"same"|"after"|-1} - Returns '-1' if element is not a direct sibling + */ +function isElementBeforeOrAfter(element, sibling) { + if (element === sibling) return "same" + if (element.parentElement !== sibling.parentElement) return -1; + + const position = element.compareDocumentPosition(sibling); + if (position & Node.DOCUMENT_POSITION_FOLLOWING) { + return "before"; + } else if (position & Node.DOCUMENT_POSITION_PRECEDING) { + return "after"; + } + + return -1; +} + +function isTouchDevice() { + return (window.matchMedia && window.matchMedia("(pointer: coarse)").matches); +} + +/** + * @param func {Function} - callback function + * @param delay {number} - delay in ms + */ +function debounce(func, delay) { + /** @type {Timer}*/ + let timer; + + /** @param args {*} */ + return (...args) => { + clearTimeout(timer); + timer = setTimeout(() => func(...args), delay); + }; +} + +var utils = { + toKebabCase: toKebabCase, + addClassName: addClassName, + removeClassName: removeClassName, + isElementScrollable: isElementScrollable, + isParentElementScrollable: isParentElementScrollable, + tryToGetScrollableParentElement: tryToGetScrollableParentElement, + hasReachedEndOfScroll: hasReachedEndOfScroll, + hasReachedStartOfScroll: hasReachedStartOfScroll, + wrapAllChildrenOf: wrapAllChildrenOf, + wrapChildrenInTag: wrapChildrenInTag, + slideParentCtnOrNull: slideParentCtnOrNull, + sectionParentOrNull: sectionParentOrNull, + getSliderListInElement: getSliderListInElement, + getSingleSlidesInElementOrNull: getSingleSlidesInElementOrNull, + getRootNodeOrThrow: getRootNodeOrThrow, + deepMerge: deepMerge, + generateRandomString: generateRandomString, + removeSpace: removeSpace, + extractClassName: extractClassName, + addPrefixToClassNames: addPrefixToClassNames, + isSection: isSection, + isSlide: isSlide, + isSlider: isSlider, + isElementBeforeOrAfter: isElementBeforeOrAfter, + isTouchDevice: isTouchDevice, + inlineString: inlineString, + debounce: debounce +}; + +// Zero-Clause BSD +// +// Permission to use, copy, modify, and/or distribute this software for +// any purpose with or without fee is hereby granted. +// +// THE SOFTWARE IS PROVIDED “AS IS” AND THE AUTHOR DISCLAIMS ALL +// WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES +// OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE +// FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY +// DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN +// AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +// OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + + +/** Global settings */ +const settings = { + constants: constants, + + logger: { + enabled: false, + levels: /** @type {Array} */ (["ALL"]), + }, + + observer: { + enabled: true, + }, + + scroll: { + unlockTimeout: 300, // Time in ms to wait before allow to scroll again + keyboardScroll: true, // Enable scroll with keyboard + swipeScroll: false, // Enable scroll with mouse draggin (click + direction) + overflowScroll: false, // Enable normal scroll when elements have overflow + speed: 69, // Scroll 'n' pixels when there is overflow scroll + + direction: /** @type {Direction} */ ("vertical"), + behavior: /** @type {ScrollBehavior} */ ("smooth"), + }, + + section: { + // pagination: true, + }, + + slider: { + // pagination: true, + }, + + keybindings: { + up: ["ArrowUp", "k", "PageUp"], + down: ["ArrowDown", "j", "PageDown"], + left: ["ArrowLeft", "h"], + right: ["ArrowRight", "l"], + }, +}; + +/** + * Helper function to validate settings based on expected types + * + * @param key {string} + * @param value {*} + */ +function _validateSettings(key, value) { + /** @type {{ [key: string]: string}} */ + const validationRules = { + "constants.CLASS_NAME_PREFIX": "string", + "constants.SECTION_CLASS_NAME": "string", + "constants.ROOT_ID_NAME": "string", + "constants.SLIDER_WRAPPER_CLASS_NAME": "string", + "constants.SINGLE_SLIDE_CLASS_NAME": "string", + "constants.TOUCH_THRESHOLD": "number", + "constants.MOUSE_SWIPE_DISCARDED_BUTTONS": "array", + + "logger.enabled": "boolean", + "logger.levels": "array", + + "observer.enabled": "boolean", + + "scroll.unlockTimeout": "number", + "scroll.keyboardScroll": "boolean", + "scroll.swipeScroll": "boolean", + "scroll.overflowScroll": "boolean", + "scroll.speed": "number", + "scroll.direction": "string", + "scroll.behavior": "string", + + "keybindings.up": "array", + "keybindings.down": "array", + "keybindings.left": "array", + "keybindings.right": "array", + }; + + if (!validationRules.hasOwnProperty(key)) { + never("[settings] Invalid Key"); + } + + const expectedType = validationRules[key]; + if (expectedType) { + if (expectedType === "array" && !Array.isArray(value)) { + never(`[settings] Invalid type for ${key}. Expected an array.`); + } else if (typeof value !== expectedType && expectedType !== "array") { + never(`[settings] Invalid type for ${key}. Expected ${expectedType}.`); + } + } +} + +/** + * Sets or updates options in the settings object. + * + * @param newOptions {Object} - Partial settings to update in the main settings object. + * @throws {Error} If any new option fails validation. + */ +function setOptions(newOptions) { + /** + * Recursively validates and merges new options into existing settings. + * + * @param current {Object.} + * @param updates {Object.} + * @param path {string} [path=""] - The current path in dot notation for validation. + */ + function _validateAndMerge(current, updates, path = "") { + for (const key in updates) { + const newKeyPath = path ? `${path}.${key}` : key; + const newValue = /** @type {Object.} */ (updates)[key]; + + // If it's an object, recurse to validate nested keys + if (typeof newValue === "object" && newValue !== null && !Array.isArray(newValue)) { + if (!current[key]) current[key] = {}; + _validateAndMerge(current[key], newValue, newKeyPath); + } else { + _validateSettings(newKeyPath, newValue); + current[key] = newValue; + } + } + } + + _validateAndMerge(settings, newOptions); + deepMerge(settings, newOptions); +} + +// Zero-Clause BSD +// +// Permission to use, copy, modify, and/or distribute this software for +// any purpose with or without fee is hereby granted. +// +// THE SOFTWARE IS PROVIDED “AS IS” AND THE AUTHOR DISCLAIMS ALL +// WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES +// OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE +// FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY +// DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN +// AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +// OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + + +let _cssAcummulator = ""; + +/** + * The `css` tagged template literal function allows you to define CSS styles dynamically. + * It processes the template literal, optionally extracts a custom class name if provided, + * or generates a random class name if no custom class is specified. The resulting styles + * are then injected into the document with the assigned class name. + * + * @usage + * - **With Custom Class Name:** Define a CSS style with a class name in the template, + * and the function will use this class name in the output. + * - **Without a Class Name:** If no class name is defined in the template, the function + * generates a unique class name for the styles. + * + * @param strings {TemplateStringsArray} - The static parts of the template literal. + * @param values {...string} - The dynamic parts of the template literal. + * @returns {string} - The generated or specified class name applied to the CSS styles. + * + * @example + * // With a custom class name + * const styles = css`.myCustomClass { color: blue; font-size: 16px; }`; + * // `styles` will be "prefix-myCustomClass", and the styles will be applied to ".prefix-myCustomClass" + * + * @example + * // Without a custom class name + * const color = 'red'; + * const styles = css`color: ${color}; font-size: 18px;`; + * // `styles` will be a randomly generated class name, e.g., "prefix-random123" + * // The styles will be injected as ".prefix-random123 { color: red; font-size: 18px; }" + */ +function css(strings, ...values) { + let result = strings.reduce((accum, str, i) => { + let value = values[i] ? values[i] : ''; + return accum + str + value; + }, ''); + + // Remove new lines and white space + let fmtResult = utils.inlineString(result, 1); + + const match = utils.extractClassName(fmtResult); + const className = match ? match[0] : utils.generateRandomString(); + + if (match) { + _cssAcummulator += fmtResult; + } else { + _cssAcummulator += `.${className} {${fmtResult}}`; + } + + return className; +} + +function createHeadStyles() { + const fmtCss = utils.addPrefixToClassNames(_cssAcummulator, constants.CLASS_NAME_PREFIX); + + const cssTemplate = ``; + document.head.insertAdjacentHTML("beforeend", cssTemplate); +} + +/** + * @param sections {SectionList} + * + * @throws {NeverError} - Throws an error if root element doesn't exist. + */ +function injectStylesOrThrow() { + const root = utils.getRootNodeOrThrow(); + + _injectTopLevelClasses(root); +} + +/** @param root {Element} */ +function _injectTopLevelClasses(root) { + const html = document.documentElement; + const body = document.getElementsByTagName("body")[0]; + + utils.addClassName(html, classes.html); + utils.addClassName(body, classes.html); + + if (settings.scroll.direction.toLowerCase() === "horizontal") { + utils.addClassName(root, classes.horizontal); + } else { + utils.addClassName(root, classes.vertical); + } +} + +const classes = (() => { + const _classes = { + html: css` + .document { + overflow: hidden; + scroll-behavior: smooth; + + margin: 0; + padding: 0; + } + `, + + vertical: css` + .vertical { + display: flex; + flex-direction: column; + } + `, + + horizontal: css` + .horizontal { + display: flex; + flex-direction: row; + } + `, + + section: css` + .section { + display: flex; + align-items: center; + justify-content: center; + + max-height: 100vh; + min-height: 100vh; + + max-width: 100vw; + min-width: 100vw; + + box-sizing: border-box; + -webkit-box-sizing: border-box; + } + `, + + overflow: css` + .overflow { + overflow: auto; + + max-height: 100vh; + + /* Hide Scrollbar */ + -ms-overflow-style: none; /* [IE And Edge] */ + scrollbar-width: none; /* [Firefox] */ + + /* [Chrome, Safari And Opera] */ + ::-webkit-scrollbar { display: none; } + } + `, + + sliderWrapper: css` + .slider-wrapper { + z-index: 1; + overflow: hidden; + position: relative; + display: flex; + flex-direction: row; + + transition: all 0.3s ease-out; + -webkit-transition: all 0.3s ease-out; /* [Safari <= 6, Android <= 4.3] */ + + max-height: 100vh; + max-width: 100vw; + } + `, + + slide: css` + .slide { + display: flex; + justify-content: center; + align-items: center; + + min-height: 100vh; + min-width: 100vw; + } + `, + + sectionPagination: css` + .section-pagination { + position: fixed; + display: flex; + flex-direction: column; + gap: 0.6rem; + border-radius: 0.3rem; + right: 1rem; + top: 50%; + z-index: 2; + list-style: none; + + margin: 0; + padding: 0.3rem; + + li { + padding: 0; + text-align: center; + } + + li > a { + display: flex; + width: 1rem; + height: 1rem; + } + + li > a:before { + content: ''; + position: absolute; + background: rgba(0, 0, 0, 0.69); + border-radius: 1rem; + width: 1rem; + height: 1rem; + } + + li > a.active:before { + background: rgba(0, 0, 0, 0.9); + width: 1.2rem; + height: 1.2rem; + } + } + `, + }; + + /** @type {Proxy} */ + return new Proxy(_classes, { + get(target, prop) { + if (prop in target) { + const property = target[/** @type {keyof _classes} */(prop)]; + return `${constants.CLASS_NAME_PREFIX}-${property}`; + } + } + }); +})(); + +// Zero-Clause BSD +// +// Permission to use, copy, modify, and/or distribute this software for +// any purpose with or without fee is hereby granted. +// +// THE SOFTWARE IS PROVIDED “AS IS” AND THE AUTHOR DISCLAIMS ALL +// WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES +// OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE +// FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY +// DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN +// AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +// OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + + +/** + * Simple console logger singleton + * + * @type {Logger} + */ +const Logger = (() => { + /** @type {LogLevel} */ + const LogLevel = Object.freeze({ + DEBUG: 1, + INFO: 2, + WARN: 4, + ERROR: 8, + + ALL: (2 ** 4) - 1 + }); + + /** @type {number} */ + let _activeLevels = (() => { + let finalLevel = 0; + for (const level of settings.logger.levels) { + if (Object.hasOwn(LogLevel, level) && finalLevel < LogLevel.ALL) { + finalLevel += LogLevel[level]; + } + } + + return finalLevel === 0 ? LogLevel.ALL : finalLevel; + })(); + + /** + * @param level {LogLevel} + * @returns {boolean} + */ + function shouldLog(level) { + return settings.logger.enabled && (_activeLevels & level) !== 0; + } + + /** + * @param message {string}- The message to log. + * @param data {...any[]} - Optional additional parameters to log. + */ + function debug(message, ...data) { + if (shouldLog(LogLevel.DEBUG)) { + console.debug("%c[DEBUG]", "color: gray;", message, ...data); + } + } + + /** + * @param message {string}- The message to log. + * @param data {...any[]} - Optional additional parameters to log. + */ + function info(message, ...data) { + if (shouldLog(LogLevel.INFO)) { + console.info("%c[INFO]", "color: blue;", message, ...data); + } + } + + /** + * @param message {string}- The message to log. + * @param data {...any[]} - Optional additional parameters to log. + */ + function warn(message, ...data) { + if (shouldLog(LogLevel.WARN)) { + console.warn("%c[WARN]", "color: orange;", message, ...data); + } + } + + /** + * @param message {string}- The message to log. + * @param data {...any[]} - Optional additional parameters to log. + */ + function error(message, ...data) { + if (shouldLog(LogLevel.ERROR)) { + console.error("%c[ERROR]", "color: red;", message, ...data); + } + } + + /** + * @param levels {LogLevel} + */ + function setLevel(levels) { + _activeLevels = levels; + } + + return { + error, + warn, + info, + debug, + setLevel + } +})(); + +// Zero-Clause BSD +// +// Permission to use, copy, modify, and/or distribute this software for +// any purpose with or without fee is hereby granted. +// +// THE SOFTWARE IS PROVIDED “AS IS” AND THE AUTHOR DISCLAIMS ALL +// WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES +// OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE +// FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY +// DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN +// AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +// OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + + +/** + * Singleton module + * @type {WheelEventHandler} + */ +const WheelEventHandler = (() => { + let _isListen = false; + let _direction = /** @type {Vector2} */ { x: 0, y: 0 }; + + /** @type {{ [key: string]: Array<(event: WheelEvent) => void | Promise>}} */ + const _listeners = { + wheel: [] + }; + + /** + * Add a listener for a specific event type + * @type {typeof WheelEventHandler.on} + */ + function on(type, callback) { + if (_listeners[type]) { + _listeners[type].push(callback); + + Logger.debug(`WheelEventHandler: event listener '${type}' [added]`); + } else { + never(`WheelEventHandler: unsupported event type: '${type}' (supported types list [${Object.keys(_listeners).join(', ')}])`); + } + } + + /** + * Remove a listener for a specific event type + * @type {typeof WheelEventHandler.off} + */ + function off(type, callback) { + if (_listeners[type]) { + _listeners[type] = _listeners[type].filter(fn => fn !== callback); + + Logger.debug(`WheelEventHandler: event listener '${type}' [removed]`); + } + } + + /** Executes all registered listeners for a specified event type. + * @param type {WheelEventType} + * @param event {WheelEvent} + */ + async function _notifyListeners(type, event) { + if (_listeners[type]) { + for (const listener of _listeners[type]) { + await listener(event); + } + } + } + + function _cleanInternalListeners() { + Object.keys(_listeners).forEach(key => _listeners[key] = []); + } + + /** @param event {WheelEvent} */ + function _handleWheel(event) { + _direction.y = 0; + _direction.x = 0; + + if (event.deltaY !== 0) { + _direction.y = (event.deltaY > 0) ? 1 : -1; + } + + if (event.deltaX !== 0) { + _direction.x = (event.deltaX > 0) ? 1 : -1; + } + + _notifyListeners("wheel", event); + } + + /* @type {typeof WheelEventHandler.getAxis} */ + function getAxis(direction = "vertical") { + if (_direction.x === 0 && _direction.y === 0) return 0; + + let result = (() => { + switch (direction) { + case "vertical": + return /** @type {Axis} */(_direction.y); + case "horizontal": + return /** @type {Axis} */(_direction.x); + } + + return 0; + })(); + + return result; + } + + /* @type {typeof WheelEventHandler.isEventAvailable} */ + function isEventAvailable() { + return (typeof window?.WheelEvent) !== "undefined"; + } + + function startListen() { + if (_isListen) return; + + if (window?.WheelEvent) { + document.addEventListener("wheel", _handleWheel, false); + } + + Logger.debug("WheelEventHandler: wheel event listeners [started]"); + + _isListen = true; + } + + function _makeCleanup() { + _cleanInternalListeners(); + _direction = { x: 0, y: 0}; + } + + function stopListen() { + if (!_isListen) return; + + if (window?.WheelEvent) { + document.removeEventListener("wheel", _handleWheel, false); + } + + Logger.debug("WheelEventHandler: wheel event listeners [stoped]"); + + _makeCleanup(); + _isListen = false; + } + + return { + startListen: startListen, + stopListen: stopListen, + getAxis: getAxis, + on: on, + off: off, + isEventAvailable: isEventAvailable + }; +})(); + +// Zero-Clause BSD +// +// Permission to use, copy, modify, and/or distribute this software for +// any purpose with or without fee is hereby granted. +// +// THE SOFTWARE IS PROVIDED “AS IS” AND THE AUTHOR DISCLAIMS ALL +// WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES +// OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE +// FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY +// DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN +// AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +// OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + + +/** + * Singleton module + * @type {SwipeEventHandler} + */ +const SwipeEventHandler = (() => { + let _isListen = false; + + const _pos = { + start: /** @type {Vector2} */ { x: 0, y: 0 }, + end: /** @type {Vector2} */ { x: 0, y: 0 }, + + reset() { + this.start = { x: 0, y: 0 }; + this.end = { x: 0, y: 0 }; + } + }; + + /** @type {{ [key: string]: Array<(event: SwipeEvent) => void | Promise>}} */ + const _listeners = { + swipeStart: [], + swipeEnd: [] + }; + + /** + * Add a listener for a specific event type + * @type {typeof SwipeEventHandler.on} + */ + function on(type, callback) { + if (_listeners[type]) { + _listeners[type].push(callback); + + Logger.debug(`SwipeEventHandler: event listener '${type}' [added]`); + } else { + never(`SwipeEventHandler: unsupported event type: '${type}' (supported types list [${Object.keys(_listeners).join(', ')}])`); + } + } + + /** + * Remove a listener for a specific event type + * @type {typeof SwipeEventHandler.off} + */ + function off(type, callback) { + if (_listeners[type]) { + _listeners[type] = _listeners[type].filter(fn => fn !== callback); + + Logger.debug(`SwipeEventHandler: event listener '${type}' [removed]`); + } + } + + /** Executes all registered listeners for a specified event type. + * @param type {SwipeEventType} + * @param event {SwipeEvent} + */ + async function _notifyListeners(type, event) { + if (_listeners[type]) { + for (const listener of _listeners[type]) { + await listener(event); + } + } + } + + function _cleanInternalListeners() { + Object.keys(_listeners).forEach(key => _listeners[key] = []); + } + + /** + * @param event {SwipeEvent} + * @returns {Promise} + */ + async function _handleSwipeStart(event) { + _pos.reset(); + + if (window?.TouchEvent && event instanceof TouchEvent) { + _pos.start.x = event.changedTouches[0].clientX; + _pos.start.y = event.changedTouches[0].clientY; + } else if (window?.PointerEvent && event instanceof PointerEvent) { + const button = /** @type {MouseButton} */ (event.button); + if (!(constants.MOUSE_SWIPE_DISCARDED_BUTTONS.includes(button))) { + _pos.start.x = event.clientX; + _pos.start.y = event.clientY; + } + Logger.debug("SwipeEventHandler: pressed mouse button code on 'swipeStart': ", [button]); + } + + _notifyListeners("swipeStart", event); + } + + /** + * @param event {SwipeEvent} + * @returns {Promise} + */ + async function _handleSwipeMove(event) { + if (window?.TouchEvent && event instanceof TouchEvent) { + _pos.end.x = event.changedTouches[0].clientX; + _pos.end.y = event.changedTouches[0].clientY; + } else if (window?.PointerEvent && event instanceof PointerEvent) { + const button = /** @type {MouseButton} */ (event.button); + if (!(constants.MOUSE_SWIPE_DISCARDED_BUTTONS.includes(button))) { + _pos.end.x = event.clientX; + _pos.end.y = event.clientY; + } + + Logger.debug("SwipeEventHandler: pressed mouse button code on 'swipeMove': ", [button]); + } + } + + /** + * @param event {SwipeEvent} + * @returns {Promise} + */ + async function _handleSwipeEnd(event) { + if (window?.TouchEvent && event instanceof TouchEvent) { + _pos.end.x = event.changedTouches[0].clientX; + _pos.end.y = event.changedTouches[0].clientY; + } else if (window?.PointerEvent && event instanceof PointerEvent) { + const button = /** @type {MouseButton} */ (event.button); + if (!(constants.MOUSE_SWIPE_DISCARDED_BUTTONS.includes(button))) { + if (event.clientX !== 0 || event.clientY !== 0) { + _pos.end.x = event.clientX; + _pos.end.y = event.clientY; + } + } + + Logger.debug("SwipeEventHandler: pressed mouse button code on 'swipeEnd': ", [button]); + } + + _notifyListeners("swipeEnd", event); + } + + /** @type {typeof SwipeEventHandler.getAxis} */ + function getAxis(direction = "vertical") { + const diffX = _pos.end.x - _pos.start.x; + const diffY = _pos.end.y - _pos.start.y; + + if (diffX === 0 && diffY === 0) return 0; + + if (Math.abs(diffX) > Math.abs(diffY)) { + if (Math.abs(diffX) >= constants.TOUCH_THRESHOLD && direction === "horizontal") { + return (diffX >= 0 ? -1 : 1); + } + } else { + if (Math.abs(diffY) >= constants.TOUCH_THRESHOLD && direction === "vertical") { + return (diffY >= 0 ? -1 : 1); + } + } + + return 0; + } + + /** @type {typeof SwipeEventHandler.isEventAvailable} */ + function isEventAvailable() { + const has_touch = (typeof window?.TouchEvent) !== "undefined"; + const has_pointer = (typeof window?.PointerEvent) !== "undefined"; + + return (has_touch || (has_pointer && settings.scroll.swipeScroll)); + } + + function startListen() { + if (_isListen) return; + + // Add pointer event listeners for non-touch devices if swipe scroll is enabled + if ((window?.PointerEvent && settings.scroll.swipeScroll) && !isTouchDevice()) { + document.addEventListener("pointerdown", _handleSwipeStart, false); + document.addEventListener("pointermove", _handleSwipeMove, false); + document.addEventListener("pointerup", _handleSwipeEnd, false); + document.addEventListener("pointercancel", _handleSwipeEnd, false); + + Logger.debug("SwipeEventHandler: pointer event listeners [started]"); + } + + // Add touch event listeners for touch-enabled devices + if (window?.TouchEvent && isTouchDevice()) { + document.addEventListener("touchstart", _handleSwipeStart, false); + document.addEventListener("touchend", _handleSwipeEnd, false); + document.addEventListener("touchmove", _handleSwipeMove, false); + document.addEventListener("touchcancel", _handleSwipeEnd, false); + + Logger.debug("SwipeEventHandler: touch event listeners [started]"); + } + + _isListen = true; + } + + function _makeCleanup() { + _cleanInternalListeners(); + _pos.reset(); + } + + function stopListen() { + if (!_isListen) return; + + // Remove pointer event listeners for non-touch devices if swipe scroll is enabled + if ((window?.PointerEvent && settings.scroll.swipeScroll) && typeof window?.TouchEvent === "undefined") { + document.removeEventListener("pointerdown", _handleSwipeStart, false); + document.removeEventListener("pointermove", _handleSwipeMove, false); + document.removeEventListener("pointerup", _handleSwipeEnd, false); + document.removeEventListener("pointercancel", _handleSwipeEnd, false); + + Logger.debug("SwipeEventHandler: pointer event listeners [stoped]"); + } + + // Remove touch event listeners for touch-enabled devices + if (window?.TouchEvent) { + document.removeEventListener("touchstart", _handleSwipeStart, false); + document.removeEventListener("touchend", _handleSwipeEnd, false); + document.removeEventListener("touchmove", _handleSwipeMove, false); + document.removeEventListener("touchcancel", _handleSwipeEnd, false); + + Logger.debug("SwipeEventHandler: touch event listeners [stopped]"); + } + + _makeCleanup(); + _isListen = false; + } + + return { + startListen: startListen, + stopListen: stopListen, + getAxis: getAxis, + on: on, + off: off, + isEventAvailable: isEventAvailable + }; +})(); + +// Zero-Clause BSD +// +// Permission to use, copy, modify, and/or distribute this software for +// any purpose with or without fee is hereby granted. +// +// THE SOFTWARE IS PROVIDED “AS IS” AND THE AUTHOR DISCLAIMS ALL +// WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES +// OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE +// FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY +// DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN +// AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +// OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + + +/** + * Singleton module + * @type {KeyEventHandler} + */ +const KeyEventHandler = (() => { + let _isListen = false; + + let _keys = { + lastKey: "", + axisKey: "", // special variable only to hold axis related stuff + + reset() { + this.lastKey = ""; + this.axisKey = ""; + } + }; + + /** + * @type {{ [key: string]: Array<(event: KeyboardEvent) => void | Promise>}} + */ + const _listeners = { + keydown: [], + keyup: [], + }; + + /** + * @param element {Element} + * @param direction {Direction} + * @returns {void} + */ + function scrollWithKeys(element, direction = "vertical") { + if (element.scrollHeight === 0 && element.scrollWidth === 0) return; + + const { speed } = settings.scroll; + + if (direction === "vertical") { + const targetScroll = (getAxis(direction) * speed) + element.scrollTop; + element.scroll({ top: targetScroll }); + } else if (direction === "horizontal") { + const targetScroll = (getAxis(direction) * speed) + element.scrollLeft; + element.scroll({ left: targetScroll }); + } + + Logger.debug(`KeyEventHandler: scroll with keys called with speed '${speed}' on element`, [element]); + } + + /** + * Add a listener for a specific event type + * @type {typeof KeyEventHandler.on} + */ + function on(type, callback) { + if (_listeners[type]) { + _listeners[type].push(callback); + + Logger.debug(`KeyEventHandler: event listener '${type}' [added]`); + } else { + never(`KeyEventHandler: unsupported event type: '${type}' (supported types list [${Object.keys(_listeners).join(', ')}])`); + } + } + + /** + * Remove a listener for a specific event type + * @type {typeof KeyEventHandler.off} + */ + function off(type, callback) { + if (_listeners[type]) { + _listeners[type] = _listeners[type].filter(fn => fn !== callback); + + Logger.debug(`KeyEventHandler: event listener '${type}' [removed]`); + } + } + + /** Executes all registered listeners for a specified event type. + * @param type {KeyEventType} + * @param event {KeyboardEvent} + */ + async function _notifyListeners(type, event) { + if (_listeners[type]) { + for (const listener of _listeners[type]) { + await listener(event); + } + } + } + + function _cleanInternalListeners() { + Object.keys(_listeners).forEach(key => _listeners[key] = []); + } + + /** + * @param event {KeyboardEvent} + * @returns {Promise} + */ + async function _keydownEventHandler(event) { + _keys.lastKey = event.key; + _keys.axisKey = event.key; + + _notifyListeners("keydown", event); + } + + /** + * @param event {KeyboardEvent} + * @returns {Promise} + */ + async function _keyupEventHandler(event) { + if (event.key === _keys.axisKey) { + _keys.axisKey = ""; + } + + _notifyListeners("keyup", event); + } + + /** @type {typeof KeyEventHandler.getAxis } */ + function getAxis(direction = "vertical") { + if (direction === "vertical") { + if (settings.keybindings.up.includes(_keys.axisKey)) return -1; + if (settings.keybindings.down.includes(_keys.axisKey)) return 1; + } else if (direction === "horizontal") { + if (settings.keybindings.right.includes(_keys.axisKey)) return 1; + if (settings.keybindings.left.includes(_keys.axisKey)) return -1; + } + + return 0; + } + + /** @type {typeof KeyEventHandler.isEventAvailable } */ + function isEventAvailable() { + return ((typeof window?.KeyboardEvent) !== "undefined" && settings.scroll.keyboardScroll); + } + + function startListen() { + if (_isListen) return; + + if (window?.KeyboardEvent && settings.scroll.keyboardScroll) { + window.addEventListener("keydown", _keydownEventHandler, false); + window.addEventListener("keydown", _keyupEventHandler, false); + + Logger.debug("KeyEventHandler: key event listeners [started]"); + } + + _isListen = true; + } + + function _makeCleanup() { + _cleanInternalListeners(); + _keys.reset(); + } + + function stopListen() { + if (!_isListen) return; + + if (window?.KeyboardEvent && settings.scroll.keyboardScroll) { + window.removeEventListener("keydown", _keydownEventHandler, false); + window.removeEventListener("keyup", _keyupEventHandler, false); + + Logger.debug("KeyEventHandler: key event listeners [stopped]"); + } + + _makeCleanup(); + _isListen = false; + } + + return { + startListen: startListen, + stopListen: stopListen, + getAxis: getAxis, + on: on, + off: off, + scrollWithKeys: scrollWithKeys, + isEventAvailable: isEventAvailable + }; +})(); + +// Zero-Clause BSD +// +// Permission to use, copy, modify, and/or distribute this software for +// any purpose with or without fee is hereby granted. +// +// THE SOFTWARE IS PROVIDED “AS IS” AND THE AUTHOR DISCLAIMS ALL +// WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES +// OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE +// FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY +// DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN +// AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +// OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + + +const scroll = (() => { + /** @type {Timer} */ + let _scrollTimer; + let _isScrolling = false; + + /** + * @param sections {SectionList} + * @returns {void} + */ + function init(sections) { + if (WheelEventHandler.isEventAvailable()) { + WheelEventHandler.on("wheel", event => _handleScroll(event, sections)); + WheelEventHandler.startListen(); + } + + if (KeyEventHandler.isEventAvailable()) { + KeyEventHandler.on("keydown", event => { + const target = /** @type {Element | null} */ (event.target); + if (target?.tagName !== "INPUT" && target?.tagName !== "TEXTAREA") { + const section = sections.getCurrentSection(); + if (_hasOverflowScroll(section, KeyEventHandler.getAxis("vertical"))) { + // TODO: Make it work well on chrome like browsers + const scrollable = section.elemRef.querySelector("." + classes.overflow); + if (scrollable !== null) { + KeyEventHandler.scrollWithKeys(scrollable, "vertical"); + event.preventDefault(); + } + } else { + _handleScroll(event, sections); + } + } + }); + + KeyEventHandler.startListen(); + } + + if (SwipeEventHandler.isEventAvailable()) { + SwipeEventHandler.on("swipeEnd", event => _handleScroll(event, sections)); + SwipeEventHandler.startListen(); + } + } + + /** + * @param section {Section} + * @param axis {Axis} + * @returns {boolean} */ + function _hasOverflowScroll(section, axis) { + const shouldScrollOnOverflow = () => { + const scrollable = section.elemRef.querySelector("." + classes.overflow); + if (scrollable != null && utils.isElementScrollable(scrollable)) { + if (axis === -1) return !(utils.hasReachedStartOfScroll(scrollable)); + if (axis === 1) return !(utils.hasReachedEndOfScroll(scrollable)); + } + + return false; + }; + + if (settings.scroll.overflowScroll) { + return shouldScrollOnOverflow(); + } + + return false; + } + + /** + * @param event {ScrollEvent} + * @param direction {Direction} + * @returns {0|1|-1} + */ + function _getEventAxis(event, direction) { + if (window?.PointerEvent && event instanceof PointerEvent) { + return SwipeEventHandler.getAxis(direction); + } else if (window?.TouchEvent && event instanceof TouchEvent) { + return SwipeEventHandler.getAxis(direction); + } else if (window?.WheelEvent && event instanceof WheelEvent) { + return WheelEventHandler.getAxis(direction); + } else if (window?.KeyboardEvent && event instanceof KeyboardEvent) { + return KeyEventHandler.getAxis(direction); + } + + return 0; + } + + /** + * @param event {ScrollEvent} + * @param slider {Slider} + * @returns {void} + */ + function _handleSlider(event, slider) { + if (window?.WheelEvent && event instanceof WheelEvent) return; + + const axis = _getEventAxis(event, "horizontal"); + if (axis > 0) { + slider.next(); + } else if (axis < 0) { + slider.prev(); + } + } + + function _lockScroll() { + _isScrolling = true; + if (_scrollTimer) { + clearTimeout(_scrollTimer); + } + + // Unlock scrolling after specified timeout + _scrollTimer = setTimeout(() => { + _isScrolling = false; + }, settings.scroll.unlockTimeout); + } + + + /** + * @param event {ScrollEvent | SwipeEvent} + * @param sections {SectionList} + * @returns {void} + */ + function _handleScroll(event, sections) { + if (_isScrolling) return; + + _lockScroll(); + const currentSection = sections.getCurrentSection(); + if (currentSection.sliderList.length >= 1) { + _handleSlider(event, currentSection.sliderList[0]); + } + + const axis = _getEventAxis(event, "vertical"); + if (axis > 0) { + if (!_hasOverflowScroll(currentSection, 1)) { + sections.scrollNext(); + } + } else if (axis < 0) { + if (!_hasOverflowScroll(currentSection, -1)) { + sections.scrollPrev(); + } + } + } + + return { + init + }; +})(); + +// Zero-Clause BSD +// +// Permission to use, copy, modify, and/or distribute this software for +// any purpose with or without fee is hereby granted. +// +// THE SOFTWARE IS PROVIDED “AS IS” AND THE AUTHOR DISCLAIMS ALL +// WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES +// OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE +// FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY +// DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN +// AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +// OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + + +/** + * @param slideList {NodeListOf} + * @param {Object} [options] + * @param {number} [options.initialIndex=0] - The starting index of the slide to display in the list. + * @returns {Slider} + */ +function Slider(slideList, options = {}) { + const elemRef = _getOrCreateElemRef(); + const slides = Array.from(slideList); + + let _currentIndex = options.initialIndex ?? 0; + + function _getOrCreateElemRef() { + let ctn = slideList.item(0).parentElement; + if (ctn === null || !utils.isSlider(ctn)) { + const ref = utils.wrapChildrenInTag(slideList, "div"); + ctn?.appendChild(ref); + + return ref; + } + + return ctn; + } + + function _addClasses() { + utils.addClassName(elemRef, classes.sliderWrapper); + + slides.forEach(slide => { + utils.addClassName(slide, classes.slide); + }); + } + + /** + * @param elementList {NodeListOf|Array} + * @param index {number} + * @returns {void} + */ + function _scrollIntoView(index, elementList) { + if (index < 0 || index >= elementList.length) return; + + elementList[index].scrollIntoView({ + behavior: settings.scroll.behavior, + block: "start", + inline: "center" + }); + } + + /** @param slideElem {Element} */ + function remove(slideElem) { + if (slides.length <= 0) return; + + const idx = slides.findIndex(slide => slide === slideElem); + if (idx >= 0) { + slides.splice(idx, 1); + } + } + + /** + * @param pos {number} + * @param slideElem {Element} + */ + function insert(pos, slideElem) { + slides.splice(pos, 0, slideElem); + } + + function next() { + _currentIndex = (_currentIndex + 1 + slides.length) % slides.length; + _scrollIntoView(_currentIndex, slides); + } + + function prev() { + _currentIndex = (_currentIndex - 1 + slides.length) % slides.length; + _scrollIntoView(_currentIndex, slides); + } + + /** @param index {number} */ + function goToSlide(index) { + if (index < 0 || index >= slides.length) return; + + _scrollIntoView(_currentIndex, slides); + _currentIndex = index; + } + + /* -------- Init -------- */ + _addClasses(); + + return { + elemRef: elemRef, + slideList: slides, + + insert: insert, + remove: remove, + prev: prev, + next: next, + goToSlide: goToSlide + }; +} + +// Zero-Clause BSD +// +// Permission to use, copy, modify, and/or distribute this software for +// any purpose with or without fee is hereby granted. +// +// THE SOFTWARE IS PROVIDED “AS IS” AND THE AUTHOR DISCLAIMS ALL +// WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES +// OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE +// FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY +// DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN +// AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +// OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + + +/** + * @param section {Element} + * @returns {SliderList} + */ +function SliderList(section) { + /** @type {Array} */ + const sliderList = new Array(); + + (function _init() { + const sliders = utils.getSliderListInElement(section); + const aloneSlides = utils.getSingleSlidesInElementOrNull(section); + if (aloneSlides !== null) { + let shouldAdd = (() => { + for (const idx in aloneSlides) { + const slideParent = aloneSlides[idx].parentElement; + if (slideParent !== null) { + return !utils.isSlider(/** @type {Element} */(slideParent)); + } + } + + return true; + })(); + + if (shouldAdd) { + sliderList.push(Slider(aloneSlides)); + } + } + + sliders.forEach(slider => { + const slides = slider.querySelectorAll("." + settings.constants.SINGLE_SLIDE_CLASS_NAME); + const sliderRef = Slider(slides); + + sliderList.push(sliderRef); + }); + })(); + + /** @param sliderElem {Element} */ + function remove(sliderElem) { + if (sliderList.length <= 0) return; + + const idx = sliderList.findIndex(slider => slider.elemRef === sliderElem); + if (idx >= 0) { + sliderList.splice(idx, 1); + } + } + + /** + * @param pos {number} + * @param sliderElem {Element} + */ + function insert(pos, sliderElem) { + const slides = sliderElem.querySelectorAll("." + settings.constants.SINGLE_SLIDE_CLASS_NAME); + const sliderRef = Slider(slides); + + sliderList.splice(pos, 0, sliderRef); + } + + /** + * @type {ProxyHandler} + */ + const handler = { + /** + * Intercepts property access on the proxy. + * + * @param {SliderList} target - The original slider array. + * @param {string | symbol} prop - The property being accessed. + */ + get(target, prop) { + if (prop in target) { + return target[/** @type {keyof Array}*/(prop)]; + } + + switch (prop) { + case "insert": + return insert; + case "remove": + return remove; + default: + return undefined; + } + }, + }; + + return new Proxy(/** @type {SliderList} */(sliderList), handler); +} + +// Zero-Clause BSD +// +// Permission to use, copy, modify, and/or distribute this software for +// any purpose with or without fee is hereby granted. +// +// THE SOFTWARE IS PROVIDED “AS IS” AND THE AUTHOR DISCLAIMS ALL +// WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES +// OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE +// FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY +// DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN +// AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +// OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + + +/** + * @param section {Element} + * @returns {Section} + */ +function Section(section) { + const elemRef = section; + const sliderList = SliderList(section); + + (function _addClasses() { + utils.addClassName(section, classes.section); + + // create a separated function + if (settings.scroll.overflowScroll) { + const wrapper = utils.wrapAllChildrenOf(section); + utils.addClassName(wrapper, classes.overflow); + } + })(); + + return { + elemRef: elemRef, + sliderList: sliderList, + }; +} + +// Zero-Clause BSD +// +// Permission to use, copy, modify, and/or distribute this software for +// any purpose with or without fee is hereby granted. +// +// THE SOFTWARE IS PROVIDED “AS IS” AND THE AUTHOR DISCLAIMS ALL +// WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES +// OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE +// FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY +// DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN +// AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +// OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + + +/** + * @param sections {NodeListOf} + * @returns {SectionList} + */ +function SectionList(sections) { + /** @type {Array
} */ + const sectionList = new Array(sections.length); + + let _currentIndex = 0; + + (function _init() { + sections.forEach((section, idx) => { + if (section.classList.contains("active")) { + _currentIndex = idx; + } + + const sectionRef = Section(section); + + sectionList[idx] = sectionRef; + }); + + utils.addClassName(sectionList[_currentIndex].elemRef, "active"); + scrollToSection(_currentIndex); + })(); + + function scrollNext() { + const nextIdx = (_currentIndex + 1 + sectionList.length) % sectionList.length; + _scrollIntoView(nextIdx, sectionList); + + _currentIndex = nextIdx; + } + + function scrollPrev() { + const prevIdx = (_currentIndex - 1 + sectionList.length) % sectionList.length; + _scrollIntoView(prevIdx, sectionList); + + _currentIndex = prevIdx; + } + + /** @param index {number} */ + function scrollToSection(index) { + if (index < 0 || index >= sectionList.length) return; + + _scrollIntoView(index, sectionList); + _currentIndex = index; + } + + /** @param sectionElem {Element} */ + function remove(sectionElem) { + if (sectionList.length <= 0) return; + + const idx = sectionList.findIndex(section => section.elemRef === sectionElem); + if (idx >= 0) { + sectionList.splice(idx, 1); + } + } + + /** + * @param pos {number} + * @param sectionElem {Element} + */ + function insert(pos, sectionElem) { + const sectionRef = Section(sectionElem); + + sectionList.splice(pos, 0, sectionRef); + } + + function getCurrentSection() { + return sectionList[_currentIndex]; + } + + /** + * @param sectionList {Array
} + * @param index {number} + * @returns {void} + */ + function _scrollIntoView(index, sectionList) { + if (index < 0 || index >= sectionList.length) return; + + sectionList[index].elemRef.scrollIntoView({ + behavior: settings.scroll.behavior || "smooth", + block: "start", + }); + + utils.removeClassName(sectionList[_currentIndex].elemRef, "active"); + utils.addClassName(sectionList[index].elemRef, "active"); + } + + /** + * @type {ProxyHandler} + */ + const handler = { + /** + * Intercepts property access on the proxy. + * + * @param {SectionList} target - The original slider array. + * @param {string | symbol} prop - The property being accessed. + */ + get(target, prop) { + if (prop in target) { + return target[/** @type {keyof Array}*/(prop)]; + } + + switch (prop) { + case "insert": + return insert; + case "remove": + return remove; + case "scrollNext": + return scrollNext; + case "scrollPrev": + return scrollPrev; + case "scrollToSection": + return scrollToSection; + case "getCurrentSection": + return getCurrentSection; + default: + return undefined; + } + }, + }; + + return new Proxy(/** @type {SectionList} */(sectionList), handler); +} + +// Zero-Clause BSD +// +// Permission to use, copy, modify, and/or distribute this software for +// any purpose with or without fee is hereby granted. +// +// THE SOFTWARE IS PROVIDED “AS IS” AND THE AUTHOR DISCLAIMS ALL +// WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES +// OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE +// FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY +// DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN +// AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +// OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + + +/** + * Singleton module + * @type {DOMLoadEventHandler} + */ +const DOMLoadEventHandler = (() => { + let _isListen = false; + + /** + * @type {{ [key: string]: Array<(event: Event) => void | Promise>}} + */ + const _listeners = { + DOMContentLoaded: [], + load: [], + beforeUnload: [], + unload: [], + }; + + /** + * Add a listener for a specific event type + * @type {typeof DOMLoadEventHandler.on} + */ + function on(type, callback) { + if (_listeners[type]) { + _listeners[type].push(callback); + + Logger.debug(`DOMEventHandler: event listener '${type}' [added]`); + } else { + never(`DOMEventHandler: unsupported event type: '${type}' (supported types list [${Object.keys(_listeners).join(', ')}])`); + } + } + + /** + * Remove a listener for a specific event type + * @type {typeof DOMLoadEventHandler.off} + */ + function off(type, callback) { + if (_listeners[type]) { + _listeners[type] = _listeners[type].filter(fn => fn !== callback); + + Logger.debug(`DOMEventHandler: event listener '${type}' [removed]`); + } + } + + /** Executes all registered listeners for a specified event type. + * @param type {DOMLoadEventType} + * @param event {Event} + */ + async function _notifyListeners(type, event) { + if (_listeners[type]) { + for (const listener of _listeners[type]) { + await listener(event); + } + } + } + + function _cleanInternalListeners() { + Object.keys(_listeners).forEach(key => _listeners[key] = []); + } + + /** + * Handler for the DOMContentLoaded event, triggering "ready" listeners. + * @param {Event} event - The DOMContentLoaded event object. + */ + function _DOMContentLoadedEvent(event) { + _notifyListeners("DOMContentLoaded", event); + } + + /** + * Handler for the load event, triggering "complete" listeners. + * @param {Event} event - The load event object. + */ + function _loadEvent(event) { + _notifyListeners("load", event); + } + + /** + * Handler for the beforeunload event, triggering "beforeExit" listeners. + * @param {Event} event - The beforeunload event object. + */ + function _beforeUnloadEvent(event) { + _notifyListeners("beforeUnload", event); + } + + /** + * Handler for the unload event, triggering "exit" listeners. + * @param {Event} event - The unload event object. + */ + function _unloadEvent(event) { + _notifyListeners("unload", event); + } + + function startListen() { + if (_isListen) return; + + document.addEventListener("DOMContentLoaded", _DOMContentLoadedEvent); + window.addEventListener("load", _loadEvent); + window.addEventListener("beforeunload", _beforeUnloadEvent); + window.addEventListener("unload", _unloadEvent); + + Logger.debug("DOMLoadEventHandler: Listeners [started]"); + + _isListen = true; + } + + function stopListen() { + if (!_isListen) return; + + document.removeEventListener("DOMContentLoaded", _DOMContentLoadedEvent); + window.removeEventListener("load", _loadEvent); + window.removeEventListener("beforeunload", _beforeUnloadEvent); + window.removeEventListener("unload", _unloadEvent); + + Logger.debug("DOMLoadEventHandler: Listeners [stopped]"); + + _cleanInternalListeners(); + _isListen = false; + } + + return { + startListen, + stopListen, + + on: on, + off: off, + }; +})(); + +// Zero-Clause BSD +// +// Permission to use, copy, modify, and/or distribute this software for +// any purpose with or without fee is hereby granted. +// +// THE SOFTWARE IS PROVIDED “AS IS” AND THE AUTHOR DISCLAIMS ALL +// WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES +// OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE +// FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY +// DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN +// AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +// OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + + +/** + * Singleton module + * @type {ObserverEventHandler} + */ +const ObserverEventHandler = (() => { + const _observableClasses = [ + constants.SECTION_CLASS_NAME, + constants.SLIDER_WRAPPER_CLASS_NAME, + constants.SINGLE_SLIDE_CLASS_NAME, + ]; + + /** @type {MutationObserver|null} */ + let _observer = null; + + /** @type {{ [key: string]: Array<(event: ObserverEventObject) => void | Promise>}} */ + const _listeners = { + added: [], + removed: [], + changed: [], + }; + + /** + * Add a listener for a specific event type + * @type {typeof ObserverEventHandler.on} + */ + function on(type, callback) { + if (_listeners[type]) { + _listeners[type].push(callback); + + Logger.debug(`ObserverEventHandler: event listener "${type}" [added]`, [{ callback: callback, total: _listeners[type].length }]); + } else { + never(`ObserverEventHandler: unsupported event type: "${type}" (supported types list [${Object.keys(_listeners).join(", ")}])`); + } + } + + /** + * Remove a listener for a specific event type + * @type {typeof ObserverEventHandler.off} + */ + function off(type, callback) { + if (_listeners[type]) { + _listeners[type] = _listeners[type].filter(fn => fn !== callback); + + Logger.debug(`ObserverEventHandler: event listener "${type}" [removed]`, [{ callback: callback, total: _listeners[type].length }]); + } + } + + /** Executes all registered listeners for a specified event type. + * @param type {ObserverEventType} + * @param event {ObserverEventObject} + */ + async function _notifyListeners(type, event) { + if (_listeners[type]) { + for (const listener of _listeners[type]) { + await listener(event); + } + } + } + + function _cleanInternalListeners() { + Object.keys(_listeners).forEach(key => _listeners[key] = []); + } + + /** + * @param node {Node} + * @returns {Element | null} + */ + function _elementNodeOrNull(node) { + if (node.nodeType === node.ELEMENT_NODE) { + return /** @type {Element} */(node); + } + + return null; + } + + /** + * @param elem {Element} + * @returns {ObserverEventObject} + */ + function _genEventObject(elem) { + if (utils.isSection(elem)) { + return { targetType: "section", element: elem }; + } else if (utils.isSlider(elem)) { + return { targetType: "slider", element: elem }; + } else if (utils.isSlide(elem)) { + return { targetType: "slide", element: elem }; + } + + return { targetType: "other", element: elem }; + } + + /** + * @param elem {Element} + * @returns {boolean} + */ + function _isElementObservable(elem) { + return _observableClasses.some(className => elem.classList.contains(className)); + } + + /** @param mutation {MutationRecord} */ + function _handleChildListMutation(mutation) { + mutation.addedNodes.forEach((node) => { + const elem = _elementNodeOrNull(node); + if (elem !== null && _isElementObservable(elem)) { + _notifyListeners("added", _genEventObject(elem)); + } + }); + + mutation.removedNodes.forEach((node) => { + const elem = _elementNodeOrNull(node); + if (elem !== null && _isElementObservable(elem)) { + _notifyListeners("removed", _genEventObject(elem)); + } + }); + } + + /** @param mutation {MutationRecord} */ + function _handleAttributesMutation(mutation) { + const elem = _elementNodeOrNull(mutation.target); + if (elem !== null && _isElementObservable(elem)) { + if (mutation.attributeName === "class") { + _notifyListeners("changed", _genEventObject(elem)); + } + } + } + + /** @throws {NeverError} - If not root element is founded */ + function startListen() { + if (_observer !== null) return; + + _observer = new MutationObserver((mutationsList) => { + mutationsList.forEach(mutation => { + if (mutation.type === "childList") { + _handleChildListMutation(mutation); + } else if (mutation.type === "attributes") { + _handleAttributesMutation(mutation); + } + }); + }); + + const root = utils.getRootNodeOrThrow(); + _observer.observe(root, { + childList: true, // Watch for added/removed nodes + attributes: true, // Watch for changes to attributes + subtree: true, // Watch all child nodes + attributeFilter: ["class"], // Filter for changes to the "class" attribute + }); + + Logger.debug("ObserverEventHandler: Listeners [started]"); + } + + function _makeCleanup() { + _cleanInternalListeners(); + _observer = null; + } + + function stopListen() { + if (_observer === null) return; + + _observer.disconnect(); + _makeCleanup(); + + Logger.debug("ObserverEventHandler: Listeners [stopped]"); + } + + return { + startListen, + stopListen, + + on: on, + off: off, + }; +})(); + +// Zero-Clause BSD +// +// Permission to use, copy, modify, and/or distribute this software for +// any purpose with or without fee is hereby granted. +// +// THE SOFTWARE IS PROVIDED “AS IS” AND THE AUTHOR DISCLAIMS ALL +// WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES +// OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE +// FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY +// DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN +// AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +// OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + + +/** + * Global + * @type {OnePage} + */ +const Onepage = (() => { + /** @type {SectionList}*/ + let sections; + + /** @type {{ [key: string]: Array<() => void | Promise>}} */ + const _listeners = {}; + + /** + * Add a listener for a specific event type + * @param type {OnePageEventType} + * @param callback {() => void | Promise} + */ + function on(type, callback) { + if (_listeners[type]) { + _listeners[type].push(callback); + + Logger.debug(`OnePageEventHandler: event listener '${type}' [added]`); + } else { + never(`OnePageEventHandler: unsupported event type: '${type}' (supported types list [${Object.keys(_listeners).join(', ')}])`); + } + } + + /** + * Remove a listener for a specific event type + * @param type {OnePageEventType} + * @param callback {() => void | Promise} + */ + function off(type, callback) { + if (_listeners[type]) { + _listeners[type] = _listeners[type].filter(fn => fn !== callback); + + Logger.debug(`OnePageEventHandler: event listener '${type}' [removed]`); + } + } + + /** + * @throws {NeverError} + * @returns {NodeListOf} + */ + function _getSectionNodesOrThrow() { + const root = utils.getRootNodeOrThrow(); + + const sections = root.querySelectorAll("." + settings.constants.SECTION_CLASS_NAME); + return sections; + } + + // TODO: Improve observable stuff + function _attachObserver() { + ObserverEventHandler.on("removed", event => { + if (event.targetType !== "slider") return; + + sections.forEach(section => { + const slider = section.sliderList.find(slider => slider.elemRef === event.element); + if (slider) { + section.sliderList.remove(event.element); + } + }); + }); + + ObserverEventHandler.on("added", event => { + if (event.targetType !== "slider") return; + + const parentSection = utils.sectionParentOrNull(event.element); + sections.forEach(section => { + if (section.elemRef !== parentSection) return; + + let insertPos = 0; + if (section.sliderList.length >= 1) { + insertPos = section.sliderList.findIndex(slider => { + return (utils.isElementBeforeOrAfter(event.element, slider.elemRef) === "after"); + }); + } + + if (insertPos !== -1) { + section.sliderList.insert(insertPos, event.element); + utils.addClassName(event.element, classes.sliderWrapper); + } + }); + }); + + ObserverEventHandler.startListen(); + } + + (function _init() { + DOMLoadEventHandler.startListen(); + DOMLoadEventHandler.on("DOMContentLoaded", async () => { + createHeadStyles(); + injectStylesOrThrow(); + + if (settings.observer.enabled) { + _attachObserver(); + } + + const nodes = _getSectionNodesOrThrow(); + if (!(nodes instanceof Error)) { + sections = SectionList(nodes); + scroll.init(sections); + + Logger.info(`OnePage: has been initialized correctly`); + } + + }); + }()); + + return { + setOptions: setOptions, + + on: on, + off: off + }; +})(); + +export { Onepage as default }; diff --git a/dist/onepage.esm.min.js b/dist/onepage.esm.min.js new file mode 100644 index 0000000..b1fcb22 --- /dev/null +++ b/dist/onepage.esm.min.js @@ -0,0 +1,111 @@ +const e={CLASS_NAME_PREFIX:"op",SECTION_CLASS_NAME:"section",ROOT_ID_NAME:"onepage",SLIDER_WRAPPER_CLASS_NAME:"slider-ctn",SINGLE_SLIDE_CLASS_NAME:"slide",TOUCH_THRESHOLD:30,MOUSE_SWIPE_DISCARDED_BUTTONS:[2]};class n extends Error{constructor(e){super(e),this.name="NeverError"}}function t(e,...t){throw console.error("%c[FATAL ERROR]","color: red;",e,...t),new n(e)}function r(e,n){for(const t in n)n.hasOwnProperty(t)&&(n[t]instanceof Object&&e[t]instanceof Object?e[t]=r(e[t],n[t]):e[t]=n[t]);return e}function o(){return window.matchMedia&&window.matchMedia("(pointer: coarse)").matches}var i={toKebabCase:function(e){return e.replace(/_/g,"-").replace(/([a-z])([A-Z])/g,"$1-$2").toLowerCase()},addClassName:function(e,n){e.classList.add(n)},removeClassName:function(e,n){e.classList.remove(n)},isElementScrollable:function(e){const n=e.scrollHeight>e.clientHeight,t=e.scrollWidth>e.clientWidth;return!(!n&&!t)},isParentElementScrollable:function(e,n){let t=e.parentElement;for(let e=n;e<=n&&null!=t;){const r=t.scrollHeight>t.clientHeight,o=t.scrollWidth>t.clientWidth;if(r||o)return!0;t=t?.parentElement,-1!==n&&(e+=1)}return!1},tryToGetScrollableParentElement:function(e,n=0){let t=e.parentElement;for(let e=n;e<=n&&null!=t;){const r=t.scrollHeight>t.clientHeight,o=t.scrollWidth>t.clientWidth;if(r||o)return t;t=t?.parentElement,-1!==n&&(e+=1)}return null},hasReachedEndOfScroll:function(e,n="vertical"){switch(n.toLowerCase()){case"vertical":return e.scrollTop+e.clientHeight+1>=e.scrollHeight;case"horizontal":return e.scrollLeft+e.clientWidth+1>=e.scrollWidth}return!1},hasReachedStartOfScroll:function(e,n="vertical"){switch(n.toLowerCase()){case"vertical":return e.scrollTop<=1;case"horizontal":return e.scrollLeft<=1}return!1},wrapAllChildrenOf:function(e,n="div"){const t=document.createElement(n);for(;e.firstChild;)t.appendChild(e.firstChild);return e.appendChild(t),t},wrapChildrenInTag:function(e,n="div"){const t=document.createElement(n);return e.forEach((e=>{t.appendChild(e)})),t},slideParentCtnOrNull:function(n,t=0){let r=n.parentElement;for(let n=t;n<=t&&null!=r;){if(r.classList.contains(e.SLIDER_WRAPPER_CLASS_NAME))return r;r=r?.parentElement,-1!==t&&(n+=1)}return null},sectionParentOrNull:function(n,t=0){let r=n.parentElement;for(let n=t;n<=t&&null!=r;){if(r.classList.contains(e.SECTION_CLASS_NAME))return r;r=r?.parentElement,-1!==t&&(n+=1)}return null},getSliderListInElement:function(n){return n.querySelectorAll("."+e.SLIDER_WRAPPER_CLASS_NAME)},getSingleSlidesInElementOrNull:function(n){const t=n.querySelectorAll("."+e.SINGLE_SLIDE_CLASS_NAME);return t.length>=1?t:null},getRootNodeOrThrow:function(){const n=document.getElementById("#"+e.ROOT_ID_NAME);return null==n&&t(`${e.ROOT_ID_NAME} element not founded in DOOM`),n},deepMerge:r,generateRandomString:function(e=16){const n="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";let t="";for(let r=0;re.replace(/^\.\s*|\s*{\s*$/g,""))):[]},addPrefixToClassNames:function(e,n){return e.replace(/\.([a-zA-Z0-9_-]+)(?=\s*{|\s*:)/g,((e,t)=>`.${n}-${t}`))},isSection:function(n){return n.classList.contains(e.SECTION_CLASS_NAME)},isSlide:function(n){return n.classList.contains(e.SINGLE_SLIDE_CLASS_NAME)},isSlider:function(n){return n.classList.contains(e.SLIDER_WRAPPER_CLASS_NAME)},isElementBeforeOrAfter:function(e,n){if(e===n)return"same";if(e.parentElement!==n.parentElement)return-1;const t=e.compareDocumentPosition(n);return t&Node.DOCUMENT_POSITION_FOLLOWING?"before":t&Node.DOCUMENT_POSITION_PRECEDING?"after":-1},isTouchDevice:o,inlineString:function(e,n=1){const t=e.replace(/[\r\n]+/g," "),r=new RegExp(` {${n+1},}`,"g");return t.replace(r," ".repeat(n)).trim()},debounce:function(e,n){let t;return(...r)=>{clearTimeout(t),t=setTimeout((()=>e(...r)),n)}}};const l={constants:e,logger:{enabled:!1,levels:["ALL"]},observer:{enabled:!0},scroll:{unlockTimeout:300,keyboardScroll:!0,swipeScroll:!1,overflowScroll:!1,speed:69,direction:"vertical",behavior:"smooth"},section:{},slider:{},keybindings:{up:["ArrowUp","k","PageUp"],down:["ArrowDown","j","PageDown"],left:["ArrowLeft","h"],right:["ArrowRight","l"]}};function s(e,n){const r={"constants.CLASS_NAME_PREFIX":"string","constants.SECTION_CLASS_NAME":"string","constants.ROOT_ID_NAME":"string","constants.SLIDER_WRAPPER_CLASS_NAME":"string","constants.SINGLE_SLIDE_CLASS_NAME":"string","constants.TOUCH_THRESHOLD":"number","constants.MOUSE_SWIPE_DISCARDED_BUTTONS":"array","logger.enabled":"boolean","logger.levels":"array","observer.enabled":"boolean","scroll.unlockTimeout":"number","scroll.keyboardScroll":"boolean","scroll.swipeScroll":"boolean","scroll.overflowScroll":"boolean","scroll.speed":"number","scroll.direction":"string","scroll.behavior":"string","keybindings.up":"array","keybindings.down":"array","keybindings.left":"array","keybindings.right":"array"};r.hasOwnProperty(e)||t("[settings] Invalid Key");const o=r[e];o&&("array"!==o||Array.isArray(n)?typeof n!==o&&"array"!==o&&t(`[settings] Invalid type for ${e}. Expected ${o}.`):t(`[settings] Invalid type for ${e}. Expected an array.`))}function c(e){!function e(n,t,r=""){for(const o in t){const i=r?`${r}.${o}`:o,l=t[o];"object"!=typeof l||null===l||Array.isArray(l)?(s(i,l),n[o]=l):(n[o]||(n[o]={}),e(n[o],l,i))}}(l,e),r(l,e)}let a="";function d(e,...n){let t=e.reduce(((e,t,r)=>e+t+(n[r]?n[r]:"")),""),r=i.inlineString(t,1);const o=i.extractClassName(r),l=o?o[0]:i.generateRandomString();return a+=o?r:`.${l} {${r}}`,l}function u(){!function(e){const n=document.documentElement,t=document.getElementsByTagName("body")[0];i.addClassName(n,f.html),i.addClassName(t,f.html),"horizontal"===l.scroll.direction.toLowerCase()?i.addClassName(e,f.horizontal):i.addClassName(e,f.vertical)}(i.getRootNodeOrThrow())}const f=(()=>{const n={html:d` + .document { + overflow: hidden; + scroll-behavior: smooth; + + margin: 0; + padding: 0; + } + `,vertical:d` + .vertical { + display: flex; + flex-direction: column; + } + `,horizontal:d` + .horizontal { + display: flex; + flex-direction: row; + } + `,section:d` + .section { + display: flex; + align-items: center; + justify-content: center; + + max-height: 100vh; + min-height: 100vh; + + max-width: 100vw; + min-width: 100vw; + + box-sizing: border-box; + -webkit-box-sizing: border-box; + } + `,overflow:d` + .overflow { + overflow: auto; + + max-height: 100vh; + + /* Hide Scrollbar */ + -ms-overflow-style: none; /* [IE And Edge] */ + scrollbar-width: none; /* [Firefox] */ + + /* [Chrome, Safari And Opera] */ + ::-webkit-scrollbar { display: none; } + } + `,sliderWrapper:d` + .slider-wrapper { + z-index: 1; + overflow: hidden; + position: relative; + display: flex; + flex-direction: row; + + transition: all 0.3s ease-out; + -webkit-transition: all 0.3s ease-out; /* [Safari <= 6, Android <= 4.3] */ + + max-height: 100vh; + max-width: 100vw; + } + `,slide:d` + .slide { + display: flex; + justify-content: center; + align-items: center; + + min-height: 100vh; + min-width: 100vw; + } + `,sectionPagination:d` + .section-pagination { + position: fixed; + display: flex; + flex-direction: column; + gap: 0.6rem; + border-radius: 0.3rem; + right: 1rem; + top: 50%; + z-index: 2; + list-style: none; + + margin: 0; + padding: 0.3rem; + + li { + padding: 0; + text-align: center; + } + + li > a { + display: flex; + width: 1rem; + height: 1rem; + } + + li > a:before { + content: ''; + position: absolute; + background: rgba(0, 0, 0, 0.69); + border-radius: 1rem; + width: 1rem; + height: 1rem; + } + + li > a.active:before { + background: rgba(0, 0, 0, 0.9); + width: 1.2rem; + height: 1.2rem; + } + } + `};return new Proxy(n,{get(n,t){if(t in n){const r=n[t];return`${e.CLASS_NAME_PREFIX}-${r}`}}})})(),v=(()=>{const e=Object.freeze({DEBUG:1,INFO:2,WARN:4,ERROR:8,ALL:15});let n=(()=>{let n=0;for(const t of l.logger.levels)Object.hasOwn(e,t)&&n{let e=!1,n={x:0,y:0};const r={wheel:[]};function o(e){n.y=0,n.x=0,0!==e.deltaY&&(n.y=e.deltaY>0?1:-1),0!==e.deltaX&&(n.x=e.deltaX>0?1:-1),async function(e,n){if(r[e])for(const t of r[e])await t(n)}("wheel",e)}function i(){Object.keys(r).forEach((e=>r[e]=[])),n={x:0,y:0}}return{startListen:function(){e||(window?.WheelEvent&&document.addEventListener("wheel",o,!1),v.debug("WheelEventHandler: wheel event listeners [started]"),e=!0)},stopListen:function(){e&&(window?.WheelEvent&&document.removeEventListener("wheel",o,!1),v.debug("WheelEventHandler: wheel event listeners [stoped]"),i(),e=!1)},getAxis:function(e="vertical"){return 0===n.x&&0===n.y?0:(()=>{switch(e){case"vertical":return n.y;case"horizontal":return n.x}return 0})()},on:function(e,n){r[e]?(r[e].push(n),v.debug(`WheelEventHandler: event listener '${e}' [added]`)):t(`WheelEventHandler: unsupported event type: '${e}' (supported types list [${Object.keys(r).join(", ")}])`)},off:function(e,n){r[e]&&(r[e]=r[e].filter((e=>e!==n)),v.debug(`WheelEventHandler: event listener '${e}' [removed]`))},isEventAvailable:function(){return void 0!==window?.WheelEvent}}})(),h=(()=>{let n=!1;const r={start:{x:0,y:0},end:{x:0,y:0},reset(){this.start={x:0,y:0},this.end={x:0,y:0}}},i={swipeStart:[],swipeEnd:[]};async function s(e,n){if(i[e])for(const t of i[e])await t(n)}async function c(n){if(r.reset(),window?.TouchEvent&&n instanceof TouchEvent)r.start.x=n.changedTouches[0].clientX,r.start.y=n.changedTouches[0].clientY;else if(window?.PointerEvent&&n instanceof PointerEvent){const t=n.button;e.MOUSE_SWIPE_DISCARDED_BUTTONS.includes(t)||(r.start.x=n.clientX,r.start.y=n.clientY),v.debug("SwipeEventHandler: pressed mouse button code on 'swipeStart': ",[t])}s("swipeStart",n)}async function a(n){if(window?.TouchEvent&&n instanceof TouchEvent)r.end.x=n.changedTouches[0].clientX,r.end.y=n.changedTouches[0].clientY;else if(window?.PointerEvent&&n instanceof PointerEvent){const t=n.button;e.MOUSE_SWIPE_DISCARDED_BUTTONS.includes(t)||(r.end.x=n.clientX,r.end.y=n.clientY),v.debug("SwipeEventHandler: pressed mouse button code on 'swipeMove': ",[t])}}async function d(n){if(window?.TouchEvent&&n instanceof TouchEvent)r.end.x=n.changedTouches[0].clientX,r.end.y=n.changedTouches[0].clientY;else if(window?.PointerEvent&&n instanceof PointerEvent){const t=n.button;e.MOUSE_SWIPE_DISCARDED_BUTTONS.includes(t)||0===n.clientX&&0===n.clientY||(r.end.x=n.clientX,r.end.y=n.clientY),v.debug("SwipeEventHandler: pressed mouse button code on 'swipeEnd': ",[t])}s("swipeEnd",n)}function u(){Object.keys(i).forEach((e=>i[e]=[])),r.reset()}return{startListen:function(){n||(window?.PointerEvent&&l.scroll.swipeScroll&&!o()&&(document.addEventListener("pointerdown",c,!1),document.addEventListener("pointermove",a,!1),document.addEventListener("pointerup",d,!1),document.addEventListener("pointercancel",d,!1),v.debug("SwipeEventHandler: pointer event listeners [started]")),window?.TouchEvent&&o()&&(document.addEventListener("touchstart",c,!1),document.addEventListener("touchend",d,!1),document.addEventListener("touchmove",a,!1),document.addEventListener("touchcancel",d,!1),v.debug("SwipeEventHandler: touch event listeners [started]")),n=!0)},stopListen:function(){n&&(window?.PointerEvent&&l.scroll.swipeScroll&&void 0===window?.TouchEvent&&(document.removeEventListener("pointerdown",c,!1),document.removeEventListener("pointermove",a,!1),document.removeEventListener("pointerup",d,!1),document.removeEventListener("pointercancel",d,!1),v.debug("SwipeEventHandler: pointer event listeners [stoped]")),window?.TouchEvent&&(document.removeEventListener("touchstart",c,!1),document.removeEventListener("touchend",d,!1),document.removeEventListener("touchmove",a,!1),document.removeEventListener("touchcancel",d,!1),v.debug("SwipeEventHandler: touch event listeners [stopped]")),u(),n=!1)},getAxis:function(n="vertical"){const t=r.end.x-r.start.x,o=r.end.y-r.start.y;if(0===t&&0===o)return 0;if(Math.abs(t)>Math.abs(o)){if(Math.abs(t)>=e.TOUCH_THRESHOLD&&"horizontal"===n)return t>=0?-1:1}else if(Math.abs(o)>=e.TOUCH_THRESHOLD&&"vertical"===n)return o>=0?-1:1;return 0},on:function(e,n){i[e]?(i[e].push(n),v.debug(`SwipeEventHandler: event listener '${e}' [added]`)):t(`SwipeEventHandler: unsupported event type: '${e}' (supported types list [${Object.keys(i).join(", ")}])`)},off:function(e,n){i[e]&&(i[e]=i[e].filter((e=>e!==n)),v.debug(`SwipeEventHandler: event listener '${e}' [removed]`))},isEventAvailable:function(){const e=void 0!==window?.TouchEvent,n=void 0!==window?.PointerEvent;return e||n&&l.scroll.swipeScroll}}})(),p=(()=>{let e=!1,n={lastKey:"",axisKey:"",reset(){this.lastKey="",this.axisKey=""}};const r={keydown:[],keyup:[]};async function o(e,n){if(r[e])for(const t of r[e])await t(n)}async function i(e){n.lastKey=e.key,n.axisKey=e.key,o("keydown",e)}async function s(e){e.key===n.axisKey&&(n.axisKey=""),o("keyup",e)}function c(e="vertical"){if("vertical"===e){if(l.keybindings.up.includes(n.axisKey))return-1;if(l.keybindings.down.includes(n.axisKey))return 1}else if("horizontal"===e){if(l.keybindings.right.includes(n.axisKey))return 1;if(l.keybindings.left.includes(n.axisKey))return-1}return 0}function a(){Object.keys(r).forEach((e=>r[e]=[])),n.reset()}return{startListen:function(){e||(window?.KeyboardEvent&&l.scroll.keyboardScroll&&(window.addEventListener("keydown",i,!1),window.addEventListener("keydown",s,!1),v.debug("KeyEventHandler: key event listeners [started]")),e=!0)},stopListen:function(){e&&(window?.KeyboardEvent&&l.scroll.keyboardScroll&&(window.removeEventListener("keydown",i,!1),window.removeEventListener("keyup",s,!1),v.debug("KeyEventHandler: key event listeners [stopped]")),a(),e=!1)},getAxis:c,on:function(e,n){r[e]?(r[e].push(n),v.debug(`KeyEventHandler: event listener '${e}' [added]`)):t(`KeyEventHandler: unsupported event type: '${e}' (supported types list [${Object.keys(r).join(", ")}])`)},off:function(e,n){r[e]&&(r[e]=r[e].filter((e=>e!==n)),v.debug(`KeyEventHandler: event listener '${e}' [removed]`))},scrollWithKeys:function(e,n="vertical"){if(0===e.scrollHeight&&0===e.scrollWidth)return;const{speed:t}=l.scroll;if("vertical"===n){const r=c(n)*t+e.scrollTop;e.scroll({top:r})}else if("horizontal"===n){const r=c(n)*t+e.scrollLeft;e.scroll({left:r})}v.debug(`KeyEventHandler: scroll with keys called with speed '${t}' on element`,[e])},isEventAvailable:function(){return void 0!==window?.KeyboardEvent&&l.scroll.keyboardScroll}}})(),m=(()=>{let e,n=!1;function t(e,n){return!!l.scroll.overflowScroll&&(()=>{const t=e.elemRef.querySelector("."+f.overflow);if(null!=t&&i.isElementScrollable(t)){if(-1===n)return!i.hasReachedStartOfScroll(t);if(1===n)return!i.hasReachedEndOfScroll(t)}return!1})()}function r(e,n){return window?.PointerEvent&&e instanceof PointerEvent||window?.TouchEvent&&e instanceof TouchEvent?h.getAxis(n):window?.WheelEvent&&e instanceof WheelEvent?E.getAxis(n):window?.KeyboardEvent&&e instanceof KeyboardEvent?p.getAxis(n):0}function o(o,i){if(n)return;n=!0,e&&clearTimeout(e),e=setTimeout((()=>{n=!1}),l.scroll.unlockTimeout);const s=i.getCurrentSection();s.sliderList.length>=1&&function(e,n){if(window?.WheelEvent&&e instanceof WheelEvent)return;const t=r(e,"horizontal");t>0?n.next():t<0&&n.prev()}(o,s.sliderList[0]);const c=r(o,"vertical");c>0?t(s,1)||i.scrollNext():c<0&&(t(s,-1)||i.scrollPrev())}return{init:function(e){E.isEventAvailable()&&(E.on("wheel",(n=>o(n,e))),E.startListen()),p.isEventAvailable()&&(p.on("keydown",(n=>{const r=n.target;if("INPUT"!==r?.tagName&&"TEXTAREA"!==r?.tagName){const r=e.getCurrentSection();if(t(r,p.getAxis("vertical"))){const e=r.elemRef.querySelector("."+f.overflow);null!==e&&(p.scrollWithKeys(e,"vertical"),n.preventDefault())}else o(n,e)}})),p.startListen()),h.isEventAvailable()&&(h.on("swipeEnd",(n=>o(n,e))),h.startListen())}}})();function g(e,n={}){const t=function(){let n=e.item(0).parentElement;if(null===n||!i.isSlider(n)){const t=i.wrapChildrenInTag(e,"div");return n?.appendChild(t),t}return n}(),r=Array.from(e);let o=n.initialIndex??0;function s(e,n){e<0||e>=n.length||n[e].scrollIntoView({behavior:l.scroll.behavior,block:"start",inline:"center"})}return i.addClassName(t,f.sliderWrapper),r.forEach((e=>{i.addClassName(e,f.slide)})),{elemRef:t,slideList:r,insert:function(e,n){r.splice(e,0,n)},remove:function(e){if(r.length<=0)return;const n=r.findIndex((n=>n===e));n>=0&&r.splice(n,1)},prev:function(){o=(o-1+r.length)%r.length,s(o,r)},next:function(){o=(o+1+r.length)%r.length,s(o,r)},goToSlide:function(e){e<0||e>=r.length||(s(o,r),o=e)}}}function w(e){const n=e,t=function(e){const n=new Array;function t(e){if(n.length<=0)return;const t=n.findIndex((n=>n.elemRef===e));t>=0&&n.splice(t,1)}function r(e,t){const r=g(t.querySelectorAll("."+l.constants.SINGLE_SLIDE_CLASS_NAME));n.splice(e,0,r)}return function(){const t=i.getSliderListInElement(e),r=i.getSingleSlidesInElementOrNull(e);null!==r&&(()=>{for(const e in r){const n=r[e].parentElement;if(null!==n)return!i.isSlider(n)}return!0})()&&n.push(g(r)),t.forEach((e=>{const t=g(e.querySelectorAll("."+l.constants.SINGLE_SLIDE_CLASS_NAME));n.push(t)}))}(),new Proxy(n,{get(e,n){if(n in e)return e[n];switch(n){case"insert":return r;case"remove":return t;default:return}}})}(e);return function(){if(i.addClassName(e,f.section),l.scroll.overflowScroll){const n=i.wrapAllChildrenOf(e);i.addClassName(n,f.overflow)}}(),{elemRef:n,sliderList:t}}const S=(()=>{let e=!1;const n={DOMContentLoaded:[],load:[],beforeUnload:[],unload:[]};async function r(e,t){if(n[e])for(const r of n[e])await r(t)}function o(e){r("DOMContentLoaded",e)}function i(e){r("load",e)}function l(e){r("beforeUnload",e)}function s(e){r("unload",e)}return{startListen:function(){e||(document.addEventListener("DOMContentLoaded",o),window.addEventListener("load",i),window.addEventListener("beforeunload",l),window.addEventListener("unload",s),v.debug("DOMLoadEventHandler: Listeners [started]"),e=!0)},stopListen:function(){e&&(document.removeEventListener("DOMContentLoaded",o),window.removeEventListener("load",i),window.removeEventListener("beforeunload",l),window.removeEventListener("unload",s),v.debug("DOMLoadEventHandler: Listeners [stopped]"),Object.keys(n).forEach((e=>n[e]=[])),e=!1)},on:function(e,r){n[e]?(n[e].push(r),v.debug(`DOMEventHandler: event listener '${e}' [added]`)):t(`DOMEventHandler: unsupported event type: '${e}' (supported types list [${Object.keys(n).join(", ")}])`)},off:function(e,t){n[e]&&(n[e]=n[e].filter((e=>e!==t)),v.debug(`DOMEventHandler: event listener '${e}' [removed]`))}}})(),y=(()=>{const n=[e.SECTION_CLASS_NAME,e.SLIDER_WRAPPER_CLASS_NAME,e.SINGLE_SLIDE_CLASS_NAME];let r=null;const o={added:[],removed:[],changed:[]};async function l(e,n){if(o[e])for(const t of o[e])await t(n)}function s(e){return e.nodeType===e.ELEMENT_NODE?e:null}function c(e){return i.isSection(e)?{targetType:"section",element:e}:i.isSlider(e)?{targetType:"slider",element:e}:i.isSlide(e)?{targetType:"slide",element:e}:{targetType:"other",element:e}}function a(e){return n.some((n=>e.classList.contains(n)))}function d(){Object.keys(o).forEach((e=>o[e]=[])),r=null}return{startListen:function(){if(null!==r)return;r=new MutationObserver((e=>{e.forEach((e=>{"childList"===e.type?function(e){e.addedNodes.forEach((e=>{const n=s(e);null!==n&&a(n)&&l("added",c(n))})),e.removedNodes.forEach((e=>{const n=s(e);null!==n&&a(n)&&l("removed",c(n))}))}(e):"attributes"===e.type&&function(e){const n=s(e.target);null!==n&&a(n)&&"class"===e.attributeName&&l("changed",c(n))}(e)}))}));const e=i.getRootNodeOrThrow();r.observe(e,{childList:!0,attributes:!0,subtree:!0,attributeFilter:["class"]}),v.debug("ObserverEventHandler: Listeners [started]")},stopListen:function(){null!==r&&(r.disconnect(),d(),v.debug("ObserverEventHandler: Listeners [stopped]"))},on:function(e,n){o[e]?(o[e].push(n),v.debug(`ObserverEventHandler: event listener "${e}" [added]`,[{callback:n,total:o[e].length}])):t(`ObserverEventHandler: unsupported event type: "${e}" (supported types list [${Object.keys(o).join(", ")}])`)},off:function(e,n){o[e]&&(o[e]=o[e].filter((e=>e!==n)),v.debug(`ObserverEventHandler: event listener "${e}" [removed]`,[{callback:n,total:o[e].length}]))}}})(),b=(()=>{let n;const r={};return S.startListen(),S.on("DOMContentLoaded",(async()=>{!function(){const n=``;document.head.insertAdjacentHTML("beforeend",n)}(),u(),l.observer.enabled&&(y.on("removed",(e=>{"slider"===e.targetType&&n.forEach((n=>{n.sliderList.find((n=>n.elemRef===e.element))&&n.sliderList.remove(e.element)}))})),y.on("added",(e=>{if("slider"!==e.targetType)return;const t=i.sectionParentOrNull(e.element);n.forEach((n=>{if(n.elemRef!==t)return;let r=0;n.sliderList.length>=1&&(r=n.sliderList.findIndex((n=>"after"===i.isElementBeforeOrAfter(e.element,n.elemRef)))),-1!==r&&(n.sliderList.insert(r,e.element),i.addClassName(e.element,f.sliderWrapper))}))})),y.startListen());const t=i.getRootNodeOrThrow().querySelectorAll("."+l.constants.SECTION_CLASS_NAME);t instanceof Error||(n=function(e){const n=new Array(e.length);let t=0;function r(){const e=(t+1+n.length)%n.length;u(e,n),t=e}function o(){const e=(t-1+n.length)%n.length;u(e,n),t=e}function s(e){e<0||e>=n.length||(u(e,n),t=e)}function c(e){if(n.length<=0)return;const t=n.findIndex((n=>n.elemRef===e));t>=0&&n.splice(t,1)}function a(e,t){const r=w(t);n.splice(e,0,r)}function d(){return n[t]}function u(e,n){e<0||e>=n.length||(n[e].elemRef.scrollIntoView({behavior:l.scroll.behavior||"smooth",block:"start"}),i.removeClassName(n[t].elemRef,"active"),i.addClassName(n[e].elemRef,"active"))}return e.forEach(((e,r)=>{e.classList.contains("active")&&(t=r);const o=w(e);n[r]=o})),i.addClassName(n[t].elemRef,"active"),s(t),new Proxy(n,{get(e,n){if(n in e)return e[n];switch(n){case"insert":return a;case"remove":return c;case"scrollNext":return r;case"scrollPrev":return o;case"scrollToSection":return s;case"getCurrentSection":return d;default:return}}})}(t),m.init(n),v.info("OnePage: has been initialized correctly"))})),{setOptions:c,on:function(e,n){r[e]?(r[e].push(n),v.debug(`OnePageEventHandler: event listener '${e}' [added]`)):t(`OnePageEventHandler: unsupported event type: '${e}' (supported types list [${Object.keys(r).join(", ")}])`)},off:function(e,n){r[e]&&(r[e]=r[e].filter((e=>e!==n)),v.debug(`OnePageEventHandler: event listener '${e}' [removed]`))}}})();export{b as default}; \ No newline at end of file diff --git a/dist/onepage.js b/dist/onepage.js index 7db5b19..06eed1d 100644 --- a/dist/onepage.js +++ b/dist/onepage.js @@ -129,7 +129,7 @@ var Onepage = (function () { function isParentElementScrollable(element, depth) { let parent = element.parentElement; - for (let level = depth; level <= depth; level += 1) { + for (let level = depth; level <= depth;) { if (parent == null) break; const hasVerticalScroll = parent.scrollHeight > parent.clientHeight; @@ -141,7 +141,9 @@ var Onepage = (function () { parent = parent?.parentElement; // Keep Iterating Until There Is No More Parents Elements - if (depth === -1) level = depth; + if (depth !== -1) { + level += 1; + } } return false; @@ -171,7 +173,7 @@ var Onepage = (function () { function tryToGetScrollableParentElement(element, depth = 0) { let parent = element.parentElement; - for (let level = depth; level <= depth; level += 1) { + for (let level = depth; level <= depth;) { if (parent == null) break; const hasVerticalScroll = parent.scrollHeight > parent.clientHeight; @@ -183,7 +185,9 @@ var Onepage = (function () { parent = parent?.parentElement; // Keep Iterating Until There Is No More Parents Elements - if (depth === -1) level = depth; + if (depth !== -1) { + level += 1; + } } return null; @@ -197,7 +201,7 @@ var Onepage = (function () { function slideParentCtnOrNull(slide, depth = 0) { let parent = slide.parentElement; - for (let level = depth; level <= depth; level += 1) { + for (let level = depth; level <= depth;) { if (parent == null) break; if (parent.classList.contains(constants.SLIDER_WRAPPER_CLASS_NAME)) { @@ -207,7 +211,9 @@ var Onepage = (function () { parent = parent?.parentElement; // Keep Iterating Until There Is No More Parents Elements - if (depth === -1) level = depth; + if (depth !== -1) { + level += 1; + } } return null; @@ -221,7 +227,7 @@ var Onepage = (function () { function sectionParentOrNull(element, depth = 0) { let parent = element.parentElement; - for (let level = depth; level <= depth; level += 1) { + for (let level = depth; level <= depth;) { if (parent == null) break; if (parent.classList.contains(constants.SECTION_CLASS_NAME)) { @@ -231,7 +237,10 @@ var Onepage = (function () { parent = parent?.parentElement; // Keep Iterating Until There Is No More Parents Elements - if (depth === -1) level = depth; + if (depth !== -1) { + level += 1; + } + } return null; @@ -243,11 +252,13 @@ var Onepage = (function () { * @returns {boolean} */ function hasReachedEndOfScroll(element, direction = "vertical") { + const tolerance = 1; // Allow a small margin for rounding errors + switch (direction.toLowerCase()) { case "vertical": - return ((element.scrollTop + element.clientHeight) >= element.scrollHeight); + return ((element.scrollTop + element.clientHeight + tolerance) >= element.scrollHeight); case "horizontal": - return ((element.scrollLeft + element.clientWidth) >= element.scrollWidth); + return ((element.scrollLeft + element.clientWidth + tolerance) >= element.scrollWidth); } return false; @@ -259,11 +270,13 @@ var Onepage = (function () { * @returns {boolean} */ function hasReachedStartOfScroll(element, direction = "vertical") { + const tolerance = 1; // Allow a small margin for rounding errors + switch (direction.toLowerCase()) { case "vertical": - return (element.scrollTop === 0); + return (element.scrollTop <= tolerance); case "horizontal": - return (element.scrollLeft === 0); + return (element.scrollLeft <= tolerance); } return false; @@ -361,9 +374,9 @@ var Onepage = (function () { if (source.hasOwnProperty(key)) { if (source[key] instanceof Object && target[key] instanceof Object) { target[key] = deepMerge(target[key], source[key]); + } else { + target[key] = source[key]; } - } else { - target[key] = source[key]; } } @@ -402,6 +415,24 @@ var Onepage = (function () { return raw.replace(regex, ""); } + /** + * Inlines a string by removing excessive line breaks and spaces, while + * ensuring no more than a specified number of consecutive spaces are retained. + * + * @param string {string} + * @param maxWhitespace {number} [maxWhitespace=1] + * @returns {string} + */ + function inlineString(string, maxWhitespace = 1) { + const noLineBreaksText = string.replace(/[\r\n]+/g, " "); + + // Replace multiple spaces with exactly maxWhitespace spaces + const regex = new RegExp(` {${maxWhitespace + 1},}`, "g"); + const inlinedText = noLineBreaksText.replace(regex, " ".repeat(maxWhitespace)); + + return inlinedText.trim(); + } + /** * Extracts class names from a CSS-like string. * Matches strings that start with a period (.) followed by the class name, @@ -416,7 +447,7 @@ var Onepage = (function () { const matches = raw.match(regex); if (matches) { // Clean up matches to remove the leading dot and trailing `{` - return matches.map(match => match.replace(/^\.\s*|[{]\s*$/g, '')); + return matches.map(match => match.replace(/^\.\s*|\s*{\s*$/g, "")); } return []; @@ -431,10 +462,8 @@ var Onepage = (function () { * @returns {string} - The modified CSS string with prefixed class names. */ function addPrefixToClassNames(raw, prefix) { - // Regular expression to match class names, capturing the name part after the dot - // \. - Matches a literal dot (.) - // ([\w-]+) - Captures one or more word characters or hyphens as the class name - return raw.replace(/\.(\w[\w-]*)/g, `.${prefix}-$1`); + const classRegex = /\.([a-zA-Z0-9_-]+)(?=\s*{|\s*:)/g; + return raw.replace(classRegex, (_match, className) => `.${prefix}-${className}`); } /** @@ -470,17 +499,37 @@ var Onepage = (function () { */ function isElementBeforeOrAfter(element, sibling) { if (element === sibling) return "same" + if (element.parentElement !== sibling.parentElement) return -1; const position = element.compareDocumentPosition(sibling); - if (position & Node.DOCUMENT_POSITION_PRECEDING) { + if (position & Node.DOCUMENT_POSITION_FOLLOWING) { return "before"; - } else if (position & Node.DOCUMENT_POSITION_FOLLOWING) { + } else if (position & Node.DOCUMENT_POSITION_PRECEDING) { return "after"; } return -1; } + function isTouchDevice() { + return (window.matchMedia && window.matchMedia("(pointer: coarse)").matches); + } + + /** + * @param func {Function} - callback function + * @param delay {number} - delay in ms + */ + function debounce(func, delay) { + /** @type {Timer}*/ + let timer; + + /** @param args {*} */ + return (...args) => { + clearTimeout(timer); + timer = setTimeout(() => func(...args), delay); + }; + } + var utils = { toKebabCase: toKebabCase, addClassName: addClassName, @@ -506,6 +555,9 @@ var Onepage = (function () { isSlide: isSlide, isSlider: isSlider, isElementBeforeOrAfter: isElementBeforeOrAfter, + isTouchDevice: isTouchDevice, + inlineString: inlineString, + debounce: debounce }; // Zero-Clause BSD @@ -541,7 +593,7 @@ var Onepage = (function () { keyboardScroll: true, // Enable scroll with keyboard swipeScroll: false, // Enable scroll with mouse draggin (click + direction) overflowScroll: false, // Enable normal scroll when elements have overflow - speed: 256, // Scroll 'n' pixels when there is overflow scroll + speed: 69, // Scroll 'n' pixels when there is overflow scroll direction: /** @type {Direction} */ ("vertical"), behavior: /** @type {ScrollBehavior} */ ("smooth"), @@ -699,7 +751,7 @@ var Onepage = (function () { }, ''); // Remove new lines and white space - let fmtResult = utils.removeSpace(result); + let fmtResult = utils.inlineString(result, 1); const match = utils.extractClassName(fmtResult); const className = match ? match[0] : utils.generateRandomString(); @@ -751,11 +803,12 @@ var Onepage = (function () { html: css` .document { overflow: hidden; + scroll-behavior: smooth; margin: 0; padding: 0; } - `, + `, vertical: css` .vertical { @@ -785,7 +838,6 @@ var Onepage = (function () { box-sizing: border-box; -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; } `, @@ -833,45 +885,43 @@ var Onepage = (function () { sectionPagination: css` .section-pagination { - position: absolute; - right: 10px; + position: fixed; + display: flex; + flex-direction: column; + gap: 0.6rem; + border-radius: 0.3rem; + right: 1rem; top: 50%; z-index: 2; list-style: none; margin: 0; - padding: 0; + padding: 0.3rem; li { padding: 0; text-align: center; } - li a { - padding: 10px; - width: 4px; - height: 4px; - display: block; + li > a { + display: flex; + width: 1rem; + height: 1rem; } - li a:before { + li > a:before { content: ''; position: absolute; - width: 4px; - height: 4px; - background: rgba(0,0,0,0.85); - border-radius: 10px; - -webkit-border-radius: 10px; - -moz-border-radius: 10px; - } - - li a.active:before { - width: 10px; - height: 10px; - background: none; - border: 1px solid black; - margin-top: -4px; - left: 8px; + background: rgba(0, 0, 0, 0.69); + border-radius: 1rem; + width: 1rem; + height: 1rem; + } + + li > a.active:before { + background: rgba(0, 0, 0, 0.9); + width: 1.2rem; + height: 1.2rem; } } `, @@ -1020,7 +1070,7 @@ var Onepage = (function () { /** @type {{ [key: string]: Array<(event: WheelEvent) => void | Promise>}} */ const _listeners = { - "wheel": [] + wheel: [] }; /** @@ -1061,10 +1111,22 @@ var Onepage = (function () { } } + function _cleanInternalListeners() { + Object.keys(_listeners).forEach(key => _listeners[key] = []); + } + /** @param event {WheelEvent} */ function _handleWheel(event) { - _direction.y = (event.deltaY > 0) ? 1 : -1; - _direction.x = (event.deltaX > 0) ? 1 : -1; + _direction.y = 0; + _direction.x = 0; + + if (event.deltaY !== 0) { + _direction.y = (event.deltaY > 0) ? 1 : -1; + } + + if (event.deltaX !== 0) { + _direction.x = (event.deltaX > 0) ? 1 : -1; + } _notifyListeners("wheel", event); } @@ -1073,14 +1135,18 @@ var Onepage = (function () { function getAxis(direction = "vertical") { if (_direction.x === 0 && _direction.y === 0) return 0; - switch (direction) { - case "vertical": - return /** @type {Axis} */(_direction.y); - case "horizontal": - return /** @type {Axis} */(_direction.x); - } + let result = (() => { + switch (direction) { + case "vertical": + return /** @type {Axis} */(_direction.y); + case "horizontal": + return /** @type {Axis} */(_direction.x); + } - return 0; + return 0; + })(); + + return result; } /* @type {typeof WheelEventHandler.isEventAvailable} */ @@ -1100,6 +1166,11 @@ var Onepage = (function () { _isListen = true; } + function _makeCleanup() { + _cleanInternalListeners(); + _direction = { x: 0, y: 0}; + } + function stopListen() { if (!_isListen) return; @@ -1109,6 +1180,7 @@ var Onepage = (function () { Logger.debug("WheelEventHandler: wheel event listeners [stoped]"); + _makeCleanup(); _isListen = false; } @@ -1142,11 +1214,17 @@ var Onepage = (function () { * @type {SwipeEventHandler} */ const SwipeEventHandler = (() => { - const { MOUSE_SWIPE_DISCARDED_BUTTONS } = constants; - let _isListen = false; - let _startPos = /** @type {Vector2} */ { x: 0, y: 0 }; - let _currentPos = /** @type {Vector2} */ { x: 0, y: 0 }; + + const _pos = { + start: /** @type {Vector2} */ { x: 0, y: 0 }, + end: /** @type {Vector2} */ { x: 0, y: 0 }, + + reset() { + this.start = { x: 0, y: 0 }; + this.end = { x: 0, y: 0 }; + } + }; /** @type {{ [key: string]: Array<(event: SwipeEvent) => void | Promise>}} */ const _listeners = { @@ -1192,19 +1270,25 @@ var Onepage = (function () { } } + function _cleanInternalListeners() { + Object.keys(_listeners).forEach(key => _listeners[key] = []); + } + /** * @param event {SwipeEvent} * @returns {Promise} */ async function _handleSwipeStart(event) { + _pos.reset(); + if (window?.TouchEvent && event instanceof TouchEvent) { - _startPos.x = event.changedTouches[0].screenX; - _startPos.y = event.changedTouches[0].screenY; + _pos.start.x = event.changedTouches[0].clientX; + _pos.start.y = event.changedTouches[0].clientY; } else if (window?.PointerEvent && event instanceof PointerEvent) { const button = /** @type {MouseButton} */ (event.button); if (!(constants.MOUSE_SWIPE_DISCARDED_BUTTONS.includes(button))) { - _startPos.x = event.screenX; - _startPos.y = event.screenY; + _pos.start.x = event.clientX; + _pos.start.y = event.clientY; } Logger.debug("SwipeEventHandler: pressed mouse button code on 'swipeStart': ", [button]); } @@ -1216,18 +1300,36 @@ var Onepage = (function () { * @param event {SwipeEvent} * @returns {Promise} */ - async function _handleSwipeEnd(event) { - _currentPos.x = 0; - _currentPos.y = 0; + async function _handleSwipeMove(event) { + if (window?.TouchEvent && event instanceof TouchEvent) { + _pos.end.x = event.changedTouches[0].clientX; + _pos.end.y = event.changedTouches[0].clientY; + } else if (window?.PointerEvent && event instanceof PointerEvent) { + const button = /** @type {MouseButton} */ (event.button); + if (!(constants.MOUSE_SWIPE_DISCARDED_BUTTONS.includes(button))) { + _pos.end.x = event.clientX; + _pos.end.y = event.clientY; + } + + Logger.debug("SwipeEventHandler: pressed mouse button code on 'swipeMove': ", [button]); + } + } + /** + * @param event {SwipeEvent} + * @returns {Promise} + */ + async function _handleSwipeEnd(event) { if (window?.TouchEvent && event instanceof TouchEvent) { - _currentPos.x = event.changedTouches[0].screenX; - _currentPos.y = event.changedTouches[0].screenY; + _pos.end.x = event.changedTouches[0].clientX; + _pos.end.y = event.changedTouches[0].clientY; } else if (window?.PointerEvent && event instanceof PointerEvent) { const button = /** @type {MouseButton} */ (event.button); if (!(constants.MOUSE_SWIPE_DISCARDED_BUTTONS.includes(button))) { - _currentPos.x = event.screenX; - _currentPos.y = event.screenY; + if (event.clientX !== 0 || event.clientY !== 0) { + _pos.end.x = event.clientX; + _pos.end.y = event.clientY; + } } Logger.debug("SwipeEventHandler: pressed mouse button code on 'swipeEnd': ", [button]); @@ -1238,18 +1340,18 @@ var Onepage = (function () { /** @type {typeof SwipeEventHandler.getAxis} */ function getAxis(direction = "vertical") { - if (_currentPos.x === 0 && _currentPos.y === 0) return 0; + const diffX = _pos.end.x - _pos.start.x; + const diffY = _pos.end.y - _pos.start.y; - const diffX = _startPos.x - _currentPos.x; - const diffY = _startPos.y - _currentPos.y; + if (diffX === 0 && diffY === 0) return 0; if (Math.abs(diffX) > Math.abs(diffY)) { if (Math.abs(diffX) >= constants.TOUCH_THRESHOLD && direction === "horizontal") { - return (diffX >= 0 ? 1 : -1); + return (diffX >= 0 ? -1 : 1); } } else { if (Math.abs(diffY) >= constants.TOUCH_THRESHOLD && direction === "vertical") { - return (diffY >= 0 ? 1 : -1); + return (diffY >= 0 ? -1 : 1); } } @@ -1267,27 +1369,22 @@ var Onepage = (function () { function startListen() { if (_isListen) return; - if ((window?.PointerEvent && settings.scroll.swipeScroll) || window?.TouchEvent) { + // Add pointer event listeners for non-touch devices if swipe scroll is enabled + if ((window?.PointerEvent && settings.scroll.swipeScroll) && !isTouchDevice()) { document.addEventListener("pointerdown", _handleSwipeStart, false); + document.addEventListener("pointermove", _handleSwipeMove, false); document.addEventListener("pointerup", _handleSwipeEnd, false); - document.addEventListener("pointermove", event => event.preventDefault(), false); - - if (settings.scroll.overflowScroll) { - document.addEventListener("pointercancel", _handleSwipeEnd, false); - } + document.addEventListener("pointercancel", _handleSwipeEnd, false); Logger.debug("SwipeEventHandler: pointer event listeners [started]"); } - // Fallback to touch if pointer event is not supported (android/ios) - if (window?.TouchEvent && (typeof window?.PointerEvent) === "undefined") { + // Add touch event listeners for touch-enabled devices + if (window?.TouchEvent && isTouchDevice()) { document.addEventListener("touchstart", _handleSwipeStart, false); document.addEventListener("touchend", _handleSwipeEnd, false); - document.addEventListener("touchmove", event => event.preventDefault(), false); - - if (settings.scroll.overflowScroll) { - document.addEventListener("touchcancel", _handleSwipeEnd, false); - } + document.addEventListener("touchmove", _handleSwipeMove, false); + document.addEventListener("touchcancel", _handleSwipeEnd, false); Logger.debug("SwipeEventHandler: touch event listeners [started]"); } @@ -1295,26 +1392,35 @@ var Onepage = (function () { _isListen = true; } + function _makeCleanup() { + _cleanInternalListeners(); + _pos.reset(); + } + function stopListen() { if (!_isListen) return; - if ((window?.PointerEvent && settings.scroll.swipeScroll) || window?.TouchEvent) { + // Remove pointer event listeners for non-touch devices if swipe scroll is enabled + if ((window?.PointerEvent && settings.scroll.swipeScroll) && typeof window?.TouchEvent === "undefined") { document.removeEventListener("pointerdown", _handleSwipeStart, false); + document.removeEventListener("pointermove", _handleSwipeMove, false); document.removeEventListener("pointerup", _handleSwipeEnd, false); - document.removeEventListener("pointermove", event => event.preventDefault(), false); + document.removeEventListener("pointercancel", _handleSwipeEnd, false); Logger.debug("SwipeEventHandler: pointer event listeners [stoped]"); } - // Fallback to touch if pointer event is not supported (android/ios) - if (window?.TouchEvent && (typeof window?.PointerEvent) === "undefined") { + // Remove touch event listeners for touch-enabled devices + if (window?.TouchEvent) { document.removeEventListener("touchstart", _handleSwipeStart, false); document.removeEventListener("touchend", _handleSwipeEnd, false); - document.removeEventListener("touchmove", event => event.preventDefault(), false); + document.removeEventListener("touchmove", _handleSwipeMove, false); + document.removeEventListener("touchcancel", _handleSwipeEnd, false); Logger.debug("SwipeEventHandler: touch event listeners [stopped]"); } + _makeCleanup(); _isListen = false; } @@ -1350,15 +1456,24 @@ var Onepage = (function () { const KeyEventHandler = (() => { let _isListen = false; + let _keys = { + lastKey: "", + axisKey: "", // special variable only to hold axis related stuff + + reset() { + this.lastKey = ""; + this.axisKey = ""; + } + }; + /** * @type {{ [key: string]: Array<(event: KeyboardEvent) => void | Promise>}} */ const _listeners = { keydown: [], + keyup: [], }; - let _lastKey = ""; - /** * @param element {Element} * @param direction {Direction} @@ -1367,20 +1482,14 @@ var Onepage = (function () { function scrollWithKeys(element, direction = "vertical") { if (element.scrollHeight === 0 && element.scrollWidth === 0) return; - const { speed, behavior } = settings.scroll; + const { speed } = settings.scroll; if (direction === "vertical") { - const offsetTop = element.scrollTop; - element.scroll({ - top: (getAxis(direction) * speed) + offsetTop, - behavior: behavior - }); + const targetScroll = (getAxis(direction) * speed) + element.scrollTop; + element.scroll({ top: targetScroll }); } else if (direction === "horizontal") { - const offsetLeft = element.scrollLeft; - element.scroll({ - left: (getAxis(direction) * speed) + offsetLeft, - behavior: behavior - }); + const targetScroll = (getAxis(direction) * speed) + element.scrollLeft; + element.scroll({ left: targetScroll }); } Logger.debug(`KeyEventHandler: scroll with keys called with speed '${speed}' on element`, [element]); @@ -1424,23 +1533,41 @@ var Onepage = (function () { } } + function _cleanInternalListeners() { + Object.keys(_listeners).forEach(key => _listeners[key] = []); + } + /** * @param event {KeyboardEvent} * @returns {Promise} */ async function _keydownEventHandler(event) { - _lastKey = event.key; + _keys.lastKey = event.key; + _keys.axisKey = event.key; + _notifyListeners("keydown", event); } + /** + * @param event {KeyboardEvent} + * @returns {Promise} + */ + async function _keyupEventHandler(event) { + if (event.key === _keys.axisKey) { + _keys.axisKey = ""; + } + + _notifyListeners("keyup", event); + } + /** @type {typeof KeyEventHandler.getAxis } */ function getAxis(direction = "vertical") { if (direction === "vertical") { - if (settings.keybindings.up.includes(_lastKey)) return -1; - if (settings.keybindings.down.includes(_lastKey)) return 1; + if (settings.keybindings.up.includes(_keys.axisKey)) return -1; + if (settings.keybindings.down.includes(_keys.axisKey)) return 1; } else if (direction === "horizontal") { - if (settings.keybindings.right.includes(_lastKey)) return 1; - if (settings.keybindings.left.includes(_lastKey)) return -1; + if (settings.keybindings.right.includes(_keys.axisKey)) return 1; + if (settings.keybindings.left.includes(_keys.axisKey)) return -1; } return 0; @@ -1456,6 +1583,7 @@ var Onepage = (function () { if (window?.KeyboardEvent && settings.scroll.keyboardScroll) { window.addEventListener("keydown", _keydownEventHandler, false); + window.addEventListener("keydown", _keyupEventHandler, false); Logger.debug("KeyEventHandler: key event listeners [started]"); } @@ -1463,15 +1591,22 @@ var Onepage = (function () { _isListen = true; } + function _makeCleanup() { + _cleanInternalListeners(); + _keys.reset(); + } + function stopListen() { if (!_isListen) return; if (window?.KeyboardEvent && settings.scroll.keyboardScroll) { window.removeEventListener("keydown", _keydownEventHandler, false); + window.removeEventListener("keyup", _keyupEventHandler, false); Logger.debug("KeyEventHandler: key event listeners [stopped]"); } + _makeCleanup(); _isListen = false; } @@ -1502,6 +1637,8 @@ var Onepage = (function () { const scroll = (() => { + /** @type {Timer} */ + let _scrollTimer; let _isScrolling = false; /** @@ -1509,7 +1646,6 @@ var Onepage = (function () { * @returns {void} */ function init(sections) { - if (WheelEventHandler.isEventAvailable()) { WheelEventHandler.on("wheel", event => _handleScroll(event, sections)); WheelEventHandler.startListen(); @@ -1521,9 +1657,11 @@ var Onepage = (function () { if (target?.tagName !== "INPUT" && target?.tagName !== "TEXTAREA") { const section = sections.getCurrentSection(); if (_hasOverflowScroll(section, KeyEventHandler.getAxis("vertical"))) { + // TODO: Make it work well on chrome like browsers const scrollable = section.elemRef.querySelector("." + classes.overflow); if (scrollable !== null) { KeyEventHandler.scrollWithKeys(scrollable, "vertical"); + event.preventDefault(); } } else { _handleScroll(event, sections); @@ -1589,41 +1727,51 @@ var Onepage = (function () { function _handleSlider(event, slider) { if (window?.WheelEvent && event instanceof WheelEvent) return; - if (_getEventAxis(event, "horizontal") > 0) { + const axis = _getEventAxis(event, "horizontal"); + if (axis > 0) { slider.next(); - } else if (_getEventAxis(event, "horizontal") < 0) { + } else if (axis < 0) { slider.prev(); } } + function _lockScroll() { + _isScrolling = true; + if (_scrollTimer) { + clearTimeout(_scrollTimer); + } + + // Unlock scrolling after specified timeout + _scrollTimer = setTimeout(() => { + _isScrolling = false; + }, settings.scroll.unlockTimeout); + } + + /** * @param event {ScrollEvent | SwipeEvent} * @param sections {SectionList} * @returns {void} */ function _handleScroll(event, sections) { + if (_isScrolling) return; + + _lockScroll(); const currentSection = sections.getCurrentSection(); if (currentSection.sliderList.length >= 1) { _handleSlider(event, currentSection.sliderList[0]); } - if (_isScrolling) return; - - _isScrolling = true; - if (_getEventAxis(event, "vertical") > 0) { + const axis = _getEventAxis(event, "vertical"); + if (axis > 0) { if (!_hasOverflowScroll(currentSection, 1)) { sections.scrollNext(); } - } else if (_getEventAxis(event, "vertical") < 0) { + } else if (axis < 0) { if (!_hasOverflowScroll(currentSection, -1)) { sections.scrollPrev(); } } - - // Unlock scrolling after specified timeout - setTimeout(() => { - _isScrolling = false; - }, settings.scroll.unlockTimeout); } return { @@ -1687,8 +1835,9 @@ var Onepage = (function () { if (index < 0 || index >= elementList.length) return; elementList[index].scrollIntoView({ - behavior: settings.scroll.behavior || "smooth", + behavior: settings.scroll.behavior, block: "start", + inline: "center" }); } @@ -1770,7 +1919,20 @@ var Onepage = (function () { const sliders = utils.getSliderListInElement(section); const aloneSlides = utils.getSingleSlidesInElementOrNull(section); if (aloneSlides !== null) { - sliderList.push(Slider(aloneSlides)); + let shouldAdd = (() => { + for (const idx in aloneSlides) { + const slideParent = aloneSlides[idx].parentElement; + if (slideParent !== null) { + return !utils.isSlider(/** @type {Element} */(slideParent)); + } + } + + return true; + })(); + + if (shouldAdd) { + sliderList.push(Slider(aloneSlides)); + } } sliders.forEach(slider => { @@ -2036,15 +2198,12 @@ var Onepage = (function () { * @type {{ [key: string]: Array<(event: Event) => void | Promise>}} */ const _listeners = { - complete: [], - ready: [], - beforeExit: [], - exit: [], + DOMContentLoaded: [], + load: [], + beforeUnload: [], + unload: [], }; - /** @type {typeof DOMLoadEventHandler.state} */ - let state = "loading"; - /** * Add a listener for a specific event type * @type {typeof DOMLoadEventHandler.on} @@ -2083,13 +2242,16 @@ var Onepage = (function () { } } + function _cleanInternalListeners() { + Object.keys(_listeners).forEach(key => _listeners[key] = []); + } + /** * Handler for the DOMContentLoaded event, triggering "ready" listeners. * @param {Event} event - The DOMContentLoaded event object. */ function _DOMContentLoadedEvent(event) { - state = "interactive"; - _notifyListeners("ready", event); + _notifyListeners("DOMContentLoaded", event); } /** @@ -2097,8 +2259,7 @@ var Onepage = (function () { * @param {Event} event - The load event object. */ function _loadEvent(event) { - state = "complete"; - _notifyListeners("complete", event); + _notifyListeners("load", event); } /** @@ -2106,7 +2267,7 @@ var Onepage = (function () { * @param {Event} event - The beforeunload event object. */ function _beforeUnloadEvent(event) { - _notifyListeners("beforeExit", event); + _notifyListeners("beforeUnload", event); } /** @@ -2114,7 +2275,7 @@ var Onepage = (function () { * @param {Event} event - The unload event object. */ function _unloadEvent(event) { - _notifyListeners("exit", event); + _notifyListeners("unload", event); } function startListen() { @@ -2140,6 +2301,7 @@ var Onepage = (function () { Logger.debug("DOMLoadEventHandler: Listeners [stopped]"); + _cleanInternalListeners(); _isListen = false; } @@ -2147,7 +2309,6 @@ var Onepage = (function () { startListen, stopListen, - state: state, on: on, off: off, }; @@ -2227,6 +2388,10 @@ var Onepage = (function () { } } + function _cleanInternalListeners() { + Object.keys(_listeners).forEach(key => _listeners[key] = []); + } + /** * @param node {Node} * @returns {Element | null} @@ -2315,10 +2480,17 @@ var Onepage = (function () { Logger.debug("ObserverEventHandler: Listeners [started]"); } + function _makeCleanup() { + _cleanInternalListeners(); + _observer = null; + } + function stopListen() { if (_observer === null) return; _observer.disconnect(); + _makeCleanup(); + Logger.debug("ObserverEventHandler: Listeners [stopped]"); } @@ -2435,7 +2607,7 @@ var Onepage = (function () { (function _init() { DOMLoadEventHandler.startListen(); - DOMLoadEventHandler.on("ready", async () => { + DOMLoadEventHandler.on("DOMContentLoaded", async () => { createHeadStyles(); injectStylesOrThrow(); diff --git a/dist/onepage.min.js b/dist/onepage.min.js index 7f4bb2f..9074158 100644 --- a/dist/onepage.min.js +++ b/dist/onepage.min.js @@ -1,21 +1,22 @@ -var Onepage=function(){"use strict";const e={CLASS_NAME_PREFIX:"op",SECTION_CLASS_NAME:"section",ROOT_ID_NAME:"onepage",SLIDER_WRAPPER_CLASS_NAME:"slider-ctn",SINGLE_SLIDE_CLASS_NAME:"slide",TOUCH_THRESHOLD:30,MOUSE_SWIPE_DISCARDED_BUTTONS:[2]};class n extends Error{constructor(e){super(e),this.name="NeverError"}}function t(e,...t){throw console.error("%c[FATAL ERROR]","color: red;",e,...t),new n(e)}function r(e,n){for(const t in n)n.hasOwnProperty(t)?n[t]instanceof Object&&e[t]instanceof Object&&(e[t]=r(e[t],n[t])):e[t]=n[t];return e}var o={toKebabCase:function(e){return e.replace(/_/g,"-").replace(/([a-z])([A-Z])/g,"$1-$2").toLowerCase()},addClassName:function(e,n){e.classList.add(n)},removeClassName:function(e,n){e.classList.remove(n)},isElementScrollable:function(e){const n=e.scrollHeight>e.clientHeight,t=e.scrollWidth>e.clientWidth;return!(!n&&!t)},isParentElementScrollable:function(e,n){let t=e.parentElement;for(let e=n;e<=n&&null!=t;e+=1){const r=t.scrollHeight>t.clientHeight,o=t.scrollWidth>t.clientWidth;if(r||o)return!0;t=t?.parentElement,-1===n&&(e=n)}return!1},tryToGetScrollableParentElement:function(e,n=0){let t=e.parentElement;for(let e=n;e<=n&&null!=t;e+=1){const r=t.scrollHeight>t.clientHeight,o=t.scrollWidth>t.clientWidth;if(r||o)return t;t=t?.parentElement,-1===n&&(e=n)}return null},hasReachedEndOfScroll:function(e,n="vertical"){switch(n.toLowerCase()){case"vertical":return e.scrollTop+e.clientHeight>=e.scrollHeight;case"horizontal":return e.scrollLeft+e.clientWidth>=e.scrollWidth}return!1},hasReachedStartOfScroll:function(e,n="vertical"){switch(n.toLowerCase()){case"vertical":return 0===e.scrollTop;case"horizontal":return 0===e.scrollLeft}return!1},wrapAllChildrenOf:function(e,n="div"){const t=document.createElement(n);for(;e.firstChild;)t.appendChild(e.firstChild);return e.appendChild(t),t},wrapChildrenInTag:function(e,n="div"){const t=document.createElement(n);return e.forEach((e=>{t.appendChild(e)})),t},slideParentCtnOrNull:function(n,t=0){let r=n.parentElement;for(let n=t;n<=t&&null!=r;n+=1){if(r.classList.contains(e.SLIDER_WRAPPER_CLASS_NAME))return r;r=r?.parentElement,-1===t&&(n=t)}return null},sectionParentOrNull:function(n,t=0){let r=n.parentElement;for(let n=t;n<=t&&null!=r;n+=1){if(r.classList.contains(e.SECTION_CLASS_NAME))return r;r=r?.parentElement,-1===t&&(n=t)}return null},getSliderListInElement:function(n){return n.querySelectorAll("."+e.SLIDER_WRAPPER_CLASS_NAME)},getSingleSlidesInElementOrNull:function(n){const t=n.querySelectorAll("."+e.SINGLE_SLIDE_CLASS_NAME);return t.length>=1?t:null},getRootNodeOrThrow:function(){const n=document.getElementById("#"+e.ROOT_ID_NAME);return null==n&&t(`${e.ROOT_ID_NAME} element not founded in DOOM`),n},deepMerge:r,generateRandomString:function(e=16){const n="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";let t="";for(let r=0;re.replace(/^\.\s*|[{]\s*$/g,""))):[]},addPrefixToClassNames:function(e,n){return e.replace(/\.(\w[\w-]*)/g,`.${n}-$1`)},isSection:function(n){return n.classList.contains(e.SECTION_CLASS_NAME)},isSlide:function(n){return n.classList.contains(e.SINGLE_SLIDE_CLASS_NAME)},isSlider:function(n){return n.classList.contains(e.SLIDER_WRAPPER_CLASS_NAME)},isElementBeforeOrAfter:function(e,n){if(e===n)return"same";const t=e.compareDocumentPosition(n);return t&Node.DOCUMENT_POSITION_PRECEDING?"before":t&Node.DOCUMENT_POSITION_FOLLOWING?"after":-1}};const i={constants:e,logger:{enabled:!1,levels:["ALL"]},observer:{enabled:!0},scroll:{unlockTimeout:300,keyboardScroll:!0,swipeScroll:!1,overflowScroll:!1,speed:256,direction:"vertical",behavior:"smooth"},section:{},slider:{},keybindings:{up:["ArrowUp","k","PageUp"],down:["ArrowDown","j","PageDown"],left:["ArrowLeft","h"],right:["ArrowRight","l"]}};function l(e,n){const r={"constants.CLASS_NAME_PREFIX":"string","constants.SECTION_CLASS_NAME":"string","constants.ROOT_ID_NAME":"string","constants.SLIDER_WRAPPER_CLASS_NAME":"string","constants.SINGLE_SLIDE_CLASS_NAME":"string","constants.TOUCH_THRESHOLD":"number","constants.MOUSE_SWIPE_DISCARDED_BUTTONS":"array","logger.enabled":"boolean","logger.levels":"array","observer.enabled":"boolean","scroll.unlockTimeout":"number","scroll.keyboardScroll":"boolean","scroll.swipeScroll":"boolean","scroll.overflowScroll":"boolean","scroll.speed":"number","scroll.direction":"string","scroll.behavior":"string","keybindings.up":"array","keybindings.down":"array","keybindings.left":"array","keybindings.right":"array"};r.hasOwnProperty(e)||t("[settings] Invalid Key");const o=r[e];o&&("array"!==o||Array.isArray(n)?typeof n!==o&&"array"!==o&&t(`[settings] Invalid type for ${e}. Expected ${o}.`):t(`[settings] Invalid type for ${e}. Expected an array.`))}function s(e){!function e(n,t,r=""){for(const o in t){const i=r?`${r}.${o}`:o,s=t[o];"object"!=typeof s||null===s||Array.isArray(s)?(l(i,s),n[o]=s):(n[o]||(n[o]={}),e(n[o],s,i))}}(i,e),r(i,e)}let c="";function a(e,...n){let t=e.reduce(((e,t,r)=>e+t+(n[r]?n[r]:"")),""),r=o.removeSpace(t);const i=o.extractClassName(r),l=i?i[0]:o.generateRandomString();return c+=i?r:`.${l} {${r}}`,l}function d(){!function(e){const n=document.documentElement,t=document.getElementsByTagName("body")[0];o.addClassName(n,u.html),o.addClassName(t,u.html),"horizontal"===i.scroll.direction.toLowerCase()?o.addClassName(e,u.horizontal):o.addClassName(e,u.vertical)}(o.getRootNodeOrThrow())}const u=(()=>{const n={html:a` +var Onepage=function(){"use strict";const e={CLASS_NAME_PREFIX:"op",SECTION_CLASS_NAME:"section",ROOT_ID_NAME:"onepage",SLIDER_WRAPPER_CLASS_NAME:"slider-ctn",SINGLE_SLIDE_CLASS_NAME:"slide",TOUCH_THRESHOLD:30,MOUSE_SWIPE_DISCARDED_BUTTONS:[2]};class n extends Error{constructor(e){super(e),this.name="NeverError"}}function t(e,...t){throw console.error("%c[FATAL ERROR]","color: red;",e,...t),new n(e)}function r(e,n){for(const t in n)n.hasOwnProperty(t)&&(n[t]instanceof Object&&e[t]instanceof Object?e[t]=r(e[t],n[t]):e[t]=n[t]);return e}function o(){return window.matchMedia&&window.matchMedia("(pointer: coarse)").matches}var i={toKebabCase:function(e){return e.replace(/_/g,"-").replace(/([a-z])([A-Z])/g,"$1-$2").toLowerCase()},addClassName:function(e,n){e.classList.add(n)},removeClassName:function(e,n){e.classList.remove(n)},isElementScrollable:function(e){const n=e.scrollHeight>e.clientHeight,t=e.scrollWidth>e.clientWidth;return!(!n&&!t)},isParentElementScrollable:function(e,n){let t=e.parentElement;for(let e=n;e<=n&&null!=t;){const r=t.scrollHeight>t.clientHeight,o=t.scrollWidth>t.clientWidth;if(r||o)return!0;t=t?.parentElement,-1!==n&&(e+=1)}return!1},tryToGetScrollableParentElement:function(e,n=0){let t=e.parentElement;for(let e=n;e<=n&&null!=t;){const r=t.scrollHeight>t.clientHeight,o=t.scrollWidth>t.clientWidth;if(r||o)return t;t=t?.parentElement,-1!==n&&(e+=1)}return null},hasReachedEndOfScroll:function(e,n="vertical"){switch(n.toLowerCase()){case"vertical":return e.scrollTop+e.clientHeight+1>=e.scrollHeight;case"horizontal":return e.scrollLeft+e.clientWidth+1>=e.scrollWidth}return!1},hasReachedStartOfScroll:function(e,n="vertical"){switch(n.toLowerCase()){case"vertical":return e.scrollTop<=1;case"horizontal":return e.scrollLeft<=1}return!1},wrapAllChildrenOf:function(e,n="div"){const t=document.createElement(n);for(;e.firstChild;)t.appendChild(e.firstChild);return e.appendChild(t),t},wrapChildrenInTag:function(e,n="div"){const t=document.createElement(n);return e.forEach((e=>{t.appendChild(e)})),t},slideParentCtnOrNull:function(n,t=0){let r=n.parentElement;for(let n=t;n<=t&&null!=r;){if(r.classList.contains(e.SLIDER_WRAPPER_CLASS_NAME))return r;r=r?.parentElement,-1!==t&&(n+=1)}return null},sectionParentOrNull:function(n,t=0){let r=n.parentElement;for(let n=t;n<=t&&null!=r;){if(r.classList.contains(e.SECTION_CLASS_NAME))return r;r=r?.parentElement,-1!==t&&(n+=1)}return null},getSliderListInElement:function(n){return n.querySelectorAll("."+e.SLIDER_WRAPPER_CLASS_NAME)},getSingleSlidesInElementOrNull:function(n){const t=n.querySelectorAll("."+e.SINGLE_SLIDE_CLASS_NAME);return t.length>=1?t:null},getRootNodeOrThrow:function(){const n=document.getElementById("#"+e.ROOT_ID_NAME);return null==n&&t(`${e.ROOT_ID_NAME} element not founded in DOOM`),n},deepMerge:r,generateRandomString:function(e=16){const n="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";let t="";for(let r=0;re.replace(/^\.\s*|\s*{\s*$/g,""))):[]},addPrefixToClassNames:function(e,n){return e.replace(/\.([a-zA-Z0-9_-]+)(?=\s*{|\s*:)/g,((e,t)=>`.${n}-${t}`))},isSection:function(n){return n.classList.contains(e.SECTION_CLASS_NAME)},isSlide:function(n){return n.classList.contains(e.SINGLE_SLIDE_CLASS_NAME)},isSlider:function(n){return n.classList.contains(e.SLIDER_WRAPPER_CLASS_NAME)},isElementBeforeOrAfter:function(e,n){if(e===n)return"same";if(e.parentElement!==n.parentElement)return-1;const t=e.compareDocumentPosition(n);return t&Node.DOCUMENT_POSITION_FOLLOWING?"before":t&Node.DOCUMENT_POSITION_PRECEDING?"after":-1},isTouchDevice:o,inlineString:function(e,n=1){const t=e.replace(/[\r\n]+/g," "),r=new RegExp(` {${n+1},}`,"g");return t.replace(r," ".repeat(n)).trim()},debounce:function(e,n){let t;return(...r)=>{clearTimeout(t),t=setTimeout((()=>e(...r)),n)}}};const l={constants:e,logger:{enabled:!1,levels:["ALL"]},observer:{enabled:!0},scroll:{unlockTimeout:300,keyboardScroll:!0,swipeScroll:!1,overflowScroll:!1,speed:69,direction:"vertical",behavior:"smooth"},section:{},slider:{},keybindings:{up:["ArrowUp","k","PageUp"],down:["ArrowDown","j","PageDown"],left:["ArrowLeft","h"],right:["ArrowRight","l"]}};function s(e,n){const r={"constants.CLASS_NAME_PREFIX":"string","constants.SECTION_CLASS_NAME":"string","constants.ROOT_ID_NAME":"string","constants.SLIDER_WRAPPER_CLASS_NAME":"string","constants.SINGLE_SLIDE_CLASS_NAME":"string","constants.TOUCH_THRESHOLD":"number","constants.MOUSE_SWIPE_DISCARDED_BUTTONS":"array","logger.enabled":"boolean","logger.levels":"array","observer.enabled":"boolean","scroll.unlockTimeout":"number","scroll.keyboardScroll":"boolean","scroll.swipeScroll":"boolean","scroll.overflowScroll":"boolean","scroll.speed":"number","scroll.direction":"string","scroll.behavior":"string","keybindings.up":"array","keybindings.down":"array","keybindings.left":"array","keybindings.right":"array"};r.hasOwnProperty(e)||t("[settings] Invalid Key");const o=r[e];o&&("array"!==o||Array.isArray(n)?typeof n!==o&&"array"!==o&&t(`[settings] Invalid type for ${e}. Expected ${o}.`):t(`[settings] Invalid type for ${e}. Expected an array.`))}function c(e){!function e(n,t,r=""){for(const o in t){const i=r?`${r}.${o}`:o,l=t[o];"object"!=typeof l||null===l||Array.isArray(l)?(s(i,l),n[o]=l):(n[o]||(n[o]={}),e(n[o],l,i))}}(l,e),r(l,e)}let a="";function d(e,...n){let t=e.reduce(((e,t,r)=>e+t+(n[r]?n[r]:"")),""),r=i.inlineString(t,1);const o=i.extractClassName(r),l=o?o[0]:i.generateRandomString();return a+=o?r:`.${l} {${r}}`,l}function u(){!function(e){const n=document.documentElement,t=document.getElementsByTagName("body")[0];i.addClassName(n,f.html),i.addClassName(t,f.html),"horizontal"===l.scroll.direction.toLowerCase()?i.addClassName(e,f.horizontal):i.addClassName(e,f.vertical)}(i.getRootNodeOrThrow())}const f=(()=>{const n={html:d` .document { overflow: hidden; + scroll-behavior: smooth; margin: 0; padding: 0; } - `,vertical:a` + `,vertical:d` .vertical { display: flex; flex-direction: column; } - `,horizontal:a` + `,horizontal:d` .horizontal { display: flex; flex-direction: row; } - `,section:a` + `,section:d` .section { display: flex; align-items: center; @@ -29,9 +30,8 @@ var Onepage=function(){"use strict";const e={CLASS_NAME_PREFIX:"op",SECTION_CLAS box-sizing: border-box; -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; } - `,overflow:a` + `,overflow:d` .overflow { overflow: auto; @@ -44,7 +44,7 @@ var Onepage=function(){"use strict";const e={CLASS_NAME_PREFIX:"op",SECTION_CLAS /* [Chrome, Safari And Opera] */ ::-webkit-scrollbar { display: none; } } - `,sliderWrapper:a` + `,sliderWrapper:d` .slider-wrapper { z-index: 1; overflow: hidden; @@ -58,7 +58,7 @@ var Onepage=function(){"use strict";const e={CLASS_NAME_PREFIX:"op",SECTION_CLAS max-height: 100vh; max-width: 100vw; } - `,slide:a` + `,slide:d` .slide { display: flex; justify-content: center; @@ -67,47 +67,45 @@ var Onepage=function(){"use strict";const e={CLASS_NAME_PREFIX:"op",SECTION_CLAS min-height: 100vh; min-width: 100vw; } - `,sectionPagination:a` + `,sectionPagination:d` .section-pagination { - position: absolute; - right: 10px; + position: fixed; + display: flex; + flex-direction: column; + gap: 0.6rem; + border-radius: 0.3rem; + right: 1rem; top: 50%; z-index: 2; list-style: none; margin: 0; - padding: 0; + padding: 0.3rem; li { padding: 0; text-align: center; } - li a { - padding: 10px; - width: 4px; - height: 4px; - display: block; + li > a { + display: flex; + width: 1rem; + height: 1rem; } - li a:before { + li > a:before { content: ''; position: absolute; - width: 4px; - height: 4px; - background: rgba(0,0,0,0.85); - border-radius: 10px; - -webkit-border-radius: 10px; - -moz-border-radius: 10px; + background: rgba(0, 0, 0, 0.69); + border-radius: 1rem; + width: 1rem; + height: 1rem; } - li a.active:before { - width: 10px; - height: 10px; - background: none; - border: 1px solid black; - margin-top: -4px; - left: 8px; + li > a.active:before { + background: rgba(0, 0, 0, 0.9); + width: 1.2rem; + height: 1.2rem; } } - `};return new Proxy(n,{get(n,t){if(t in n){const r=n[t];return`${e.CLASS_NAME_PREFIX}-${r}`}}})})(),f=(()=>{const e=Object.freeze({DEBUG:1,INFO:2,WARN:4,ERROR:8,ALL:15});let n=(()=>{let n=0;for(const t of i.logger.levels)Object.hasOwn(e,t)&&n{let e=!1,n={x:0,y:0};const r={wheel:[]};function o(e){n.y=e.deltaY>0?1:-1,n.x=e.deltaX>0?1:-1,async function(e,n){if(r[e])for(const t of r[e])await t(n)}("wheel",e)}return{startListen:function(){e||(window?.WheelEvent&&document.addEventListener("wheel",o,!1),f.debug("WheelEventHandler: wheel event listeners [started]"),e=!0)},stopListen:function(){e&&(window?.WheelEvent&&document.removeEventListener("wheel",o,!1),f.debug("WheelEventHandler: wheel event listeners [stoped]"),e=!1)},getAxis:function(e="vertical"){if(0===n.x&&0===n.y)return 0;switch(e){case"vertical":return n.y;case"horizontal":return n.x}return 0},on:function(e,n){r[e]?(r[e].push(n),f.debug(`WheelEventHandler: event listener '${e}' [added]`)):t(`WheelEventHandler: unsupported event type: '${e}' (supported types list [${Object.keys(r).join(", ")}])`)},off:function(e,n){r[e]&&(r[e]=r[e].filter((e=>e!==n)),f.debug(`WheelEventHandler: event listener '${e}' [removed]`))},isEventAvailable:function(){return void 0!==window?.WheelEvent}}})(),E=(()=>{const{MOUSE_SWIPE_DISCARDED_BUTTONS:n}=e;let r=!1,o={x:0,y:0},l={x:0,y:0};const s={swipeStart:[],swipeEnd:[]};async function c(e,n){if(s[e])for(const t of s[e])await t(n)}async function a(n){if(window?.TouchEvent&&n instanceof TouchEvent)o.x=n.changedTouches[0].screenX,o.y=n.changedTouches[0].screenY;else if(window?.PointerEvent&&n instanceof PointerEvent){const t=n.button;e.MOUSE_SWIPE_DISCARDED_BUTTONS.includes(t)||(o.x=n.screenX,o.y=n.screenY),f.debug("SwipeEventHandler: pressed mouse button code on 'swipeStart': ",[t])}c("swipeStart",n)}async function d(n){if(l.x=0,l.y=0,window?.TouchEvent&&n instanceof TouchEvent)l.x=n.changedTouches[0].screenX,l.y=n.changedTouches[0].screenY;else if(window?.PointerEvent&&n instanceof PointerEvent){const t=n.button;e.MOUSE_SWIPE_DISCARDED_BUTTONS.includes(t)||(l.x=n.screenX,l.y=n.screenY),f.debug("SwipeEventHandler: pressed mouse button code on 'swipeEnd': ",[t])}c("swipeEnd",n)}return{startListen:function(){r||((window?.PointerEvent&&i.scroll.swipeScroll||window?.TouchEvent)&&(document.addEventListener("pointerdown",a,!1),document.addEventListener("pointerup",d,!1),document.addEventListener("pointermove",(e=>e.preventDefault()),!1),i.scroll.overflowScroll&&document.addEventListener("pointercancel",d,!1),f.debug("SwipeEventHandler: pointer event listeners [started]")),window?.TouchEvent&&void 0===window?.PointerEvent&&(document.addEventListener("touchstart",a,!1),document.addEventListener("touchend",d,!1),document.addEventListener("touchmove",(e=>e.preventDefault()),!1),i.scroll.overflowScroll&&document.addEventListener("touchcancel",d,!1),f.debug("SwipeEventHandler: touch event listeners [started]")),r=!0)},stopListen:function(){r&&((window?.PointerEvent&&i.scroll.swipeScroll||window?.TouchEvent)&&(document.removeEventListener("pointerdown",a,!1),document.removeEventListener("pointerup",d,!1),document.removeEventListener("pointermove",(e=>e.preventDefault()),!1),f.debug("SwipeEventHandler: pointer event listeners [stoped]")),window?.TouchEvent&&void 0===window?.PointerEvent&&(document.removeEventListener("touchstart",a,!1),document.removeEventListener("touchend",d,!1),document.removeEventListener("touchmove",(e=>e.preventDefault()),!1),f.debug("SwipeEventHandler: touch event listeners [stopped]")),r=!1)},getAxis:function(n="vertical"){if(0===l.x&&0===l.y)return 0;const t=o.x-l.x,r=o.y-l.y;if(Math.abs(t)>Math.abs(r)){if(Math.abs(t)>=e.TOUCH_THRESHOLD&&"horizontal"===n)return t>=0?1:-1}else if(Math.abs(r)>=e.TOUCH_THRESHOLD&&"vertical"===n)return r>=0?1:-1;return 0},on:function(e,n){s[e]?(s[e].push(n),f.debug(`SwipeEventHandler: event listener '${e}' [added]`)):t(`SwipeEventHandler: unsupported event type: '${e}' (supported types list [${Object.keys(s).join(", ")}])`)},off:function(e,n){s[e]&&(s[e]=s[e].filter((e=>e!==n)),f.debug(`SwipeEventHandler: event listener '${e}' [removed]`))},isEventAvailable:function(){const e=void 0!==window?.TouchEvent,n=void 0!==window?.PointerEvent;return e||n&&i.scroll.swipeScroll}}})(),h=(()=>{let e=!1;const n={keydown:[]};let r="";async function o(e){r=e.key,async function(e,t){if(n[e])for(const r of n[e])await r(t)}("keydown",e)}function l(e="vertical"){if("vertical"===e){if(i.keybindings.up.includes(r))return-1;if(i.keybindings.down.includes(r))return 1}else if("horizontal"===e){if(i.keybindings.right.includes(r))return 1;if(i.keybindings.left.includes(r))return-1}return 0}return{startListen:function(){e||(window?.KeyboardEvent&&i.scroll.keyboardScroll&&(window.addEventListener("keydown",o,!1),f.debug("KeyEventHandler: key event listeners [started]")),e=!0)},stopListen:function(){e&&(window?.KeyboardEvent&&i.scroll.keyboardScroll&&(window.removeEventListener("keydown",o,!1),f.debug("KeyEventHandler: key event listeners [stopped]")),e=!1)},getAxis:l,on:function(e,r){n[e]?(n[e].push(r),f.debug(`KeyEventHandler: event listener '${e}' [added]`)):t(`KeyEventHandler: unsupported event type: '${e}' (supported types list [${Object.keys(n).join(", ")}])`)},off:function(e,t){n[e]&&(n[e]=n[e].filter((e=>e!==t)),f.debug(`KeyEventHandler: event listener '${e}' [removed]`))},scrollWithKeys:function(e,n="vertical"){if(0===e.scrollHeight&&0===e.scrollWidth)return;const{speed:t,behavior:r}=i.scroll;if("vertical"===n){const o=e.scrollTop;e.scroll({top:l(n)*t+o,behavior:r})}else if("horizontal"===n){const o=e.scrollLeft;e.scroll({left:l(n)*t+o,behavior:r})}f.debug(`KeyEventHandler: scroll with keys called with speed '${t}' on element`,[e])},isEventAvailable:function(){return void 0!==window?.KeyboardEvent&&i.scroll.keyboardScroll}}})(),p=(()=>{let e=!1;function n(e,n){return!!i.scroll.overflowScroll&&(()=>{const t=e.elemRef.querySelector("."+u.overflow);if(null!=t&&o.isElementScrollable(t)){if(-1===n)return!o.hasReachedStartOfScroll(t);if(1===n)return!o.hasReachedEndOfScroll(t)}return!1})()}function t(e,n){return window?.PointerEvent&&e instanceof PointerEvent||window?.TouchEvent&&e instanceof TouchEvent?E.getAxis(n):window?.WheelEvent&&e instanceof WheelEvent?v.getAxis(n):window?.KeyboardEvent&&e instanceof KeyboardEvent?h.getAxis(n):0}function r(r,o){const l=o.getCurrentSection();l.sliderList.length>=1&&function(e,n){window?.WheelEvent&&e instanceof WheelEvent||(t(e,"horizontal")>0?n.next():t(e,"horizontal")<0&&n.prev())}(r,l.sliderList[0]),e||(e=!0,t(r,"vertical")>0?n(l,1)||o.scrollNext():t(r,"vertical")<0&&(n(l,-1)||o.scrollPrev()),setTimeout((()=>{e=!1}),i.scroll.unlockTimeout))}return{init:function(e){v.isEventAvailable()&&(v.on("wheel",(n=>r(n,e))),v.startListen()),h.isEventAvailable()&&(h.on("keydown",(t=>{const o=t.target;if("INPUT"!==o?.tagName&&"TEXTAREA"!==o?.tagName){const o=e.getCurrentSection();if(n(o,h.getAxis("vertical"))){const e=o.elemRef.querySelector("."+u.overflow);null!==e&&h.scrollWithKeys(e,"vertical")}else r(t,e)}})),h.startListen()),E.isEventAvailable()&&(E.on("swipeEnd",(n=>r(n,e))),E.startListen())}}})();function g(e,n={}){const t=function(){let n=e.item(0).parentElement;if(null===n||!o.isSlider(n)){const t=o.wrapChildrenInTag(e,"div");return n?.appendChild(t),t}return n}(),r=Array.from(e);let l=n.initialIndex??0;function s(e,n){e<0||e>=n.length||n[e].scrollIntoView({behavior:i.scroll.behavior||"smooth",block:"start"})}return o.addClassName(t,u.sliderWrapper),r.forEach((e=>{o.addClassName(e,u.slide)})),{elemRef:t,slideList:r,insert:function(e,n){r.splice(e,0,n)},remove:function(e){if(r.length<=0)return;const n=r.findIndex((n=>n===e));n>=0&&r.splice(n,1)},prev:function(){l=(l-1+r.length)%r.length,s(l,r)},next:function(){l=(l+1+r.length)%r.length,s(l,r)},goToSlide:function(e){e<0||e>=r.length||(s(l,r),l=e)}}}function w(e){const n=e,t=function(e){const n=new Array;function t(e){if(n.length<=0)return;const t=n.findIndex((n=>n.elemRef===e));t>=0&&n.splice(t,1)}function r(e,t){const r=g(t.querySelectorAll("."+i.constants.SINGLE_SLIDE_CLASS_NAME));n.splice(e,0,r)}return function(){const t=o.getSliderListInElement(e),r=o.getSingleSlidesInElementOrNull(e);null!==r&&n.push(g(r)),t.forEach((e=>{const t=g(e.querySelectorAll("."+i.constants.SINGLE_SLIDE_CLASS_NAME));n.push(t)}))}(),new Proxy(n,{get(e,n){if(n in e)return e[n];switch(n){case"insert":return r;case"remove":return t;default:return}}})}(e);return function(){if(o.addClassName(e,u.section),i.scroll.overflowScroll){const n=o.wrapAllChildrenOf(e);o.addClassName(n,u.overflow)}}(),{elemRef:n,sliderList:t}}const m=(()=>{let e=!1;const n={complete:[],ready:[],beforeExit:[],exit:[]};let r="loading";async function o(e,t){if(n[e])for(const r of n[e])await r(t)}function i(e){r="interactive",o("ready",e)}function l(e){r="complete",o("complete",e)}function s(e){o("beforeExit",e)}function c(e){o("exit",e)}return{startListen:function(){e||(document.addEventListener("DOMContentLoaded",i),window.addEventListener("load",l),window.addEventListener("beforeunload",s),window.addEventListener("unload",c),f.debug("DOMLoadEventHandler: Listeners [started]"),e=!0)},stopListen:function(){e&&(document.removeEventListener("DOMContentLoaded",i),window.removeEventListener("load",l),window.removeEventListener("beforeunload",s),window.removeEventListener("unload",c),f.debug("DOMLoadEventHandler: Listeners [stopped]"),e=!1)},state:r,on:function(e,r){n[e]?(n[e].push(r),f.debug(`DOMEventHandler: event listener '${e}' [added]`)):t(`DOMEventHandler: unsupported event type: '${e}' (supported types list [${Object.keys(n).join(", ")}])`)},off:function(e,t){n[e]&&(n[e]=n[e].filter((e=>e!==t)),f.debug(`DOMEventHandler: event listener '${e}' [removed]`))}}})(),S=(()=>{const n=[e.SECTION_CLASS_NAME,e.SLIDER_WRAPPER_CLASS_NAME,e.SINGLE_SLIDE_CLASS_NAME];let r=null;const i={added:[],removed:[],changed:[]};async function l(e,n){if(i[e])for(const t of i[e])await t(n)}function s(e){return e.nodeType===e.ELEMENT_NODE?e:null}function c(e){return o.isSection(e)?{targetType:"section",element:e}:o.isSlider(e)?{targetType:"slider",element:e}:o.isSlide(e)?{targetType:"slide",element:e}:{targetType:"other",element:e}}function a(e){return n.some((n=>e.classList.contains(n)))}return{startListen:function(){if(null!==r)return;r=new MutationObserver((e=>{e.forEach((e=>{"childList"===e.type?function(e){e.addedNodes.forEach((e=>{const n=s(e);null!==n&&a(n)&&l("added",c(n))})),e.removedNodes.forEach((e=>{const n=s(e);null!==n&&a(n)&&l("removed",c(n))}))}(e):"attributes"===e.type&&function(e){const n=s(e.target);null!==n&&a(n)&&"class"===e.attributeName&&l("changed",c(n))}(e)}))}));const e=o.getRootNodeOrThrow();r.observe(e,{childList:!0,attributes:!0,subtree:!0,attributeFilter:["class"]}),f.debug("ObserverEventHandler: Listeners [started]")},stopListen:function(){null!==r&&(r.disconnect(),f.debug("ObserverEventHandler: Listeners [stopped]"))},on:function(e,n){i[e]?(i[e].push(n),f.debug(`ObserverEventHandler: event listener "${e}" [added]`,[{callback:n,total:i[e].length}])):t(`ObserverEventHandler: unsupported event type: "${e}" (supported types list [${Object.keys(i).join(", ")}])`)},off:function(e,n){i[e]&&(i[e]=i[e].filter((e=>e!==n)),f.debug(`ObserverEventHandler: event listener "${e}" [removed]`,[{callback:n,total:i[e].length}]))}}})();return(()=>{let n;const r={};return m.startListen(),m.on("ready",(async()=>{!function(){const n=``;document.head.insertAdjacentHTML("beforeend",n)}(),d(),i.observer.enabled&&(S.on("removed",(e=>{"slider"===e.targetType&&n.forEach((n=>{n.sliderList.find((n=>n.elemRef===e.element))&&n.sliderList.remove(e.element)}))})),S.on("added",(e=>{if("slider"!==e.targetType)return;const t=o.sectionParentOrNull(e.element);n.forEach((n=>{if(n.elemRef!==t)return;let r=0;n.sliderList.length>=1&&(r=n.sliderList.findIndex((n=>"after"===o.isElementBeforeOrAfter(e.element,n.elemRef)))),-1!==r&&(n.sliderList.insert(r,e.element),o.addClassName(e.element,u.sliderWrapper))}))})),S.startListen());const t=o.getRootNodeOrThrow().querySelectorAll("."+i.constants.SECTION_CLASS_NAME);t instanceof Error||(n=function(e){const n=new Array(e.length);let t=0;function r(){const e=(t+1+n.length)%n.length;u(e,n),t=e}function l(){const e=(t-1+n.length)%n.length;u(e,n),t=e}function s(e){e<0||e>=n.length||(u(e,n),t=e)}function c(e){if(n.length<=0)return;const t=n.findIndex((n=>n.elemRef===e));t>=0&&n.splice(t,1)}function a(e,t){const r=w(t);n.splice(e,0,r)}function d(){return n[t]}function u(e,n){e<0||e>=n.length||(n[e].elemRef.scrollIntoView({behavior:i.scroll.behavior||"smooth",block:"start"}),o.removeClassName(n[t].elemRef,"active"),o.addClassName(n[e].elemRef,"active"))}return e.forEach(((e,r)=>{e.classList.contains("active")&&(t=r);const o=w(e);n[r]=o})),o.addClassName(n[t].elemRef,"active"),s(t),new Proxy(n,{get(e,n){if(n in e)return e[n];switch(n){case"insert":return a;case"remove":return c;case"scrollNext":return r;case"scrollPrev":return l;case"scrollToSection":return s;case"getCurrentSection":return d;default:return}}})}(t),p.init(n),f.info("OnePage: has been initialized correctly"))})),{setOptions:s,on:function(e,n){r[e]?(r[e].push(n),f.debug(`OnePageEventHandler: event listener '${e}' [added]`)):t(`OnePageEventHandler: unsupported event type: '${e}' (supported types list [${Object.keys(r).join(", ")}])`)},off:function(e,n){r[e]&&(r[e]=r[e].filter((e=>e!==n)),f.debug(`OnePageEventHandler: event listener '${e}' [removed]`))}}})()}(); \ No newline at end of file + `};return new Proxy(n,{get(n,t){if(t in n){const r=n[t];return`${e.CLASS_NAME_PREFIX}-${r}`}}})})(),v=(()=>{const e=Object.freeze({DEBUG:1,INFO:2,WARN:4,ERROR:8,ALL:15});let n=(()=>{let n=0;for(const t of l.logger.levels)Object.hasOwn(e,t)&&n{let e=!1,n={x:0,y:0};const r={wheel:[]};function o(e){n.y=0,n.x=0,0!==e.deltaY&&(n.y=e.deltaY>0?1:-1),0!==e.deltaX&&(n.x=e.deltaX>0?1:-1),async function(e,n){if(r[e])for(const t of r[e])await t(n)}("wheel",e)}function i(){Object.keys(r).forEach((e=>r[e]=[])),n={x:0,y:0}}return{startListen:function(){e||(window?.WheelEvent&&document.addEventListener("wheel",o,!1),v.debug("WheelEventHandler: wheel event listeners [started]"),e=!0)},stopListen:function(){e&&(window?.WheelEvent&&document.removeEventListener("wheel",o,!1),v.debug("WheelEventHandler: wheel event listeners [stoped]"),i(),e=!1)},getAxis:function(e="vertical"){return 0===n.x&&0===n.y?0:(()=>{switch(e){case"vertical":return n.y;case"horizontal":return n.x}return 0})()},on:function(e,n){r[e]?(r[e].push(n),v.debug(`WheelEventHandler: event listener '${e}' [added]`)):t(`WheelEventHandler: unsupported event type: '${e}' (supported types list [${Object.keys(r).join(", ")}])`)},off:function(e,n){r[e]&&(r[e]=r[e].filter((e=>e!==n)),v.debug(`WheelEventHandler: event listener '${e}' [removed]`))},isEventAvailable:function(){return void 0!==window?.WheelEvent}}})(),h=(()=>{let n=!1;const r={start:{x:0,y:0},end:{x:0,y:0},reset(){this.start={x:0,y:0},this.end={x:0,y:0}}},i={swipeStart:[],swipeEnd:[]};async function s(e,n){if(i[e])for(const t of i[e])await t(n)}async function c(n){if(r.reset(),window?.TouchEvent&&n instanceof TouchEvent)r.start.x=n.changedTouches[0].clientX,r.start.y=n.changedTouches[0].clientY;else if(window?.PointerEvent&&n instanceof PointerEvent){const t=n.button;e.MOUSE_SWIPE_DISCARDED_BUTTONS.includes(t)||(r.start.x=n.clientX,r.start.y=n.clientY),v.debug("SwipeEventHandler: pressed mouse button code on 'swipeStart': ",[t])}s("swipeStart",n)}async function a(n){if(window?.TouchEvent&&n instanceof TouchEvent)r.end.x=n.changedTouches[0].clientX,r.end.y=n.changedTouches[0].clientY;else if(window?.PointerEvent&&n instanceof PointerEvent){const t=n.button;e.MOUSE_SWIPE_DISCARDED_BUTTONS.includes(t)||(r.end.x=n.clientX,r.end.y=n.clientY),v.debug("SwipeEventHandler: pressed mouse button code on 'swipeMove': ",[t])}}async function d(n){if(window?.TouchEvent&&n instanceof TouchEvent)r.end.x=n.changedTouches[0].clientX,r.end.y=n.changedTouches[0].clientY;else if(window?.PointerEvent&&n instanceof PointerEvent){const t=n.button;e.MOUSE_SWIPE_DISCARDED_BUTTONS.includes(t)||0===n.clientX&&0===n.clientY||(r.end.x=n.clientX,r.end.y=n.clientY),v.debug("SwipeEventHandler: pressed mouse button code on 'swipeEnd': ",[t])}s("swipeEnd",n)}function u(){Object.keys(i).forEach((e=>i[e]=[])),r.reset()}return{startListen:function(){n||(window?.PointerEvent&&l.scroll.swipeScroll&&!o()&&(document.addEventListener("pointerdown",c,!1),document.addEventListener("pointermove",a,!1),document.addEventListener("pointerup",d,!1),document.addEventListener("pointercancel",d,!1),v.debug("SwipeEventHandler: pointer event listeners [started]")),window?.TouchEvent&&o()&&(document.addEventListener("touchstart",c,!1),document.addEventListener("touchend",d,!1),document.addEventListener("touchmove",a,!1),document.addEventListener("touchcancel",d,!1),v.debug("SwipeEventHandler: touch event listeners [started]")),n=!0)},stopListen:function(){n&&(window?.PointerEvent&&l.scroll.swipeScroll&&void 0===window?.TouchEvent&&(document.removeEventListener("pointerdown",c,!1),document.removeEventListener("pointermove",a,!1),document.removeEventListener("pointerup",d,!1),document.removeEventListener("pointercancel",d,!1),v.debug("SwipeEventHandler: pointer event listeners [stoped]")),window?.TouchEvent&&(document.removeEventListener("touchstart",c,!1),document.removeEventListener("touchend",d,!1),document.removeEventListener("touchmove",a,!1),document.removeEventListener("touchcancel",d,!1),v.debug("SwipeEventHandler: touch event listeners [stopped]")),u(),n=!1)},getAxis:function(n="vertical"){const t=r.end.x-r.start.x,o=r.end.y-r.start.y;if(0===t&&0===o)return 0;if(Math.abs(t)>Math.abs(o)){if(Math.abs(t)>=e.TOUCH_THRESHOLD&&"horizontal"===n)return t>=0?-1:1}else if(Math.abs(o)>=e.TOUCH_THRESHOLD&&"vertical"===n)return o>=0?-1:1;return 0},on:function(e,n){i[e]?(i[e].push(n),v.debug(`SwipeEventHandler: event listener '${e}' [added]`)):t(`SwipeEventHandler: unsupported event type: '${e}' (supported types list [${Object.keys(i).join(", ")}])`)},off:function(e,n){i[e]&&(i[e]=i[e].filter((e=>e!==n)),v.debug(`SwipeEventHandler: event listener '${e}' [removed]`))},isEventAvailable:function(){const e=void 0!==window?.TouchEvent,n=void 0!==window?.PointerEvent;return e||n&&l.scroll.swipeScroll}}})(),p=(()=>{let e=!1,n={lastKey:"",axisKey:"",reset(){this.lastKey="",this.axisKey=""}};const r={keydown:[],keyup:[]};async function o(e,n){if(r[e])for(const t of r[e])await t(n)}async function i(e){n.lastKey=e.key,n.axisKey=e.key,o("keydown",e)}async function s(e){e.key===n.axisKey&&(n.axisKey=""),o("keyup",e)}function c(e="vertical"){if("vertical"===e){if(l.keybindings.up.includes(n.axisKey))return-1;if(l.keybindings.down.includes(n.axisKey))return 1}else if("horizontal"===e){if(l.keybindings.right.includes(n.axisKey))return 1;if(l.keybindings.left.includes(n.axisKey))return-1}return 0}function a(){Object.keys(r).forEach((e=>r[e]=[])),n.reset()}return{startListen:function(){e||(window?.KeyboardEvent&&l.scroll.keyboardScroll&&(window.addEventListener("keydown",i,!1),window.addEventListener("keydown",s,!1),v.debug("KeyEventHandler: key event listeners [started]")),e=!0)},stopListen:function(){e&&(window?.KeyboardEvent&&l.scroll.keyboardScroll&&(window.removeEventListener("keydown",i,!1),window.removeEventListener("keyup",s,!1),v.debug("KeyEventHandler: key event listeners [stopped]")),a(),e=!1)},getAxis:c,on:function(e,n){r[e]?(r[e].push(n),v.debug(`KeyEventHandler: event listener '${e}' [added]`)):t(`KeyEventHandler: unsupported event type: '${e}' (supported types list [${Object.keys(r).join(", ")}])`)},off:function(e,n){r[e]&&(r[e]=r[e].filter((e=>e!==n)),v.debug(`KeyEventHandler: event listener '${e}' [removed]`))},scrollWithKeys:function(e,n="vertical"){if(0===e.scrollHeight&&0===e.scrollWidth)return;const{speed:t}=l.scroll;if("vertical"===n){const r=c(n)*t+e.scrollTop;e.scroll({top:r})}else if("horizontal"===n){const r=c(n)*t+e.scrollLeft;e.scroll({left:r})}v.debug(`KeyEventHandler: scroll with keys called with speed '${t}' on element`,[e])},isEventAvailable:function(){return void 0!==window?.KeyboardEvent&&l.scroll.keyboardScroll}}})(),m=(()=>{let e,n=!1;function t(e,n){return!!l.scroll.overflowScroll&&(()=>{const t=e.elemRef.querySelector("."+f.overflow);if(null!=t&&i.isElementScrollable(t)){if(-1===n)return!i.hasReachedStartOfScroll(t);if(1===n)return!i.hasReachedEndOfScroll(t)}return!1})()}function r(e,n){return window?.PointerEvent&&e instanceof PointerEvent||window?.TouchEvent&&e instanceof TouchEvent?h.getAxis(n):window?.WheelEvent&&e instanceof WheelEvent?E.getAxis(n):window?.KeyboardEvent&&e instanceof KeyboardEvent?p.getAxis(n):0}function o(o,i){if(n)return;n=!0,e&&clearTimeout(e),e=setTimeout((()=>{n=!1}),l.scroll.unlockTimeout);const s=i.getCurrentSection();s.sliderList.length>=1&&function(e,n){if(window?.WheelEvent&&e instanceof WheelEvent)return;const t=r(e,"horizontal");t>0?n.next():t<0&&n.prev()}(o,s.sliderList[0]);const c=r(o,"vertical");c>0?t(s,1)||i.scrollNext():c<0&&(t(s,-1)||i.scrollPrev())}return{init:function(e){E.isEventAvailable()&&(E.on("wheel",(n=>o(n,e))),E.startListen()),p.isEventAvailable()&&(p.on("keydown",(n=>{const r=n.target;if("INPUT"!==r?.tagName&&"TEXTAREA"!==r?.tagName){const r=e.getCurrentSection();if(t(r,p.getAxis("vertical"))){const e=r.elemRef.querySelector("."+f.overflow);null!==e&&(p.scrollWithKeys(e,"vertical"),n.preventDefault())}else o(n,e)}})),p.startListen()),h.isEventAvailable()&&(h.on("swipeEnd",(n=>o(n,e))),h.startListen())}}})();function g(e,n={}){const t=function(){let n=e.item(0).parentElement;if(null===n||!i.isSlider(n)){const t=i.wrapChildrenInTag(e,"div");return n?.appendChild(t),t}return n}(),r=Array.from(e);let o=n.initialIndex??0;function s(e,n){e<0||e>=n.length||n[e].scrollIntoView({behavior:l.scroll.behavior,block:"start",inline:"center"})}return i.addClassName(t,f.sliderWrapper),r.forEach((e=>{i.addClassName(e,f.slide)})),{elemRef:t,slideList:r,insert:function(e,n){r.splice(e,0,n)},remove:function(e){if(r.length<=0)return;const n=r.findIndex((n=>n===e));n>=0&&r.splice(n,1)},prev:function(){o=(o-1+r.length)%r.length,s(o,r)},next:function(){o=(o+1+r.length)%r.length,s(o,r)},goToSlide:function(e){e<0||e>=r.length||(s(o,r),o=e)}}}function w(e){const n=e,t=function(e){const n=new Array;function t(e){if(n.length<=0)return;const t=n.findIndex((n=>n.elemRef===e));t>=0&&n.splice(t,1)}function r(e,t){const r=g(t.querySelectorAll("."+l.constants.SINGLE_SLIDE_CLASS_NAME));n.splice(e,0,r)}return function(){const t=i.getSliderListInElement(e),r=i.getSingleSlidesInElementOrNull(e);null!==r&&(()=>{for(const e in r){const n=r[e].parentElement;if(null!==n)return!i.isSlider(n)}return!0})()&&n.push(g(r)),t.forEach((e=>{const t=g(e.querySelectorAll("."+l.constants.SINGLE_SLIDE_CLASS_NAME));n.push(t)}))}(),new Proxy(n,{get(e,n){if(n in e)return e[n];switch(n){case"insert":return r;case"remove":return t;default:return}}})}(e);return function(){if(i.addClassName(e,f.section),l.scroll.overflowScroll){const n=i.wrapAllChildrenOf(e);i.addClassName(n,f.overflow)}}(),{elemRef:n,sliderList:t}}const S=(()=>{let e=!1;const n={DOMContentLoaded:[],load:[],beforeUnload:[],unload:[]};async function r(e,t){if(n[e])for(const r of n[e])await r(t)}function o(e){r("DOMContentLoaded",e)}function i(e){r("load",e)}function l(e){r("beforeUnload",e)}function s(e){r("unload",e)}return{startListen:function(){e||(document.addEventListener("DOMContentLoaded",o),window.addEventListener("load",i),window.addEventListener("beforeunload",l),window.addEventListener("unload",s),v.debug("DOMLoadEventHandler: Listeners [started]"),e=!0)},stopListen:function(){e&&(document.removeEventListener("DOMContentLoaded",o),window.removeEventListener("load",i),window.removeEventListener("beforeunload",l),window.removeEventListener("unload",s),v.debug("DOMLoadEventHandler: Listeners [stopped]"),Object.keys(n).forEach((e=>n[e]=[])),e=!1)},on:function(e,r){n[e]?(n[e].push(r),v.debug(`DOMEventHandler: event listener '${e}' [added]`)):t(`DOMEventHandler: unsupported event type: '${e}' (supported types list [${Object.keys(n).join(", ")}])`)},off:function(e,t){n[e]&&(n[e]=n[e].filter((e=>e!==t)),v.debug(`DOMEventHandler: event listener '${e}' [removed]`))}}})(),y=(()=>{const n=[e.SECTION_CLASS_NAME,e.SLIDER_WRAPPER_CLASS_NAME,e.SINGLE_SLIDE_CLASS_NAME];let r=null;const o={added:[],removed:[],changed:[]};async function l(e,n){if(o[e])for(const t of o[e])await t(n)}function s(e){return e.nodeType===e.ELEMENT_NODE?e:null}function c(e){return i.isSection(e)?{targetType:"section",element:e}:i.isSlider(e)?{targetType:"slider",element:e}:i.isSlide(e)?{targetType:"slide",element:e}:{targetType:"other",element:e}}function a(e){return n.some((n=>e.classList.contains(n)))}function d(){Object.keys(o).forEach((e=>o[e]=[])),r=null}return{startListen:function(){if(null!==r)return;r=new MutationObserver((e=>{e.forEach((e=>{"childList"===e.type?function(e){e.addedNodes.forEach((e=>{const n=s(e);null!==n&&a(n)&&l("added",c(n))})),e.removedNodes.forEach((e=>{const n=s(e);null!==n&&a(n)&&l("removed",c(n))}))}(e):"attributes"===e.type&&function(e){const n=s(e.target);null!==n&&a(n)&&"class"===e.attributeName&&l("changed",c(n))}(e)}))}));const e=i.getRootNodeOrThrow();r.observe(e,{childList:!0,attributes:!0,subtree:!0,attributeFilter:["class"]}),v.debug("ObserverEventHandler: Listeners [started]")},stopListen:function(){null!==r&&(r.disconnect(),d(),v.debug("ObserverEventHandler: Listeners [stopped]"))},on:function(e,n){o[e]?(o[e].push(n),v.debug(`ObserverEventHandler: event listener "${e}" [added]`,[{callback:n,total:o[e].length}])):t(`ObserverEventHandler: unsupported event type: "${e}" (supported types list [${Object.keys(o).join(", ")}])`)},off:function(e,n){o[e]&&(o[e]=o[e].filter((e=>e!==n)),v.debug(`ObserverEventHandler: event listener "${e}" [removed]`,[{callback:n,total:o[e].length}]))}}})();return(()=>{let n;const r={};return S.startListen(),S.on("DOMContentLoaded",(async()=>{!function(){const n=``;document.head.insertAdjacentHTML("beforeend",n)}(),u(),l.observer.enabled&&(y.on("removed",(e=>{"slider"===e.targetType&&n.forEach((n=>{n.sliderList.find((n=>n.elemRef===e.element))&&n.sliderList.remove(e.element)}))})),y.on("added",(e=>{if("slider"!==e.targetType)return;const t=i.sectionParentOrNull(e.element);n.forEach((n=>{if(n.elemRef!==t)return;let r=0;n.sliderList.length>=1&&(r=n.sliderList.findIndex((n=>"after"===i.isElementBeforeOrAfter(e.element,n.elemRef)))),-1!==r&&(n.sliderList.insert(r,e.element),i.addClassName(e.element,f.sliderWrapper))}))})),y.startListen());const t=i.getRootNodeOrThrow().querySelectorAll("."+l.constants.SECTION_CLASS_NAME);t instanceof Error||(n=function(e){const n=new Array(e.length);let t=0;function r(){const e=(t+1+n.length)%n.length;u(e,n),t=e}function o(){const e=(t-1+n.length)%n.length;u(e,n),t=e}function s(e){e<0||e>=n.length||(u(e,n),t=e)}function c(e){if(n.length<=0)return;const t=n.findIndex((n=>n.elemRef===e));t>=0&&n.splice(t,1)}function a(e,t){const r=w(t);n.splice(e,0,r)}function d(){return n[t]}function u(e,n){e<0||e>=n.length||(n[e].elemRef.scrollIntoView({behavior:l.scroll.behavior||"smooth",block:"start"}),i.removeClassName(n[t].elemRef,"active"),i.addClassName(n[e].elemRef,"active"))}return e.forEach(((e,r)=>{e.classList.contains("active")&&(t=r);const o=w(e);n[r]=o})),i.addClassName(n[t].elemRef,"active"),s(t),new Proxy(n,{get(e,n){if(n in e)return e[n];switch(n){case"insert":return a;case"remove":return c;case"scrollNext":return r;case"scrollPrev":return o;case"scrollToSection":return s;case"getCurrentSection":return d;default:return}}})}(t),m.init(n),v.info("OnePage: has been initialized correctly"))})),{setOptions:c,on:function(e,n){r[e]?(r[e].push(n),v.debug(`OnePageEventHandler: event listener '${e}' [added]`)):t(`OnePageEventHandler: unsupported event type: '${e}' (supported types list [${Object.keys(r).join(", ")}])`)},off:function(e,n){r[e]&&(r[e]=r[e].filter((e=>e!==n)),v.debug(`OnePageEventHandler: event listener '${e}' [removed]`))}}})()}(); \ No newline at end of file