import $ from 'jquery'; import rafSchd from 'raf-schd'; import { throttle } from 'throttle-debounce'; const { VPData } = window; const { __ } = VPData; const $wnd = $(window); /** * Emit Resize Event. */ function windowResizeEmit() { if (typeof window.Event === 'function') { // modern browsers window.dispatchEvent(new window.Event('resize')); } else { // for IE and other old browsers // causes deprecation warning on modern browsers const evt = window.document.createEvent('UIEvents'); evt.initUIEvent('resize', true, false, window, 0); window.dispatchEvent(evt); } } const visibilityData = {}; let shouldCheckVisibility = false; let checkVisibilityTimeout = false; let isFocusVisible = false; // fix portfolio inside Tabs and Accordions // check visibility by timer https://stackoverflow.com/questions/19669786/check-if-element-is-visible-in-dom/33456469 // // https://github.com/nk-crew/visual-portfolio/issues/11 // https://github.com/nk-crew/visual-portfolio/issues/113 function checkVisibility() { clearTimeout(checkVisibilityTimeout); if (!shouldCheckVisibility) { return; } const $items = $('.vp-portfolio__ready'); if ($items.length) { let isVisibilityChanged = false; $items.each(function () { const { vpf } = this; if (!vpf) { return; } const currentState = visibilityData[vpf.uid] || 'none'; visibilityData[vpf.uid] = this.offsetParent === null ? 'hidden' : 'visible'; // changed from hidden to visible. if ( currentState === 'hidden' && visibilityData[vpf.uid] === 'visible' ) { isVisibilityChanged = true; } }); // resize, if visibility changed. if (isVisibilityChanged) { windowResizeEmit(); } } else { shouldCheckVisibility = false; } // run again. checkVisibilityTimeout = setTimeout(checkVisibility, 500); } // run check function only after portfolio inited. $(document).on('inited.vpf', (event) => { if (event.namespace !== 'vpf') { return; } shouldCheckVisibility = true; checkVisibility(); }); /** * If the most recent user interaction was via the keyboard; * and the key press did not include a meta, alt/option, or control key; * then the modality is keyboard. Otherwise, the modality is not keyboard. */ document.addEventListener( 'keydown', function (e) { if (e.metaKey || e.altKey || e.ctrlKey) { return; } isFocusVisible = true; }, true ); /** * If at any point a user clicks with a pointing device, ensure that we change * the modality away from keyboard. * This avoids the situation where a user presses a key on an already focused * element, and then clicks on a different element, focusing it with a * pointing device, while we still think we're in keyboard modality. */ document.addEventListener( 'mousedown', () => { isFocusVisible = false; }, true ); document.addEventListener( 'pointerdown', () => { isFocusVisible = false; }, true ); document.addEventListener( 'touchstart', () => { isFocusVisible = false; }, true ); /** * Main VP class */ class VP { constructor($item, userOptions) { const self = this; self.$item = $item; // get id from class const classes = $item[0].className.split(/\s+/); for (let k = 0; k < classes.length; k += 1) { if (classes[k] && /^vp-uid-/.test(classes[k])) { self.uid = classes[k].replace(/^vp-uid-/, ''); } if (classes[k] && /^vp-id-/.test(classes[k])) { self.id = classes[k].replace(/^vp-id-/, ''); } } if (!self.uid) { // eslint-disable-next-line no-console console.error(__.couldnt_retrieve_vp); return; } self.href = window.location.href; self.$items_wrap = $item.find('.vp-portfolio__items'); self.$slider_thumbnails_wrap = $item.find('.vp-portfolio__thumbnails'); self.$pagination = $item.find('.vp-portfolio__pagination-wrap'); self.$filter = $item.find('.vp-portfolio__filter-wrap'); self.$sort = $item.find('.vp-portfolio__sort-wrap'); // find single filter block. if (self.id) { self.$filter = self.$filter.add( `.vp-single-filter.vp-id-${self.id} .vp-portfolio__filter-wrap` ); } // find single sort block. if (self.id) { self.$sort = self.$sort.add( `.vp-single-sort.vp-id-${self.id} .vp-portfolio__sort-wrap` ); } // user options self.userOptions = userOptions; self.firstRun = true; self.init(); } // emit event // Example: // $(document).on('init.vpf', function (event, infiniteObject) { // console.log(infiniteObject); // }); emitEvent(event, data) { data = data ? [this].concat(data) : [this]; this.$item.trigger(`${event}.vpf`, data); this.$item.trigger(`${event}.vpf-uid-${this.uid}`, data); } /** * Init */ init() { const self = this; // destroy if already inited if (!self.firstRun) { self.destroy(); } self.destroyed = false; self.$item.addClass('vp-portfolio__ready'); // init options self.initOptions(); // init events self.initEvents(); // init layout self.initLayout(); // init custom colors self.initCustomColors(); self.emitEvent('init'); if (self.id) { $(`.vp-single-filter.vp-id-${self.id}`) .addClass('vp-single-filter__ready') .parent('.vp-portfolio__layout-elements') .addClass('vp-portfolio__layout-elements__ready'); $(`.vp-single-sort.vp-id-${self.id}`) .addClass('vp-single-sort__ready') .parent('.vp-portfolio__layout-elements') .addClass('vp-portfolio__layout-elements__ready'); } // resized self.resized(); // images loaded self.imagesLoaded(); self.emitEvent('inited'); self.firstRun = false; } /** * Check if script loaded in preview. * * @return {boolean} is in preview. */ isPreview() { const self = this; return !!self.$item.closest('#vp_preview').length; } /** * Called after resized container. */ resized() { windowResizeEmit(); this.emitEvent('resized'); } /** * Images loaded. */ imagesLoaded() { const self = this; if (!self.$items_wrap.imagesLoaded) { return; } self.$items_wrap.imagesLoaded().progress(() => { this.emitEvent('imagesLoaded'); }); } /** * Destroy */ destroy() { const self = this; // remove loaded class self.$item.removeClass('vp-portfolio__ready'); if (self.id) { $(`.vp-single-filter.vp-id-${self.id}`) .removeClass('vp-single-filter__ready') .parent('.vp-portfolio__layout-elements') .removeClass('vp-portfolio__layout-elements__ready'); $(`.vp-single-sort.vp-id-${self.id}`) .removeClass('vp-single-sort__ready') .parent('.vp-portfolio__layout-elements') .removeClass('vp-portfolio__layout-elements__ready'); } // destroy events self.destroyEvents(); // remove all generated styles self.removeStyle(); self.renderStyle(); self.emitEvent('destroy'); self.destroyed = true; } /** * Add style to the current portfolio list * * @param {string} selector css selector * @param {string} styles object with styles * @param {string} media string with media query */ addStyle(selector, styles, media) { media = media || ''; const self = this; const { uid } = self; if (!self.stylesList) { self.stylesList = {}; } if (typeof self.stylesList[uid] === 'undefined') { self.stylesList[uid] = {}; } if (typeof self.stylesList[uid][media] === 'undefined') { self.stylesList[uid][media] = {}; } if (typeof self.stylesList[uid][media][selector] === 'undefined') { self.stylesList[uid][media][selector] = {}; } self.stylesList[uid][media][selector] = $.extend( self.stylesList[uid][media][selector], styles ); self.emitEvent('addStyle', [selector, styles, media, self.stylesList]); } /** * Remove style from the current portfolio list * * @param {string} selector css selector (if not set - removed all styles) * @param {string} styles object with styles * @param {string} media string with media query */ removeStyle(selector, styles, media) { media = media || ''; const self = this; const { uid } = self; if (!self.stylesList) { self.stylesList = {}; } if (typeof self.stylesList[uid] !== 'undefined' && !selector) { self.stylesList[uid] = {}; } if ( typeof self.stylesList[uid] !== 'undefined' && typeof self.stylesList[uid][media] !== 'undefined' && typeof self.stylesList[uid][media][selector] !== 'undefined' && selector ) { delete self.stylesList[uid][media][selector]; } self.emitEvent('removeStyle', [selector, styles, self.stylesList]); } /** * Render style for the current portfolio list */ renderStyle() { const self = this; // timeout for the case, when styles added one by one const { uid } = self; let stylesString = ''; if (!self.stylesList) { self.stylesList = {}; } // create string with styles if (typeof self.stylesList[uid] !== 'undefined') { Object.keys(self.stylesList[uid]).forEach((m) => { // media if (m) { stylesString += `@media ${m} {`; } Object.keys(self.stylesList[uid][m]).forEach((s) => { // selector const selectorParent = `.vp-uid-${uid}`; let selector = `${selectorParent} ${s}`; // add parent selector after `,`. selector = selector.replace( /, |,/g, `, ${selectorParent} ` ); stylesString += `${selector} {`; Object.keys(self.stylesList[uid][m][s]).forEach((p) => { // property and value stylesString += `${p}:${self.stylesList[uid][m][s][p]};`; }); stylesString += '}'; }); // media if (m) { stylesString += '}'; } }); } // add in style tag let $style = $(`#vp-style-${uid}`); if (!$style.length) { $style = $('