first commit

This commit is contained in:
2024-07-15 11:28:08 +02:00
commit f52d538ea5
21891 changed files with 6161164 additions and 0 deletions

View File

@@ -0,0 +1,61 @@
export default class extends elementorModules.ViewModule {
getDefaultSettings() {
return {
selectors: {
elements: '.elementor-element',
nestedDocumentElements: '.elementor .elementor-element',
},
classes: {
editMode: 'elementor-edit-mode',
},
};
}
getDefaultElements() {
const selectors = this.getSettings( 'selectors' );
return {
$elements: this.$element.find( selectors.elements ).not( this.$element.find( selectors.nestedDocumentElements ) ),
};
}
getDocumentSettings( setting ) {
let elementSettings;
if ( this.isEdit ) {
elementSettings = {};
const settings = elementor.settings.page.model;
jQuery.each( settings.getActiveControls(), ( controlKey ) => {
elementSettings[ controlKey ] = settings.attributes[ controlKey ];
} );
} else {
elementSettings = this.$element.data( 'elementor-settings' ) || {};
}
return this.getItems( elementSettings, setting );
}
runElementsHandlers() {
this.elements.$elements.each( ( index, element ) => elementorFrontend.elementsHandler.runReadyTrigger( element ) );
}
onInit() {
this.$element = this.getSettings( '$element' );
super.onInit();
this.isEdit = this.$element.hasClass( this.getSettings( 'classes.editMode' ) );
if ( this.isEdit ) {
elementor.on( 'document:loaded', () => {
elementor.settings.page.model.on( 'change', this.onSettingsChange.bind( this ) );
} );
} else {
this.runElementsHandlers();
}
}
onSettingsChange() {}
}

View File

@@ -0,0 +1,57 @@
import Document from './document';
export default class extends elementorModules.ViewModule {
constructor( ...args ) {
super( ...args );
this.documents = {};
this.initDocumentClasses();
this.attachDocumentsClasses();
}
getDefaultSettings() {
return {
selectors: {
document: '.elementor',
},
};
}
getDefaultElements() {
const selectors = this.getSettings( 'selectors' );
return {
$documents: jQuery( selectors.document ),
};
}
initDocumentClasses() {
this.documentClasses = {
base: Document,
};
elementorFrontend.hooks.doAction( 'elementor/frontend/documents-manager/init-classes', this );
}
addDocumentClass( documentType, documentClass ) {
this.documentClasses[ documentType ] = documentClass;
}
attachDocumentsClasses() {
this.elements.$documents.each( ( index, document ) => this.attachDocumentClass( jQuery( document ) ) );
}
attachDocumentClass( $document ) {
const documentData = $document.data(),
documentID = documentData.elementorId,
documentType = documentData.elementorType,
DocumentClass = this.documentClasses[ documentType ] || this.documentClasses.base;
this.documents[ documentID ] = new DocumentClass( {
$element: $document,
id: documentID,
} );
}
}

View File

@@ -0,0 +1,149 @@
import globalHandler from './handlers/global';
import sectionHandlers from './handlers/section/section';
import columnHandlers from './handlers/column';
module.exports = function( $ ) {
const handlersInstances = {};
this.elementsHandlers = {
'accordion.default': () => import( /* webpackChunkName: 'accordion' */ './handlers/accordion' ),
'alert.default': () => import( /* webpackChunkName: 'alert' */ './handlers/alert' ),
'counter.default': () => import( /* webpackChunkName: 'counter' */ './handlers/counter' ),
'progress.default': () => import( /* webpackChunkName: 'progress' */ './handlers/progress' ),
'tabs.default': () => import( /* webpackChunkName: 'tabs' */ './handlers/tabs' ),
'toggle.default': () => import( /* webpackChunkName: 'toggle' */ './handlers/toggle' ),
'video.default': () => import( /* webpackChunkName: 'video' */ './handlers/video' ),
'image-carousel.default': () => import( /* webpackChunkName: 'image-carousel' */ './handlers/image-carousel' ),
'text-editor.default': () => import( /* webpackChunkName: 'text-editor' */ './handlers/text-editor' ),
};
const addGlobalHandlers = () => elementorFrontend.hooks.addAction( 'frontend/element_ready/global', globalHandler );
const addElementsHandlers = () => {
this.elementsHandlers.section = sectionHandlers;
this.elementsHandlers.column = columnHandlers;
$.each( this.elementsHandlers, ( elementName, Handlers ) => {
const elementData = elementName.split( '.' );
elementName = elementData[ 0 ];
const skin = elementData[ 1 ] || null;
this.attachHandler( elementName, Handlers, skin );
} );
};
const isClassHandler = ( Handler ) => Handler.prototype.getUniqueHandlerID;
const addHandlerWithHook = ( elementName, Handler, skin = 'default' ) => {
skin = skin ? '.' + skin : '';
elementorFrontend.hooks.addAction( `frontend/element_ready/${ elementName }${ skin }`, ( $element ) => {
if ( isClassHandler( Handler ) ) {
this.addHandler( Handler, { $element }, true );
} else {
const handlerValue = Handler();
if ( handlerValue instanceof Promise ) {
handlerValue.then( ( { default: dynamicHandler } ) => {
this.addHandler( dynamicHandler, { $element }, true );
} );
} else {
this.addHandler( handlerValue, { $element }, true );
}
}
} );
};
this.addHandler = function( HandlerClass, options ) {
const elementID = options.$element.data( 'model-cid' );
let handlerID;
// If element is in edit mode
if ( elementID ) {
handlerID = HandlerClass.prototype.getConstructorID();
if ( ! handlersInstances[ elementID ] ) {
handlersInstances[ elementID ] = {};
}
const oldHandler = handlersInstances[ elementID ][ handlerID ];
if ( oldHandler ) {
oldHandler.onDestroy();
}
}
const newHandler = new HandlerClass( options );
if ( elementID ) {
handlersInstances[ elementID ][ handlerID ] = newHandler;
}
};
this.attachHandler = ( elementName, Handlers, skin ) => {
if ( ! Array.isArray( Handlers ) ) {
Handlers = [ Handlers ];
}
Handlers.forEach( ( Handler ) => addHandlerWithHook( elementName, Handler, skin ) );
};
this.getHandler = function( handlerName ) {
if ( ! handlerName ) {
return;
}
const elementHandler = this.elementsHandlers[ handlerName ];
if ( isClassHandler( elementHandler ) ) {
return elementHandler;
}
return new Promise( ( res ) => {
elementHandler().then( ( { default: dynamicHandler } ) => {
res( dynamicHandler );
} );
} );
};
this.getHandlers = function( handlerName ) {
elementorCommon.helpers.softDeprecated( 'getHandlers', '3.1.0', 'elementorFrontend.elementsHandler.getHandler' );
if ( handlerName ) {
return this.getHandler( handlerName );
}
return this.elementsHandlers;
};
this.runReadyTrigger = function( scope ) {
if ( elementorFrontend.config.is_static ) {
return;
}
// Initializing the `$scope` as frontend jQuery instance
const $scope = jQuery( scope ),
elementType = $scope.attr( 'data-element_type' );
if ( ! elementType ) {
return;
}
elementorFrontend.hooks.doAction( 'frontend/element_ready/global', $scope, $ );
elementorFrontend.hooks.doAction( 'frontend/element_ready/' + elementType, $scope, $ );
if ( 'widget' === elementType ) {
elementorFrontend.hooks.doAction( 'frontend/element_ready/' + $scope.attr( 'data-widget_type' ), $scope, $ );
}
};
this.init = () => {
addGlobalHandlers();
addElementsHandlers();
};
};

View File

@@ -0,0 +1,374 @@
/* global elementorFrontendConfig */
import '../public-path';
import DocumentsManager from './documents-manager';
import Storage from 'elementor-common/utils/storage';
import environment from 'elementor-common/utils/environment';
import YouTubeApiLoader from './utils/video-api/youtube-loader';
import VimeoApiLoader from './utils/video-api/vimeo-loader';
import URLActions from './utils/url-actions';
import Swiper from './utils/swiper-bc';
import LightboxManager from './utils/lightbox/lightbox-manager';
import AssetsLoader from './utils/assets-loader';
import Shapes from 'elementor/modules/shapes/assets/js/frontend/frontend';
const EventManager = require( 'elementor-utils/hooks' ),
ElementsHandler = require( 'elementor-frontend/elements-handlers-manager' ),
AnchorsModule = require( 'elementor-frontend/utils/anchors' );
export default class Frontend extends elementorModules.ViewModule {
constructor( ...args ) {
super( ...args );
this.config = elementorFrontendConfig;
this.config.legacyMode = {
get elementWrappers() {
if ( elementorFrontend.isEditMode() ) {
elementorCommon.helpers.hardDeprecated( 'elementorFrontend.config.legacyMode.elementWrappers', '3.1.0', 'elementorFrontend.config.experimentalFeatures.e_dom_optimization' );
}
return ! elementorFrontend.config.experimentalFeatures.e_dom_optimization;
},
};
this.populateActiveBreakpointsConfig();
}
// TODO: BC since 2.5.0
get Module() {
if ( this.isEditMode() ) {
parent.elementorCommon.helpers.hardDeprecated( 'elementorFrontend.Module', '2.5.0', 'elementorModules.frontend.handlers.Base' );
}
return elementorModules.frontend.handlers.Base;
}
getDefaultSettings() {
return {
selectors: {
elementor: '.elementor',
adminBar: '#wpadminbar',
},
classes: {
ie: 'elementor-msie',
},
};
}
getDefaultElements() {
const defaultElements = {
window: window,
$window: jQuery( window ),
$document: jQuery( document ),
$head: jQuery( document.head ),
$body: jQuery( document.body ),
$deviceMode: jQuery( '<span>', { id: 'elementor-device-mode', class: 'elementor-screen-only' } ),
};
defaultElements.$body.append( defaultElements.$deviceMode );
return defaultElements;
}
bindEvents() {
this.elements.$window.on( 'resize', () => this.setDeviceModeData() );
}
/**
* @deprecated 2.4.0 Use just `this.elements` instead
*/
getElements( elementName ) {
return this.getItems( this.elements, elementName );
}
/**
* @deprecated 2.4.0 This method was never in use
*/
getPageSettings( settingName ) {
const settingsObject = this.isEditMode() ? elementor.settings.page.model.attributes : this.config.settings.page;
return this.getItems( settingsObject, settingName );
}
getGeneralSettings( settingName ) {
if ( this.isEditMode() ) {
parent.elementorCommon.helpers.softDeprecated( 'getGeneralSettings', '3.0.0', 'getKitSettings and remove the `elementor_` prefix' );
}
return this.getKitSettings( `elementor_${ settingName }` );
}
getKitSettings( settingName ) {
// TODO: use Data API.
return this.getItems( this.config.kit, settingName );
}
getCurrentDeviceMode() {
return getComputedStyle( this.elements.$deviceMode[ 0 ], ':after' ).content.replace( /"/g, '' );
}
getDeviceSetting( deviceMode, settings, settingKey ) {
const devices = [ 'desktop', 'tablet', 'mobile' ];
let deviceIndex = devices.indexOf( deviceMode );
while ( deviceIndex > 0 ) {
const currentDevice = devices[ deviceIndex ],
fullSettingKey = settingKey + '_' + currentDevice,
deviceValue = settings[ fullSettingKey ];
if ( deviceValue ) {
return deviceValue;
}
deviceIndex--;
}
return settings[ settingKey ];
}
getCurrentDeviceSetting( settings, settingKey ) {
return this.getDeviceSetting( elementorFrontend.getCurrentDeviceMode(), settings, settingKey );
}
isEditMode() {
return this.config.environmentMode.edit;
}
isWPPreviewMode() {
return this.config.environmentMode.wpPreview;
}
initDialogsManager() {
let dialogsManager;
this.getDialogsManager = () => {
if ( ! dialogsManager ) {
dialogsManager = new DialogsManager.Instance();
}
return dialogsManager;
};
}
initOnReadyComponents() {
this.utils = {
youtube: new YouTubeApiLoader(),
vimeo: new VimeoApiLoader(),
anchors: new AnchorsModule(),
get lightbox() {
return LightboxManager.getLightbox();
},
urlActions: new URLActions(),
swiper: Swiper,
environment: environment,
assetsLoader: new AssetsLoader(),
};
// TODO: BC since 2.4.0
this.modules = {
StretchElement: elementorModules.frontend.tools.StretchElement,
Masonry: elementorModules.utils.Masonry,
};
this.elementsHandler.init();
if ( this.isEditMode() ) {
elementor.once( 'document:loaded', () => this.onDocumentLoaded() );
} else {
this.onDocumentLoaded();
}
}
initOnReadyElements() {
this.elements.$wpAdminBar = this.elements.$document.find( this.getSettings( 'selectors.adminBar' ) );
}
addUserAgentClasses() {
for ( const [ key, value ] of Object.entries( environment ) ) {
if ( value ) {
this.elements.$body.addClass( 'e--ua-' + key );
}
}
}
addIeCompatibility() {
const el = document.createElement( 'div' ),
supportsGrid = 'string' === typeof el.style.grid;
if ( ! environment.ie && supportsGrid ) {
return;
}
this.elements.$body.addClass( this.getSettings( 'classes.ie' ) );
const msieCss = '<link rel="stylesheet" id="elementor-frontend-css-msie" href="' + this.config.urls.assets + 'css/frontend-msie.min.css?' + this.config.version + '" type="text/css" />';
this.elements.$body.append( msieCss );
}
setDeviceModeData() {
this.elements.$body.attr( 'data-elementor-device-mode', this.getCurrentDeviceMode() );
}
addListenerOnce( listenerID, event, callback, to ) {
if ( ! to ) {
to = this.elements.$window;
}
if ( ! this.isEditMode() ) {
to.on( event, callback );
return;
}
this.removeListeners( listenerID, event, to );
if ( to instanceof jQuery ) {
const eventNS = event + '.' + listenerID;
to.on( eventNS, callback );
} else {
to.on( event, callback, listenerID );
}
}
removeListeners( listenerID, event, callback, from ) {
if ( ! from ) {
from = this.elements.$window;
}
if ( from instanceof jQuery ) {
const eventNS = event + '.' + listenerID;
from.off( eventNS, callback );
} else {
from.off( event, callback, listenerID );
}
}
// Based on underscore function
debounce( func, wait ) {
let timeout;
return function() {
const context = this,
args = arguments;
const later = () => {
timeout = null;
func.apply( context, args );
};
const callNow = ! timeout;
clearTimeout( timeout );
timeout = setTimeout( later, wait );
if ( callNow ) {
func.apply( context, args );
}
};
}
waypoint( $element, callback, options ) {
const defaultOptions = {
offset: '100%',
triggerOnce: true,
};
options = jQuery.extend( defaultOptions, options );
const correctCallback = function() {
const element = this.element || this,
result = callback.apply( element, arguments );
// If is Waypoint new API and is frontend
if ( options.triggerOnce && this.destroy ) {
this.destroy();
}
return result;
};
return $element.elementorWaypoint( correctCallback, options );
}
muteMigrationTraces() {
jQuery.migrateMute = true;
jQuery.migrateTrace = false;
}
/**
* Initialize the modules' widgets handlers.
*/
initModules() {
let handlers = {
shapes: Shapes,
};
elementorFrontend.trigger( 'elementor/modules/init:before' );
Object.entries( handlers ).forEach( ( [ moduleName, ModuleClass ] ) => {
this.modulesHandlers[ moduleName ] = new ModuleClass();
} );
}
populateActiveBreakpointsConfig() {
this.config.responsive.activeBreakpoints = {};
Object.entries( this.config.responsive.breakpoints ).forEach( ( [ breakpointKey, breakpointData ] ) => {
if ( breakpointData.is_enabled ) {
this.config.responsive.activeBreakpoints[ breakpointKey ] = breakpointData;
}
} );
}
init() {
this.hooks = new EventManager();
this.storage = new Storage();
this.elementsHandler = new ElementsHandler( jQuery );
this.modulesHandlers = {};
this.addUserAgentClasses();
this.addIeCompatibility();
this.setDeviceModeData();
this.initDialogsManager();
if ( this.isEditMode() ) {
this.muteMigrationTraces();
}
// Keep this line before `initOnReadyComponents` call
this.elements.$window.trigger( 'elementor/frontend/init' );
this.initModules();
this.initOnReadyElements();
this.initOnReadyComponents();
}
onDocumentLoaded() {
this.documentsManager = new DocumentsManager();
this.trigger( 'components:init' );
new LightboxManager();
}
}
window.elementorFrontend = new Frontend();
if ( ! elementorFrontend.isEditMode() ) {
jQuery( () => elementorFrontend.init() );
}

View File

@@ -0,0 +1,13 @@
import TabsModule from './base-tabs';
export default class Accordion extends TabsModule {
getDefaultSettings() {
const defaultSettings = super.getDefaultSettings();
return {
...defaultSettings,
showTabFn: 'slideDown',
hideTabFn: 'slideUp',
};
}
}

View File

@@ -0,0 +1,25 @@
export default class Alert extends elementorModules.frontend.handlers.Base {
getDefaultSettings() {
return {
selectors: {
dismissButton: '.elementor-alert-dismiss',
},
};
}
getDefaultElements() {
const selectors = this.getSettings( 'selectors' );
return {
$dismissButton: this.$element.find( selectors.dismissButton ),
};
}
bindEvents() {
this.elements.$dismissButton.on( 'click', this.onDismissButtonClick.bind( this ) );
}
onDismissButtonClick() {
this.$element.fadeOut();
}
}

View File

@@ -0,0 +1,161 @@
export default class BackgroundSlideshow extends elementorModules.frontend.handlers.SwiperBase {
getDefaultSettings() {
return {
classes: {
swiperContainer: 'elementor-background-slideshow swiper-container',
swiperWrapper: 'swiper-wrapper',
swiperSlide: 'elementor-background-slideshow__slide swiper-slide',
slideBackground: 'elementor-background-slideshow__slide__image',
kenBurns: 'elementor-ken-burns',
kenBurnsActive: 'elementor-ken-burns--active',
kenBurnsIn: 'elementor-ken-burns--in',
kenBurnsOut: 'elementor-ken-burns--out',
},
};
}
getSwiperOptions() {
const elementSettings = this.getElementSettings();
const swiperOptions = {
grabCursor: false,
slidesPerView: 1,
slidesPerGroup: 1,
loop: 'yes' === elementSettings.background_slideshow_loop,
speed: elementSettings.background_slideshow_transition_duration,
autoplay: {
delay: elementSettings.background_slideshow_slide_duration,
stopOnLastSlide: ! elementSettings.background_slideshow_loop,
},
handleElementorBreakpoints: true,
on: {
slideChange: () => {
if ( elementSettings.background_slideshow_ken_burns ) {
this.handleKenBurns();
}
},
},
};
if ( 'yes' === elementSettings.background_slideshow_loop ) {
swiperOptions.loopedSlides = this.getSlidesCount();
}
switch ( elementSettings.background_slideshow_slide_transition ) {
case 'fade':
swiperOptions.effect = 'fade';
swiperOptions.fadeEffect = {
crossFade: true,
};
break;
case 'slide_down':
swiperOptions.autoplay.reverseDirection = true;
case 'slide_up':
swiperOptions.direction = 'vertical';
break;
}
return swiperOptions;
}
buildSwiperElements() {
const classes = this.getSettings( 'classes' ),
elementSettings = this.getElementSettings(),
direction = 'slide_left' === elementSettings.background_slideshow_slide_transition ? 'ltr' : 'rtl',
$container = jQuery( '<div>', { class: classes.swiperContainer, dir: direction } ),
$wrapper = jQuery( '<div>', { class: classes.swiperWrapper } ),
kenBurnsActive = elementSettings.background_slideshow_ken_burns;
let slideInnerClass = classes.slideBackground;
if ( kenBurnsActive ) {
slideInnerClass += ' ' + classes.kenBurns;
const kenBurnsDirection = 'in' === elementSettings.background_slideshow_ken_burns_zoom_direction ? 'kenBurnsIn' : 'kenBurnsOut';
slideInnerClass += ' ' + classes[ kenBurnsDirection ];
}
this.elements.$slides = jQuery();
elementSettings.background_slideshow_gallery.forEach( ( slide ) => {
const $slide = jQuery( '<div>', { class: classes.swiperSlide } ),
$slidebg = jQuery( '<div>', {
class: slideInnerClass,
style: 'background-image: url("' + slide.url + '");',
} );
$slide.append( $slidebg );
$wrapper.append( $slide );
this.elements.$slides = this.elements.$slides.add( $slide );
} );
$container.append( $wrapper );
this.$element.prepend( $container );
this.elements.$backgroundSlideShowContainer = $container;
}
async initSlider() {
if ( 1 >= this.getSlidesCount() ) {
return;
}
const elementSettings = this.getElementSettings();
const Swiper = elementorFrontend.utils.swiper;
this.swiper = await new Swiper( this.elements.$backgroundSlideShowContainer, this.getSwiperOptions() );
// Expose the swiper instance in the frontend
this.elements.$backgroundSlideShowContainer.data( 'swiper', this.swiper );
if ( elementSettings.background_slideshow_ken_burns ) {
this.handleKenBurns();
}
}
activate() {
this.buildSwiperElements();
this.initSlider();
}
deactivate() {
if ( this.swiper ) {
this.swiper.destroy();
this.elements.$backgroundSlideShowContainer.remove();
}
}
run() {
if ( 'slideshow' === this.getElementSettings( 'background_background' ) ) {
this.activate();
} else {
this.deactivate();
}
}
onInit() {
super.onInit();
if ( this.getElementSettings( 'background_slideshow_gallery' ) ) {
this.run();
}
}
onDestroy() {
super.onDestroy();
this.deactivate();
}
onElementChange( propertyName ) {
if ( 'background_background' === propertyName ) {
this.run();
}
}
}

View File

@@ -0,0 +1,47 @@
import BaseHandler from './base';
export default class SwiperHandlerBase extends BaseHandler {
getInitialSlide() {
const editSettings = this.getEditSettings();
return editSettings.activeItemIndex ? editSettings.activeItemIndex - 1 : 0;
}
getSlidesCount() {
return this.elements.$slides.length;
}
// This method live-handles the 'Pause On Hover' control's value being changed in the Editor Panel
togglePauseOnHover( toggleOn ) {
if ( toggleOn ) {
this.elements.$swiperContainer.on( {
mouseenter: () => {
this.swiper.autoplay.stop();
},
mouseleave: () => {
this.swiper.autoplay.start();
},
} );
} else {
this.elements.$swiperContainer.off( 'mouseenter mouseleave' );
}
}
handleKenBurns() {
const settings = this.getSettings();
if ( this.$activeImageBg ) {
this.$activeImageBg.removeClass( settings.classes.kenBurnsActive );
}
this.activeItemIndex = this.swiper ? this.swiper.activeIndex : this.getInitialSlide();
if ( this.swiper ) {
this.$activeImageBg = jQuery( this.swiper.slides[ this.activeItemIndex ] ).children( '.' + settings.classes.slideBackground );
} else {
this.$activeImageBg = jQuery( this.elements.$slides[ 0 ] ).children( '.' + settings.classes.slideBackground );
}
this.$activeImageBg.addClass( settings.classes.kenBurnsActive );
}
}

View File

@@ -0,0 +1,206 @@
export default class baseTabs extends elementorModules.frontend.handlers.Base {
getDefaultSettings() {
return {
selectors: {
tablist: '[role="tablist"]',
tabTitle: '.elementor-tab-title',
tabContent: '.elementor-tab-content',
},
classes: {
active: 'elementor-active',
},
showTabFn: 'show',
hideTabFn: 'hide',
toggleSelf: true,
hidePrevious: true,
autoExpand: true,
keyDirection: {
ArrowLeft: elementorFrontendConfig.is_rtl ? 1 : -1,
ArrowUp: -1,
ArrowRight: elementorFrontendConfig.is_rtl ? -1 : 1,
ArrowDown: 1,
},
};
}
getDefaultElements() {
const selectors = this.getSettings( 'selectors' );
return {
$tabTitles: this.findElement( selectors.tabTitle ),
$tabContents: this.findElement( selectors.tabContent ),
};
}
activateDefaultTab() {
const settings = this.getSettings();
if ( ! settings.autoExpand || ( 'editor' === settings.autoExpand && ! this.isEdit ) ) {
return;
}
const defaultActiveTab = this.getEditSettings( 'activeItemIndex' ) || 1,
originalToggleMethods = {
showTabFn: settings.showTabFn,
hideTabFn: settings.hideTabFn,
};
// Toggle tabs without animation to avoid jumping
this.setSettings( {
showTabFn: 'show',
hideTabFn: 'hide',
} );
this.changeActiveTab( defaultActiveTab );
// Return back original toggle effects
this.setSettings( originalToggleMethods );
}
handleKeyboardNavigation( event ) {
const tab = event.currentTarget,
$tabList = jQuery( tab.closest( this.getSettings( 'selectors' ).tablist ) ),
$tabs = $tabList.find( this.getSettings( 'selectors' ).tabTitle ),
isVertical = 'vertical' === $tabList.attr( 'aria-orientation' );
switch ( event.key ) {
case 'ArrowLeft':
case 'ArrowRight':
if ( isVertical ) {
return;
}
break;
case 'ArrowUp':
case 'ArrowDown':
if ( ! isVertical ) {
return;
}
event.preventDefault();
break;
case 'Home':
event.preventDefault();
$tabs.first().focus();
return;
case 'End':
event.preventDefault();
$tabs.last().focus();
return;
default:
return;
}
const tabIndex = tab.getAttribute( 'data-tab' ) - 1,
direction = this.getSettings( 'keyDirection' )[ event.key ],
nextTab = $tabs[ tabIndex + direction ];
if ( nextTab ) {
nextTab.focus();
} else if ( -1 === tabIndex + direction ) {
$tabs.last().focus();
} else {
$tabs.first().focus();
}
}
deactivateActiveTab( tabIndex ) {
const settings = this.getSettings(),
activeClass = settings.classes.active,
activeFilter = tabIndex ? '[data-tab="' + tabIndex + '"]' : '.' + activeClass,
$activeTitle = this.elements.$tabTitles.filter( activeFilter ),
$activeContent = this.elements.$tabContents.filter( activeFilter );
$activeTitle.add( $activeContent ).removeClass( activeClass );
$activeTitle.attr( {
tabindex: '-1',
'aria-selected': 'false',
'aria-expanded': 'false',
} );
$activeContent[ settings.hideTabFn ]();
$activeContent.attr( 'hidden', 'hidden' );
}
activateTab( tabIndex ) {
const settings = this.getSettings(),
activeClass = settings.classes.active,
$requestedTitle = this.elements.$tabTitles.filter( '[data-tab="' + tabIndex + '"]' ),
$requestedContent = this.elements.$tabContents.filter( '[data-tab="' + tabIndex + '"]' ),
animationDuration = 'show' === settings.showTabFn ? 0 : 400;
$requestedTitle.add( $requestedContent ).addClass( activeClass );
$requestedTitle.attr( {
tabindex: '0',
'aria-selected': 'true',
'aria-expanded': 'true',
} );
$requestedContent[ settings.showTabFn ]( animationDuration, () => elementorFrontend.elements.$window.trigger( 'resize' ) );
$requestedContent.removeAttr( 'hidden' );
}
isActiveTab( tabIndex ) {
return this.elements.$tabTitles.filter( '[data-tab="' + tabIndex + '"]' ).hasClass( this.getSettings( 'classes.active' ) );
}
bindEvents() {
this.elements.$tabTitles.on( {
keydown: ( event ) => {
// Support for old markup that includes an `<a>` tag in the tab
if ( jQuery( event.target ).is( 'a' ) && `Enter` === event.key ) {
event.preventDefault();
}
// We listen to keydowon event for these keys in order to prevent undesired page scrolling
if ( [ 'End', 'Home', 'ArrowUp', 'ArrowDown' ].includes( event.key ) ) {
this.handleKeyboardNavigation( event );
}
},
keyup: ( event ) => {
switch ( event.key ) {
case 'ArrowLeft':
case 'ArrowRight':
this.handleKeyboardNavigation( event );
break;
case 'Enter':
case 'Space':
event.preventDefault();
this.changeActiveTab( event.currentTarget.getAttribute( 'data-tab' ) );
break;
}
},
click: ( event ) => {
event.preventDefault();
this.changeActiveTab( event.currentTarget.getAttribute( 'data-tab' ) );
},
} );
}
onInit( ...args ) {
super.onInit( ...args );
this.activateDefaultTab();
}
onEditSettingsChange( propertyName ) {
if ( 'activeItemIndex' === propertyName ) {
this.activateDefaultTab();
}
}
changeActiveTab( tabIndex ) {
const isActiveTab = this.isActiveTab( tabIndex ),
settings = this.getSettings();
if ( ( settings.toggleSelf || ! isActiveTab ) && settings.hidePrevious ) {
this.deactivateActiveTab();
}
if ( ! settings.hidePrevious && isActiveTab ) {
this.deactivateActiveTab( tabIndex );
}
if ( ! isActiveTab ) {
this.activateTab( tabIndex );
}
}
}

View File

@@ -0,0 +1,242 @@
module.exports = elementorModules.ViewModule.extend( {
$element: null,
editorListeners: null,
onElementChange: null,
onEditSettingsChange: null,
onPageSettingsChange: null,
isEdit: null,
__construct: function( settings ) {
if ( ! this.isActive( settings ) ) {
return;
}
this.$element = settings.$element;
this.isEdit = this.$element.hasClass( 'elementor-element-edit-mode' );
if ( this.isEdit ) {
this.addEditorListeners();
}
},
isActive: function() {
return true;
},
findElement: function( selector ) {
var $mainElement = this.$element;
return $mainElement.find( selector ).filter( function() {
return jQuery( this ).closest( '.elementor-element' ).is( $mainElement );
} );
},
getUniqueHandlerID: function( cid, $element ) {
if ( ! cid ) {
cid = this.getModelCID();
}
if ( ! $element ) {
$element = this.$element;
}
return cid + $element.attr( 'data-element_type' ) + this.getConstructorID();
},
initEditorListeners: function() {
var self = this;
self.editorListeners = [
{
event: 'element:destroy',
to: elementor.channels.data,
callback: function( removedModel ) {
if ( removedModel.cid !== self.getModelCID() ) {
return;
}
self.onDestroy();
},
},
];
if ( self.onElementChange ) {
const elementType = self.getWidgetType() || self.getElementType();
let eventName = 'change';
if ( 'global' !== elementType ) {
eventName += ':' + elementType;
}
self.editorListeners.push( {
event: eventName,
to: elementor.channels.editor,
callback: function( controlView, elementView ) {
var elementViewHandlerID = self.getUniqueHandlerID( elementView.model.cid, elementView.$el );
if ( elementViewHandlerID !== self.getUniqueHandlerID() ) {
return;
}
self.onElementChange( controlView.model.get( 'name' ), controlView, elementView );
},
} );
}
if ( self.onEditSettingsChange ) {
self.editorListeners.push( {
event: 'change:editSettings',
to: elementor.channels.editor,
callback: function( changedModel, view ) {
if ( view.model.cid !== self.getModelCID() ) {
return;
}
self.onEditSettingsChange( Object.keys( changedModel.changed )[ 0 ] );
},
} );
}
[ 'page' ].forEach( function( settingsType ) {
var listenerMethodName = 'on' + settingsType[ 0 ].toUpperCase() + settingsType.slice( 1 ) + 'SettingsChange';
if ( self[ listenerMethodName ] ) {
self.editorListeners.push( {
event: 'change',
to: elementor.settings[ settingsType ].model,
callback: function( model ) {
self[ listenerMethodName ]( model.changed );
},
} );
}
} );
},
getEditorListeners: function() {
if ( ! this.editorListeners ) {
this.initEditorListeners();
}
return this.editorListeners;
},
addEditorListeners: function() {
var uniqueHandlerID = this.getUniqueHandlerID();
this.getEditorListeners().forEach( function( listener ) {
elementorFrontend.addListenerOnce( uniqueHandlerID, listener.event, listener.callback, listener.to );
} );
},
removeEditorListeners: function() {
var uniqueHandlerID = this.getUniqueHandlerID();
this.getEditorListeners().forEach( function( listener ) {
elementorFrontend.removeListeners( uniqueHandlerID, listener.event, null, listener.to );
} );
},
getElementType: function() {
return this.$element.data( 'element_type' );
},
getWidgetType: function() {
const widgetType = this.$element.data( 'widget_type' );
if ( ! widgetType ) {
return;
}
return widgetType.split( '.' )[ 0 ];
},
getID: function() {
return this.$element.data( 'id' );
},
getModelCID: function() {
return this.$element.data( 'model-cid' );
},
getElementSettings: function( setting ) {
let elementSettings = {};
const modelCID = this.getModelCID();
if ( this.isEdit && modelCID ) {
const settings = elementorFrontend.config.elements.data[ modelCID ],
attributes = settings.attributes;
let type = attributes.widgetType || attributes.elType;
if ( attributes.isInner ) {
type = 'inner-' + type;
}
let settingsKeys = elementorFrontend.config.elements.keys[ type ];
if ( ! settingsKeys ) {
settingsKeys = elementorFrontend.config.elements.keys[ type ] = [];
jQuery.each( settings.controls, ( name, control ) => {
if ( control.frontend_available ) {
settingsKeys.push( name );
}
} );
}
jQuery.each( settings.getActiveControls(), function( controlKey ) {
if ( -1 !== settingsKeys.indexOf( controlKey ) ) {
let value = attributes[ controlKey ];
if ( value.toJSON ) {
value = value.toJSON();
}
elementSettings[ controlKey ] = value;
}
} );
} else {
elementSettings = this.$element.data( 'settings' ) || {};
}
return this.getItems( elementSettings, setting );
},
getEditSettings: function( setting ) {
var attributes = {};
if ( this.isEdit ) {
attributes = elementorFrontend.config.elements.editSettings[ this.getModelCID() ].attributes;
}
return this.getItems( attributes, setting );
},
getCurrentDeviceSetting: function( settingKey ) {
return elementorFrontend.getCurrentDeviceSetting( this.getElementSettings(), settingKey );
},
onInit: function() {
if ( this.isActive( this.getSettings() ) ) {
elementorModules.ViewModule.prototype.onInit.apply( this, arguments );
}
},
onDestroy: function() {
if ( this.isEdit ) {
this.removeEditorListeners();
}
if ( this.unbindEvents ) {
this.unbindEvents();
}
},
} );

View File

@@ -0,0 +1,4 @@
import BackgroundSlideshow from './background-slideshow';
export default [ BackgroundSlideshow ];

View File

@@ -0,0 +1,31 @@
export default class Counter extends elementorModules.frontend.handlers.Base {
getDefaultSettings() {
return {
selectors: {
counterNumber: '.elementor-counter-number',
},
};
}
getDefaultElements() {
const selectors = this.getSettings( 'selectors' );
return {
$counterNumber: this.$element.find( selectors.counterNumber ),
};
}
onInit() {
super.onInit();
elementorFrontend.waypoint( this.elements.$counterNumber, () => {
const data = this.elements.$counterNumber.data(),
decimalDigits = data.toValue.toString().match( /\.(.*)/ );
if ( decimalDigits ) {
data.rounding = decimalDigits[ 1 ].length;
}
this.elements.$counterNumber.numerator( data );
} );
}
}

View File

@@ -0,0 +1,51 @@
class GlobalHandler extends elementorModules.frontend.handlers.Base {
getWidgetType() {
return 'global';
}
animate() {
const $element = this.$element,
animation = this.getAnimation();
if ( 'none' === animation ) {
$element.removeClass( 'elementor-invisible' );
return;
}
const elementSettings = this.getElementSettings(),
animationDelay = elementSettings._animation_delay || elementSettings.animation_delay || 0;
$element.removeClass( animation );
if ( this.currentAnimation ) {
$element.removeClass( this.currentAnimation );
}
this.currentAnimation = animation;
setTimeout( () => {
$element.removeClass( 'elementor-invisible' ).addClass( 'animated ' + animation );
}, animationDelay );
}
getAnimation() {
return this.getCurrentDeviceSetting( 'animation' ) || this.getCurrentDeviceSetting( '_animation' );
}
onInit( ...args ) {
super.onInit( ...args );
if ( this.getAnimation() ) {
elementorFrontend.waypoint( this.$element, () => this.animate() );
}
}
onElementChange( propertyName ) {
if ( /^_?animation/.test( propertyName ) ) {
this.animate();
}
}
}
export default ( $scope ) => {
elementorFrontend.elementsHandler.addHandler( GlobalHandler, { $element: $scope } );
};

View File

@@ -0,0 +1,165 @@
export default class ImageCarousel extends elementorModules.frontend.handlers.SwiperBase {
getDefaultSettings() {
return {
selectors: {
carousel: '.elementor-image-carousel-wrapper',
slideContent: '.swiper-slide',
},
};
}
getDefaultElements() {
const selectors = this.getSettings( 'selectors' );
const elements = {
$swiperContainer: this.$element.find( selectors.carousel ),
};
elements.$slides = elements.$swiperContainer.find( selectors.slideContent );
return elements;
}
getSwiperSettings() {
const elementSettings = this.getElementSettings(),
slidesToShow = +elementSettings.slides_to_show || 3,
isSingleSlide = 1 === slidesToShow,
defaultLGDevicesSlidesCount = isSingleSlide ? 1 : 2,
elementorBreakpoints = elementorFrontend.config.responsive.activeBreakpoints;
const swiperOptions = {
slidesPerView: slidesToShow,
loop: 'yes' === elementSettings.infinite,
speed: elementSettings.speed,
handleElementorBreakpoints: true,
};
swiperOptions.breakpoints = {};
swiperOptions.breakpoints[ elementorBreakpoints.mobile.value ] = {
slidesPerView: +elementSettings.slides_to_show_mobile || 1,
slidesPerGroup: +elementSettings.slides_to_scroll_mobile || 1,
};
swiperOptions.breakpoints[ elementorBreakpoints.tablet.value ] = {
slidesPerView: +elementSettings.slides_to_show_tablet || defaultLGDevicesSlidesCount,
slidesPerGroup: +elementSettings.slides_to_scroll_tablet || 1,
};
if ( 'yes' === elementSettings.autoplay ) {
swiperOptions.autoplay = {
delay: elementSettings.autoplay_speed,
disableOnInteraction: 'yes' === elementSettings.pause_on_interaction,
};
}
if ( isSingleSlide ) {
swiperOptions.effect = elementSettings.effect;
if ( 'fade' === elementSettings.effect ) {
swiperOptions.fadeEffect = { crossFade: true };
}
} else {
swiperOptions.slidesPerGroup = +elementSettings.slides_to_scroll || 1;
}
if ( elementSettings.image_spacing_custom ) {
swiperOptions.spaceBetween = elementSettings.image_spacing_custom.size;
}
const showArrows = 'arrows' === elementSettings.navigation || 'both' === elementSettings.navigation,
showDots = 'dots' === elementSettings.navigation || 'both' === elementSettings.navigation;
if ( showArrows ) {
swiperOptions.navigation = {
prevEl: '.elementor-swiper-button-prev',
nextEl: '.elementor-swiper-button-next',
};
}
if ( showDots ) {
swiperOptions.pagination = {
el: '.swiper-pagination',
type: 'bullets',
clickable: true,
};
}
return swiperOptions;
}
async onInit( ...args ) {
super.onInit( ...args );
const elementSettings = this.getElementSettings();
if ( ! this.elements.$swiperContainer.length || 2 > this.elements.$slides.length ) {
return;
}
const Swiper = elementorFrontend.utils.swiper;
this.swiper = await new Swiper( this.elements.$swiperContainer, this.getSwiperSettings() );
// Expose the swiper instance in the frontend
this.elements.$swiperContainer.data( 'swiper', this.swiper );
if ( 'yes' === elementSettings.pause_on_hover ) {
this.togglePauseOnHover( true );
}
}
updateSwiperOption( propertyName ) {
const elementSettings = this.getElementSettings(),
newSettingValue = elementSettings[ propertyName ],
params = this.swiper.params;
// Handle special cases where the value to update is not the value that the Swiper library accepts.
switch ( propertyName ) {
case 'image_spacing_custom':
params.spaceBetween = newSettingValue.size || 0;
break;
case 'autoplay_speed':
params.autoplay.delay = newSettingValue;
break;
case 'speed':
params.speed = newSettingValue;
break;
}
this.swiper.update();
}
getChangeableProperties() {
return {
pause_on_hover: 'pauseOnHover',
autoplay_speed: 'delay',
speed: 'speed',
image_spacing_custom: 'spaceBetween',
};
}
onElementChange( propertyName ) {
const changeableProperties = this.getChangeableProperties();
if ( changeableProperties[ propertyName ] ) {
// 'pause_on_hover' is implemented by the handler with event listeners, not the Swiper library.
if ( 'pause_on_hover' === propertyName ) {
const newSettingValue = this.getElementSettings( 'pause_on_hover' );
this.togglePauseOnHover( 'yes' === newSettingValue );
} else {
this.updateSwiperOption( propertyName );
}
}
}
onEditSettingsChange( propertyName ) {
if ( 'activeItemIndex' === propertyName ) {
this.swiper.slideToLoop( this.getEditSettings( 'activeItemIndex' ) - 1 );
}
}
}

View File

@@ -0,0 +1,26 @@
export default class Progress extends elementorModules.frontend.handlers.Base {
getDefaultSettings() {
return {
selectors: {
progressNumber: '.elementor-progress-bar',
},
};
}
getDefaultElements() {
const selectors = this.getSettings( 'selectors' );
return {
$progressNumber: this.$element.find( selectors.progressNumber ),
};
}
onInit() {
super.onInit();
elementorFrontend.waypoint( this.elements.$progressNumber, () => {
const $progressbar = this.elements.$progressNumber;
$progressbar.css( 'width', $progressbar.data( 'max' ) + '%' );
} );
}
}

View File

@@ -0,0 +1,290 @@
export default class BackgroundVideo extends elementorModules.frontend.handlers.Base {
getDefaultSettings() {
return {
selectors: {
backgroundVideoContainer: '.elementor-background-video-container',
backgroundVideoEmbed: '.elementor-background-video-embed',
backgroundVideoHosted: '.elementor-background-video-hosted',
},
};
}
getDefaultElements() {
const selectors = this.getSettings( 'selectors' ),
elements = {
$backgroundVideoContainer: this.$element.find( selectors.backgroundVideoContainer ),
};
elements.$backgroundVideoEmbed = elements.$backgroundVideoContainer.children( selectors.backgroundVideoEmbed );
elements.$backgroundVideoHosted = elements.$backgroundVideoContainer.children( selectors.backgroundVideoHosted );
return elements;
}
calcVideosSize( $video ) {
let aspectRatioSetting = '16:9';
if ( 'vimeo' === this.videoType ) {
aspectRatioSetting = $video[ 0 ].width + ':' + $video[ 0 ].height;
}
const containerWidth = this.elements.$backgroundVideoContainer.outerWidth(),
containerHeight = this.elements.$backgroundVideoContainer.outerHeight(),
aspectRatioArray = aspectRatioSetting.split( ':' ),
aspectRatio = aspectRatioArray[ 0 ] / aspectRatioArray[ 1 ],
ratioWidth = containerWidth / aspectRatio,
ratioHeight = containerHeight * aspectRatio,
isWidthFixed = containerWidth / containerHeight > aspectRatio;
return {
width: isWidthFixed ? containerWidth : ratioHeight,
height: isWidthFixed ? ratioWidth : containerHeight,
};
}
changeVideoSize() {
if ( ! ( 'hosted' === this.videoType ) && ! this.player ) {
return;
}
let $video;
if ( 'youtube' === this.videoType ) {
$video = jQuery( this.player.getIframe() );
} else if ( 'vimeo' === this.videoType ) {
$video = jQuery( this.player.element );
} else if ( 'hosted' === this.videoType ) {
$video = this.elements.$backgroundVideoHosted;
}
if ( ! $video ) {
return;
}
const size = this.calcVideosSize( $video );
$video.width( size.width ).height( size.height );
}
startVideoLoop( firstTime ) {
// If the section has been removed
if ( ! this.player.getIframe().contentWindow ) {
return;
}
const elementSettings = this.getElementSettings(),
startPoint = elementSettings.background_video_start || 0,
endPoint = elementSettings.background_video_end;
if ( elementSettings.background_play_once && ! firstTime ) {
this.player.stopVideo();
return;
}
this.player.seekTo( startPoint );
if ( endPoint ) {
const durationToEnd = endPoint - startPoint + 1;
setTimeout( () => {
this.startVideoLoop( false );
}, durationToEnd * 1000 );
}
}
prepareVimeoVideo( Vimeo, videoId ) {
const elementSettings = this.getElementSettings(),
startTime = elementSettings.background_video_start ? elementSettings.background_video_start : 0,
videoSize = this.elements.$backgroundVideoContainer.outerWidth(),
vimeoOptions = {
id: videoId,
width: videoSize.width,
autoplay: true,
loop: ! elementSettings.background_play_once,
transparent: false,
background: true,
muted: true,
};
this.player = new Vimeo.Player( this.elements.$backgroundVideoContainer, vimeoOptions );
// Handle user-defined start/end times
this.handleVimeoStartEndTimes( elementSettings );
this.player.ready().then( () => {
jQuery( this.player.element ).addClass( 'elementor-background-video-embed' );
this.changeVideoSize();
} );
}
handleVimeoStartEndTimes( elementSettings ) {
// If a start time is defined, set the start time
if ( elementSettings.background_video_start ) {
this.player.on( 'play', ( data ) => {
if ( 0 === data.seconds ) {
this.player.setCurrentTime( elementSettings.background_video_start );
}
} );
}
this.player.on( 'timeupdate', ( data ) => {
// If an end time is defined, handle ending the video
if ( elementSettings.background_video_end && elementSettings.background_video_end < data.seconds ) {
if ( elementSettings.background_play_once ) {
// Stop at user-defined end time if not loop
this.player.pause();
} else {
// Go to start time if loop
this.player.setCurrentTime( elementSettings.background_video_start );
}
}
// If start time is defined but an end time is not, go to user-defined start time at video end.
// Vimeo JS API has an 'ended' event, but it never fires when infinite loop is defined, so we
// get the video duration (returns a promise) then use duration-0.5s as end time
this.player.getDuration().then( ( duration ) => {
if ( elementSettings.background_video_start && ! elementSettings.background_video_end && data.seconds > duration - 0.5 ) {
this.player.setCurrentTime( elementSettings.background_video_start );
}
} );
} );
}
prepareYTVideo( YT, videoID ) {
const $backgroundVideoContainer = this.elements.$backgroundVideoContainer,
elementSettings = this.getElementSettings();
let startStateCode = YT.PlayerState.PLAYING;
// Since version 67, Chrome doesn't fire the `PLAYING` state at start time
if ( window.chrome ) {
startStateCode = YT.PlayerState.UNSTARTED;
}
const playerOptions = {
videoId: videoID,
events: {
onReady: () => {
this.player.mute();
this.changeVideoSize();
this.startVideoLoop( true );
this.player.playVideo();
},
onStateChange: ( event ) => {
switch ( event.data ) {
case startStateCode:
$backgroundVideoContainer.removeClass( 'elementor-invisible elementor-loading' );
break;
case YT.PlayerState.ENDED:
this.player.seekTo( elementSettings.background_video_start || 0 );
if ( elementSettings.background_play_once ) {
this.player.destroy();
}
}
},
},
playerVars: {
controls: 0,
rel: 0,
playsinline: 1,
},
};
// To handle CORS issues, when the default host is changed, the origin parameter has to be set.
if ( elementSettings.background_privacy_mode ) {
playerOptions.host = 'https://www.youtube-nocookie.com';
playerOptions.origin = window.location.hostname;
}
$backgroundVideoContainer.addClass( 'elementor-loading elementor-invisible' );
this.player = new YT.Player( this.elements.$backgroundVideoEmbed[ 0 ], playerOptions );
}
activate() {
let videoLink = this.getElementSettings( 'background_video_link' ),
videoID;
const playOnce = this.getElementSettings( 'background_play_once' );
if ( -1 !== videoLink.indexOf( 'vimeo.com' ) ) {
this.videoType = 'vimeo';
this.apiProvider = elementorFrontend.utils.vimeo;
} else if ( videoLink.match( /^(?:https?:\/\/)?(?:www\.)?(?:m\.)?(?:youtu\.be\/|youtube\.com)/ ) ) {
this.videoType = 'youtube';
this.apiProvider = elementorFrontend.utils.youtube;
}
if ( this.apiProvider ) {
videoID = this.apiProvider.getVideoIDFromURL( videoLink );
this.apiProvider.onApiReady( ( apiObject ) => {
if ( 'youtube' === this.videoType ) {
this.prepareYTVideo( apiObject, videoID );
}
if ( 'vimeo' === this.videoType ) {
this.prepareVimeoVideo( apiObject, videoID );
}
} );
} else {
this.videoType = 'hosted';
const startTime = this.getElementSettings( 'background_video_start' ),
endTime = this.getElementSettings( 'background_video_end' );
if ( startTime || endTime ) {
videoLink += '#t=' + ( startTime || 0 ) + ( endTime ? ',' + endTime : '' );
}
this.elements.$backgroundVideoHosted.attr( 'src', videoLink ).one( 'canplay', this.changeVideoSize.bind( this ) );
if ( playOnce ) {
this.elements.$backgroundVideoHosted.on( 'ended', () => {
this.elements.$backgroundVideoHosted.hide();
} );
}
}
elementorFrontend.elements.$window.on( 'resize', this.changeVideoSize );
}
deactivate() {
if ( ( 'youtube' === this.videoType && this.player.getIframe() ) || 'vimeo' === this.videoType ) {
this.player.destroy();
} else {
this.elements.$backgroundVideoHosted.removeAttr( 'src' ).off( 'ended' );
}
elementorFrontend.elements.$window.off( 'resize', this.changeVideoSize );
}
run() {
const elementSettings = this.getElementSettings();
if ( ! elementSettings.background_play_on_mobile && 'mobile' === elementorFrontend.getCurrentDeviceMode() ) {
return;
}
if ( 'video' === elementSettings.background_background && elementSettings.background_video_link ) {
this.activate();
} else {
this.deactivate();
}
}
onInit( ...args ) {
super.onInit( ...args );
this.changeVideoSize = this.changeVideoSize.bind( this );
this.run();
}
onElementChange( propertyName ) {
if ( 'background_background' === propertyName ) {
this.run();
}
}
}

View File

@@ -0,0 +1,62 @@
export default class HandlesPosition extends elementorModules.frontend.handlers.Base {
isActive() {
return elementorFrontend.isEditMode();
}
isFirstSection() {
return this.$element[ 0 ] === document.querySelector( '.elementor-edit-mode .elementor-top-section' );
}
isOverflowHidden() {
return 'hidden' === this.$element.css( 'overflow' );
}
getOffset() {
if ( 'body' === elementor.config.document.container ) {
return this.$element.offset().top;
}
const $container = jQuery( elementor.config.document.container );
return this.$element.offset().top - $container.offset().top;
}
setHandlesPosition() {
const document = elementor.documents.getCurrent();
if ( ! document || ! document.container.isEditable() ) {
return;
}
const isOverflowHidden = this.isOverflowHidden();
if ( ! isOverflowHidden && ! this.isFirstSection() ) {
return;
}
const offset = isOverflowHidden ? 0 : this.getOffset(),
$handlesElement = this.$element.find( '> .elementor-element-overlay > .elementor-editor-section-settings' ),
insideHandleClass = 'elementor-section--handles-inside';
if ( offset < 25 ) {
this.$element.addClass( insideHandleClass );
if ( offset < -5 ) {
$handlesElement.css( 'top', -offset );
} else {
$handlesElement.css( 'top', '' );
}
} else {
this.$element.removeClass( insideHandleClass );
}
}
onInit() {
if ( ! this.isActive() ) {
return;
}
this.setHandlesPosition();
this.$element.on( 'mouseenter', this.setHandlesPosition.bind( this ) );
}
}

View File

@@ -0,0 +1,13 @@
import BackgroundSlideshow from '../background-slideshow';
import BackgroundVideo from './background-video';
import HandlesPosition from './handles-position';
import StretchedSection from './stretched-section';
import Shapes from './shapes';
export default [
StretchedSection, // Must run before BackgroundSlideshow to init the slideshow only after the stretch.
BackgroundSlideshow,
BackgroundVideo,
HandlesPosition,
Shapes,
];

View File

@@ -0,0 +1,99 @@
export default class Shapes extends elementorModules.frontend.handlers.Base {
getDefaultSettings() {
return {
selectors: {
container: '> .elementor-shape-%s',
},
svgURL: elementorFrontend.config.urls.assets + 'shapes/',
};
}
getDefaultElements() {
const elements = {},
selectors = this.getSettings( 'selectors' );
elements.$topContainer = this.$element.find( selectors.container.replace( '%s', 'top' ) );
elements.$bottomContainer = this.$element.find( selectors.container.replace( '%s', 'bottom' ) );
return elements;
}
isActive() {
return elementorFrontend.isEditMode();
}
getSvgURL( shapeType, fileName ) {
let svgURL = this.getSettings( 'svgURL' ) + fileName + '.svg';
if ( elementor.config.additional_shapes && shapeType in elementor.config.additional_shapes ) {
svgURL = elementor.config.additional_shapes[ shapeType ];
if ( -1 < fileName.indexOf( '-negative' ) ) {
svgURL = svgURL.replace( '.svg', '-negative.svg' );
}
}
return svgURL;
}
buildSVG( side ) {
const baseSettingKey = 'shape_divider_' + side,
shapeType = this.getElementSettings( baseSettingKey ),
$svgContainer = this.elements[ '$' + side + 'Container' ];
$svgContainer.attr( 'data-shape', shapeType );
if ( ! shapeType ) {
$svgContainer.empty(); // Shape-divider set to 'none'
return;
}
let fileName = shapeType;
if ( this.getElementSettings( baseSettingKey + '_negative' ) ) {
fileName += '-negative';
}
const svgURL = this.getSvgURL( shapeType, fileName );
jQuery.get( svgURL, ( data ) => {
$svgContainer.empty().append( data.childNodes[ 0 ] );
} );
this.setNegative( side );
}
setNegative( side ) {
this.elements[ '$' + side + 'Container' ].attr( 'data-negative', !! this.getElementSettings( 'shape_divider_' + side + '_negative' ) );
}
onInit( ...args ) {
if ( ! this.isActive( this.getSettings() ) ) {
return;
}
super.onInit( ...args );
[ 'top', 'bottom' ].forEach( ( side ) => {
if ( this.getElementSettings( 'shape_divider_' + side ) ) {
this.buildSVG( side );
}
} );
}
onElementChange( propertyName ) {
const shapeChange = propertyName.match( /^shape_divider_(top|bottom)$/ );
if ( shapeChange ) {
this.buildSVG( shapeChange[ 1 ] );
return;
}
const negativeChange = propertyName.match( /^shape_divider_(top|bottom)_negative$/ );
if ( negativeChange ) {
this.buildSVG( negativeChange[ 1 ] );
this.setNegative( negativeChange[ 1 ] );
}
}
}

View File

@@ -0,0 +1,79 @@
export default class StretchedSection extends elementorModules.frontend.handlers.Base {
bindEvents() {
const handlerID = this.getUniqueHandlerID();
elementorFrontend.addListenerOnce( handlerID, 'resize', this.stretch );
elementorFrontend.addListenerOnce( handlerID, 'sticky:stick', this.stretch, this.$element );
elementorFrontend.addListenerOnce( handlerID, 'sticky:unstick', this.stretch, this.$element );
if ( elementorFrontend.isEditMode() ) {
this.onKitChangeStretchContainerChange = this.onKitChangeStretchContainerChange.bind( this );
elementor.channels.editor.on( 'kit:change:stretchContainer', this.onKitChangeStretchContainerChange );
}
}
unbindEvents() {
elementorFrontend.removeListeners( this.getUniqueHandlerID(), 'resize', this.stretch );
if ( elementorFrontend.isEditMode() ) {
elementor.channels.editor.off( 'kit:change:stretchContainer', this.onKitChangeStretchContainerChange );
}
}
isActive( settings ) {
return elementorFrontend.isEditMode() || settings.$element.hasClass( 'elementor-section-stretched' );
}
initStretch() {
this.stretch = this.stretch.bind( this );
this.stretchElement = new elementorModules.frontend.tools.StretchElement( {
element: this.$element,
selectors: {
container: this.getStretchContainer(),
},
} );
}
getStretchContainer() {
return elementorFrontend.getKitSettings( 'stretched_section_container' ) || window;
}
stretch() {
if ( ! this.getElementSettings( 'stretch_section' ) ) {
return;
}
this.stretchElement.stretch();
}
onInit( ...args ) {
if ( ! this.isActive( this.getSettings() ) ) {
return;
}
this.initStretch();
super.onInit( ...args );
this.stretch();
}
onElementChange( propertyName ) {
if ( 'stretch_section' === propertyName ) {
if ( this.getElementSettings( 'stretch_section' ) ) {
this.stretch();
} else {
this.stretchElement.reset();
}
}
}
onKitChangeStretchContainerChange() {
this.stretchElement.setSettings( 'selectors.container', this.getStretchContainer() );
this.stretch();
}
}

View File

@@ -0,0 +1,12 @@
import TabsModule from './base-tabs';
export default class Tabs extends TabsModule {
getDefaultSettings() {
const defaultSettings = super.getDefaultSettings();
return {
...defaultSettings,
toggleSelf: false,
};
}
}

View File

@@ -0,0 +1,88 @@
export default class TextEditor extends elementorModules.frontend.handlers.Base {
getDefaultSettings() {
return {
selectors: {
paragraph: 'p:first',
},
classes: {
dropCap: 'elementor-drop-cap',
dropCapLetter: 'elementor-drop-cap-letter',
},
};
}
getDefaultElements() {
const selectors = this.getSettings( 'selectors' ),
classes = this.getSettings( 'classes' ),
$dropCap = jQuery( '<span>', { class: classes.dropCap } ),
$dropCapLetter = jQuery( '<span>', { class: classes.dropCapLetter } );
$dropCap.append( $dropCapLetter );
return {
$paragraph: this.$element.find( selectors.paragraph ),
$dropCap: $dropCap,
$dropCapLetter: $dropCapLetter,
};
}
wrapDropCap() {
const isDropCapEnabled = this.getElementSettings( 'drop_cap' );
if ( ! isDropCapEnabled ) {
// If there is an old drop cap inside the paragraph
if ( this.dropCapLetter ) {
this.elements.$dropCap.remove();
this.elements.$paragraph.prepend( this.dropCapLetter );
this.dropCapLetter = '';
}
return;
}
const $paragraph = this.elements.$paragraph;
if ( ! $paragraph.length ) {
return;
}
const paragraphContent = $paragraph.html().replace( /&nbsp;/g, ' ' ),
firstLetterMatch = paragraphContent.match( /^ *([^ ] ?)/ );
if ( ! firstLetterMatch ) {
return;
}
const firstLetter = firstLetterMatch[ 1 ],
trimmedFirstLetter = firstLetter.trim();
// Don't apply drop cap when the content starting with an HTML tag
if ( '<' === trimmedFirstLetter ) {
return;
}
this.dropCapLetter = firstLetter;
this.elements.$dropCapLetter.text( trimmedFirstLetter );
const restoredParagraphContent = paragraphContent.slice( firstLetter.length ).replace( /^ */, ( match ) => {
return new Array( match.length + 1 ).join( '&nbsp;' );
} );
$paragraph.html( restoredParagraphContent ).prepend( this.elements.$dropCap );
}
onInit( ...args ) {
super.onInit( ...args );
this.wrapDropCap();
}
onElementChange( propertyName ) {
if ( 'drop_cap' === propertyName ) {
this.wrapDropCap();
}
}
}

View File

@@ -0,0 +1,15 @@
import TabsModule from './base-tabs';
export default class Toggle extends TabsModule {
getDefaultSettings() {
const defaultSettings = super.getDefaultSettings();
return {
...defaultSettings,
showTabFn: 'slideDown',
hideTabFn: 'slideUp',
hidePrevious: false,
autoExpand: 'editor',
};
}
}

View File

@@ -0,0 +1,206 @@
export default class Video extends elementorModules.frontend.handlers.Base {
getDefaultSettings() {
return {
selectors: {
imageOverlay: '.elementor-custom-embed-image-overlay',
video: '.elementor-video',
videoIframe: '.elementor-video-iframe',
},
};
}
getDefaultElements() {
const selectors = this.getSettings( 'selectors' );
return {
$imageOverlay: this.$element.find( selectors.imageOverlay ),
$video: this.$element.find( selectors.video ),
$videoIframe: this.$element.find( selectors.videoIframe ),
};
}
handleVideo() {
if ( this.getElementSettings( 'lightbox' ) ) {
return;
}
if ( 'youtube' === this.getElementSettings( 'video_type' ) ) {
this.apiProvider.onApiReady( ( apiObject ) => {
this.elements.$imageOverlay.remove();
this.prepareYTVideo( apiObject, true );
} );
} else {
this.elements.$imageOverlay.remove();
this.playVideo();
}
}
playVideo() {
if ( this.elements.$video.length ) {
// this.youtubePlayer exists only for YouTube videos, and its play function is different.
if ( this.youtubePlayer ) {
this.youtubePlayer.playVideo();
} else {
this.elements.$video[ 0 ].play();
}
return;
}
const $videoIframe = this.elements.$videoIframe,
lazyLoad = $videoIframe.data( 'lazy-load' );
if ( lazyLoad ) {
$videoIframe.attr( 'src', lazyLoad );
}
const newSourceUrl = $videoIframe[ 0 ].src.replace( '&autoplay=0', '' );
$videoIframe[ 0 ].src = newSourceUrl + '&autoplay=1';
if ( $videoIframe[ 0 ].src.includes( 'vimeo.com' ) ) {
const videoSrc = $videoIframe[ 0 ].src,
timeMatch = /#t=[^&]*/.exec( videoSrc );
// Param '#t=' must be last in the URL
$videoIframe[ 0 ].src = videoSrc.slice( 0, timeMatch.index ) + videoSrc.slice( timeMatch.index + timeMatch[ 0 ].length ) + timeMatch[ 0 ];
}
}
async animateVideo() {
const lightbox = await elementorFrontend.utils.lightbox;
lightbox.setEntranceAnimation( this.getCurrentDeviceSetting( 'lightbox_content_animation' ) );
}
async handleAspectRatio() {
const lightbox = await elementorFrontend.utils.lightbox;
lightbox.setVideoAspectRatio( this.getElementSettings( 'aspect_ratio' ) );
}
async hideLightbox() {
const lightbox = await elementorFrontend.utils.lightbox;
lightbox.getModal().hide();
}
prepareYTVideo( YT, onOverlayClick ) {
const elementSettings = this.getElementSettings(),
playerOptions = {
videoId: this.videoID,
events: {
onReady: () => {
if ( elementSettings.mute ) {
this.youtubePlayer.mute();
}
if ( elementSettings.autoplay || onOverlayClick ) {
this.youtubePlayer.playVideo();
}
},
onStateChange: ( event ) => {
if ( event.data === YT.PlayerState.ENDED && elementSettings.loop ) {
this.youtubePlayer.seekTo( elementSettings.start || 0 );
}
},
},
playerVars: {
controls: elementSettings.controls ? 1 : 0,
rel: elementSettings.rel ? 1 : 0,
playsinline: elementSettings.play_on_mobile ? 1 : 0,
modestbranding: elementSettings.modestbranding ? 1 : 0,
autoplay: elementSettings.autoplay ? 1 : 0,
start: elementSettings.start,
end: elementSettings.end,
},
};
// To handle CORS issues, when the default host is changed, the origin parameter has to be set.
if ( elementSettings.yt_privacy ) {
playerOptions.host = 'https://www.youtube-nocookie.com';
playerOptions.origin = window.location.hostname;
}
this.youtubePlayer = new YT.Player( this.elements.$video[ 0 ], playerOptions );
}
bindEvents() {
this.elements.$imageOverlay.on( 'click', this.handleVideo.bind( this ) );
}
onInit() {
super.onInit();
const elementSettings = this.getElementSettings();
if ( 'youtube' !== elementSettings.video_type ) {
// Currently the only API integration in the Video widget is for the YT API
return;
}
this.apiProvider = elementorFrontend.utils.youtube;
this.videoID = this.apiProvider.getVideoIDFromURL( elementSettings.youtube_url );
// If there is an image overlay, the YouTube video prep method will be triggered on click
if ( ! this.videoID ) {
return;
}
// If the user is using an image overlay, loading the API happens on overlay click instead of on init.
if ( elementSettings.show_image_overlay && elementSettings.image_overlay.url ) {
return;
}
if ( elementSettings.lazy_load ) {
this.intersectionObserver = elementorModules.utils.Scroll.scrollObserver( {
callback: ( event ) => {
if ( event.isInViewport ) {
this.intersectionObserver.unobserve( this.elements.$video.parent()[ 0 ] );
this.apiProvider.onApiReady( ( apiObject ) => this.prepareYTVideo( apiObject ) );
}
},
} );
// We observe the parent, since the video container has a height of 0.
this.intersectionObserver.observe( this.elements.$video.parent()[ 0 ] );
return;
}
// When Optimized asset loading is set to off, the video type is set to 'Youtube', and 'Privacy Mode' is set
// to 'On', there might be a conflict with other videos that are loaded WITHOUT privacy mode, such as a
// video bBackground in a section. In these cases, to avoid the conflict, a timeout is added to postpone the
// initialization of the Youtube API object.
if ( ! elementorFrontend.config.experimentalFeatures[ 'e_optimized_assets_loading' ] ) {
setTimeout( () => {
this.apiProvider.onApiReady( ( apiObject ) => this.prepareYTVideo( apiObject ) );
}, 0 );
} else {
this.apiProvider.onApiReady( ( apiObject ) => this.prepareYTVideo( apiObject ) );
}
}
onElementChange( propertyName ) {
if ( 0 === propertyName.indexOf( 'lightbox_content_animation' ) ) {
this.animateVideo();
return;
}
const isLightBoxEnabled = this.getElementSettings( 'lightbox' );
if ( 'lightbox' === propertyName && ! isLightBoxEnabled ) {
this.hideLightbox();
return;
}
if ( 'aspect_ratio' === propertyName && isLightBoxEnabled ) {
this.handleAspectRatio();
}
}
}

View File

@@ -0,0 +1,16 @@
import elementorModules from '../modules/modules';
import Document from './document';
import StretchElement from './tools/stretch-element';
import BaseHandler from './handlers/base';
import SwiperBase from './handlers/base-swiper';
elementorModules.frontend = {
Document: Document,
tools: {
StretchElement: StretchElement,
},
handlers: {
Base: BaseHandler,
SwiperBase: SwiperBase,
},
};

View File

@@ -0,0 +1,32 @@
import Accordion from './handlers/accordion';
import Alert from './handlers/alert';
import Counter from './handlers/counter';
import Progress from './handlers/progress';
import Tabs from './handlers/tabs';
import Toggle from './handlers/toggle';
import Video from './handlers/video';
import ImageCarousel from './handlers/image-carousel';
import TextEditor from './handlers/text-editor';
import LightboxModule from 'elementor-frontend/utils/lightbox/lightbox';
elementorFrontend.elements.$window.on( 'elementor/frontend/init', () => {
elementorFrontend.elementsHandler.elementsHandlers = {
'accordion.default': Accordion,
'alert.default': Alert,
'counter.default': Counter,
'progress.default': Progress,
'tabs.default': Tabs,
'toggle.default': Toggle,
'video.default': Video,
'image-carousel.default': ImageCarousel,
'text-editor.default': TextEditor,
};
elementorFrontend.on( 'components:init', () => {
// We first need to delete the property because by default it's a getter function that cannot be overwritten.
delete elementorFrontend.utils.lightbox;
elementorFrontend.utils.lightbox = new LightboxModule();
} );
} );

View File

@@ -0,0 +1,75 @@
module.exports = elementorModules.ViewModule.extend( {
getDefaultSettings: function() {
return {
element: null,
direction: elementorFrontend.config.is_rtl ? 'right' : 'left',
selectors: {
container: window,
},
};
},
getDefaultElements: function() {
return {
$element: jQuery( this.getSettings( 'element' ) ),
};
},
stretch: function() {
var containerSelector = this.getSettings( 'selectors.container' ),
$container;
try {
$container = jQuery( containerSelector );
} catch ( e ) {}
if ( ! $container || ! $container.length ) {
$container = jQuery( this.getDefaultSettings().selectors.container );
}
this.reset();
var $element = this.elements.$element,
containerWidth = $container.innerWidth(),
elementOffset = $element.offset().left,
isFixed = 'fixed' === $element.css( 'position' ),
correctOffset = isFixed ? 0 : elementOffset;
if ( window !== $container[ 0 ] ) {
var containerOffset = $container.offset().left;
if ( isFixed ) {
correctOffset = containerOffset;
}
if ( elementOffset > containerOffset ) {
correctOffset = elementOffset - containerOffset;
}
}
if ( ! isFixed ) {
if ( elementorFrontend.config.is_rtl ) {
correctOffset = containerWidth - ( $element.outerWidth() + correctOffset );
}
correctOffset = -correctOffset;
}
var css = {};
css.width = containerWidth + 'px';
css[ this.getSettings( 'direction' ) ] = correctOffset + 'px';
$element.css( css );
},
reset: function() {
var css = {};
css.width = '';
css[ this.getSettings( 'direction' ) ] = '';
this.elements.$element.css( css );
},
} );

View File

@@ -0,0 +1,78 @@
module.exports = elementorModules.ViewModule.extend( {
getDefaultSettings: function() {
return {
scrollDuration: 500,
selectors: {
links: 'a[href*="#"]',
targets: '.elementor-element, .elementor-menu-anchor',
scrollable: 'html, body',
},
};
},
getDefaultElements: function() {
var $ = jQuery,
selectors = this.getSettings( 'selectors' );
return {
$scrollable: $( selectors.scrollable ),
};
},
bindEvents: function() {
elementorFrontend.elements.$document.on( 'click', this.getSettings( 'selectors.links' ), this.handleAnchorLinks );
},
handleAnchorLinks: function( event ) {
var clickedLink = event.currentTarget,
isSamePathname = ( location.pathname === clickedLink.pathname ),
isSameHostname = ( location.hostname === clickedLink.hostname ),
$anchor;
if ( ! isSameHostname || ! isSamePathname || clickedLink.hash.length < 2 ) {
return;
}
try {
$anchor = jQuery( clickedLink.hash ).filter( this.getSettings( 'selectors.targets' ) );
} catch ( e ) {
return;
}
if ( ! $anchor.length ) {
return;
}
var scrollTop = $anchor.offset().top,
$wpAdminBar = elementorFrontend.elements.$wpAdminBar,
$activeStickies = jQuery( '.elementor-section.elementor-sticky--active:visible' ),
maxStickyHeight = 0;
if ( $wpAdminBar.length > 0 ) {
scrollTop -= $wpAdminBar.height();
}
// Offset height of tallest sticky
if ( $activeStickies.length > 0 ) {
maxStickyHeight = Math.max.apply( null, $activeStickies.map( function() {
return jQuery( this ).outerHeight();
} ).get() );
scrollTop -= maxStickyHeight;
}
event.preventDefault();
scrollTop = elementorFrontend.hooks.applyFilters( 'frontend/handlers/menu_anchor/scroll_top_distance', scrollTop );
this.elements.$scrollable.animate( {
scrollTop: scrollTop,
}, this.getSettings( 'scrollDuration' ), 'linear' );
},
onInit: function() {
elementorModules.ViewModule.prototype.onInit.apply( this, arguments );
this.bindEvents();
},
} );

View File

@@ -0,0 +1,53 @@
export default class AssetsLoader {
getScriptElement( src ) {
const scriptElement = document.createElement( 'script' );
scriptElement.src = src;
return scriptElement;
}
getStyleElement( src ) {
const styleElement = document.createElement( 'link' );
styleElement.rel = 'stylesheet';
styleElement.href = src;
return styleElement;
}
load( type, key ) {
const assetData = AssetsLoader.assets[ type ][ key ];
if ( ! assetData.loader ) {
assetData.loader = new Promise( ( resolve ) => {
const element = 'style' === type ? this.getStyleElement( assetData.src ) : this.getScriptElement( assetData.src );
element.onload = () => resolve( true );
const parent = 'head' === assetData.parent ? assetData.parent : 'body';
document[ parent ].appendChild( element );
} );
}
return assetData.loader;
}
}
const fileSuffix = elementorFrontendConfig.environmentMode.isScriptDebug ? '' : '.min';
AssetsLoader.assets = {
script: {
dialog: {
src: `${ elementorFrontendConfig.urls.assets }lib/dialog/dialog${ fileSuffix }.js?ver=4.8.1`,
},
'share-link': {
src: `${ elementorFrontendConfig.urls.assets }lib/share-link/share-link${ fileSuffix }.js?ver=${ elementorFrontendConfig.version }`,
},
swiper: {
src: `${ elementorFrontendConfig.urls.assets }lib/swiper/swiper${ fileSuffix }.js?ver=5.3.6`,
},
},
style: {},
};

View File

@@ -0,0 +1,95 @@
export default class LightboxManager extends elementorModules.ViewModule {
static getLightbox() {
const lightboxPromise = new Promise( ( resolveLightbox ) => {
import(
/* webpackChunkName: 'lightbox' */
`elementor-frontend/utils/lightbox/lightbox`
).then( ( { default: LightboxModule } ) => resolveLightbox( new LightboxModule() ) );
} ),
dialogPromise = elementorFrontend.utils.assetsLoader.load( 'script', 'dialog' ),
shareLinkPromise = elementorFrontend.utils.assetsLoader.load( 'script', 'share-link' );
return Promise.all( [ lightboxPromise, dialogPromise, shareLinkPromise ] ).then( () => lightboxPromise );
}
getDefaultSettings() {
return {
selectors: {
links: 'a, [data-elementor-lightbox]',
},
};
}
getDefaultElements() {
return {
$links: jQuery( this.getSettings( 'selectors.links' ) ),
};
}
isLightboxLink( element ) {
// Check for lowercase `a` to make sure it works also for links inside SVGs.
if ( 'a' === element.tagName.toLowerCase() && ( element.hasAttribute( 'download' ) || ! /^[^?]+\.(png|jpe?g|gif|svg|webp)(\?.*)?$/i.test( element.href ) ) ) {
return false;
}
const generalOpenInLightbox = elementorFrontend.getKitSettings( 'global_image_lightbox' ),
currentLinkOpenInLightbox = element.dataset.elementorOpenLightbox;
return 'yes' === currentLinkOpenInLightbox || ( generalOpenInLightbox && 'no' !== currentLinkOpenInLightbox );
}
async onLinkClick( event ) {
const element = event.currentTarget,
$target = jQuery( event.target ),
editMode = elementorFrontend.isEditMode(),
isClickInsideElementor = ! ! $target.closest( '.elementor-edit-area' ).length;
if ( ! this.isLightboxLink( element ) ) {
if ( editMode && isClickInsideElementor ) {
event.preventDefault();
}
return;
}
event.preventDefault();
if ( editMode && ! elementor.getPreferences( 'lightbox_in_editor' ) ) {
return;
}
const lightbox = this.isOptimizedAssetsLoading() ? await LightboxManager.getLightbox() : elementorFrontend.utils.lightbox;
lightbox.createLightbox( element );
}
isOptimizedAssetsLoading() {
return elementorFrontend.config.experimentalFeatures.e_optimized_assets_loading;
}
bindEvents() {
elementorFrontend.elements.$document.on(
'click',
this.getSettings( 'selectors.links' ),
( event ) => this.onLinkClick( event )
);
}
onInit( ...args ) {
super.onInit( ...args );
if ( ! this.isOptimizedAssetsLoading() || elementorFrontend.isEditMode() ) {
return;
}
// Detecting lightbox links on init will reduce the time of waiting to the lightbox to be display on slow connections.
this.elements.$links.each( ( index, element ) => {
if ( this.isLightboxLink( element ) ) {
LightboxManager.getLightbox();
// Breaking the iteration when the library loading has already been triggered.
return false;
}
} );
}
}

View File

@@ -0,0 +1,935 @@
import screenfull from './screenfull';
module.exports = elementorModules.ViewModule.extend( {
oldAspectRatio: null,
oldAnimation: null,
swiper: null,
player: null,
getDefaultSettings: function() {
return {
classes: {
aspectRatio: 'elementor-aspect-ratio-%s',
item: 'elementor-lightbox-item',
image: 'elementor-lightbox-image',
videoContainer: 'elementor-video-container',
videoWrapper: 'elementor-fit-aspect-ratio',
playButton: 'elementor-custom-embed-play',
playButtonIcon: 'fa',
playing: 'elementor-playing',
hidden: 'elementor-hidden',
invisible: 'elementor-invisible',
preventClose: 'elementor-lightbox-prevent-close',
slideshow: {
container: 'swiper-container',
slidesWrapper: 'swiper-wrapper',
prevButton: 'elementor-swiper-button elementor-swiper-button-prev',
nextButton: 'elementor-swiper-button elementor-swiper-button-next',
prevButtonIcon: 'eicon-chevron-left',
nextButtonIcon: 'eicon-chevron-right',
slide: 'swiper-slide',
header: 'elementor-slideshow__header',
footer: 'elementor-slideshow__footer',
title: 'elementor-slideshow__title',
description: 'elementor-slideshow__description',
counter: 'elementor-slideshow__counter',
iconExpand: 'eicon-frame-expand',
iconShrink: 'eicon-frame-minimize',
iconZoomIn: 'eicon-zoom-in-bold',
iconZoomOut: 'eicon-zoom-out-bold',
iconShare: 'eicon-share-arrow',
shareMenu: 'elementor-slideshow__share-menu',
shareLinks: 'elementor-slideshow__share-links',
hideUiVisibility: 'elementor-slideshow--ui-hidden',
shareMode: 'elementor-slideshow--share-mode',
fullscreenMode: 'elementor-slideshow--fullscreen-mode',
zoomMode: 'elementor-slideshow--zoom-mode',
},
},
selectors: {
image: '.elementor-lightbox-image',
links: 'a, [data-elementor-lightbox]',
slideshow: {
activeSlide: '.swiper-slide-active',
prevSlide: '.swiper-slide-prev',
nextSlide: '.swiper-slide-next',
},
},
modalOptions: {
id: 'elementor-lightbox',
entranceAnimation: 'zoomIn',
videoAspectRatio: 169,
position: {
enable: false,
},
},
};
},
getModal: function() {
if ( ! module.exports.modal ) {
this.initModal();
}
return module.exports.modal;
},
initModal: function() {
const modal = module.exports.modal = elementorFrontend.getDialogsManager().createWidget( 'lightbox', {
className: 'elementor-lightbox',
closeButton: true,
closeButtonOptions: {
iconClass: 'eicon-close',
attributes: {
tabindex: 0,
role: 'button',
'aria-label': elementorFrontend.config.i18n.close + ' (Esc)',
},
},
selectors: {
preventClose: '.' + this.getSettings( 'classes.preventClose' ),
},
hide: {
onClick: true,
},
} );
modal.on( 'hide', function() {
modal.setMessage( '' );
} );
},
showModal: function( options ) {
if ( options.url && ! options.url.startsWith( 'http' ) ) {
return;
}
this.elements.$closeButton = this.getModal().getElements( 'closeButton' );
this.$buttons = this.elements.$closeButton;
this.focusedButton = null;
const self = this,
defaultOptions = self.getDefaultSettings().modalOptions;
self.id = options.id;
self.setSettings( 'modalOptions', jQuery.extend( defaultOptions, options.modalOptions ) );
const modal = self.getModal();
modal.setID( self.getSettings( 'modalOptions.id' ) );
modal.onShow = function() {
DialogsManager.getWidgetType( 'lightbox' ).prototype.onShow.apply( modal, arguments );
self.setEntranceAnimation();
};
modal.onHide = function() {
DialogsManager.getWidgetType( 'lightbox' ).prototype.onHide.apply( modal, arguments );
modal.getElements( 'message' ).removeClass( 'animated' );
if ( screenfull.isFullscreen ) {
self.deactivateFullscreen();
}
self.unbindHotKeys();
};
switch ( options.type ) {
case 'video':
self.setVideoContent( options );
break;
case 'image':
const slides = [ {
image: options.url,
index: 0,
title: options.title,
description: options.description,
} ];
options.slideshow = {
slides,
swiper: {
loop: false,
pagination: false,
},
};
case 'slideshow':
self.setSlideshowContent( options.slideshow );
break;
default:
self.setHTMLContent( options.html );
}
modal.show();
},
createLightbox: function( element ) {
let lightboxData = {};
if ( element.dataset.elementorLightbox ) {
lightboxData = JSON.parse( element.dataset.elementorLightbox );
}
if ( lightboxData.type && 'slideshow' !== lightboxData.type ) {
this.showModal( lightboxData );
return;
}
if ( ! element.dataset.elementorLightboxSlideshow ) {
const slideshowID = 'single-img';
this.showModal( {
type: 'image',
id: slideshowID,
url: element.href,
title: element.dataset.elementorLightboxTitle,
description: element.dataset.elementorLightboxDescription,
modalOptions: {
id: 'elementor-lightbox-slideshow-' + slideshowID,
},
} );
return;
}
const initialSlideURL = element.dataset.elementorLightboxVideo || element.href;
this.openSlideshow( element.dataset.elementorLightboxSlideshow, initialSlideURL );
},
setHTMLContent: function( html ) {
if ( window.elementorCommon ) {
elementorCommon.helpers.hardDeprecated( 'elementorFrontend.utils.lightbox.setHTMLContent', '3.1.4' );
}
this.getModal().setMessage( html );
},
setVideoContent: function( options ) {
const $ = jQuery,
classes = this.getSettings( 'classes' ),
$videoContainer = $( '<div>', { class: `${ classes.videoContainer } ${ classes.preventClose }` } ),
$videoWrapper = $( '<div>', { class: classes.videoWrapper } ),
modal = this.getModal();
let $videoElement;
if ( 'hosted' === options.videoType ) {
const videoParams = $.extend( { src: options.url, autoplay: '' }, options.videoParams );
$videoElement = $( '<video>', videoParams );
} else {
const videoURL = options.url.replace( '&autoplay=0', '' ) + '&autoplay=1';
$videoElement = $( '<iframe>', { src: videoURL, allowfullscreen: 1 } );
}
$videoContainer.append( $videoWrapper );
$videoWrapper.append( $videoElement );
modal.setMessage( $videoContainer );
this.setVideoAspectRatio();
const onHideMethod = modal.onHide;
modal.onHide = function() {
onHideMethod();
this.$buttons = jQuery();
this.focusedButton = null;
modal.getElements( 'message' ).removeClass( 'elementor-fit-aspect-ratio' );
};
},
getShareLinks: function() {
const { i18n } = elementorFrontend.config,
socialNetworks = {
facebook: i18n.shareOnFacebook,
twitter: i18n.shareOnTwitter,
pinterest: i18n.pinIt,
},
$ = jQuery,
classes = this.getSettings( 'classes' ),
selectors = this.getSettings( 'selectors' ),
$linkList = $( '<div>', { class: classes.slideshow.shareLinks } ),
$activeSlide = this.getSlide( 'active' ),
$image = $activeSlide.find( selectors.image ),
videoUrl = $activeSlide.data( 'elementor-slideshow-video' );
let itemUrl;
if ( videoUrl ) {
itemUrl = videoUrl;
} else {
itemUrl = $image.attr( 'src' );
}
$.each( socialNetworks, ( key, networkLabel ) => {
const $link = $( '<a>', { href: this.createShareLink( key, itemUrl ), target: '_blank' } ).text( networkLabel );
$link.prepend( $( '<i>', { class: 'eicon-' + key } ) );
$linkList.append( $link );
} );
if ( ! videoUrl ) {
$linkList.append( $( '<a>', { href: itemUrl, download: '' } )
.text( i18n.downloadImage )
.prepend( $( '<i>', { class: 'eicon-download-bold', 'aria-label': i18n.download } ) ) );
}
return $linkList;
},
createShareLink: function( networkName, itemUrl ) {
const options = {};
if ( 'pinterest' === networkName ) {
options.image = encodeURIComponent( itemUrl );
} else {
const hash = elementorFrontend.utils.urlActions.createActionHash( 'lightbox', {
id: this.id,
url: itemUrl,
} );
options.url = encodeURIComponent( location.href.replace( /#.*/, '' ) ) + hash;
}
return ShareLink.getNetworkLink( networkName, options );
},
getSlideshowHeader: function() {
const { i18n } = elementorFrontend.config,
$ = jQuery,
showCounter = 'yes' === elementorFrontend.getKitSettings( 'lightbox_enable_counter' ),
showFullscreen = 'yes' === elementorFrontend.getKitSettings( 'lightbox_enable_fullscreen' ),
showZoom = 'yes' === elementorFrontend.getKitSettings( 'lightbox_enable_zoom' ),
showShare = 'yes' === elementorFrontend.getKitSettings( 'lightbox_enable_share' ),
classes = this.getSettings( 'classes' ),
slideshowClasses = classes.slideshow,
elements = this.elements;
if ( ! ( showCounter || showFullscreen || showZoom || showShare ) ) {
return;
}
elements.$header = $( '<header>', { class: slideshowClasses.header + ' ' + classes.preventClose } );
if ( showShare ) {
elements.$iconShare = $( '<i>', {
class: slideshowClasses.iconShare,
role: 'button',
'aria-label': i18n.share,
'aria-expanded': false,
} ).append( $( '<span>' ) );
const $shareLinks = $( '<div>' );
$shareLinks.on( 'click', ( e ) => {
e.stopPropagation();
} );
elements.$shareMenu = $( '<div>', { class: slideshowClasses.shareMenu } ).append( $shareLinks );
elements.$iconShare.add( elements.$shareMenu ).on( 'click', this.toggleShareMenu );
elements.$header.append( elements.$iconShare, elements.$shareMenu );
this.$buttons = this.$buttons.add( elements.$iconShare );
}
if ( showZoom ) {
elements.$iconZoom = $( '<i>', {
class: slideshowClasses.iconZoomIn,
role: 'switch',
'aria-checked': false,
'aria-label': i18n.zoom,
} );
elements.$iconZoom.on( 'click', this.toggleZoomMode );
elements.$header.append( elements.$iconZoom );
this.$buttons = this.$buttons.add( elements.$iconZoom );
}
if ( showFullscreen ) {
elements.$iconExpand = $( '<i>', {
class: slideshowClasses.iconExpand,
role: 'switch',
'aria-checked': false,
'aria-label': i18n.fullscreen,
} ).append( $( '<span>' ), $( '<span>' ) );
elements.$iconExpand.on( 'click', this.toggleFullscreen );
elements.$header.append( elements.$iconExpand );
this.$buttons = this.$buttons.add( elements.$iconExpand );
}
if ( showCounter ) {
elements.$counter = $( '<span>', { class: slideshowClasses.counter } );
elements.$header.append( elements.$counter );
}
return elements.$header;
},
toggleFullscreen: function() {
if ( screenfull.isFullscreen ) {
this.deactivateFullscreen();
} else if ( screenfull.isEnabled ) {
this.activateFullscreen();
}
},
toggleZoomMode: function() {
if ( 1 !== this.swiper.zoom.scale ) {
this.deactivateZoom();
} else {
this.activateZoom();
}
},
toggleShareMenu: function() {
if ( this.shareMode ) {
this.deactivateShareMode();
} else {
this.elements.$shareMenu.html( this.getShareLinks() );
this.activateShareMode();
}
},
activateShareMode: function() {
const classes = this.getSettings( 'classes' );
this.elements.$container.addClass( classes.slideshow.shareMode );
this.elements.$iconShare.attr( 'aria-expanded', true );
// Prevent swiper interactions while in share mode
this.swiper.detachEvents();
// Temporarily replace tabbable buttons with share-menu items
this.$originalButtons = this.$buttons;
this.$buttons = this.elements.$iconShare.add( this.elements.$shareMenu.find( 'a' ) );
this.shareMode = true;
},
deactivateShareMode: function() {
const classes = this.getSettings( 'classes' );
this.elements.$container.removeClass( classes.slideshow.shareMode );
this.elements.$iconShare.attr( 'aria-expanded', false );
this.swiper.attachEvents();
this.$buttons = this.$originalButtons;
this.shareMode = false;
},
activateFullscreen: function() {
const classes = this.getSettings( 'classes' );
screenfull.request( this.elements.$container.parents( '.dialog-widget' )[ 0 ] );
this.elements.$iconExpand.removeClass( classes.slideshow.iconExpand )
.addClass( classes.slideshow.iconShrink )
.attr( 'aria-checked', 'true' );
this.elements.$container.addClass( classes.slideshow.fullscreenMode );
},
deactivateFullscreen: function() {
const classes = this.getSettings( 'classes' );
screenfull.exit();
this.elements.$iconExpand.removeClass( classes.slideshow.iconShrink )
.addClass( classes.slideshow.iconExpand )
.attr( 'aria-checked', 'false' );
this.elements.$container.removeClass( classes.slideshow.fullscreenMode );
},
activateZoom: function() {
const swiper = this.swiper,
elements = this.elements,
classes = this.getSettings( 'classes' );
swiper.zoom.in();
swiper.allowSlideNext = false;
swiper.allowSlidePrev = false;
swiper.allowTouchMove = false;
elements.$container.addClass( classes.slideshow.zoomMode );
elements.$iconZoom.removeClass( classes.slideshow.iconZoomIn ).addClass( classes.slideshow.iconZoomOut );
},
deactivateZoom: function() {
const swiper = this.swiper,
elements = this.elements,
classes = this.getSettings( 'classes' );
swiper.zoom.out();
swiper.allowSlideNext = true;
swiper.allowSlidePrev = true;
swiper.allowTouchMove = true;
elements.$container.removeClass( classes.slideshow.zoomMode );
elements.$iconZoom.removeClass( classes.slideshow.iconZoomOut ).addClass( classes.slideshow.iconZoomIn );
},
getSlideshowFooter: function() {
const $ = jQuery,
classes = this.getSettings( 'classes' ),
$footer = $( '<footer>', { class: classes.slideshow.footer + ' ' + classes.preventClose } ),
$title = $( '<div>', { class: classes.slideshow.title } ),
$description = $( '<div>', { class: classes.slideshow.description } );
$footer.append( $title, $description );
return $footer;
},
setSlideshowContent: function( options ) {
const { i18n } = elementorFrontend.config,
$ = jQuery,
isSingleSlide = 1 === options.slides.length,
hasTitle = '' !== elementorFrontend.getKitSettings( 'lightbox_title_src' ),
hasDescription = '' !== elementorFrontend.getKitSettings( 'lightbox_description_src' ),
showFooter = hasTitle || hasDescription,
classes = this.getSettings( 'classes' ),
slideshowClasses = classes.slideshow,
$container = $( '<div>', { class: slideshowClasses.container } ),
$slidesWrapper = $( '<div>', { class: slideshowClasses.slidesWrapper } );
let $prevButton, $nextButton;
options.slides.forEach( ( slide ) => {
let slideClass = slideshowClasses.slide + ' ' + classes.item;
if ( slide.video ) {
slideClass += ' ' + classes.video;
}
const $slide = $( '<div>', { class: slideClass } );
if ( slide.video ) {
$slide.attr( 'data-elementor-slideshow-video', slide.video );
const $playIcon = $( '<div>', { class: classes.playButton } ).html( $( '<i>', { class: classes.playButtonIcon, 'aria-label': i18n.playVideo } ) );
$slide.append( $playIcon );
} else {
const $zoomContainer = $( '<div>', { class: 'swiper-zoom-container' } ),
$slidePlaceholder = $( '<div class="swiper-lazy-preloader"></div>' ),
imageAttributes = {
'data-src': slide.image,
class: classes.image + ' ' + classes.preventClose + ' swiper-lazy',
};
if ( slide.title ) {
imageAttributes[ 'data-title' ] = slide.title;
imageAttributes.alt = slide.title;
}
if ( slide.description ) {
imageAttributes[ 'data-description' ] = slide.description;
imageAttributes.alt += ' - ' + slide.description;
}
const $slideImage = $( '<img>', imageAttributes );
$zoomContainer.append( [ $slideImage, $slidePlaceholder ] );
$slide.append( $zoomContainer );
}
$slidesWrapper.append( $slide );
} );
this.elements.$container = $container;
this.elements.$header = this.getSlideshowHeader();
$container
.prepend( this.elements.$header )
.append( $slidesWrapper );
if ( ! isSingleSlide ) {
$prevButton = $( '<div>', { class: slideshowClasses.prevButton + ' ' + classes.preventClose, 'aria-label': i18n.previous } ).html( $( '<i>', { class: slideshowClasses.prevButtonIcon } ) );
$nextButton = $( '<div>', { class: slideshowClasses.nextButton + ' ' + classes.preventClose, 'aria-label': i18n.next } ).html( $( '<i>', { class: slideshowClasses.nextButtonIcon } ) );
$container.append(
$nextButton,
$prevButton,
);
this.$buttons = this.$buttons.add( $nextButton ).add( $prevButton );
}
if ( showFooter ) {
this.elements.$footer = this.getSlideshowFooter();
$container.append( this.elements.$footer );
}
this.setSettings( 'hideUiTimeout', '' );
$container.on( 'click mousemove keypress', this.showLightboxUi );
const modal = this.getModal();
modal.setMessage( $container );
const onShowMethod = modal.onShow;
modal.onShow = async () => {
onShowMethod();
const swiperOptions = {
pagination: {
el: '.' + slideshowClasses.counter,
type: 'fraction',
},
on: {
slideChangeTransitionEnd: this.onSlideChange,
},
lazy: {
loadPrevNext: true,
},
zoom: true,
spaceBetween: 100,
grabCursor: true,
runCallbacksOnInit: false,
loop: true,
keyboard: true,
handleElementorBreakpoints: true,
};
if ( ! isSingleSlide ) {
swiperOptions.navigation = {
prevEl: $prevButton,
nextEl: $nextButton,
};
}
if ( options.swiper ) {
$.extend( swiperOptions, options.swiper );
}
const Swiper = elementorFrontend.utils.swiper;
this.swiper = await new Swiper( $container, swiperOptions );
// Expose the swiper instance in the frontend
$container.data( 'swiper', this.swiper );
this.setVideoAspectRatio();
this.playSlideVideo();
if ( showFooter ) {
this.updateFooterText();
}
this.bindHotKeys();
this.makeButtonsAccessible();
};
},
makeButtonsAccessible: function() {
this.$buttons
.attr( 'tabindex', 0 )
.on( 'keypress', ( event ) => {
const ENTER_KEY = 13,
SPACE_KEY = 32;
if ( ENTER_KEY === event.which || SPACE_KEY === event.which ) {
jQuery( event.currentTarget ).trigger( 'click' );
}
} );
},
showLightboxUi: function() {
const slideshowClasses = this.getSettings( 'classes' ).slideshow;
this.elements.$container.removeClass( slideshowClasses.hideUiVisibility );
clearTimeout( this.getSettings( 'hideUiTimeout' ) );
this.setSettings( 'hideUiTimeout', setTimeout( () => {
if ( ! this.shareMode ) {
this.elements.$container.addClass( slideshowClasses.hideUiVisibility );
}
}, 3500 ) );
},
bindHotKeys: function() {
this.getModal().getElements( 'window' ).on( 'keydown', this.activeKeyDown );
},
unbindHotKeys: function() {
this.getModal().getElements( 'window' ).off( 'keydown', this.activeKeyDown );
},
activeKeyDown: function( event ) {
this.showLightboxUi();
const TAB_KEY = 9;
if ( event.which === TAB_KEY ) {
const $buttons = this.$buttons;
let focusedButton,
isFirst = false,
isLast = false;
$buttons.each( ( index ) => {
const item = $buttons[ index ];
if ( jQuery( item ).is( ':focus' ) ) {
focusedButton = item;
isFirst = 0 === index;
isLast = $buttons.length - 1 === index;
return false;
}
} );
if ( event.shiftKey ) {
if ( isFirst ) {
event.preventDefault();
$buttons.last().trigger( 'focus' );
}
} else if ( isLast || ! focusedButton ) {
event.preventDefault();
$buttons.first().trigger( 'focus' );
}
}
},
setVideoAspectRatio: function( aspectRatio ) {
aspectRatio = aspectRatio || this.getSettings( 'modalOptions.videoAspectRatio' );
const $widgetContent = this.getModal().getElements( 'widgetContent' ),
oldAspectRatio = this.oldAspectRatio,
aspectRatioClass = this.getSettings( 'classes.aspectRatio' );
this.oldAspectRatio = aspectRatio;
if ( oldAspectRatio ) {
$widgetContent.removeClass( aspectRatioClass.replace( '%s', oldAspectRatio ) );
}
if ( aspectRatio ) {
$widgetContent.addClass( aspectRatioClass.replace( '%s', aspectRatio ) );
}
},
getSlide: function( slideState ) {
return jQuery( this.swiper.slides ).filter( this.getSettings( 'selectors.slideshow.' + slideState + 'Slide' ) );
},
updateFooterText: function() {
if ( ! this.elements.$footer ) {
return;
}
const classes = this.getSettings( 'classes' ),
$activeSlide = this.getSlide( 'active' ),
$image = $activeSlide.find( '.elementor-lightbox-image' ),
titleText = $image.data( 'title' ),
descriptionText = $image.data( 'description' ),
$title = this.elements.$footer.find( '.' + classes.slideshow.title ),
$description = this.elements.$footer.find( '.' + classes.slideshow.description );
$title.text( titleText || '' );
$description.text( descriptionText || '' );
},
playSlideVideo: function() {
const $activeSlide = this.getSlide( 'active' ),
videoURL = $activeSlide.data( 'elementor-slideshow-video' );
if ( ! videoURL ) {
return;
}
const classes = this.getSettings( 'classes' ),
$videoContainer = jQuery( '<div>', { class: classes.videoContainer + ' ' + classes.invisible } ),
$videoWrapper = jQuery( '<div>', { class: classes.videoWrapper } ),
$playIcon = $activeSlide.children( '.' + classes.playButton );
let videoType, apiProvider;
$videoContainer.append( $videoWrapper );
$activeSlide.append( $videoContainer );
if ( -1 !== videoURL.indexOf( 'vimeo.com' ) ) {
videoType = 'vimeo';
apiProvider = elementorFrontend.utils.vimeo;
} else if ( videoURL.match( /^(?:https?:\/\/)?(?:www\.)?(?:m\.)?(?:youtu\.be\/|youtube\.com)/ ) ) {
videoType = 'youtube';
apiProvider = elementorFrontend.utils.youtube;
}
const videoID = apiProvider.getVideoIDFromURL( videoURL );
apiProvider.onApiReady( ( apiObject ) => {
if ( 'youtube' === videoType ) {
this.prepareYTVideo( apiObject, videoID, $videoContainer, $videoWrapper, $playIcon );
} else if ( 'vimeo' === videoType ) {
this.prepareVimeoVideo( apiObject, videoID, $videoContainer, $videoWrapper, $playIcon );
}
} );
$playIcon.addClass( classes.playing ).removeClass( classes.hidden );
},
prepareYTVideo: function( YT, videoID, $videoContainer, $videoWrapper, $playIcon ) {
const classes = this.getSettings( 'classes' ),
$videoPlaceholderElement = jQuery( '<div>' );
let startStateCode = YT.PlayerState.PLAYING;
$videoWrapper.append( $videoPlaceholderElement );
// Since version 67, Chrome doesn't fire the `PLAYING` state at start time
if ( window.chrome ) {
startStateCode = YT.PlayerState.UNSTARTED;
}
$videoContainer.addClass( 'elementor-loading' + ' ' + classes.invisible );
this.player = new YT.Player( $videoPlaceholderElement[ 0 ], {
videoId: videoID,
events: {
onReady: () => {
$playIcon.addClass( classes.hidden );
$videoContainer.removeClass( classes.invisible );
this.player.playVideo();
},
onStateChange: ( event ) => {
if ( event.data === startStateCode ) {
$videoContainer.removeClass( 'elementor-loading' + ' ' + classes.invisible );
}
},
},
playerVars: {
controls: 0,
rel: 0,
},
} );
},
prepareVimeoVideo: function( Vimeo, videoId, $videoContainer, $videoWrapper, $playIcon ) {
const classes = this.getSettings( 'classes' ),
vimeoOptions = {
id: videoId,
autoplay: true,
transparent: false,
playsinline: false,
};
this.player = new Vimeo.Player( $videoWrapper, vimeoOptions );
this.player.ready().then( () => {
$playIcon.addClass( classes.hidden );
$videoContainer.removeClass( classes.invisible );
} );
},
setEntranceAnimation: function( animation ) {
animation = animation || elementorFrontend.getCurrentDeviceSetting( this.getSettings( 'modalOptions' ), 'entranceAnimation' );
const $widgetMessage = this.getModal().getElements( 'message' );
if ( this.oldAnimation ) {
$widgetMessage.removeClass( this.oldAnimation );
}
this.oldAnimation = animation;
if ( animation ) {
$widgetMessage.addClass( 'animated ' + animation );
}
},
openSlideshow: function( slideshowID, initialSlideURL ) {
const $allSlideshowLinks = jQuery( this.getSettings( 'selectors.links' ) ).filter( ( index, element ) => {
const $element = jQuery( element );
return slideshowID === element.dataset.elementorLightboxSlideshow && ! $element.parent( '.swiper-slide-duplicate' ).length && ! $element.parents( '.slick-cloned' ).length;
} );
const slides = [];
let initialSlideIndex = 0;
$allSlideshowLinks.each( function() {
const slideVideo = this.dataset.elementorLightboxVideo;
let slideIndex = this.dataset.elementorLightboxIndex;
if ( undefined === slideIndex ) {
slideIndex = $allSlideshowLinks.index( this );
}
if ( initialSlideURL === this.href || ( slideVideo && initialSlideURL === slideVideo ) ) {
initialSlideIndex = slideIndex;
}
const slideData = {
image: this.href,
index: slideIndex,
title: this.dataset.elementorLightboxTitle,
description: this.dataset.elementorLightboxDescription,
};
if ( slideVideo ) {
slideData.video = slideVideo;
}
slides.push( slideData );
} );
slides.sort( ( a, b ) => a.index - b.index );
this.showModal( {
type: 'slideshow',
id: slideshowID,
modalOptions: {
id: 'elementor-lightbox-slideshow-' + slideshowID,
},
slideshow: {
slides: slides,
swiper: {
initialSlide: +initialSlideIndex,
},
},
} );
},
onSlideChange: function() {
this
.getSlide( 'prev' )
.add( this.getSlide( 'next' ) )
.add( this.getSlide( 'active' ) )
.find( '.' + this.getSettings( 'classes.videoWrapper' ) )
.remove();
this.playSlideVideo();
this.updateFooterText();
},
} );

View File

@@ -0,0 +1,172 @@
( function() {
'use strict';
var document = typeof window !== 'undefined' && typeof window.document !== 'undefined' ? window.document : {};
var isCommonjs = typeof module !== 'undefined' && module.exports;
var fn = ( function() {
var val;
var fnMap = [
[
'requestFullscreen',
'exitFullscreen',
'fullscreenElement',
'fullscreenEnabled',
'fullscreenchange',
'fullscreenerror',
],
// New WebKit
[
'webkitRequestFullscreen',
'webkitExitFullscreen',
'webkitFullscreenElement',
'webkitFullscreenEnabled',
'webkitfullscreenchange',
'webkitfullscreenerror',
],
// Old WebKit
[
'webkitRequestFullScreen',
'webkitCancelFullScreen',
'webkitCurrentFullScreenElement',
'webkitCancelFullScreen',
'webkitfullscreenchange',
'webkitfullscreenerror',
],
[
'mozRequestFullScreen',
'mozCancelFullScreen',
'mozFullScreenElement',
'mozFullScreenEnabled',
'mozfullscreenchange',
'mozfullscreenerror',
],
[
'msRequestFullscreen',
'msExitFullscreen',
'msFullscreenElement',
'msFullscreenEnabled',
'MSFullscreenChange',
'MSFullscreenError',
],
];
var i = 0;
var l = fnMap.length;
var ret = {};
for ( ; i < l; i++ ) {
val = fnMap[ i ];
if ( val && val[ 1 ] in document ) {
var valLength = val.length;
for ( i = 0; i < valLength; i++ ) {
ret[ fnMap[ 0 ][ i ] ] = val[ i ];
}
return ret;
}
}
return false;
} )();
var eventNameMap = {
change: fn.fullscreenchange,
error: fn.fullscreenerror,
};
var screenfull = {
request: function( element ) {
return new Promise( function( resolve, reject ) {
var onFullScreenEntered = function() {
this.off( 'change', onFullScreenEntered );
resolve();
}.bind( this );
this.on( 'change', onFullScreenEntered );
element = element || document.documentElement;
Promise.resolve( element[ fn.requestFullscreen ]() ).catch( reject );
}.bind( this ) );
},
exit: function() {
return new Promise( function( resolve, reject ) {
if ( ! this.isFullscreen ) {
resolve();
return;
}
var onFullScreenExit = function() {
this.off( 'change', onFullScreenExit );
resolve();
}.bind( this );
this.on( 'change', onFullScreenExit );
Promise.resolve( document[ fn.exitFullscreen ]() ).catch( reject );
}.bind( this ) );
},
toggle: function( element ) {
return this.isFullscreen ? this.exit() : this.request( element );
},
onchange: function( callback ) {
this.on( 'change', callback );
},
onerror: function( callback ) {
this.on( 'error', callback );
},
on: function( event, callback ) {
var eventName = eventNameMap[ event ];
if ( eventName ) {
document.addEventListener( eventName, callback, false );
}
},
off: function( event, callback ) {
var eventName = eventNameMap[ event ];
if ( eventName ) {
document.removeEventListener( eventName, callback, false );
}
},
raw: fn,
};
if ( ! fn ) {
if ( isCommonjs ) {
module.exports = { isEnabled: false };
} else {
window.screenfull = { isEnabled: false };
}
return;
}
Object.defineProperties( screenfull, {
isFullscreen: {
get: function() {
return Boolean( document[ fn.fullscreenElement ] );
},
},
element: {
enumerable: true,
get: function() {
return document[ fn.fullscreenElement ];
},
},
isEnabled: {
enumerable: true,
get: function() {
// Coerce to boolean in case of old WebKit
return Boolean( document[ fn.fullscreenEnabled ] );
},
},
} );
if ( isCommonjs ) {
module.exports = screenfull;
} else {
window.screenfull = screenfull;
}
} )();

View File

@@ -0,0 +1,120 @@
export default class SwiperBC {
constructor( container, config ) {
this.config = config;
if ( this.config.breakpoints ) {
// The config is passed as a param to allow adjustConfig to be called outside of this wrapper
this.config = this.adjustConfig( config );
}
// In case of a legacy behaviour the constructor should return a new Swiper instance instead of a Promise.
if ( config.legacy ) {
return this.createSwiperInstance( container, this.config );
}
return new Promise( ( resolve ) => {
if ( ! elementorFrontend.config.experimentalFeatures.e_optimized_assets_loading ) {
return resolve( this.createSwiperInstance( container, this.config ) );
}
elementorFrontend.utils.assetsLoader.load( 'script', 'swiper' )
.then( () => resolve( this.createSwiperInstance( container, this.config ) ) );
} );
}
createSwiperInstance( container, config ) {
// The condition should run only once to prevent an additional overwrite of the SwiperSource.
if ( ! SwiperBC.isSwiperLoaded && elementorFrontend.config.experimentalFeatures.e_optimized_assets_loading ) {
SwiperSource = window.Swiper;
SwiperBC.isSwiperLoaded = true;
// Once the SwiperSource has the Swiper lib function, we need to overwrite window.Swiper with the legacySwiper class.
legacySwiper();
}
SwiperSource.prototype.adjustConfig = this.adjustConfig;
return new SwiperSource( container, config );
}
getElementorBreakpointValues() {
const elementorBreakpoints = elementorFrontend.config.responsive.activeBreakpoints,
elementorBreakpointValues = [];
Object.values( elementorBreakpoints ).forEach( ( breakpointConfig ) => {
elementorBreakpointValues.push( breakpointConfig.value );
} );
return elementorBreakpointValues;
}
// Backwards compatibility for Elementor Pro <2.9.0 (old Swiper version - <5.0.0)
// In Swiper 5.0.0 and up, breakpoints changed from acting as max-width to acting as min-width
adjustConfig( config ) {
// Only reverse the breakpoints if the handle param has been defined
if ( ! config.handleElementorBreakpoints ) {
return config;
}
const elementorBreakpoints = elementorFrontend.config.responsive.activeBreakpoints,
elementorBreakpointValues = this.getElementorBreakpointValues();
Object.keys( config.breakpoints ).forEach( ( configBPKey ) => {
const configBPKeyInt = parseInt( configBPKey );
let breakpointToUpdate;
// The `configBPKeyInt + 1` is a BC Fix for Elementor Pro Carousels from 2.8.0-2.8.3 used with Elementor >= 2.9.0
if ( configBPKeyInt === elementorBreakpoints.mobile.value || ( configBPKeyInt + 1 ) === elementorBreakpoints.mobile.value ) {
// This handles the mobile breakpoint. Elementor's default sm breakpoint is never actually used,
// so the mobile breakpoint (md) needs to be handled separately and set to the 0 breakpoint (xs)
breakpointToUpdate = 0;
} else if ( elementorBreakpoints.widescreen && ( configBPKeyInt === elementorBreakpoints.widescreen.value || ( configBPKeyInt + 1 ) === elementorBreakpoints.widescreen.value ) ) {
// Widescreen is a min-width breakpoint. Since in Swiper >5.0 the breakpoint system is min-width based,
// the value we pass to the Swiper instance in this case is the breakpoint from the user, unchanged.
breakpointToUpdate = configBPKeyInt;
} else {
// Find the index of the current config breakpoint in the Elementor Breakpoints array
const currentBPIndexInElementorBPs = elementorBreakpointValues.findIndex( ( elementorBP ) => {
// BC Fix for Elementor Pro Carousels from 2.8.0-2.8.3 used with Elementor >= 2.9.0
return configBPKeyInt === elementorBP || ( configBPKeyInt + 1 ) === elementorBP;
} );
// For all other Swiper config breakpoints, move them one breakpoint down on the breakpoint list,
// according to the array of Elementor's global breakpoints
breakpointToUpdate = elementorBreakpointValues[ currentBPIndexInElementorBPs - 1 ];
}
config.breakpoints[ breakpointToUpdate ] = config.breakpoints[ configBPKey ];
// Then reset the settings in the original breakpoint key to the default values
config.breakpoints[ configBPKey ] = {
slidesPerView: config.slidesPerView,
slidesPerGroup: config.slidesPerGroup ? config.slidesPerGroup : 1,
};
} );
return config;
}
}
// The following code is needed to support Pro version < 3.1.0.
SwiperBC.isSwiperLoaded = false;
// In the legacy behavior, window.Swiper was a class that returns an instance of the Swiper lib function after config adjustments.
function legacySwiper() {
window.Swiper = class {
constructor( container, config ) {
config.legacy = true;
return new SwiperBC( container, config );
}
};
}
let SwiperSource = window.Swiper;
// In case that the Swiper lib exists (meaning not in optimized mode) we overwrite the window.Swiper with a class that supports legacy behavior.
if ( SwiperSource ) {
legacySwiper();
}

View File

@@ -0,0 +1,81 @@
export default class extends elementorModules.ViewModule {
getDefaultSettings() {
return {
selectors: {
links: 'a[href^="%23elementor-action"], a[href^="#elementor-action"]',
},
};
}
bindEvents() {
elementorFrontend.elements.$document.on( 'click', this.getSettings( 'selectors.links' ), this.runLinkAction.bind( this ) );
}
initActions() {
this.actions = {
lightbox: async ( settings ) => {
const lightbox = await elementorFrontend.utils.lightbox;
if ( settings.id ) {
lightbox.openSlideshow( settings.id, settings.url );
} else {
lightbox.showModal( settings );
}
},
};
}
addAction( name, callback ) {
this.actions[ name ] = callback;
}
runAction( url, ...restArgs ) {
url = decodeURIComponent( url );
const actionMatch = url.match( /action=(.+?)&/ ),
settingsMatch = url.match( /settings=(.+)/ );
if ( ! actionMatch ) {
return;
}
const action = this.actions[ actionMatch[ 1 ] ];
if ( ! action ) {
return;
}
let settings = {};
if ( settingsMatch ) {
settings = JSON.parse( atob( settingsMatch[ 1 ] ) );
}
action( settings, ...restArgs );
}
runLinkAction( event ) {
event.preventDefault();
this.runAction( jQuery( event.currentTarget ).attr( 'href' ), event );
}
runHashAction() {
if ( location.hash ) {
this.runAction( location.hash );
}
}
createActionHash( action, settings ) {
// We need to encode the hash tag (#) here, in order to support share links for a variety of providers
return encodeURIComponent( `#elementor-action:action=${ action }&settings=${ btoa( JSON.stringify( settings ) ) }` );
}
onInit() {
super.onInit();
this.initActions();
elementorFrontend.on( 'components:init', this.runHashAction.bind( this ) );
}
}

View File

@@ -0,0 +1,12 @@
// Escape HTML special chars to prevent XSS.
export const escapeHTML = ( str ) => {
const specialChars = {
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
"'": '&#39;',
'"': '&quot;',
};
return str.replace( /[&<>'"]/g, ( tag ) => specialChars[ tag ] || tag );
};

View File

@@ -0,0 +1,43 @@
export default class BaseLoader extends elementorModules.ViewModule {
getDefaultSettings() {
return {
isInserted: false,
selectors: {
firstScript: 'script:first',
},
};
}
getDefaultElements() {
return {
$firstScript: jQuery( this.getSettings( 'selectors.firstScript' ) ),
};
}
insertAPI() {
this.elements.$firstScript.before( jQuery( '<script>', { src: this.getApiURL() } ) );
this.setSettings( 'isInserted', true );
}
getVideoIDFromURL( url ) {
const videoIDParts = url.match( this.getURLRegex() );
return videoIDParts && videoIDParts[ 1 ];
}
onApiReady( callback ) {
if ( ! this.getSettings( 'isInserted' ) ) {
this.insertAPI();
}
if ( this.isApiLoaded() ) {
callback( this.getApiObject() );
} else {
// If not ready check again by timeout..
setTimeout( () => {
this.onApiReady( callback );
}, 350 );
}
}
}

View File

@@ -0,0 +1,19 @@
import BaseLoader from './base-loader';
export default class VimeoLoader extends BaseLoader {
getApiURL() {
return 'https://player.vimeo.com/api/player.js';
}
getURLRegex() {
return /^(?:https?:\/\/)?(?:www|player\.)?(?:vimeo\.com\/)?(?:video\/|external\/)?(\d+)([^.?&#"'>]?)/;
}
isApiLoaded() {
return window.Vimeo;
}
getApiObject() {
return Vimeo;
}
}

View File

@@ -0,0 +1,19 @@
import BaseLoader from './base-loader';
export default class YoutubeLoader extends BaseLoader {
getApiURL() {
return 'https://www.youtube.com/iframe_api';
}
getURLRegex() {
return /^(?:https?:\/\/)?(?:www\.)?(?:m\.)?(?:youtu\.be\/|youtube\.com\/(?:(?:watch)?\?(?:.*&)?vi?=|(?:embed|v|vi|user)\/))([^?&"'>]+)/;
}
isApiLoaded() {
return window.YT && YT.loaded;
}
getApiObject() {
return YT;
}
}