UNPKG

@mdbootstrap/bootstrap-dark-mode

Version:

Responsive Dark Mode theme built with Bootstrap 5 with Dark Mode toggle button that switches between dark and light themes.

341 lines (273 loc) 9.01 kB
/** * -------------------------------------------------------------------------- * Bootstrap (v5.1.3): util/index.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ const MAX_UID = 1000000; const MILLISECONDS_MULTIPLIER = 1000; const TRANSITION_END = 'transitionend'; // Shoutout AngusCroll (https://goo.gl/pxwQGp) const toType = (obj) => { if (obj === null || obj === undefined) { return `${obj}`; } return {}.toString .call(obj) .match(/\s([a-z]+)/i)[1] .toLowerCase(); }; /** * -------------------------------------------------------------------------- * Public Util Api * -------------------------------------------------------------------------- */ const getUID = (prefix) => { do { prefix += Math.floor(Math.random() * MAX_UID); } while (document.getElementById(prefix)); return prefix; }; const getSelector = (element) => { let selector = element.getAttribute('data-mdb-target'); if (!selector || selector === '#') { let hrefAttr = element.getAttribute('href'); // The only valid content that could double as a selector are IDs or classes, // so everything starting with `#` or `.`. If a "real" URL is used as the selector, // `document.querySelector` will rightfully complain it is invalid. // See https://github.com/twbs/bootstrap/issues/32273 if (!hrefAttr || (!hrefAttr.includes('#') && !hrefAttr.startsWith('.'))) { return null; } // Just in case some CMS puts out a full URL with the anchor appended if (hrefAttr.includes('#') && !hrefAttr.startsWith('#')) { hrefAttr = `#${hrefAttr.split('#')[1]}`; } selector = hrefAttr && hrefAttr !== '#' ? hrefAttr.trim() : null; } return selector; }; const getSelectorFromElement = (element) => { const selector = getSelector(element); if (selector) { return document.querySelector(selector) ? selector : null; } return null; }; const getElementFromSelector = (element) => { const selector = getSelector(element); return selector ? document.querySelector(selector) : null; }; const getTransitionDurationFromElement = (element) => { if (!element) { return 0; } // Get transition-duration of the element let { transitionDuration, transitionDelay } = window.getComputedStyle(element); const floatTransitionDuration = Number.parseFloat(transitionDuration); const floatTransitionDelay = Number.parseFloat(transitionDelay); // Return 0 if element or transition duration is not found if (!floatTransitionDuration && !floatTransitionDelay) { return 0; } // If multiple durations are defined, take the first transitionDuration = transitionDuration.split(',')[0]; transitionDelay = transitionDelay.split(',')[0]; return ( (Number.parseFloat(transitionDuration) + Number.parseFloat(transitionDelay)) * MILLISECONDS_MULTIPLIER ); }; const triggerTransitionEnd = (element) => { element.dispatchEvent(new Event(TRANSITION_END)); }; const isElement = (obj) => { if (!obj || typeof obj !== 'object') { return false; } if (typeof obj.jquery !== 'undefined') { obj = obj[0]; } return typeof obj.nodeType !== 'undefined'; }; const getElement = (obj) => { if (isElement(obj)) { // it's a jQuery object or a node element return obj.jquery ? obj[0] : obj; } if (typeof obj === 'string' && obj.length > 0) { return document.querySelector(obj); } return null; }; const typeCheckConfig = (componentName, config, configTypes) => { Object.keys(configTypes).forEach((property) => { const expectedTypes = configTypes[property]; const value = config[property]; const valueType = value && isElement(value) ? 'element' : toType(value); if (!new RegExp(expectedTypes).test(valueType)) { throw new TypeError( `${componentName.toUpperCase()}: Option "${property}" provided type "${valueType}" but expected type "${expectedTypes}".` ); } }); }; const isVisible = (element) => { if (!isElement(element) || element.getClientRects().length === 0) { return false; } return getComputedStyle(element).getPropertyValue('visibility') === 'visible'; }; const isDisabled = (element) => { if (!element || element.nodeType !== Node.ELEMENT_NODE) { return true; } if (element.classList.contains('disabled')) { return true; } if (typeof element.disabled !== 'undefined') { return element.disabled; } return element.hasAttribute('disabled') && element.getAttribute('disabled') !== 'false'; }; const findShadowRoot = (element) => { if (!document.documentElement.attachShadow) { return null; } // Can find the shadow root otherwise it'll return the document if (typeof element.getRootNode === 'function') { const root = element.getRootNode(); return root instanceof ShadowRoot ? root : null; } if (element instanceof ShadowRoot) { return element; } // when we don't find a shadow root if (!element.parentNode) { return null; } return findShadowRoot(element.parentNode); }; const noop = () => {}; /** * Trick to restart an element's animation * * @param {HTMLElement} element * @return void * * @see https://www.charistheo.io/blog/2021/02/restart-a-css-animation-with-javascript/#restarting-a-css-animation */ const reflow = (element) => { // eslint-disable-next-line no-unused-expressions element.offsetHeight; }; const getjQuery = () => { const { jQuery } = window; if (jQuery && !document.body.hasAttribute('data-mdb-no-jquery')) { return jQuery; } return null; }; const DOMContentLoadedCallbacks = []; const onDOMContentLoaded = (callback) => { if (document.readyState === 'loading') { // add listener on the first call when the document is in loading state if (!DOMContentLoadedCallbacks.length) { document.addEventListener('DOMContentLoaded', () => { DOMContentLoadedCallbacks.forEach((callback) => callback()); }); } DOMContentLoadedCallbacks.push(callback); } else { callback(); } }; const isRTL = () => document.documentElement.dir === 'rtl'; const defineJQueryPlugin = (plugin) => { onDOMContentLoaded(() => { const $ = getjQuery(); /* istanbul ignore if */ if ($) { const name = plugin.NAME; const JQUERY_NO_CONFLICT = $.fn[name]; $.fn[name] = plugin.jQueryInterface; $.fn[name].Constructor = plugin; $.fn[name].noConflict = () => { $.fn[name] = JQUERY_NO_CONFLICT; return plugin.jQueryInterface; }; } }); }; const execute = (callback) => { if (typeof callback === 'function') { callback(); } }; const executeAfterTransition = (callback, transitionElement, waitForTransition = true) => { if (!waitForTransition) { execute(callback); return; } const durationPadding = 5; const emulatedDuration = getTransitionDurationFromElement(transitionElement) + durationPadding; let called = false; const handler = ({ target }) => { if (target !== transitionElement) { return; } called = true; transitionElement.removeEventListener(TRANSITION_END, handler); execute(callback); }; transitionElement.addEventListener(TRANSITION_END, handler); setTimeout(() => { if (!called) { triggerTransitionEnd(transitionElement); } }, emulatedDuration); }; /** * Return the previous/next element of a list. * * @param {array} list The list of elements * @param activeElement The active element * @param shouldGetNext Choose to get next or previous element * @param isCycleAllowed * @return {Element|elem} The proper element */ const getNextActiveElement = (list, activeElement, shouldGetNext, isCycleAllowed) => { let index = list.indexOf(activeElement); // if the element does not exist in the list return an element depending on the direction and if cycle is allowed if (index === -1) { return list[!shouldGetNext && isCycleAllowed ? list.length - 1 : 0]; } const listLength = list.length; index += shouldGetNext ? 1 : -1; if (isCycleAllowed) { index = (index + listLength) % listLength; } return list[Math.max(0, Math.min(index, listLength - 1))]; }; export { getElement, getUID, getSelectorFromElement, getElementFromSelector, getTransitionDurationFromElement, triggerTransitionEnd, isElement, typeCheckConfig, isVisible, isDisabled, findShadowRoot, noop, getNextActiveElement, reflow, getjQuery, onDOMContentLoaded, isRTL, defineJQueryPlugin, execute, executeAfterTransition, };