UNPKG

@cxco/ui-faq

Version:

FAQ Module using @cxco default packages

374 lines (312 loc) 12.2 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.delay = delay; exports.scroll = scroll; exports.scrollToEnd = scrollToEnd; exports.setNavigationButtons = setNavigationButtons; exports.onCreateAnimate = onCreateAnimate; exports.onRemoveAnimate = onRemoveAnimate; exports.toggle = toggle; exports.isDifferentArray = isDifferentArray; exports.urlRegexBuilder = urlRegexBuilder; exports.findCategoriesByFaqId = findCategoriesByFaqId; exports.prepareAndCategoriesByFaqId = prepareAndCategoriesByFaqId; exports.addLightBoxListeners = addLightBoxListeners; exports.getCategoryById = exports.toggleCategory = exports.ANIMATION_DURATION = void 0; var basicLightBox = require('basiclightbox'); /* eslint-disable no-useless-escape */ // refers to the CSS animation duration var ANIMATION_DURATION = 400; /** * Selects or Deselects a category in a list. * Unselected categories are marked as -1. * Selected categories are marked with their category ID. * @param {Object} category - the category object containing ID and depth. * @param {number[]} selectedCategories - the array containing the selected categories. * @returns {number[]} the modified selected categories list. */ exports.ANIMATION_DURATION = ANIMATION_DURATION; var toggleCategory = function toggleCategory(category, selectedCategories) { // -1 when not selected, if (selectedCategories[category.depth] === -1) { selectedCategories = selectCategoryAtDepth(category, selectedCategories); } else { selectedCategories = selectedCategories.splice(0, category.depth + 1); // if is already the selected category, deselect it. if (selectedCategories[category.depth] === category.id) { selectedCategories[category.depth] = -1; } else { selectedCategories = selectCategoryAtDepth(category, selectedCategories); } } return selectedCategories; }; /** * Add the category ID at the correct depth in the selectedCategories category list. * If the category has sub-categories add the next unselected step (-1) to the list. * @param {Object} category - the category object containing ID and depth. * @param {number[]} selectedCategories - the array containing the selected categories. * @returns {number[]} the modified selected categories list. */ exports.toggleCategory = toggleCategory; var selectCategoryAtDepth = function selectCategoryAtDepth(category, selectedCategories) { selectedCategories[category.depth] = category.id; // if it's possible to continue to drill down if (category.subCategories.length > 0) { selectedCategories.push(-1); } return selectedCategories; }; /** * Calculates total height of children for a given element collection * @param {HTMLElement[]} HTMLCollection */ var checkContentHeight = function checkContentHeight(HTMLCollection) { var acc = 0; for (var i = 0; i < HTMLCollection.length; i++) { var elm = HTMLCollection[i]; var elmStyle = window.getComputedStyle(elm); var verticalMargins = parseInt(elmStyle.getPropertyValue('margin-top')) + parseInt(elmStyle.getPropertyValue('margin-bottom')); var height = elm.offsetHeight || 0; acc += height + verticalMargins; } return acc; }; /** * Returns a property value when being animated at a specific point in time. * @param {number} time - current time in milliseconds * @param {number} beginVal - initial property value (e.g. height) * @param {number} endVal - end property value (e.g. height) * @param {number} duration - total animation time * * @return {number} - computed property at the specific point in time `{time}` */ var easeOutQuad = function easeOutQuad(time, beginVal, endVal, duration) { return beginVal > endVal ? beginVal - (time /= duration) * beginVal : -endVal * (time /= duration) * (time - 2) + beginVal; }; /** * Animates property `styleAttr` in element `elm`. * @param {HTMLElement} elm * @param {string} styleAttr - css property to be animated * @param {number} beginVal * @param {number} endVal * @param {number} duration */ var animate = function animate(elm, styleAttr, beginVal, endVal, duration) { var startTime = new Date().getTime(); var request; var step = function step() { var elapsedTime = new Date().getTime() - startTime; if (elapsedTime >= duration) { window.cancelAnimationFrame(request); elm.removeAttribute('style'); } else { elm.style[styleAttr] = "".concat(easeOutQuad(elapsedTime, beginVal, endVal, duration), "px"); request = window.requestAnimationFrame(step); } }; request = window.requestAnimationFrame(step); }; /** * Go through the nodes of a tree to find a category by it's ID. * @param {number} id * @param {Object[]} categoryTree */ var getCategoryById = function getCategoryById(id, categoryTree) { var found = categoryTree.find(function (ele) { return ele.id === id; }); if (typeof found === 'undefined') { var nextChildren; if (Array.isArray(categoryTree)) { nextChildren = categoryTree.flatMap(function (cat) { return cat.subCategories.length > 0 ? cat.subCategories : []; }); } if (typeof nextChildren === 'undefined' || nextChildren.length === 0) { return undefined; } return getCategoryById(id, nextChildren); } return found; }; /** * Delays the output to next() for _delay milliseconds * * @param {number} _delay seconds of delay */ exports.getCategoryById = getCategoryById; function delay(_delay) { return function (value) { return new Promise(function (resolve, reject) { setTimeout(function () { resolve(value); }, _delay); }); }; } /** * Calculates the scroll amount and scrolls left or right. * Requires `.cxco-c-slide__content` and `.cxco-c-slide__item` * @param {string} direction */ function scroll(direction) { var container = document.querySelector('.cxco-c-slide__content'); var item = document.querySelector('.cxco-c-slide__item'); var itemWidth = item.getBoundingClientRect().width; var style = window.getComputedStyle ? getComputedStyle(item, null) : item.currentStyle; var marginLeft = parseFloat(style.marginLeft) || 0; var marginRight = parseFloat(style.marginRight) || 0; var outerWidth = itemWidth + marginRight + marginLeft; return new Promise(function (resolve, reject) { if (direction === 'right') { container.scrollLeft = Math.ceil(container.scrollLeft + outerWidth); } else if (direction === 'left') { container.scrollLeft = Math.ceil(container.scrollLeft - outerWidth); } resolve(container); }); } function scrollToEnd() { var container = document.querySelector('.cxco-c-slide__content'); new Promise(function (resolve, reject) { container.scrollLeft = container.scrollWidth; resolve(container); }).then(delay(ANIMATION_DURATION)).then(setNavigationButtons(container)); } /** * Enables or disables the nav buttons by calculating container width and scrollwidth * Requires `.cxco-c-slide__content`, `.cxco-c-slide__button--prev` and `.cxco-c-slide__button--next` * @param {HTMLElement} element */ function setNavigationButtons() { var container = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : document.querySelector('.cxco-c-slide__content'); return new Promise(function (resolve, reject) { var navPrev = document.querySelector('.cxco-c-slide__button--prev'); var navNext = document.querySelector('.cxco-c-slide__button--next'); var left = Math.ceil(container.scrollLeft); var right = Math.ceil(container.offsetWidth); var width = Math.ceil(container.scrollWidth); var showPrev = left === 0 || left === 1; var showNext = left + right === width || left + right === width + 1; navPrev.disabled = showPrev; navNext.disabled = showNext; resolve(); }); } /** * Toggles `is-creating` class, with a delay in between. * Scrolls container. * @param {HTMLElement} element */ function onCreateAnimate(element, shouldScroll) { element.setAttribute('data-state', 'creating'); return new Promise(function (resolve) { return resolve(element); }).then(delay(ANIMATION_DURATION)).then(function () { element.setAttribute('data-state', 'created'); if (shouldScroll) { scroll('right'); } }); } /** * Adds `removing` value to `data-state` * @param {HTMLElement} element */ function onRemoveAnimate(element) { element.setAttribute('data-state', 'removing'); return new Promise(function (resolve) { return resolve(element); }).then(delay(ANIMATION_DURATION)); } /** * * @param {HTMLElement} element */ function toggle(element) { var isOpening = element.getAttribute('aria-hidden') === 'false'; var beginVal = isOpening ? 0 : checkContentHeight(element.children); var endVal = isOpening ? checkContentHeight(element.children) : 0; animate(element, 'height', beginVal, endVal, ANIMATION_DURATION); } /** * Compare two arrays and check if they have the same content * @param {number[]} arr1 * @param {number[]} arr2 */ function isDifferentArray(arr1, arr2) { if (arr1 === arr2) return false; if (arr1.length !== arr2.length) return true; for (var i = 0; i < arr1.length; ++i) { if (arr1[i] !== arr2[i]) return true; } return false; } /** * Creates a RegEx based on a given `formatString`. * The `formatString` can have multiple formats such as * "#{categorycrumbs}/{categoryname}/{faqid}/{question}" or "#crumbs={categorycrumbs}&category={categoryname}&faqid={faqid}&question={question}". * - Replaces the separator between the {placeholders} with a non-capturing group * - Escapes "/" character * - Replaces the `{categorycrumbs}`, `{categoryname}`, `{faqid}` and `{question}` placeholders with a correct regex groups * @param {string} formatString */ function urlRegexBuilder(formatString) { return formatString.replace(/#([\?a-z=]*)/i, function (match, g1) { return g1 ? '#(?:' + g1 + ')' : '#'; }).replace(/(?:})([^{]*)(?:{)/gmi, '}(?:$1)\?{').replace(/\//gmi, '\\/').replace('{categorycrumbs}', '((?:(?:\\\d)+-?)*)').replace('{categoryname}', '([A-Z0-9 %]+)').replace('{faqid}', '(\\\d+)?').replace('{question}', '(.+)?'); } /** * Find the `categoryName` and `categoryCrumbs` for a FAQ in a Category tree. * It finds the first match, so if a FAQ is in multiple categories it only return the first. * This recursive function is best used when the categories start from a single node, and for that we can create a fake node and add the categories as it's children. * That fake node needs some fake properties, like the id=0 and thus '0-' is removed from the crumbs. * @param {Object} category * @param {Number} faqId * @param {String} crumbs */ function findCategoriesByFaqId(category, faqId, crumbs) { var _FAQ = category.items.find(function (faq) { return faq.id === faqId; }); if (_FAQ) { return { categoryCrumbs: (crumbs === '' && category.id !== 0 ? category.id : crumbs + '-' + category.id).replace(/^0-/, ''), categoryName: category.name }; } else if (category.subCategories.length > 0) { var result; for (var i = 0; result === undefined && i < category.subCategories.length; i++) { result = findCategoriesByFaqId(category.subCategories[i], faqId, crumbs === '' ? String(category.id) : crumbs + '-' + category.id); } return result; } return undefined; } /** * create a fake root node for the tree and add categories as it's children * @param {*} categories * @param {*} faqId * @param {*} crumbs */ function prepareAndCategoriesByFaqId(categories, faqId, crumbs) { var category = { id: 0, name: 'root', items: [] }; category.subCategories = categories; return findCategoriesByFaqId(category, faqId, crumbs); } function addLightBoxListeners(element) { element.querySelectorAll('.cxco-lightbox').forEach(function (element) { element.addEventListener('click', function (e) { try { basicLightBox.create(e.target.outerHTML).show(); } catch (e) { console.error(e); } }); }); }