version 4.13.0

This commit is contained in:
2021-12-07 11:08:05 +00:00
commit cb26d2c0c4
1285 changed files with 254735 additions and 0 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
!function(e){var t={};function o(n){if(t[n])return t[n].exports;var r=t[n]={i:n,l:!1,exports:{}};return e[n].call(r.exports,r,r.exports,o),r.l=!0,r.exports}o.m=e,o.c=t,o.d=function(e,t,n){o.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:n})},o.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},o.t=function(e,t){if(1&t&&(e=o(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var n=Object.create(null);if(o.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var r in e)o.d(n,r,function(t){return e[t]}.bind(null,r));return n},o.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return o.d(t,"a",t),t},o.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},o.p="",o(o.s=151)}({0:function(e,t){e.exports=jQuery},151:function(e,t,o){"use strict";(function(e){var t,n=(t=o(23))&&t.__esModule?t:{default:t};window.et_error_modal_shown=!1,window.et_builder_version=window.et_builder_version||"";var r=e("#et-builder-cache-notice-template");et_pb_notice_options.product_version!==window.et_builder_version&&(e("body").addClass("et_pb_stop_scroll").append(r.html()),(0,n.default)(e(".et_pb_prompt_modal")),window.et_error_modal_shown=!0)}).call(this,o(0))},23:function(e,t,o){"use strict";(function(e){Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var o=function(t,o){var n=e(window),r=e("#wpadminbar"),i=n.height(),u=t.outerHeight(),l=r.outerHeight(),a=0-u/2+l/2;u>i-l?t.css({top:"".concat(l+15,"px"),bottom:15,marginTop:0,minHeight:0}):t.css({top:"50%",marginTop:"".concat(a,"px")}),t.addClass("et_pb_auto_centerize_modal")};t.default=o}).call(this,o(0))}});

View File

@ -0,0 +1 @@
!function(e){var t={};function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)n.d(r,o,function(t){return e[t]}.bind(null,o));return r},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s=152)}({0:function(e,t){e.exports=jQuery},152:function(e,t,n){"use strict";(function(e){et_modules_wrapper.builderCssContainerPrefix;var t=et_modules_wrapper.builderCssLayoutPrefix,n=e(".et_pb_module:not(".concat(t," .et_pb_module, .et_pb_section .et_pb_module), .et_pb_row:not(").concat(t," .et_pb_row, .et_pb_section .et_pb_row), .et_pb_section:not(").concat(t," .et_pb_section)"));n.length>0&&n.each((function(){var t=e(this);0===t.closest("#et-boc").length&&t.wrap('<div id="et-boc"></div>'),0===t.closest(".et-l").length&&t.wrap('<div class="et-l"></div>')}))}).call(this,n(0))}});

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,23 @@
/*
Copyright (c) 2012 Digital Fusion, http://teamdf.com/
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
!function(t){var i=t(window);t.fn.visible=function(t,e,o){if(!(this.length<1)){var r=this.length>1?this.eq(0):this,n=r.get(0),f=i.width(),h=i.height(),o=o?o:"both",l=e===!0?n.offsetWidth*n.offsetHeight:!0;if("function"==typeof n.getBoundingClientRect){var g=n.getBoundingClientRect(),u=g.top>=0&&g.top<h,s=g.bottom>0&&g.bottom<=h,c=g.left>=0&&g.left<f,a=g.right>0&&g.right<=f,v=t?u||s:u&&s,b=t?c||a:c&&a;if("both"===o)return l&&v&&b;if("vertical"===o)return l&&v;if("horizontal"===o)return l&&b}else{var d=i.scrollTop(),p=d+h,w=i.scrollLeft(),m=w+f,y=r.offset(),z=y.top,B=z+r.height(),C=y.left,R=C+r.width(),j=t===!0?B:z,q=t===!0?z:B,H=t===!0?R:C,L=t===!0?C:R;if("both"===o)return!!l&&p>=q&&j>=d&&m>=L&&H>=w;if("vertical"===o)return!!l&&p>=q&&j>=d;if("horizontal"===o)return!!l&&m>=L&&H>=w}}}}(jQuery);

View File

@ -0,0 +1,12 @@
/*!
* Copyright (c) 2013 Pieroxy <pieroxy@pieroxy.net>
* This work is free. You can redistribute it and/or modify it
* under the terms of the WTFPL, Version 2
* For more information see LICENSE.txt or http://www.wtfpl.net/
*
* For more information, the home page:
* http://pieroxy.net/blog/pages/lz-string/testing.html
*
* LZ-based compression algorithm, version 1.4.4
*/
var LZString=function(){function o(o,r){if(!t[o]){t[o]={};for(var n=0;n<o.length;n++)t[o][o.charAt(n)]=n}return t[o][r]}var r=String.fromCharCode,n="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",e="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+-$",t={},i={compressToBase64:function(o){if(null==o)return"";var r=i._compress(o,6,function(o){return n.charAt(o)});switch(r.length%4){default:case 0:return r;case 1:return r+"===";case 2:return r+"==";case 3:return r+"="}},decompressFromBase64:function(r){return null==r?"":""==r?null:i._decompress(r.length,32,function(e){return o(n,r.charAt(e))})},compressToUTF16:function(o){return null==o?"":i._compress(o,15,function(o){return r(o+32)})+" "},decompressFromUTF16:function(o){return null==o?"":""==o?null:i._decompress(o.length,16384,function(r){return o.charCodeAt(r)-32})},compressToUint8Array:function(o){for(var r=i.compress(o),n=new Uint8Array(2*r.length),e=0,t=r.length;t>e;e++){var s=r.charCodeAt(e);n[2*e]=s>>>8,n[2*e+1]=s%256}return n},decompressFromUint8Array:function(o){if(null===o||void 0===o)return i.decompress(o);for(var n=new Array(o.length/2),e=0,t=n.length;t>e;e++)n[e]=256*o[2*e]+o[2*e+1];var s=[];return n.forEach(function(o){s.push(r(o))}),i.decompress(s.join(""))},compressToEncodedURIComponent:function(o){return null==o?"":i._compress(o,6,function(o){return e.charAt(o)})},decompressFromEncodedURIComponent:function(r){return null==r?"":""==r?null:(r=r.replace(/ /g,"+"),i._decompress(r.length,32,function(n){return o(e,r.charAt(n))}))},compress:function(o){return i._compress(o,16,function(o){return r(o)})},_compress:function(o,r,n){if(null==o)return"";var e,t,i,s={},p={},u="",c="",a="",l=2,f=3,h=2,d=[],m=0,v=0;for(i=0;i<o.length;i+=1)if(u=o.charAt(i),Object.prototype.hasOwnProperty.call(s,u)||(s[u]=f++,p[u]=!0),c=a+u,Object.prototype.hasOwnProperty.call(s,c))a=c;else{if(Object.prototype.hasOwnProperty.call(p,a)){if(a.charCodeAt(0)<256){for(e=0;h>e;e++)m<<=1,v==r-1?(v=0,d.push(n(m)),m=0):v++;for(t=a.charCodeAt(0),e=0;8>e;e++)m=m<<1|1&t,v==r-1?(v=0,d.push(n(m)),m=0):v++,t>>=1}else{for(t=1,e=0;h>e;e++)m=m<<1|t,v==r-1?(v=0,d.push(n(m)),m=0):v++,t=0;for(t=a.charCodeAt(0),e=0;16>e;e++)m=m<<1|1&t,v==r-1?(v=0,d.push(n(m)),m=0):v++,t>>=1}l--,0==l&&(l=Math.pow(2,h),h++),delete p[a]}else for(t=s[a],e=0;h>e;e++)m=m<<1|1&t,v==r-1?(v=0,d.push(n(m)),m=0):v++,t>>=1;l--,0==l&&(l=Math.pow(2,h),h++),s[c]=f++,a=String(u)}if(""!==a){if(Object.prototype.hasOwnProperty.call(p,a)){if(a.charCodeAt(0)<256){for(e=0;h>e;e++)m<<=1,v==r-1?(v=0,d.push(n(m)),m=0):v++;for(t=a.charCodeAt(0),e=0;8>e;e++)m=m<<1|1&t,v==r-1?(v=0,d.push(n(m)),m=0):v++,t>>=1}else{for(t=1,e=0;h>e;e++)m=m<<1|t,v==r-1?(v=0,d.push(n(m)),m=0):v++,t=0;for(t=a.charCodeAt(0),e=0;16>e;e++)m=m<<1|1&t,v==r-1?(v=0,d.push(n(m)),m=0):v++,t>>=1}l--,0==l&&(l=Math.pow(2,h),h++),delete p[a]}else for(t=s[a],e=0;h>e;e++)m=m<<1|1&t,v==r-1?(v=0,d.push(n(m)),m=0):v++,t>>=1;l--,0==l&&(l=Math.pow(2,h),h++)}for(t=2,e=0;h>e;e++)m=m<<1|1&t,v==r-1?(v=0,d.push(n(m)),m=0):v++,t>>=1;for(;;){if(m<<=1,v==r-1){d.push(n(m));break}v++}return d.join("")},decompress:function(o){return null==o?"":""==o?null:i._decompress(o.length,32768,function(r){return o.charCodeAt(r)})},_decompress:function(o,n,e){var t,i,s,p,u,c,a,l,f=[],h=4,d=4,m=3,v="",w=[],A={val:e(0),position:n,index:1};for(i=0;3>i;i+=1)f[i]=i;for(p=0,c=Math.pow(2,2),a=1;a!=c;)u=A.val&A.position,A.position>>=1,0==A.position&&(A.position=n,A.val=e(A.index++)),p|=(u>0?1:0)*a,a<<=1;switch(t=p){case 0:for(p=0,c=Math.pow(2,8),a=1;a!=c;)u=A.val&A.position,A.position>>=1,0==A.position&&(A.position=n,A.val=e(A.index++)),p|=(u>0?1:0)*a,a<<=1;l=r(p);break;case 1:for(p=0,c=Math.pow(2,16),a=1;a!=c;)u=A.val&A.position,A.position>>=1,0==A.position&&(A.position=n,A.val=e(A.index++)),p|=(u>0?1:0)*a,a<<=1;l=r(p);break;case 2:return""}for(f[3]=l,s=l,w.push(l);;){if(A.index>o)return"";for(p=0,c=Math.pow(2,m),a=1;a!=c;)u=A.val&A.position,A.position>>=1,0==A.position&&(A.position=n,A.val=e(A.index++)),p|=(u>0?1:0)*a,a<<=1;switch(l=p){case 0:for(p=0,c=Math.pow(2,8),a=1;a!=c;)u=A.val&A.position,A.position>>=1,0==A.position&&(A.position=n,A.val=e(A.index++)),p|=(u>0?1:0)*a,a<<=1;f[d++]=r(p),l=d-1,h--;break;case 1:for(p=0,c=Math.pow(2,16),a=1;a!=c;)u=A.val&A.position,A.position>>=1,0==A.position&&(A.position=n,A.val=e(A.index++)),p|=(u>0?1:0)*a,a<<=1;f[d++]=r(p),l=d-1,h--;break;case 2:return w.join("")}if(0==h&&(h=Math.pow(2,m),m++),f[l])v=f[l];else{if(l!==d)return null;v=s+s.charAt(0)}w.push(v),f[d++]=s+v.charAt(0),h--,s=v,0==h&&(h=Math.pow(2,m),m++)}}};return i}();"function"==typeof define&&define.amd?define(function(){return LZString}):"undefined"!=typeof module&&null!=module&&(module.exports=LZString);

View File

@ -0,0 +1,157 @@
/* global wp */
/**
* media-library.js
*
* Adapted from WordPress
*
* @copyright 2017 by the WordPress contributors.
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
* This program incorporates work covered by the following copyright and
* permission notices:
*
* b2 is (c) 2001, 2002 Michel Valdrighi - m@tidakada.com - http://tidakada.com
*
* b2 is released under the GPL
*
* WordPress - Web publishing software
*
* Copyright 2003-2010 by the contributors
*
* WordPress is released under the GPL
*/
var Select = wp.media.view.MediaFrame.Select,
Library = wp.media.controller.Library,
l10n = wp.media.view.l10n;
wp.media.view.MediaFrame.ETSelect = wp.media.view.MediaFrame.Select.extend({
initialize: function() {
_.defaults( this.options, {
multiple: true,
editing: false,
embed: true,
state: 'insert',
metadata: {},
title: l10n.insertMediaTitle,
button: {
text: l10n.insertIntoPost
},
});
// Call 'initialize' directly on the parent class.
Select.prototype.initialize.apply( this, arguments );
this.createIframeStates();
},
/**
* Create the default states.
*/
createStates: function() {
var options = this.options;
var states = [
// Main states.
new Library({
id: 'insert',
title: options.title,
priority: 20,
toolbar: 'main-insert',
filterable: 'all',
library: wp.media.query( options.library ),
multiple: options.multiple ? 'reset' : false,
editable: true,
allowLocalEdits: true,
displaySettings: true,
displayUserSettings: true
}),
];
if (options.embed) {
// Embed states.
states.push(new wp.media.controller.Embed( { metadata: options.metadata } ))
}
this.states.add(states);
},
bindHandlers: function() {
var handlers;
Select.prototype.bindHandlers.apply( this, arguments );
this.on( 'toolbar:create:main-insert', this.createToolbar, this );
this.on( 'toolbar:create:main-embed', this.mainEmbedToolbar, this );
handlers = {
content: {
'embed': 'embedContent',
},
toolbar: {
'main-insert': 'mainInsertToolbar',
}
};
_.each( handlers, function( regionHandlers, region ) {
_.each( regionHandlers, function( callback, handler ) {
this.on( region + ':render:' + handler, this[ callback ], this );
}, this );
}, this );
},
// Content
embedContent: function() {
var view = new wp.media.view.Embed({
controller: this,
model: this.state()
}).render();
this.content.set( view );
if ( ! wp.media.isTouchDevice ) {
view.url.input.focus();
}
},
// Toolbars
mainInsertToolbar: function( view ) {
var options = this.options;
var controller = this;
view.set( 'insert', {
style: 'primary',
priority: 80,
text: options.button.text,
requires: { selection: true },
/**
* @fires wp.media.controller.State#insert
*/
click: function() {
var state = controller.state(),
selection = state.get('selection');
controller.close();
state.trigger( 'insert', selection ).reset();
}
});
},
mainEmbedToolbar: function( toolbar ) {
toolbar.view = new wp.media.view.Toolbar.Embed({
controller: this
});
}
});

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,92 @@
( function($) {
$(function() {
// WP 5.8 above - Widget Block Editor.
var is_widgets_block_editor = $('.edit-widgets-block-editor').length > 0;
// 1.a Appends widget area creator panel.
var widget_writing_area = is_widgets_block_editor ? '.block-editor-writing-flow > div' : '.widget-liquid-right';
$(widget_writing_area).append(et_pb_options.widget_info);
var $create_box = $( '#et_pb_widget_area_create' ),
$widget_name_input = $create_box.find( '#et_pb_new_widget_area_name' ),
$et_pb_sidebars = $( 'div[id^=et_pb_widget_area_]' );
// 1.b. Handles create widget area action.
$create_box.find('.et_pb_create_widget_area').on('click', function(event) {
var $this_el = $(this);
event.preventDefault();
if ( $widget_name_input.val() === '' ) return;
$.ajax( {
type: "POST",
url: et_pb_options.ajaxurl,
data:
{
action : 'et_pb_add_widget_area',
et_admin_load_nonce : et_pb_options.et_admin_load_nonce,
et_widget_area_name : $widget_name_input.val()
},
success: function( data ){
$this_el.closest( '#et_pb_widget_area_create' ).find( '.et_pb_widget_area_result' ).hide().html( data ).slideToggle();
}
} );
});
// 2.a. Append custom widget area remove button and handles remove action.
var et_pb_sidebars_append_delete_button = function() {
// 2.a.1. Append custom widget area remove button.
var widget_area_id = is_widgets_block_editor ? $(this).data('widget-area-id') : $(this).attr('id');
var widget_wrapper = is_widgets_block_editor ? '.block-editor-block-list__block' : '.widgets-holder-wrap';
var widget_title = is_widgets_block_editor ? '.components-panel__body-toggle' : '.sidebar-name h2, .sidebar-name h3';
$(this).closest(widget_wrapper).find(widget_title).before('<a href="#" class="et_pb_widget_area_remove" data-et-widget-area-id="' + widget_area_id + '">' + et_pb_options.delete_string + '</a>');
// 2.a.1. andles remove widget area action.
$('.et_pb_widget_area_remove').on('click', function(event) {
var $this_el = $(this);
event.preventDefault();
$.ajax( {
type: "POST",
url: et_pb_options.ajaxurl,
data:
{
action : 'et_pb_remove_widget_area',
et_admin_load_nonce : et_pb_options.et_admin_load_nonce,
et_widget_area_name : $this_el.data('et-widget-area-id'),
},
success: function( data ){
$('a[data-et-widget-area-id="' + data + '"]').closest(widget_wrapper).remove();
}
} );
return false;
});
};
// 2.b. Appends custom widget area remove button on each custom sidebar.
if (is_widgets_block_editor) {
var widgetBlockListMutation = _.debounce(function(mutations, observer) {
$('div[data-widget-area-id^=et_pb_widget_area_]').each(et_pb_sidebars_append_delete_button);
// Disconnect once we know the delete buttons are added.
if ($('.et_pb_widget_area_remove').length > 0) {
observer.disconnect();
}
}, 1000);
// Watch for widget block editor to load Widget Area blocks. There is no event to
// know when those blocks are added on the editor, hence we use mutation observer.
var widgetBlockListObserver = new MutationObserver(widgetBlockListMutation);
widgetBlockListObserver.observe(document.querySelector('.block-editor-block-list__layout'), {childList: true});
} else {
$et_pb_sidebars.each(et_pb_sidebars_append_delete_button);
}
} );
} )(jQuery);

View File

@ -0,0 +1,424 @@
/**
* wp-color-picker-alpha
*
* Version 1.0
* Copyright (c) 2017 Elegant Themes.
* Licensed under the GPLv2 license.
*
* Overwrite Automattic Iris for enabled Alpha Channel in wpColorPicker
* Only run in input and is defined data alpha in true
* Add custom colorpicker UI
*
* This is modified version made by Elegant Themes based on the work covered by
* the following copyright:
*
* wp-color-picker-alpha Version: 1.1
* https://github.com/23r9i0/wp-color-picker-alpha
* Copyright (c) 2015 Sergio P.A. (23r9i0).
* Licensed under the GPLv2 license.
*/
( function( $ ) {
// Variable for some backgrounds
var image = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAAHnlligAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAHJJREFUeNpi+P///4EDBxiAGMgCCCAGFB5AADGCRBgYDh48CCRZIJS9vT2QBAggFBkmBiSAogxFBiCAoHogAKIKAlBUYTELAiAmEtABEECk20G6BOmuIl0CIMBQ/IEMkO0myiSSraaaBhZcbkUOs0HuBwDplz5uFJ3Z4gAAAABJRU5ErkJggg==';
// html stuff for wpColorPicker copy of the original color-picker.js
var _before = '<a tabindex="0" class="wp-color-result" />',
_after = '<div class="wp-picker-holder" />',
_wrap = '<div class="wp-picker-container" />',
_button = '<input type="button" class="button button-small button-clear hidden" />',
_close_button = '<button type="button" class="button button-confirm" />',
_close_button_icon = '<div style="fill: #3EF400; width: 25px; height: 25px; margin-top: -1px;"><svg viewBox="0 0 28 28" preserveAspectRatio="xMidYMid meet" shapeRendering="geometricPrecision"><g><path d="M19.203 9.21a.677.677 0 0 0-.98 0l-5.71 5.9-2.85-2.95a.675.675 0 0 0-.98 0l-1.48 1.523a.737.737 0 0 0 0 1.015l4.82 4.979a.677.677 0 0 0 .98 0l7.68-7.927a.737.737 0 0 0 0-1.015l-1.48-1.525z" fillRule="evenodd" /></g></svg></div>';
/**
* Overwrite Color
* for enable support rbga
*/
Color.fn.toString = function() {
if ( this._alpha < 1 )
return this.toCSS( 'rgba', this._alpha ).replace( /\s+/g, '' );
var hex = parseInt( this._color, 10 ).toString( 16 );
if ( this.error )
return '';
if ( hex.length < 6 ) {
for ( var i = 6 - hex.length - 1; i >= 0; i-- ) {
hex = '0' + hex;
}
}
return '#' + hex;
};
/**
* Overwrite wpColorPicker
*/
$.widget( 'wp.wpColorPicker', $.wp.wpColorPicker, {
_create: function() {
// bail early for unsupported Iris.
if ( ! $.support.iris ) {
return;
}
var self = this,
el = self.element;
$.extend( self.options, el.data() );
// keep close bound so it can be attached to a body listener
self.close = $.proxy( self.close, self );
self.initialValue = el.val();
// Set up HTML structure, hide things
el.addClass( 'wp-color-picker' ).hide().wrap( _wrap );
self.wrap = el.parent();
self.toggler = $( _before ).insertBefore( el ).css( { backgroundColor: self.initialValue } ).attr( 'title', wpColorPickerL10n.pick ).attr( 'data-current', wpColorPickerL10n.current );
self.pickerContainer = $( _after ).insertAfter( el );
self.button = $( _button );
self.close_button = $( _close_button );
if ( self.options.defaultColor ) {
self.button.addClass( 'wp-picker-default' ).val( wpColorPickerL10n.defaultString );
} else {
self.button.addClass( 'wp-picker-clear' ).val( wpColorPickerL10n.clear );
}
el.wrap( '<span class="wp-picker-input-wrap" />' ).after(self.button);
if ( self.options.diviColorpicker ) {
self.close_button.html( _close_button_icon );
el.after( self.close_button );
}
el.iris( {
target: self.pickerContainer,
hide: self.options.hide,
width: self.options.width,
height: self.options.height,
diviColorpicker: self.options.diviColorpicker,
mode: self.options.mode,
palettes: self.options.palettes,
change: function( event, ui ) {
if ( self.options.alpha ) {
self.toggler.css( { 'background-image': 'url(' + image + ')' } ).html('<span />');
self.toggler.find('span').css({
'width': '100%',
'height': '100%',
'position': 'absolute',
'top': 0,
'left': 0,
'border-top-left-radius': '3px',
'border-bottom-left-radius': '3px',
'background': ui.color.toString()
});
} else {
self.toggler.css( { backgroundColor: ui.color.toString() } );
}
// check for a custom cb
if ( $.isFunction( self.options.change ) ) {
self.options.change.call( this, event, ui );
}
}
} );
el.val( self.initialValue );
self._addListeners();
if ( ! self.options.hide ) {
self.toggler.click();
}
},
_addListeners: function() {
var self = this;
// prevent any clicks inside this widget from leaking to the top and closing it
self.wrap.on( 'click.wpcolorpicker', function( event ) {
event.stopPropagation();
});
self.toggler.click( function(){
if ( self.toggler.hasClass( 'wp-picker-open' ) ) {
self.close();
} else {
self.open();
}
});
self.element.change( function( event ) {
var me = $( this ),
val = me.val();
// Empty or Error = clear
if ( val === '' || self.element.hasClass('iris-error') ) {
if ( self.options.alpha ) {
self.toggler.css( 'backgroundColor', '' );
self.toggler.find('span').css( 'backgroundColor', '' );
} else {
self.toggler.css( 'backgroundColor', '' );
}
// fire clear callback if we have one
if ( $.isFunction( self.options.clear ) ) {
self.options.clear.call( this, event );
}
}
});
// open a keyboard-focused closed picker with space or enter
self.toggler.on( 'keyup', function( event ) {
if ( event.keyCode === 13 || event.keyCode === 32 ) {
event.preventDefault();
self.toggler.trigger( 'click' ).next().focus();
}
});
self.button.click( function( event ) {
var me = $( this );
if ( me.hasClass( 'wp-picker-clear' ) ) {
self.element.val( '' );
if ( self.options.alpha ) {
self.toggler.css( 'backgroundColor', '' );
self.toggler.find('span').css( 'backgroundColor', '' );
} else {
self.toggler.css( 'backgroundColor', '' );
}
if ( $.isFunction( self.options.clear ) ) {
self.options.clear.call( this, event );
}
} else if ( me.hasClass( 'wp-picker-default' ) ) {
self.element.val( self.options.defaultColor ).change();
}
});
self.close_button.click( function( event ) {
event.preventDefault();
self.close();
});
},
close: function() {
this._super();
var self = this;
if ($.isFunction(self.options.onClose)) {
self.options.onClose.call(this);
}
},
});
/**
* Overwrite iris
*/
$.widget( 'a8c.iris', $.a8c.iris, {
_create: function() {
this._super();
// Global option for check is mode rbga is enabled
this.options.alpha = this.element.data( 'alpha' ) || false;
// Is not input disabled
if ( ! this.element.is( ':input' ) ) {
this.options.alpha = false;
}
if ( typeof this.options.alpha !== 'undefined' && this.options.alpha ) {
var self = this,
el = self.element,
_html = '<div class="iris-strip iris-slider iris-alpha-slider"><div class="iris-slider-offset iris-slider-offset-alpha"></div></div>',
aContainer = $( _html ).appendTo( self.picker.find( '.iris-picker-inner' ) ),
aSlider = aContainer.find( '.iris-slider-offset-alpha' ),
controls = {
aContainer: aContainer,
aSlider: aSlider
};
// Set default width for input reset
self.options.defaultWidth = el.width();
// Update width for input
if ( self._color._alpha < 1 || self._color.toString().indexOf('rgb') != 1 ) {
el.width( parseInt( self.options.defaultWidth+100 ) );
}
// Push new controls
$.each( controls, function( k, v ){
self.controls[k] = v;
});
// Change size strip and add margin for sliders
self.controls.square.css({'margin-right': '0'});
var emptyWidth = ( self.picker.width() - self.controls.square.width() - 20 ),
stripsMargin = emptyWidth/6,
stripsWidth = (emptyWidth/2) - stripsMargin;
$.each( [ 'aContainer', 'strip' ], function( k, v ) {
self.controls[v].width( stripsWidth ).css({ 'margin-left': stripsMargin + 'px' });
});
// Add new slider
self._initControls();
// For updated widget
self._change();
}
},
_initControls: function() {
this._super();
if ( this.options.alpha ) {
var self = this,
controls = self.controls;
controls.aSlider.slider({
orientation: 'vertical',
min: 0,
max: 100,
step: 1,
value: parseInt( self._color._alpha*100 ),
slide: function( event, ui ) {
// Update alpha value
self._color._alpha = parseFloat( ui.value/100 );
self._change.apply( self, arguments );
}
});
}
},
_change: function() {
this._super();
var self = this,
el = self.element;
if ( this.options.alpha ) {
var controls = self.controls,
alpha = parseInt( self._color._alpha*100 ),
color = self._color.toRgb(),
gradient = [
'rgb(' + color.r + ',' + color.g + ',' + color.b + ') 0%',
'rgba(' + color.r + ',' + color.g + ',' + color.b + ', 0) 100%'
],
defaultWidth = self.options.defaultWidth,
target = self.picker.closest('.wp-picker-container').find( '.wp-color-result' );
// Generate background slider alpha, only for CSS3 old browser fuck!! :)
controls.aContainer.css({ 'background': 'linear-gradient(to bottom, ' + gradient.join( ', ' ) + '), url(' + image + ')' });
if ( target.hasClass('wp-picker-open') ) {
// Update alpha value
controls.aSlider.slider( 'value', alpha );
/**
* Disabled change opacity in default slider Saturation ( only is alpha enabled )
* and change input width for view all value
*/
if ( self._color._alpha < 1 ) {
var style = controls.strip.attr( 'style' ).replace( /rgba\(([0-9]+,)(\s+)?([0-9]+,)(\s+)?([0-9]+)(,(\s+)?[0-9\.]+)\)/g, 'rgb($1$3$5)' );
controls.strip.attr( 'style', style );
el.width( parseInt( defaultWidth+100 ) );
} else {
el.width( defaultWidth );
}
}
}
var reset = el.data('reset-alpha') || false;
if ( reset ) {
self.picker.find( '.iris-palette-container' ).on( 'click.palette', '.iris-palette', function() {
self._color._alpha = 1;
self.active = 'external';
self._change();
});
}
},
_addInputListeners: function( input ) {
var self = this,
debounceTimeout = 700, // originally set to 100, but some user perceive it as "jumps to random colors at third digit"
callback = function( event ){
var color = new Color( input.val() ),
val = input.val();
input.removeClass( 'iris-error' );
// we gave a bad color
if ( color.error ) {
// don't error on an empty input
if ( val !== '' ) {
input.addClass( 'iris-error' );
}
} else {
if ( color.toString() !== self._color.toString() ) {
// let's not do this on keyup for hex shortcodes
if ( ! ( event.type === 'keyup' && val.match( /^[0-9a-fA-F]{3}$/ ) ) ) {
self._setOption( 'color', color.toString() );
}
}
}
};
input.on( 'change', callback ).on( 'keyup', self._debounce( callback, debounceTimeout ) );
// If we initialized hidden, show on first focus. The rest is up to you.
if ( self.options.hide ) {
input.one( 'focus', function() {
self.show();
});
}
},
_dimensions: function( reset ) {
// whatever size
var self = this,
opts = self.options,
controls = self.controls,
square = controls.square,
strip = self.picker.find( '.iris-strip' ),
squareWidth = '77.5%',
stripWidth = '12%',
totalPadding = 20,
innerWidth = opts.border ? opts.width - totalPadding : opts.width,
controlsHeight,
paletteCount = $.isArray( opts.palettes ) ? opts.palettes.length : self._palettes.length,
paletteMargin, paletteWidth, paletteContainerWidth;
if ( reset ) {
square.css( 'width', '' );
strip.css( 'width', '' );
self.picker.css( {width: '', height: ''} );
}
squareWidth = innerWidth * ( parseFloat( squareWidth ) / 100 );
stripWidth = innerWidth * ( parseFloat( stripWidth ) / 100 );
controlsHeight = opts.border ? squareWidth + totalPadding : squareWidth;
if (opts.diviColorpicker ) {
square.width( opts.width ).height( opts.height );
controlsHeight = opts.height;
} else {
square.width( squareWidth ).height( squareWidth );
}
strip.height( squareWidth ).width( stripWidth );
self.picker.css( { width: opts.width, height: controlsHeight } );
if ( ! opts.palettes ) {
return self.picker.css( 'paddingBottom', '' );
}
// single margin at 2%
paletteMargin = squareWidth * 2 / 100;
paletteContainerWidth = squareWidth - ( ( paletteCount - 1 ) * paletteMargin );
paletteWidth = paletteContainerWidth / paletteCount;
self.picker.find('.iris-palette').each( function( i ) {
var margin = i === 0 ? 0 : paletteMargin;
$( this ).css({
width: paletteWidth,
height: paletteWidth,
marginLeft: margin
});
});
self.picker.css( 'paddingBottom', paletteWidth + paletteMargin );
strip.height( paletteWidth + paletteMargin + squareWidth );
}
} );
}( jQuery ) );
// Auto Call plugin is class is color-picker
jQuery( document ).ready( function( $ ) {
$( '.color-picker' ).wpColorPicker();
} );

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,529 @@
/**
* wp-color-picker-alpha
*
* Version 1.0
* Copyright (c) 2017 Elegant Themes.
* Licensed under the GPLv2 license.
*
* Overwrite Automattic Iris for enabled Alpha Channel in wpColorPicker
* Only run in input and is defined data alpha in true
* Add custom colorpicker UI
*
* This is modified version made by Elegant Themes based on the work covered by
* the following copyright:
*
* wp-color-picker-alpha Version: 1.1
* https://github.com/23r9i0/wp-color-picker-alpha
* Copyright (c) 2015 Sergio P.A. (23r9i0).
* Licensed under the GPLv2 license.
*/
( function( $ ) {
// Variable for some backgrounds
var image = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAAHnlligAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAHJJREFUeNpi+P///4EDBxiAGMgCCCAGFB5AADGCRBgYDh48CCRZIJS9vT2QBAggFBkmBiSAogxFBiCAoHogAKIKAlBUYTELAiAmEtABEECk20G6BOmuIl0CIMBQ/IEMkO0myiSSraaaBhZcbkUOs0HuBwDplz5uFJ3Z4gAAAABJRU5ErkJggg==';
// html stuff for wpColorPicker copy of the original color-picker.js
var _before = '<button type="button" class="button wp-color-result" aria-expanded="false"><span class="wp-color-result-text"></span></button>',
_after = '<div class="wp-picker-holder" />',
_wrap = '<div class="wp-picker-container" />',
_button = '<input type="button" class="button button-small button-clear hidden" />',
_wrappingLabel = '<label></label>',
_wrappingLabelText = '<span class="screen-reader-text"></span>',
_close_button = '<button type="button" class="button button-confirm" />',
_close_button_icon = '<div style="fill: #3EF400; width: 25px; height: 25px; margin-top: -1px;"><svg viewBox="0 0 28 28" preserveAspectRatio="xMidYMid meet" shapeRendering="geometricPrecision"><g><path d="M19.203 9.21a.677.677 0 0 0-.98 0l-5.71 5.9-2.85-2.95a.675.675 0 0 0-.98 0l-1.48 1.523a.737.737 0 0 0 0 1.015l4.82 4.979a.677.677 0 0 0 .98 0l7.68-7.927a.737.737 0 0 0 0-1.015l-1.48-1.525z" fillRule="evenodd" /></g></svg></div>';
// Color picker label translation. By default, set label manually without translation.
var _defaultString = 'Default';
var _defaultAriaLabel = 'Select default color';
var _clearString = 'Clear';
var _clearAriaLabel = 'Clear color';
var _colorValue = 'Color value';
var _selectColor = 'Select Color';
if ('undefined' !== typeof wp && 'undefined' !== typeof wp.i18n && 'undefined' !== typeof wp.i18n.__) {
// Directly use wp.i18n if it exists. wp.i18n is added on 5.0.
var __ = wp.i18n.__;
_defaultString = __( 'Default' );
_defaultAriaLabel = __( 'Select default color' );
_clearString = __( 'Clear' );
_clearAriaLabel = __( 'Clear color' );
_colorValue = __( 'Color value' );
_selectColor = __( 'Select Color' );
} else if ('undefined' !== typeof wpColorPickerL10n && 'undefined' !== typeof wpColorPickerL10n.current) {
// Or use wpColorPickerL10n if it's still supported. wpColorPickerL10n is deprecated
// since 5.5.
_defaultString = wpColorPickerL10n.defaultString;
_defaultAriaLabel = wpColorPickerL10n.defaultAriaLabel;
_clearString = wpColorPickerL10n.clear,
_clearAriaLabel = wpColorPickerL10n.clearAriaLabel;
_colorValue = wpColorPickerL10n.defaultLabel;
_selectColor = wpColorPickerL10n.pick;
}
/**
* Overwrite Color
* for enable support rbga
*/
Color.fn.toString = function() {
if ( this._alpha < 1 )
return this.toCSS( 'rgba', this._alpha ).replace( /\s+/g, '' );
var hex = parseInt( this._color, 10 ).toString( 16 );
if ( this.error )
return '';
if ( hex.length < 6 ) {
for ( var i = 6 - hex.length - 1; i >= 0; i-- ) {
hex = '0' + hex;
}
}
return '#' + hex;
};
/**
* Overwrite wpColorPicker
*/
$.widget( 'wp.wpColorPicker', $.wp.wpColorPicker, {
_create: function() {
// Return early if Iris support is missing.
if ( ! $.support.iris ) {
return;
}
var self = this,
el = self.element;
// Override default options with options bound to the element.
$.extend( self.options, el.data() );
// Create a color picker which only allows adjustments to the hue.
if ( self.options.type === 'hue' ) {
return self._createHueOnly();
}
// Bind the close event.
self.close = self.close.bind(self);
self.initialValue = el.val();
// Add a CSS class to the input field.
el.addClass( 'wp-color-picker' );
/*
* Check if there's already a wrapping label, e.g. in the Customizer.
* If there's no label, add a default one to match the Customizer template.
*/
if ( ! el.parent( 'label' ).length ) {
// Wrap the input field in the default label.
el.wrap( _wrappingLabel );
// Insert the default label text.
self.wrappingLabelText = $( _wrappingLabelText )
.insertBefore( el )
.text( _colorValue );
}
/*
* At this point, either it's the standalone version or the Customizer
* one, we have a wrapping label to use as hook in the DOM, let's store it.
*/
self.wrappingLabel = el.parent();
// Wrap the label in the main wrapper.
self.wrappingLabel.wrap( _wrap );
// Store a reference to the main wrapper.
self.wrap = self.wrappingLabel.parent();
// Set up the toggle button and insert it before the wrapping label.
self.toggler = $( _before )
.insertBefore( self.wrappingLabel )
.css( { backgroundColor: self.initialValue } )
.attr( 'title', _selectColor )
.addClass( 'et-wp-color-result-updated');
// some strings were changed in recent colorpicker update, but we still need to use legacy strings in some places
if ( typeof et_pb_color_picker_strings !== 'undefined' ) {
self.toggler.attr( 'data-legacy_title', et_pb_color_picker_strings.legacy_pick ).attr( 'data-current', et_pb_color_picker_strings.legacy_current );
}
// Set the toggle button span element text.
self.toggler.find( '.wp-color-result-text' ).text( _selectColor );
// Set up the Iris container and insert it after the wrapping label.
self.pickerContainer = $( _after ).insertAfter( self.wrappingLabel );
// Store a reference to the Clear/Default button.
self.button = $( _button );
self.close_button = $( _close_button ).html( _close_button_icon );
if ( self.options.diviColorpicker ) {
el.after( self.close_button );
}
// Set up the Clear/Default button.
if ( self.options.defaultColor ) {
self.button
.addClass( 'wp-picker-default' )
.val( _defaultString )
.attr( 'aria-label', _defaultAriaLabel );
} else {
self.button
.addClass( 'wp-picker-clear' )
.val( _clearString )
.attr( 'aria-label', _clearAriaLabel );
}
// Wrap the wrapping label in its wrapper and append the Clear/Default button.
self.wrappingLabel
.wrap( '<span class="wp-picker-input-wrap hidden" />' )
.after( self.button );
/*
* The input wrapper now contains the label+input+Clear/Default button.
* Store a reference to the input wrapper: we'll use this to toggle
* the controls visibility.
*/
self.inputWrapper = el.closest( '.wp-picker-input-wrap' );
/*
* CSS for support < 4.9
*/
self.toggler.css({
'height': '24px',
'margin': '0 6px 6px 0',
'padding': '0 0 0 30px',
'font-size': '11px'
});
el.iris( {
target: self.pickerContainer,
hide: self.options.hide,
width: self.options.width,
height: self.options.height,
mode: self.options.mode,
palettes: self.options.palettes,
diviColorpicker: self.options.diviColorpicker,
change: function( event, ui ) {
if ( self.options.alpha ) {
self.toggler.css( {
'background-image' : 'url(' + image + ')',
'position' : 'relative'
} );
if ( self.toggler.find('span.color-alpha').length == 0 ) {
self.toggler.append('<span class="color-alpha" />');
}
self.toggler.find( 'span.color-alpha' ).css( {
'width' : '100%',
'height' : '100%',
'position' : 'absolute',
'top' : '0px',
'left' : '0px',
'border-top-left-radius' : '3px',
'border-bottom-left-radius' : '3px',
'background' : ui.color.toString()
} );
} else {
self.toggler.css( { backgroundColor: ui.color.toString() } );
}
// check for a custom cb
if ( 'function' === typeof self.options.change ) {
self.options.change.call( this, event, ui );
}
}
} );
el.val( self.initialValue );
self._addListeners();
// Force the color picker to always be closed on initial load.
if ( ! self.options.hide ) {
self.toggler.trigger('click');
}
},
_addListeners: function() {
var self = this;
// Prevent any clicks inside this widget from leaking to the top and closing it.
self.wrap.on( 'click.wpcolorpicker', function( event ) {
event.stopPropagation();
return false;
});
self.toggler.on('click', function() {
if ( self.toggler.hasClass( 'wp-picker-open' ) ) {
self.close();
} else {
self.open();
}
});
self.element.on( 'change', function( event ) {
// Empty or Error = clear
if ( $( this ).val() === '' || self.element.hasClass( 'iris-error' ) ) {
if ( self.options.alpha ) {
self.toggler.find( 'span.color-alpha' ).css( 'backgroundColor', '' );
} else {
self.toggler.css( 'backgroundColor', '' );
}
// fire clear callback if we have one
if ( 'function' === typeof self.options.clear )
self.options.clear.call( this, event );
}
} );
self.button.on( 'click', function( event ) {
if ( $( this ).hasClass( 'wp-picker-clear' ) ) {
self.element.val( '' );
if ( self.options.alpha ) {
self.toggler.find( 'span.color-alpha' ).css( 'backgroundColor', '' );
} else {
self.toggler.css( 'backgroundColor', '' );
}
if ( 'function' === typeof self.options.clear )
self.options.clear.call( this, event );
} else if ( $( this ).hasClass( 'wp-picker-default' ) ) {
self.element.val( self.options.defaultColor ).trigger('change');
}
});
self.close_button.on('click', function(event) {
event.preventDefault();
self.close();
});
},
close: function() {
this._super();
var self = this;
if ('function' === typeof self.options.onClose) {
self.options.onClose.call(this);
}
},
});
/**
* Overwrite iris
*/
$.widget( 'a8c.iris', $.a8c.iris, {
_create: function() {
this._super();
// Global option for check is mode rbga is enabled
this.options.alpha = this.element.data( 'alpha' ) || false;
// Is not input disabled
if ( ! this.element.is( ':input' ) ) {
this.options.alpha = false;
}
if ( typeof this.options.alpha !== 'undefined' && this.options.alpha ) {
var self = this,
el = self.element,
_html = '<div class="iris-strip iris-slider iris-alpha-slider"><div class="iris-slider-offset iris-slider-offset-alpha"></div></div>',
aContainer = $( _html ).appendTo( self.picker.find( '.iris-picker-inner' ) ),
aSlider = aContainer.find( '.iris-slider-offset-alpha' ),
controls = {
aContainer: aContainer,
aSlider: aSlider
};
// Set default width for input reset
self.options.defaultWidth = el.width();
// Update width for input
if ( self._color._alpha < 1 || self._color.toString().indexOf('rgb') != 1 ) {
el.width( parseInt( self.options.defaultWidth+100 ) );
}
// Push new controls
$.each( controls, function( k, v ){
self.controls[k] = v;
});
// Change size strip and add margin for sliders
self.controls.square.css({'margin-right': '0px'});
var emptyWidth = ( self.picker.width() - self.controls.square.width() - 20 ),
stripsMargin = emptyWidth/6,
stripsWidth = (emptyWidth/2) - stripsMargin;
$.each( [ 'aContainer', 'strip' ], function( k, v ) {
self.controls[v].width( stripsWidth ).css({ 'margin-left': stripsMargin + 'px' });
});
// Add new slider
self._initControls();
// For updated widget
self._change();
}
},
_initControls: function() {
this._super();
if ( this.options.alpha ) {
var self = this,
controls = self.controls;
controls.aSlider.slider({
orientation: 'vertical',
min: 0,
max: 100,
step: 1,
value: parseInt( self._color._alpha*100 ),
slide: function( event, ui ) {
// Update alpha value
self._color._alpha = parseFloat( ui.value/100 );
self._change.apply( self, arguments );
}
});
}
},
_change: function() {
this._super();
var self = this,
el = self.element;
if ( this.options.alpha ) {
var controls = self.controls,
alpha = parseInt( self._color._alpha*100 ),
color = self._color.toRgb(),
gradient = [
'rgb(' + color.r + ',' + color.g + ',' + color.b + ') 0%',
'rgba(' + color.r + ',' + color.g + ',' + color.b + ', 0) 100%'
],
defaultWidth = self.options.defaultWidth,
target = self.picker.closest('.wp-picker-container').find( '.wp-color-result' );
// Generate background slider alpha, only for CSS3 old browser fuck!! :)
controls.aContainer.css({ 'background': 'linear-gradient(to bottom, ' + gradient.join( ', ' ) + '), url(' + image + ')' });
if ( target.hasClass('wp-picker-open') ) {
// Update alpha value
controls.aSlider.slider( 'value', alpha );
/**
* Disabled change opacity in default slider Saturation ( only is alpha enabled )
* and change input width for view all value
*/
if ( self._color._alpha < 1 ) {
var style = controls.strip.attr( 'style' ).replace( /rgba\(([0-9]+,)(\s+)?([0-9]+,)(\s+)?([0-9]+)(,(\s+)?[0-9\.]+)\)/g, 'rgb($1$3$5)' );
controls.strip.attr( 'style', style );
el.width( parseInt( defaultWidth+100 ) );
} else {
el.width( defaultWidth );
}
}
}
var reset = el.data('reset-alpha') || false;
if ( reset ) {
self.picker.find( '.iris-palette-container' ).on( 'click.palette', '.iris-palette', function() {
self._color._alpha = 1;
self.active = 'external';
self._change();
});
}
},
_addInputListeners: function( input ) {
var self = this,
debounceTimeout = 700, // originally set to 100, but some user perceive it as "jumps to random colors at third digit"
callback = function( event ){
var color = new Color( input.val() ),
val = input.val();
input.removeClass( 'iris-error' );
// we gave a bad color
if ( color.error ) {
// don't error on an empty input
if ( val !== '' ) {
input.addClass( 'iris-error' );
}
} else {
if ( color.toString() !== self._color.toString() ) {
// let's not do this on keyup for hex shortcodes
if ( ! ( event.type === 'keyup' && val.match( /^[0-9a-fA-F]{3}$/ ) ) ) {
self._setOption( 'color', color.toString() );
}
}
}
};
input.on( 'change', callback ).on( 'keyup', self._debounce( callback, debounceTimeout ) );
// If we initialized hidden, show on first focus. The rest is up to you.
if ( self.options.hide ) {
input.one( 'focus', function() {
self.show();
});
}
},
_dimensions: function( reset ) {
// whatever size
var self = this,
opts = self.options,
controls = self.controls,
square = controls.square,
strip = self.picker.find( '.iris-strip' ),
squareWidth = '77.5%',
stripWidth = '12%',
totalPadding = 20,
innerWidth = opts.border ? opts.width - totalPadding : opts.width,
controlsHeight,
paletteCount = Array.isArray( opts.palettes ) ? opts.palettes.length : self._palettes.length,
paletteMargin, paletteWidth, paletteContainerWidth;
if ( reset ) {
square.css( 'width', '' );
strip.css( 'width', '' );
self.picker.css( {width: '', height: ''} );
}
squareWidth = innerWidth * ( parseFloat( squareWidth ) / 100 );
stripWidth = innerWidth * ( parseFloat( stripWidth ) / 100 );
controlsHeight = opts.border ? squareWidth + totalPadding : squareWidth;
if (opts.diviColorpicker ) {
square.width( opts.width ).height( opts.height );
controlsHeight = opts.height;
} else {
square.width( squareWidth ).height( squareWidth );
}
strip.height( squareWidth ).width( stripWidth );
self.picker.css({
width: 'number' === typeof opts.width ? opts.width + 'px' : opts.width,
height: 'number' === typeof controlsHeight ? controlsHeight + 'px' : controlsHeight,
});
if ( ! opts.palettes ) {
return self.picker.css( 'paddingBottom', '' );
}
// single margin at 2%
paletteMargin = squareWidth * 2 / 100;
paletteContainerWidth = squareWidth - ( ( paletteCount - 1 ) * paletteMargin );
paletteWidth = paletteContainerWidth / paletteCount;
self.picker.find('.iris-palette').each( function( i ) {
var margin = i === 0 ? 0 : paletteMargin;
$( this ).css({
width: paletteWidth + 'px',
height: paletteWidth + 'px',
marginLeft: margin + 'px',
});
});
self.picker.css( 'paddingBottom', paletteWidth + paletteMargin + 'px' );
strip.height( paletteWidth + paletteMargin + squareWidth );
}
} );
}( jQuery ) );
// Auto Call plugin is class is color-picker.
jQuery(function($) {
$('.color-picker').wpColorPicker();
});

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
!function(e){var t={};function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)n.d(r,o,function(t){return e[t]}.bind(null,o));return r},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s=153)}({0:function(e,t){e.exports=jQuery},153:function(e,t,n){"use strict";(function(e){e((function(){e("body").on("click",".et_pb_prompt_dont_proceed",(function(){var t=e(this).closest(".et_pb_modal_overlay");return e("body").removeClass("et_pb_stop_scroll"),t.addClass("et_pb_modal_closing"),setTimeout((function(){t.remove()}),600),!1}))}))}).call(this,n(0))}});

View File

@ -0,0 +1 @@
!function(e){var n={};function t(r){if(n[r])return n[r].exports;var o=n[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,t),o.l=!0,o.exports}t.m=e,t.c=n,t.d=function(e,n,r){t.o(e,n)||Object.defineProperty(e,n,{enumerable:!0,get:r})},t.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},t.t=function(e,n){if(1&n&&(e=t(e)),8&n)return e;if(4&n&&"object"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(t.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&n&&"string"!=typeof e)for(var o in e)t.d(r,o,function(n){return e[n]}.bind(null,o));return r},t.n=function(e){var n=e&&e.__esModule?function(){return e.default}:function(){return e};return t.d(n,"a",n),n},t.o=function(e,n){return Object.prototype.hasOwnProperty.call(e,n)},t.p="",t(t.s=154)}({0:function(e,n){e.exports=jQuery},154:function(e,n,t){"use strict";(function(e){e((function(){var n=e(".wp-list-table"),t=e("li.wp-menu-open");n.find(".row-actions .edit, .row-actions .view").css("display","none"),t.find("a.wp-menu-open").removeClass("wp-menu-open wp-has-current-submenu"),t.find(".wp-submenu").addClass("wp-not-current-submenu"),t.removeClass("wp-menu-open wp-has-current-submenu"),n.on("click",".row-title",(function(){return e(this).closest("td").find(".row-actions .inline a").trigger("click"),!1})),n.on("click","td.column-posts a",(function(){return!1}))}))}).call(this,t(0))}});

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
!function(t){var e={};function n(i){if(e[i])return e[i].exports;var _=e[i]={i:i,l:!1,exports:{}};return t[i].call(_.exports,_,_.exports,n),_.l=!0,_.exports}n.m=t,n.c=e,n.d=function(t,e,i){n.o(t,e)||Object.defineProperty(t,e,{enumerable:!0,get:i})},n.r=function(t){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},n.t=function(t,e){if(1&e&&(t=n(t)),8&e)return t;if(4&e&&"object"==typeof t&&t&&t.__esModule)return t;var i=Object.create(null);if(n.r(i),Object.defineProperty(i,"default",{enumerable:!0,value:t}),2&e&&"string"!=typeof t)for(var _ in t)n.d(i,_,function(e){return t[e]}.bind(null,_));return i},n.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return n.d(e,"a",e),e},n.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},n.p="",n(n.s=156)}({0:function(t,e){t.exports=jQuery},156:function(t,e,n){"use strict";(function(t){var e=t("#et_settings_meta_box"),n=e.find(".et_pb_page_settings_container").first(),i=t(".et_pb_toggle_builder_wrapper"),_=t(".et_pb_page_setting"),s=t(".et_pb_page_layout_settings"),o=t("#formatdiv"),r=window.et_pb_options;i.hasClass("et_pb_builder_is_used")&&function(){var e=s.closest("#et_settings_meta_box").find(".et_pb_page_layout_settings");if(_.filter(":visible").length>1?(e.hide(),s.find(".et_pb_side_nav_settings").show()):("post"!==r.post_type&&"no"===r.is_third_party_post_type&&e.hide(),s.closest("#et_settings_meta_box").find(".et_pb_side_nav_settings").show(),s.closest("#et_settings_meta_box").find(".et_pb_single_title").show()),e.length>0){var n=e.find('option[value="et_full_width_page"]');n.length>0&&n.show()}if(o.length){var i=o.find('input[type="radio"]:checked').val();o.hide(),t(".et_divi_format_setting.et_divi_".concat(i,"_settings")).hide()}"project"===r.post_type&&s.closest("#et_settings_meta_box").find(".et_pb_project_nav").show()}(),n.hasClass("et_pb_page_settings_container--tb-has-header")&&e.find(".et_pb_nav_settings").hide(),n.hasClass("et_pb_page_settings_container--tb-has-body")&&e.find(".et_pb_page_layout_settings, .et_pb_single_title").hide(),0===n.height()&&e.hide()}).call(this,n(0))}});

View File

@ -0,0 +1 @@
!function(e){var t={};function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)n.d(r,o,function(t){return e[t]}.bind(null,o));return r},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s=157)}({0:function(e,t){e.exports=jQuery},157:function(e,t,n){"use strict";(function(e){e((function(){var t=e("#epanel-ajax-saving");e(".et_disable_memory_limit_increase").on("click",(function(n){n.preventDefault(),e.ajax({type:"POST",url:ajaxurl,data:{action:"et_reset_memory_limit_increase",et_builder_reset_memory_limit_nonce:et_reset_memory_limit_increase.et_builder_reset_memory_limit_nonce},beforeSend:function(e){t.addClass("et_loading").removeClass("success-animation"),t.fadeIn("fast")},success:function(n){t.removeClass("et_loading").removeClass("success-animation"),setTimeout((function(){t.fadeOut("slow")}),500),"success"===n&&(e(".et_disable_memory_limit_increase").closest(".epanel-box").hide(),t.addClass("success-animation"))}})}))}))}).call(this,n(0))}});

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,238 @@
// External dependencies
import { EventEmitter } from 'events';
import debounce from 'lodash/debounce';
import get from 'lodash/get';
// Internal dependencies
import {
maybeDecreaseEmitterMaxListeners,
maybeIncreaseEmitterMaxListeners,
registerFrontendComponent,
} from '../utils/utils';
const HEIGHT_CHANGE = 'height_change';
const WIDTH_CHANGE = 'width_change';
const DIMENSION_CHANGE = 'dimension_change';
// States
const states = {
height: 0,
width: 0,
};
/**
* Document store; track document height (at the moment) and its changes. Builder elements
* should listen and get this store's value instead of directly getting it from document.
* ETScriptDocumentStore is not exported; intentionally export its instance so there'll only be one
* ETScriptDocumentStore instance.
*
* @since 4.6.0
*/
class ETScriptDocumentStore extends EventEmitter {
/**
* ETScriptDocumentStore constructor.
*
* @since 4.6.0
*/
constructor() {
super();
this.setHeight(get(document, 'documentElement.offsetHeight'));
this.setWidth(get(document, 'documentElement.offsetWidth'));
}
/**
* Record document height.
*
* @since 4.6.0
*
* @param {number} height
*
* @returns {Window}
*/
setHeight = height => {
if (height === states.height) {
return this;
}
states.height = height;
this.emit(HEIGHT_CHANGE);
this.emit(DIMENSION_CHANGE);
return this;
};
/**
* Record document width.
*
* @since 4.6.0
*
* @param {number} width
*
* @returns {Window}
*/
setWidth = width => {
if (width === states.width) {
return this;
}
states.width = width;
this.emit(WIDTH_CHANGE);
this.emit(DIMENSION_CHANGE);
return this;
};
/**
* Get recorded document height.
*
* @since 4.6.0
*
* @returns {number}
*/
get height() {
return states.height;
}
/**
* Get recorded document width.
*
* @since 4.6.0
*
* @returns {number}
*/
get width() {
return states.width;
}
/**
* Add document dimension change event listener.
*
* @since 4.6.0
*
* @param {Function} callback
*
* @returns {Window}
*/
addDimensionChangeListener = callback => {
maybeIncreaseEmitterMaxListeners(this, DIMENSION_CHANGE);
this.on(DIMENSION_CHANGE, callback);
return this;
};
/**
* Remove document dimension change event listener.
*
* @since 4.6.0
*
* @param {Function} callback
*
* @returns {Window}
*/
removeDimensionChangeListener = callback => {
this.removeListener(DIMENSION_CHANGE, callback);
maybeDecreaseEmitterMaxListeners(this, DIMENSION_CHANGE);
return this;
};
/**
* Add document height change event listener.
*
* @since 4.6.0
*
* @param {Function} callback
*
* @returns {Window}
*/
addHeightChangeListener = callback => {
maybeIncreaseEmitterMaxListeners(this, HEIGHT_CHANGE);
this.on(HEIGHT_CHANGE, callback);
return this;
};
/**
* Remove document height change event listener.
*
* @since 4.6.0
*
* @param {Function} callback
*
* @returns {Window}
*/
removeHeightChangeListener = callback => {
this.removeListener(HEIGHT_CHANGE, callback);
maybeDecreaseEmitterMaxListeners(this, HEIGHT_CHANGE);
return this;
};
/**
* Add document width change event listener.
*
* @since 4.6.0
*
* @param {Function} callback
*
* @returns {Window}
*/
addWidthChangeListener = callback => {
maybeIncreaseEmitterMaxListeners(this, WIDTH_CHANGE);
this.on(WIDTH_CHANGE, callback);
return this;
};
/**
* Remove document width change event listener.
*
* @since 4.6.0
*
* @param {Function} callback
*
* @returns {Window}
*/
removeWidthChangeListener = callback => {
this.removeListener(WIDTH_CHANGE, callback);
maybeDecreaseEmitterMaxListeners(this, WIDTH_CHANGE);
return this;
};
}
// Create document store instance
const documentStoreInstance = new ETScriptDocumentStore();
/**
* Event's function callback to update document store's props
*
* @since 4.6.2
*/
function updateDocumentStoreProps() {
const documentHeight = get(document, 'documentElement.offsetHeight');
const documentWidth = get(document, 'documentElement.offsetWidth');
// Store automatically ignore if given height value is equal to the current one; so this is fine
documentStoreInstance.setHeight(documentHeight).setWidth(documentWidth);
}
// Listen to document's DOM change, debounce its callback, and update store's props
const documentObserver = new MutationObserver(debounce(updateDocumentStoreProps, 50));
// Observe document change
// @todo probably plug this on only when necessary
// @todo also enable to plug this off
documentObserver.observe(document, {
attributes: true,
childList: true,
subtree: true,
});
// Update document store properties when Divi's fixed header transition is completed
window.addEventListener('ETDiviFixedHeaderTransitionEnd', updateDocumentStoreProps);
// Register store instance as component to be exposed via global object
registerFrontendComponent('stores', 'document', documentStoreInstance);
// Export store instance.
// IMPORTANT: For uniformity, import this as ETScriptDocumentStore
export default documentStoreInstance;

View File

@ -0,0 +1,946 @@
// External dependencies
import { EventEmitter } from 'events';
import assign from 'lodash/assign';
import cloneDeep from 'lodash/cloneDeep';
import compact from 'lodash/compact';
import filter from 'lodash/filter';
import forEach from 'lodash/forEach';
import get from 'lodash/get';
import has from 'lodash/has';
import head from 'lodash/head';
import includes from 'lodash/includes';
import isEqual from 'lodash/isEqual';
import isFunction from 'lodash/isFunction';
import isObject from 'lodash/isObject';
import isUndefined from 'lodash/isUndefined';
import keys from 'lodash/keys';
import last from 'lodash/last';
import map from 'lodash/map';
import mapKeys from 'lodash/mapKeys';
import set from 'lodash/set';
import size from 'lodash/size';
import slice from 'lodash/slice';
import sortBy from 'lodash/sortBy';
import $ from 'jquery';
// Internal dependencies
import {
isOrHasValue,
} from '@frontend-builder/utils/responsive-options-pure';
import {
top_window,
} from '@core-ui/utils/frame-helpers';
import ETScriptDocumentStore from './document';
import ETScriptWindowStore from './window';
import {
getOffsets,
isBFB,
isBuilder,
isDiviTheme,
isExtraTheme,
isLBB,
isTB,
isVB,
maybeDecreaseEmitterMaxListeners,
maybeIncreaseEmitterMaxListeners,
registerFrontendComponent,
} from '../utils/utils';
import {
filterInvalidModules,
getLimit,
} from '../utils/sticky';
// Event Constants
const SETTINGS_CHANGE = 'settings_change';
// Variables
const $body = $('body');
const hasFixedNav = $body.hasClass('et_fixed_nav');
/**
* Saved sticky elements. In FE, this means all the sticky settings that exist on current page.
* In VB (and other builder context) this means sticky settings that exist on current page but
* is rendered outside current builder type. Removed nested sticky module (sticky inside another
* sticky module) from the module list.
*
* @since 4.6.0
*
* @type {object}
*/
const savedStickyElements = filterInvalidModules(cloneDeep(window.et_pb_sticky_elements));
/**
* Defaults of known non module elements which its stickiness needs to be considered.
*
* @since 4.6.0
*
* @type {object}
*/
const elementsDefaults = {
wpAdminBar: {
id: 'wpAdminBar',
selector: '#wpadminbar',
exist: false,
height: 0,
window: 'top',
condition: () => {
// Admin bar doesn't have fixed position in smaller breakpoint
const isPositionFixed = 'fixed' === top_window.jQuery(elements.wpAdminBar.selector).css('position');
// When Responsive View's control is visible, admin bar offset becomes irrelevant. Note:
// At this point the `height` value might not be updated yet, so manually get the height
// value via `getHeight()` method.
const hasVbAppFramePaddingTop = elements.builderAppFramePaddingTop.getHeight() > 0;
return ! hasVbAppFramePaddingTop && ! isTB && ! isLBB && isPositionFixed;
},
},
diviFixedPrimaryNav: {
id: 'diviPrimaryNav',
selector: '#main-header',
exist: false,
height: 0,
window: 'app',
condition: () => {
// Divi Theme has fixed nav. Note: vertical header automatically removes .et_fixed_nav
// classname so it is fine just to test fixed nav state against .et_fixed_nav classname only
const hasFixedNavBodyClass = isDiviTheme && hasFixedNav;
// Check for element's existence
const isNavExist = $(elements.diviFixedPrimaryNav.selector).length > 0;
// Primary nav is doesn't have fixed position in smaller breakpoint
const isPositionFixed = 'fixed' === $(elements.diviFixedPrimaryNav.selector).css('position');
return hasFixedNavBodyClass && isNavExist && isPositionFixed;
},
getHeight: () => {
const $mainHeader = $(elementsDefaults.diviFixedPrimaryNav.selector);
// Bail if this isn't Divi
if (! isDiviTheme && 1 > $mainHeader.length) {
return 0;
}
// Clone header
const $clone = $mainHeader.clone();
// Emulate fixed header state. Fixed header state is emulated as soon as the window is
// scrolled so it is safe to assume that any sticky module on its sticky state will "meet"
// header on its fixed state; this will avoid unwanted "jump" effect that happens because
// fixed header has 400ms transition which could be slower than scroll speed; The fixed header
// state also adds negative margin top state to #page-container which triggers document
// dimension change event. Also add classname which will ensure that this clone won't
// be visible to end user even if we only render it for a split second to avoid issues
$clone.addClass('et-fixed-header et-script-temporary-measurement');
// Add it to layout so its dimension can be measured
$mainHeader.parent().append($clone);
// Measure the fixed header height
const height = $clone.outerHeight();
// Immediately remove the cloned DOM from layout
$clone.remove();
return parseFloat(height);
},
},
diviFixedSecondaryNav: {
id: 'diviPrimaryNav',
selector: '#top-header',
exist: false,
height: 0,
window: 'app',
condition: () => {
// Divi Theme has fixed nav. Note: vertical header automatically removes .et_fixed_nav
// classname so it is fine just to test fixed nav state against .et_fixed_nav classname only
const hasFixedNavBodyClass = isDiviTheme && hasFixedNav;
// Check for element's existence
const isNavExist = $(elements.diviFixedSecondaryNav.selector).length > 0;
// Primary nav is doesn't have fixed position in smaller breakpoint
const isPositionFixed = 'fixed' === $(elements.diviFixedSecondaryNav.selector).css('position');
return hasFixedNavBodyClass && isNavExist && isPositionFixed;
},
},
extraFixedPrimaryNav: {
id: 'extraFixedPrimaryNav',
selector: '#main-header',
exist: false,
height: 0,
window: 'app',
condition: () => {
if (! isObject(ETScriptWindowStore) || ! isExtraTheme) {
return false;
}
// Extra Theme has fixed nav.
const hasFixedNavBodyClass = isExtraTheme && hasFixedNav;
// Check for element's existence.
const isNavExist = $(elements.extraFixedPrimaryNav.selector).length > 0;
// Extra has its own breakpoint for fixed nav. Detecting computed style is most likely fail
// because retrieved value is always one step behind before the computed style result is retrieved
const isPositionFixed = 1024 <= (ETScriptWindowStore.width + ETScriptWindowStore.verticalScrollBar);
return hasFixedNavBodyClass && isNavExist && isPositionFixed;
},
getHeight: () => {
const $mainHeader = $(elementsDefaults.extraFixedPrimaryNav.selector);
// Bail if this isn't Extra
if (! isExtraTheme && 1 > $mainHeader.length) {
return 0;
}
// Clone header
const $clone = $mainHeader.clone();
// Emulate fixed header state. Fixed header state is emulated as soon as the window is
// scrolled so it is safe to assume that any sticky module on its sticky state will "meet"
// header on its fixed state; this will avoid unwanted "jump" effect that happens because
// fixed header has 500ms transition which could be slower than scroll speed; The fixed header
// state also adds negative margin top state to #page-container which triggers document
// dimension change event. Also add classname which will ensure that this clone won't
// be visible to end user even if we only render it for a split second to avoid issues
$clone.addClass('et-fixed-header et-script-temporary-measurement');
// Add it to layout so its dimension can be measured
$mainHeader.parent().append($clone);
// Measure the fixed header height
const height = $clone.outerHeight();
// Immediately remove the cloned DOM from layout
$clone.remove();
return parseFloat(height);
},
},
builderAppFramePaddingTop: {
id: 'builderAppFramePaddingTop',
selector: isBFB ? '#et-bfb-app-frame' : '#et-fb-app-frame',
exist: false,
height: 0,
window: 'top',
getHeight: () => {
const selector = elements.builderAppFramePaddingTop.selector;
const cssProperty = isBFB ? 'marginTop' : 'paddingTop';
const paddingTop = top_window.jQuery(selector).css(cssProperty);
return parseFloat(paddingTop);
}
},
tbHeader: {
id: 'et-tb-branded-modal__header',
selector: '.et-tb-branded-modal__header',
exist: false,
height: 0,
window: 'top',
},
lbbHeader: {
id: 'et-block-builder-modal--header',
selector: '.et-block-builder-modal--header',
exist: false,
height: 0,
window: 'top',
},
gbHeader: {
id: 'edit-post-header',
// This selector exist on WP 5.4 and below; hence these are used instead of `.block-editor-editor-skeleton__header`
selector: '.edit-post-header',
exist: false,
height: 0,
window: 'top',
},
gbFooter: {
id: 'block-editor-editor-skeleton__footer',
selector: '.block-editor-editor-skeleton__footer',
exist: false,
height: 0,
window: 'top',
},
gbComponentsNoticeList: {
id: 'components-notice-list',
selector: '.components-notice-list',
exist: false,
height: 0,
window: 'top',
multiple: true,
},
};
/**
* Known non module elements which its stickiness needs to be considered.
*
* @since 4.6.0
*
* @type {object}
*/
const elements = cloneDeep(elementsDefaults);
// States
/**
* Hold all sticky elements modules' properties.
*
* @since 4.6.0
*
* @type {object}
*/
let modules = {};
/**
* Sticky Elements store.
*
* This store stores selected properties of all sticky elements on the page so a sticky element
* can use other sticky element's calculated value quickly.
*
* @since 4.6.0
*/
class ETScriptStickyStore extends EventEmitter {
/**
* ETScriptStickyStore constructor.
*
* @since 4.6.0
*/
constructor() {
super();
// Load modules passed via global variable from server via wp_localize_script()
assign(modules, savedStickyElements);
// Caculate top/bottom offsetModules which are basically list of sticky elements that need
// to be considered for additional offset calculation when `Offset From Surrounding Sticky Elements`
// option is toggled `on`
this.generateOffsetModules();
// Calculate known elements' properties. This needs to be done after DOM is ready
if (isVB) {
$(window).on('et_fb_init_app_after', () => {
this.setElementsProps();
});
} else {
$(() => {
this.setElementsProps();
});
}
// Some props need to be updated when document height is changed (eg. fixed nav's height)
ETScriptDocumentStore.addHeightChangeListener(this.onDocumentHeightChange);
// Builder specific event callback
if (isBuilder) {
// Event callback once the builder has been mounted
$(window).on('et_fb_root_did_mount', this.onBuilderDidMount);
// Listen to builder change if current window is builder window
window.addEventListener('ETBuilderStickySettingsSyncs', this.onBuilderSettingsChange);
}
}
/**
* Get registered modules.
*
* @since 4.6.0
*
* @type {object}
*/
get modules() {
return modules;
}
/**
* List of builder options (that is used by sticky elements) that has responsive mode.
*
* @since 4.6.0
*
* @returns {Array}
*/
get responsiveOptions() {
const options = [
'position',
'topOffset',
'bottomOffset',
'topLimit',
'bottomLimit',
'offsetSurrounding',
'transition',
'topOffsetModules',
'bottomOffsetModules',
];
return options;
}
/**
* Update selected module / elements prop on document height change.
*
* @since 4.6.0
*/
onDocumentHeightChange = () => {
// Update Divi fixed nav height property. Divi fixed nav height change when it enters its sticky state
// thus making it having different height when sits on top of viewport and during window scroll
if (this.getElementProp('diviFixedPrimaryNav', 'exist', false)) {
const getHeight = this.getElementProp('diviFixedPrimaryNav', 'getHeight');
this.setElementProp('diviFixedPrimaryNav', 'height', getHeight());
}
// Update Extra's fixed height property. Extra fixed nav height changes as the window is scrolled
if (this.getElementProp('extraFixedPrimaryNav', 'exist', false)) {
const getExtraFixedMainHeaderHeight = this.getElementProp('extraFixedPrimaryNav', 'getHeight');
this.setElementProp('extraFixedPrimaryNav', 'height', getExtraFixedMainHeaderHeight());
}
if (this.getElementProp('builderAppFramePaddingTop', 'exist', false)) {
this.setElementHeight('builderAppFramePaddingTop');
}
}
/**
* Builder did mount listener callback.
*
* @since 4.6.0
*/
onBuilderDidMount = () => {
const stickyOnloadModuleKeys = keys(window.et_pb_sticky_elements);
const stickyMountedModuleKeys = keys(this.modules);
// Has sticky elements but builder has no saved sticky module; sticky element on current
// page is outside current builder (eg. page builder has with no sticky element saved but
// TB header of current page has sticky element). Need to emit change to kickstart the stick
// element initialization and generating offset modules
if (stickyOnloadModuleKeys.length > 0 && isEqual(stickyOnloadModuleKeys, stickyMountedModuleKeys)) {
this.onBuilderSettingsChange(undefined, true);
}
}
/**
* Builder settings change listener callback.
*
* @since 4.6.0
*
* @param {object} event
* @param {bool} forceUpdate
*/
onBuilderSettingsChange = (event, forceUpdate = false) => {
const settings = get(event, 'detail.settings');
if (isEqual(settings, this.modules) && ! forceUpdate) {
return;
}
// Update sticky settings. Removed nested sticky module (sticky inside another
// sticky module) from the module list.
modules = filterInvalidModules(cloneDeep(settings), modules);
// Append saved sticky elements settings which is rendered outside of current builder
// type because it won't be generated by current builder's components
assign(modules, savedStickyElements);
// Generate offset modules
this.generateOffsetModules();
this.emit(SETTINGS_CHANGE);
}
/**
* Get id of all modules.
*
* @since 4.6.0
*
* @type {object} modules
*
* @returns {Array}
*/
getModulesId = modules => map(modules, module => module.id)
/**
* Get modules based on its rendering position; also consider its offset surrounding setting if needed.
*
* @since 4.6.0
* @param {string} top|bottom
* @param position
* @param offsetSurrounding
* @param {string|bool} on|off|false When false, ignore offset surrounding value.
* @returns {bool}
*/
getModulesByPosition = (position, offsetSurrounding = false) => filter(modules, (module, id) => {
// Check offset surrounding value; if param set to `false`, ignore it. If `on`|`off`, only
// pass module that has matching value
const isOffsetSurrounding = ! offsetSurrounding ? true : isOrHasValue(module.offsetSurrounding, offsetSurrounding);
return includes(['top_bottom', position], this.getProp(id, 'position')) && isOffsetSurrounding;
})
/**
* Sort modules from top to down based on offset prop. Passed module has no id or index prop so
* offset which visually indicate module's position in the page will do.
*
* @since 4.6.0
*/
sortModules = () => {
const storeModules = this.modules;
const modulesSize = size(storeModules);
// Return modules as-is if it is less than two modules; no need to sort it
if (modulesSize < 2) {
return storeModules;
}
// There's no index whatsoever, but offset's top and left indicates module's position
const sortedModules = sortBy(storeModules, [
module => module.offsets.top,
module => module.offsets.left,
]);
// sortBy returns array type value; remap id as object key
const remappedModules = mapKeys(sortedModules, module => module.id);
modules = cloneDeep(remappedModules);
}
/**
* Set prop value.
*
* @since 4.6.0
*
* @param {string} id Need to be unique.
* @param {string} name
* @param {string} value
*/
setProp = (id, name, value) => {
// Skip updating if the id isn't exist
if (! has(modules, id) || isUndefined(id)) {
return;
}
const currentValue = this.getProp(id, name);
// Skip updating prop if the value is the same
if (currentValue === value) {
return;
}
set(modules, `${id}.${name}`, value);
}
/**
* Get prop.
*
* @since 4.6.0
* @param {string} id
* @param {string} name
* @param {mixed} defaultValue
* @param returnCurrentBreakpoint
* @param {bool} return
* @returns {mixed}
*/
getProp = (id, name, defaultValue, returnCurrentBreakpoint = true) => {
const value = get(modules, `${id}.${name}`, defaultValue);
const isResponsive = returnCurrentBreakpoint
&& isObject(value)
&& has(value, 'desktop')
&& includes(this.responsiveOptions, name);
return isResponsive ? get(value, get(ETScriptWindowStore, 'breakpoint', 'desktop'), defaultValue) : value;
}
/**
* Set known elements' props.
*
* @since 4.6.0
*/
setElementsProps = () => {
forEach(elements, (settings, name) => {
if (! has(settings, 'window')) {
return;
}
if (has(settings, 'condition') && isFunction(settings.condition) && ! settings.condition()) {
// Reset props if it fails on condition check
this.setElementProp(name, 'exist', get(elementsDefaults, `${name}.exist`, false));
this.setElementProp(name, 'height', get(elementsDefaults, `${name}.height`, 0));
return;
}
const currentWindow = 'top' === this.getElementProp(name, 'window') ? top_window : window;
const $element = currentWindow.jQuery(settings.selector);
const hasElement = $element.length > 0 && $element.is(':visible');
if (hasElement) {
this.setElementProp(name, 'exist', hasElement);
this.setElementHeight(name);
}
});
}
/**
* Set known element prop value.
*
* @since 4.6.0
*
* @param {string} id Need to be unique.
* @param {string} name
* @param {string} value
*/
setElementProp = (id, name, value) => {
const currentValue = this.getElementProp(id, name);
// Skip updating prop if the value is the same
if (currentValue === value) {
return;
}
set(elements, `${id}.${name}`, value);
}
/**
* Get known element prop.
*
* @since 4.6.0
*
* @param {string} id
* @param {string} name
* @param {mixed} defaultValue
*
* @returns {mixed}
*/
getElementProp = (id, name, defaultValue) => get(elements, `${id}.${name}`, defaultValue)
/**
* Set element height.
*
* @since 4.6.0
*
* @param {string} name
*/
setElementHeight = name => {
const selector = this.getElementProp(name, 'selector');
const currentWindow = 'top' === this.getElementProp(name, 'window', 'app') ? top_window : window;
const $selector = currentWindow.jQuery(selector);
let height = 0;
forEach($selector, item => {
const getHeight = this.getElementProp(name, 'getHeight', false);
if (isFunction(getHeight)) {
height += getHeight();
} else {
height += currentWindow.jQuery(item).outerHeight();
}
});
this.setElementProp(name, 'height', parseInt(height));
}
/**
* Generate offset modules for offset surrounding option.
*
* @since 4.6.0
*/
generateOffsetModules = () => {
// Get module's width, height, and offsets. These are needed to calculate offset module's
// adjacent column adjustment. stickyElement will update this later on its initialization
// This needs to be on earlier and different loop than the one below for generating offset
// modules because in builder the modules need to be sorted from top to down first
forEach(this.modules, (module, id) => {
const $module = $(this.getProp(id, 'selector'));
const moduleWidth = parseInt($module.outerWidth());
const moduleHeight = parseInt($module.outerHeight());
const moduleOffsets = getOffsets($module, moduleWidth, moduleHeight);
// Only update dimension props if module isn't on sticky state
if (! this.isSticky(id)) {
this.setProp(id, 'width', moduleWidth);
this.setProp(id, 'height', moduleHeight);
this.setProp(id, 'offsets', moduleOffsets);
}
// Set limits
const position = this.getProp(id, 'position', 'none');
const isStickyBottom = includes(['bottom', 'top_bottom'], position);
const isStickyTop = includes(['top', 'top_bottom'], position);
if (isStickyBottom) {
const topLimit = this.getProp(id, 'topLimit');
const topLimitSettings = getLimit($module, topLimit);
this.setProp(id, 'topLimitSettings', topLimitSettings);
}
if (isStickyTop) {
const bottomLimit = this.getProp(id, 'bottomLimit');
const bottomLimitSettings = getLimit($module, bottomLimit);
this.setProp(id, 'bottomLimitSettings', bottomLimitSettings);
}
});
// Sort modules in builder to ensure top to bottom module order for generating offset modules
if (isBuilder) {
this.sortModules();
}
const { modules } = this;
const modulesSize = size(modules);
const topPositionModules = this.getModulesByPosition('top', 'on');
const topPositionModulesId = this.getModulesId(topPositionModules);
const bottomPositionModules = this.getModulesByPosition('bottom', 'on');
const bottomPositionModulesId = this.getModulesId(bottomPositionModules);
// Capture top/bottom offsetModules updates for later loop
const offsetModulesUpdates = [];
forEach(modules, (module, id) => {
if (isOrHasValue(module.offsetSurrounding, 'on')) {
// Top position sticky: get all module id that uses top / top_bottom position +
// has its offset surrounding turn on, that are rendered BEFORE THIS sticky element
if (includes(['top', 'top_bottom'], this.getProp(id, 'position'))) {
const topOffsetModuleIndex = topPositionModulesId.indexOf(id);
const topOffsetModule = slice(topPositionModulesId, 0, topOffsetModuleIndex);
// Saves all top offset modules for reference. This still needs to be processed to
// filter adjacent column later
this.setProp(id, 'topOffsetModulesAll', topOffsetModule);
// Mark for adjacent column filtering
offsetModulesUpdates.push({
prop: 'topOffsetModules',
id,
});
}
// Bottom position sticky: get all module id that uses bottom / top_bottom position +
// has its offset surrounding turn on, that are rendered AFTER THIS sticky element
if (includes(['bottom', 'top_bottom'], this.getProp(id, 'position'))) {
const bottomOffsetModuleIndex = bottomPositionModulesId.indexOf(id);
const bottomOffsetModules = slice(bottomPositionModulesId, (bottomOffsetModuleIndex + 1), modulesSize);
// Saves all bottom offset modules for reference. This still needs to be processed to
// filter adjacent column later
this.setProp(id, 'bottomOffsetModulesAll', bottomOffsetModules);
// Mark for adjacent column filtering
offsetModulesUpdates.push({
prop: 'bottomOffsetModules',
id,
});
}
}
});
// Top / bottom offset modules adjacent column filtering
if (offsetModulesUpdates.length > 0) {
// Default offsets. Make sure all sides element is available
const defaultOffsets = {
top: 0,
right: 0,
bottom: 0,
left: 0,
};
// Proper limit settings based on current offset modules position
const offsetLimitPropMaps = {
topOffsetModules: 'bottomLimitSettings',
bottomOffsetModules: 'topLimitSettings',
};
forEach(offsetModulesUpdates, update => {
// module's id
const moduleId = update.id;
// Need to be defined inside offsetModulesUpdates loop so each surrounding loop starts new
// Will be updated on every loop so next loop has reference of what is prev modules has
const prevSurroundingOffsets = {
...defaultOffsets,
};
// Loop over module's top/bottom offset module ids
const offsetModules = filter(this.getProp(moduleId, `${update.prop}All`), id => {
// Modules that are defined at top/bottomOffsetModules prop which is positioned after
// current module is referred as surrounding (modules) offset
const surroundingOffsets = {
...defaultOffsets,
...this.getProp(id, 'offsets', {}),
};
// Current module's offset
const moduleOffsets = {
...defaultOffsets,
...this.getProp(moduleId, 'offsets'),
};
// Module limit's offset
const moduleLimitOffsets = this.getProp(moduleId, `${offsetLimitPropMaps[update.prop]}.offsets`);
const surroundingLimitOffsets = this.getProp(id, `${offsetLimitPropMaps[update.prop]}.offsets`);
// If current and surrounding modules both have limit offsets, their top and bottom needs
// to be put in consideration in case they will never offset each other
if (moduleLimitOffsets && surroundingLimitOffsets) {
if (surroundingLimitOffsets.top < moduleLimitOffsets.top || surroundingLimitOffsets.bottom > moduleLimitOffsets.bottom) {
return false;
}
}
// If module has no limits, offset from surrounding sticky elements most likely not a
// valid offset surrounding. There is a case where surrounding can be valid offset, which
// is when current module on sticky state between surrounding limit top and bottom.
// However this rarely happens and requires conditional offset based on current window
// scroll top which might be over-engineer. Thus this is kept this way until further
// confirmation with design team
// @todo probably add conditional offset surrounding; confirm to design team
if (! moduleLimitOffsets && surroundingLimitOffsets) {
return false;
}
// Top Offset modules (sticky position top): modules rendered before current module
// Bottom Offset module (sticky position bottom): modules rendered after current module
// caveat: offset modules that are not vertically aligned with current module should not
// be considered as offset modules and affecting current module's auto-added offset.
// Hence this filter. Initially, all offset module should affect module's auto offset
let shouldPass = true;
// Surrounding module is beyond current module's right side
// ***********
// * current *
// ***********
// ***************
// * surrounding *
// ***************
const isSurroundingBeyondCurrentRight = surroundingOffsets.left >= moduleOffsets.right;
// Surrounding module is beyond current module's left side
// ***********
// * current *
// ***********
// ***************
// * surrounding *
// ***************
const isSurroundingBeyondCurrentLeft = surroundingOffsets.right < moduleOffsets.left;
// Surrounding module overlaps with current module's right side
// *********** ************************
// * current * * current *
// *********** OR ************************
// *************** ***************
// * surrounding * * surrounding *
// *************** ***************
const isSurroundingOverlapsCurrent = surroundingOffsets.left > moduleOffsets.left && surroundingOffsets.right > moduleOffsets.left;
// Previous surrounding module overlaps with current module's left side.
// ************************
// * current *
// ************************
// ******************** ******************************
// * prev surrounding * * surrounding (on this loop) *
// ******************** ******************************
const isPrevSurroundingOverlapsWithCurrent = moduleOffsets.left <= prevSurroundingOffsets.right && surroundingOffsets.top < prevSurroundingOffsets.bottom;
// Ignore surrounding height if previous surrounding height has affected current module's offset
// See isPrevSurroundingOverlapsWithCurrent's figure above
const isPrevSurroundingHasAffectCurrent = isSurroundingOverlapsCurrent && isPrevSurroundingOverlapsWithCurrent;
// Ignore the surrounding's height given the following scenarios
if (isSurroundingBeyondCurrentRight || isSurroundingBeyondCurrentLeft || isPrevSurroundingHasAffectCurrent) {
shouldPass = false;
}
// Save current surrounding offsets for next surrounding offsets comparison
assign(prevSurroundingOffsets, surroundingOffsets);
// true: surrounding's height is considered for current module's auto offset
// false: surrounding's height is ignored
return shouldPass;
});
// Set ${top/bottom}OffsetModules prop which will be synced to stickyElement
this.setProp(moduleId, `${update.prop}Align`, offsetModules);
});
}
// Perform secondary offset module calculation. The above works by getting the first surrounding
// sticky on the next row that affects current sticky. This works well when the row is filled
// like a grid, but fail if there is row in between which is not vertically overlap. Thus,
// get the closest surrounding offset sticky from last calculation, then fetch it. The idea is
// the last surrounding sticky might have offset which is not vertically align / overlap to
// current sticky element
forEach(this.modules, (module, moduleId) => {
if (module.topOffsetModulesAlign) {
const lastTopOffsetModule = last(module.topOffsetModulesAlign);
const pervTopOffsetModule = this.getProp(lastTopOffsetModule, 'topOffsetModules', this.getProp(lastTopOffsetModule, 'topOffsetModulesAlign', []));
this.setProp(moduleId, 'topOffsetModules', compact([
...pervTopOffsetModule,
...[lastTopOffsetModule],
]));
}
if (module.bottomOffsetModulesAlign) {
const firstBottomOffsetModule = head(module.bottomOffsetModulesAlign);
const pervBottomOffsetModule = this.getProp(firstBottomOffsetModule, 'bottomOffsetModules', this.getProp(firstBottomOffsetModule, 'bottomOffsetModulesAlign', []));
this.setProp(moduleId, 'bottomOffsetModules', compact([
...[firstBottomOffsetModule],
...pervBottomOffsetModule,
]));
}
});
}
/**
* Check if module with given id is on sticky state.
*
* @since 4.6.0
*
* @param {string} id
*
* @returns {bool}
*/
isSticky = id => get(this.modules, [id, 'isSticky'], false)
/**
* Add listener callback for settings change event.
*
* @since 4.6.0
* @param callback
* @param {Function}
*/
addSettingsChangeListener = callback => {
maybeIncreaseEmitterMaxListeners(this, SETTINGS_CHANGE);
this.on(SETTINGS_CHANGE, callback);
return this;
}
/**
* Remove listener callback for settings change event.
*
* @since 4.6.0
* @param callback
* @param {Function}
*/
removeSettingsChangeListener = callback => {
this.removeListener(SETTINGS_CHANGE, callback);
maybeDecreaseEmitterMaxListeners(this, SETTINGS_CHANGE);
return this;
}
}
const stickyStoreInstance = new ETScriptStickyStore;
// Register store instance as component to be exposed via global object
registerFrontendComponent('stores', 'sticky', stickyStoreInstance);
// Export store instance
// IMPORTANT: For uniformity, import this as ETScriptStickyStore
export default stickyStoreInstance;

View File

@ -0,0 +1,755 @@
// External dependencies
import { EventEmitter } from 'events';
import forEach from 'lodash/forEach';
import get from 'lodash/get';
import includes from 'lodash/includes';
import isEqual from 'lodash/isEqual';
import $ from 'jquery';
// Internal dependencies
import { top_window } from '@core-ui/utils/frame-helpers';
import ETScriptStickyStore from './sticky';
import {
getContentAreaSelector,
getTemplateEditorIframe,
} from '../../frontend-builder/gutenberg/utils/selectors';
import { isTemplateEditor } from '../../frontend-builder/gutenberg/utils/conditionals';
import {
getBuilderUtilsParams,
isBFB,
isExtraTheme,
isFE,
isLBB,
isLBP,
isTB,
isVB,
maybeDecreaseEmitterMaxListeners,
maybeIncreaseEmitterMaxListeners,
registerFrontendComponent,
} from '../utils/utils';
// Builder window
const $window = $(window);
const $topWindow = top_window.jQuery(top_window);
const hasTopWindow = ! isEqual(window, top_window);
const windowLocations = hasTopWindow ? ['app', 'top'] : ['app'];
// Event Constants
const HEIGHT_CHANGE = 'height_change';
const WIDTH_CHANGE = 'width_change';
const SCROLL_TOP_CHANGE = 'scroll_top_change';
const BREAKPOINT_CHANGE = 'breakpoint_change';
const SCROLL_LOCATION_CHANGE = 'scroll_location_change';
const VERTICAL_SCROLL_BAR_CHANGE = 'vertical_scroll_bar_change';
// States.
// Private, limited to this module (ETScriptWindowStore class) only
const states = {
breakpoint: 'desktop',
extraMobileBreakpoint: false,
isBuilderZoomed: false,
scrollLocation: getBuilderUtilsParams().onloadScrollLocation, // app|top
scrollTop: {
app: 0,
top: 0,
},
height: {
app: 0,
top: 0,
},
width: {
app: 0,
top: 0,
},
bfbIframeOffset: {
top: 0,
left: 0,
},
lbpIframeOffset: {
top: 0,
left: 0,
},
verticalScrollBar: {
app: 0,
top: 0,
},
};
// Valid values.
// Retrieved from server, used for validating values
const validValues = {
scrollLocation: [...getBuilderUtilsParams().scrollLocations],
};
// Variables
const builderScrollLocations = {
...getBuilderUtilsParams().builderScrollLocations,
};
// @todo need to change how this works since builder already have et_screen_sizes(), unless
// we prefer to add another breakpoint functions
const deviceMinimumBreakpoints = {
desktop: 980,
tablet: 767,
phone: 0,
};
const bfbFrameId = '#et-bfb-app-frame';
/**
* Window store.
*
* This store listen to direct window's events; builder callback listen to this store's events
* to avoid dom-based calculation whenever possible; use the property passed by this store.
*
* @since 4.6.0
*/
class ETScriptWindowStore extends EventEmitter {
/**
* ETScriptWindowStore constructor.
*
* @since 4.6.0
*/
constructor() {
super();
// Set app window onload values
const windowWidth = $window.innerWidth();
const windowHeight = $window.innerHeight();
const windowScrollTop = $window.scrollTop();
this.setWidth('app', windowWidth).setHeight('app', windowHeight);
this.setScrollTop('app', windowScrollTop);
this.setVerticalScrollBarWidth('app', (window.outerWidth - windowWidth));
// Set top window onload values (if top window exist)
if (hasTopWindow) {
const topWindowWidth = $topWindow.innerWidth();
const topWindowHeight = $topWindow.innerHeight();
const topWindowScrollTop = top_window.jQuery(top_window).scrollTop();
this.setWidth('top', topWindowWidth).setHeight('top', topWindowHeight);
this.setScrollTop('top', topWindowScrollTop);
this.setVerticalScrollBarWidth('top', (top_window.outerWidth - topWindowWidth));
}
// Set iframe offset
if (isBFB) {
this.setBfbIframeOffset();
}
// Set Layout Block iframe offset
if (isLBP) {
this.setLayoutBlockPreviewIframeOffset();
}
}
/**
* Set window height.
*
* @since 4.6.0
*
* @param {string} windowLocation App|top.
* @param {number} height
*
* @returns {Window}
*/
setHeight = (windowLocation = 'app', height) => {
if (height === states.height[windowLocation]) {
return this;
}
states.height[windowLocation] = height;
this.emit(HEIGHT_CHANGE);
return this;
};
/**
* Set window width.
*
* @since 4.6.0
*
* @param {string} windowLocation App|top.
* @param {number} width
*
* @returns {Window}
*/
setWidth = (windowLocation = 'app', width) => {
if (width === states.width[windowLocation]) {
return this;
}
// Only app window could set breakpoint
if ('app' === windowLocation) {
this.setBreakpoint(width);
// Extra theme has its own "mobile breakpoint" (below 1024px)
if (isExtraTheme) {
const outerWidth = this.width + this.verticalScrollBar;
const extraMobileBreakpoint = 1024;
const fixedNavActivation = ! states.extraMobileBreakpoint && outerWidth >= extraMobileBreakpoint;
const fixedNavDeactivation = states.extraMobileBreakpoint && outerWidth < extraMobileBreakpoint;
// Re-set element props when Extra mobile breakpoint change happens
if (fixedNavActivation || fixedNavDeactivation) {
states.extraMobileBreakpoint = (outerWidth >= extraMobileBreakpoint);
ETScriptStickyStore.setElementsProps();
}
}
}
states.width[windowLocation] = width;
this.emit(WIDTH_CHANGE);
return this;
};
/**
* Set scroll location value.
*
* @since 4.6.0
*
* @param {string} scrollLocation App|top.
*
* @returns {ETScriptWindowStore}
*/
setScrollLocation = scrollLocation => {
// Prevent incorrect scroll location value from being saved
if (! includes(validValues.scrollLocation, scrollLocation)) {
return false;
}
if (scrollLocation === states.scrollLocation) {
return this;
}
states.scrollLocation = scrollLocation;
this.emit(SCROLL_LOCATION_CHANGE);
return this;
}
/**
* Set scroll top value.
*
* @since 4.6.0
*
* @param {string} windowLocation App|top.
* @param {number} scrollTop
*
* @returns {ETScriptWindowStore}
*/
setScrollTop = (windowLocation, scrollTop) => {
if (scrollTop === states.scrollTop[windowLocation]) {
return this;
}
states.scrollTop[windowLocation] = scrollTop;
this.emit(SCROLL_TOP_CHANGE);
return this;
}
/**
* Set builder zoomed status (on builder only).
*
* @since 4.6.0
*
* @param {string} builderPreviewMode Desktop|tablet|phone|zoom|wireframe.
*/
setBuilderZoomedStatus = builderPreviewMode => {
const isBuilderZoomed = 'zoom' === builderPreviewMode;
states.isBuilderZoomed = isBuilderZoomed;
}
/**
* Set BFB iframe offset.
*
* @since 4.6.0
*/
setBfbIframeOffset = () => {
states.bfbIframeOffset = top_window.jQuery(bfbFrameId).offset();
}
/**
* Set Layout Block iframe offset.
*
* @since 4.6.0
*/
setLayoutBlockPreviewIframeOffset = () => {
const blockId = get(window.ETBlockLayoutModulesScript, 'blockId', '');
const previewIframeId = `#divi-layout-iframe-${blockId}`;
const $block = top_window.jQuery(previewIframeId).closest('.wp-block[data-type="divi/layout"]');
const blockPosition = $block.position();
const contentSelectors = [
// WordPress 5.4
'block-editor-editor-skeleton__content',
// WordPress 5.5
'interface-interface-skeleton__content',
];
let blockOffsetTop = parseInt(get(blockPosition, 'top', 0));
// Since WordPress 5.4, blocks list position to its parent somehow is not considered
// Previous inserted DOM are also gone + Block item now has collapsing margin top/bottom
// These needs to be manually calculated here since the result is no longer identical
if (includes(contentSelectors, getContentAreaSelector(top_window, false))) {
// Find Block List Layout. By default, it's located on editor of top window.
// When Template Editor is active, it's "moved" to editor of iframe window.
const $blockEditorLayout = isTemplateEditor() ? getTemplateEditorIframe(top_window).find('.block-editor-block-list__layout.is-root-container') : top_window.jQuery('.block-editor-block-list__layout');
// Blocks list position to its parent (title + content wrapper)
// WordPress 5.4 = 183px
// WordPress 5.5 = 161px
if ($blockEditorLayout.length) {
blockOffsetTop += $blockEditorLayout.position().top;
}
// Compensating collapsing block item margin top
blockOffsetTop += parseInt($block.css('marginTop')) || 0;
}
// Admin bar in less than 600 width window uses absolute positioning which stays on top of
// document and affecting iframe top offset
if (600 > this.width && ETScriptStickyStore.getElementProp('wpAdminBar', 'exist', false)) {
blockOffsetTop += ETScriptStickyStore.getElementProp('wpAdminBar', 'height', 0);
}
states.lbpIframeOffset.top = blockOffsetTop;
}
/**
* Set vertical scrollbar width.
*
* @since 4.6.0
*
* @param {string} windowLocation
* @param {number} width
*/
setVerticalScrollBarWidth = (windowLocation = 'app', width) => {
if (width === states.verticalScrollBar[windowLocation]) {
return this;
}
states.verticalScrollBar[windowLocation] = width;
this.emit(VERTICAL_SCROLL_BAR_CHANGE);
return this;
}
/**
* Get current window width.
*
* @since 4.6.0
*
* @returns {number}
*/
get width() {
return states.width[this.scrollLocation];
}
/**
* Get current window height.
*
* @since 4.6.0
*
* @returns {number}
*/
get height() {
return states.height[this.scrollLocation];
}
/**
* Get current window scroll location.
*
* @since 4.6.0
*
* @returns {string} App|top.
*/
get scrollLocation() {
return states.scrollLocation;
}
/**
* Get current window scroll top / distance to document.
*
* @since 4.6.0
*
* @returns {number}
*/
get scrollTop() {
const multiplier = this.isBuilderZoomed ? 2 : 1;
let appFrameOffset = 0;
// Add app iframe offset on scrollTop calculation in BFB
if (isBFB) {
appFrameOffset += states.bfbIframeOffset.top;
}
// Add Layout Block preview iframe on scrollTop calculation
if (isLBP) {
appFrameOffset += states.lbpIframeOffset.top;
}
return (states.scrollTop[this.scrollLocation] - appFrameOffset) * multiplier;
}
/**
* Get current app window breakpoint (by device).
*
* @since 4.6.0
*
* @returns {string}
*/
get breakpoint() {
return states.breakpoint;
}
/**
* Get builder zoomed status.
*
* @since 4.6.0
*
* @returns {bool}
*/
get isBuilderZoomed() {
return states.isBuilderZoomed;
}
/**
* Get current window vertical scrollbar width.
*
* @since 4.6.0
*
* @returns {number}
*/
get verticalScrollBar() {
return states.verticalScrollBar[this.scrollLocation];
}
/**
* Get builder scroll location of builder context + preview mode.
*
* @since 4.6.0
*
* @param {string} previewMode Desktop|tablet|phone|zoom|wireframe.
*
* @returns {string} App|top.
*/
getBuilderScrollLocation = previewMode => get(builderScrollLocations, previewMode, 'app')
/**
* Add width change event listener.
*
* @since 4.6.0
*
* @param {Function} callback
*
* @returns {Window}
*/
addWidthChangeListener = callback => {
maybeIncreaseEmitterMaxListeners(this, WIDTH_CHANGE);
this.on(WIDTH_CHANGE, callback);
return this;
};
/**
* Remove width change event listener.
*
* @since 4.6.0
*
* @param {Function} callback
*
* @returns {Window}
*/
removeWidthChangeListener = callback => {
this.removeListener(WIDTH_CHANGE, callback);
maybeDecreaseEmitterMaxListeners(this, WIDTH_CHANGE);
return this;
};
/**
* Add height change event listener.
*
* @since 4.6.0
*
* @param {Function} callback
*
* @returns {Window}
*/
addHeightChangeListener = callback => {
maybeIncreaseEmitterMaxListeners(this, HEIGHT_CHANGE);
this.on(HEIGHT_CHANGE, callback);
return this;
};
/**
* Remove height change event listener.
*
* @since 4.6.0
*
* @param {Function} callback
*
* @returns {Window}
*/
removeHeightChangeListener = callback => {
this.removeListener(HEIGHT_CHANGE, callback);
maybeDecreaseEmitterMaxListeners(this, HEIGHT_CHANGE);
return this;
};
/**
* Add scroll location change event listener.
*
* @param callback
* @since 4.6.0
* @returns {ETScriptWindowStore}
*/
addScrollLocationChangeListener = callback => {
maybeIncreaseEmitterMaxListeners(this, SCROLL_LOCATION_CHANGE);
this.on(SCROLL_LOCATION_CHANGE, callback);
return this;
}
/**
* Remove scroll location change event listener.
*
* @param callback
* @since 4.6.0
* @returns {ETScriptWindowStore}
*/
removeScrollLocationChangeListener = callback => {
this.removeListener(SCROLL_LOCATION_CHANGE, callback);
maybeDecreaseEmitterMaxListeners(this, SCROLL_LOCATION_CHANGE);
return this;
}
/**
* Add scroll top change event listener.
*
* @param callback
* @since 4.6.0
* @returns {ETScriptWindowStore}
*/
addScrollTopChangeListener = callback => {
maybeIncreaseEmitterMaxListeners(this, SCROLL_TOP_CHANGE);
this.on(SCROLL_TOP_CHANGE, callback);
return this;
}
/**
* Remove scroll top change event listener.
*
* @param callback
* @since 4.6.0
* @returns {ETScriptWindowStore}
*/
removeScrollTopChangeListener = callback => {
this.removeListener(SCROLL_TOP_CHANGE, callback);
maybeDecreaseEmitterMaxListeners(this, SCROLL_TOP_CHANGE);
return this;
}
/**
* Set breakpoint (by device) based on window width.
*
* @since 4.6.0
*
* @todo Update breakpoint setting mechanic so this won't need to define another screen size definition
* and able to reuse (et_screen_size()).
*
* @param {number} windowWidth
*
* @returns {ETScriptWindowStore}
*/
setBreakpoint = windowWidth => {
let newBreakpoint = '';
forEach(deviceMinimumBreakpoints, (minWidth, device) => {
if (windowWidth > minWidth) {
newBreakpoint = device;
// equals to "break"
return false;
}
});
// No need to update breakpoint property if it is unchanged
if (this.breakpoint === newBreakpoint) {
return;
}
states.breakpoint = newBreakpoint;
this.emit(BREAKPOINT_CHANGE);
return this;
}
/**
* Add breakpoint change event listener.
*
* @since 4.6.0
*
* @param {Function} callback
*/
addBreakpointChangeListener = callback => {
maybeIncreaseEmitterMaxListeners(this, BREAKPOINT_CHANGE);
this.on(BREAKPOINT_CHANGE, callback);
return this;
}
/**
* Remove breakpoint change event listener.
*
* @since 4.6.0
*
* @param {Function} callback
*/
removeBreakpointChangeListener = callback => {
this.removeListener(BREAKPOINT_CHANGE, callback);
maybeDecreaseEmitterMaxListeners(this, BREAKPOINT_CHANGE);
return this;
}
}
// initiate window store instance
const windowStoreInstance = new ETScriptWindowStore();
/**
* Listen for (app/top) window events, and update store's value
* store is listener free; it only hold / set / get values.
*/
forEach(windowLocations, windowLocation => {
const isTop = 'top' === windowLocation;
const isApp = 'app' === windowLocation;
const currentWindow = isApp ? window : top_window;
const $currentWindow = currentWindow.jQuery(currentWindow);
// Scroll in Theme Builder & Layout Block Builder happens on element; adjustment needed
// const scrollWindow = isTop && (isTB || isLBB) ? currentWindow.document.getElementById('et-fb-app') : currentWindow;
const scrollWindow = () => {
// Theme Builder & Layout Block Builder
if (isTop && (isTB || isLBB)) {
return currentWindow.document.getElementById('et-fb-app');
}
// Layout Block Preview / Gutenberg
if (isTop && isLBP) {
return currentWindow.document.getElementsByClassName(getContentAreaSelector(currentWindow, false))[0];
}
return currentWindow;
};
// listen to current (app/top) window resize event
currentWindow.addEventListener('resize', () => {
const width = currentWindow.jQuery(currentWindow).innerWidth();
const height = currentWindow.jQuery(currentWindow).innerHeight();
windowStoreInstance.setWidth(windowLocation, width).setHeight(windowLocation, height);
windowStoreInstance.setVerticalScrollBarWidth(windowLocation, (currentWindow.outerWidth - width));
if ((windowStoreInstance.width > 782 && height <= 782) || (windowStoreInstance.width <= 782 && height > 782)) {
// Wait until admin bar's viewport style kicks in
setTimeout(() => {
ETScriptStickyStore.setElementHeight('wpAdminBar');
windowStoreInstance.emit(SCROLL_TOP_CHANGE);
}, 300);
}
});
// listen to current (app/top) window scroll event
scrollWindow().addEventListener('scroll', () => {
const scrollTop = isTop && (isTB || isLBB || isLBP) ? scrollWindow().scrollTop : scrollWindow().pageYOffset;
windowStoreInstance.setScrollTop(windowLocation, scrollTop);
});
// Top window listener only
if (isTop) {
// Listen to builder's preview mode change that is passed via top window event
$currentWindow.on('et_fb_preview_mode_changed', (event, screenMode, builderMode) => {
const scrollLocation = windowStoreInstance.getBuilderScrollLocation(builderMode);
windowStoreInstance.setBuilderZoomedStatus(builderMode);
windowStoreInstance.setScrollLocation(scrollLocation);
});
// Update iframe offset if any metabox is moved
if (isBFB) {
currentWindow.addEventListener('ETBFBMetaboxSortStopped', () => {
windowStoreInstance.setBfbIframeOffset();
});
}
// Gutenberg moves the scroll back to window if window's width is less than 600px
if (isLBP) {
currentWindow.addEventListener('scroll', () => {
if (windowStoreInstance.width > 600) {
return;
}
const scrollTop = currentWindow.pageYOffset;
windowStoreInstance.setScrollTop(windowLocation, scrollTop);
});
}
// When scroll is located on top window, there is a chance that the top window actually scrolls
// before the builder is loaded which means initial scroll top value actually has changed
// to avoid issue caused by it, when app window that carries this script is loaded, trigger
// scroll event on the top window's scrolling element
scrollWindow().dispatchEvent(new CustomEvent('scroll'));
}
// App window listener only
if (isApp) {
// Update known element props when breakpoint changes. Breakpoint change is basically less
// aggressive resize event, happened between known window's width
if (isFE || isVB) {
windowStoreInstance.addBreakpointChangeListener(() => {
ETScriptStickyStore.setElementsProps();
});
}
// Update iframe offset if layout block is moved
if (isLBP) {
currentWindow.addEventListener('ETBlockGbBlockOrderChange', () => {
// Need to wait at least 300ms until GB animation is done
setTimeout(() => {
windowStoreInstance.setLayoutBlockPreviewIframeOffset();
windowStoreInstance.emit(SCROLL_TOP_CHANGE);
}, 300);
});
// Update iframe offset if notice size is changed
currentWindow.addEventListener('ETGBNoticeSizeChange', () => {
if (ETScriptStickyStore.getElementProp('gbComponentsNoticeList', 'exist', false)) {
ETScriptStickyStore.setElementHeight('gbComponentsNoticeList');
windowStoreInstance.emit(SCROLL_TOP_CHANGE);
}
});
}
}
});
// Register store instance as component to be exposed via global object
registerFrontendComponent('stores', 'window', windowStoreInstance);
// Export store instance
// IMPORTANT: For uniformity, import this as ETScriptWindowStore
export default windowStoreInstance;

View File

@ -0,0 +1,345 @@
// Sticky Elements specific utils, used accross files
// External dependencies
import filter from 'lodash/filter';
import forEach from 'lodash/forEach';
import get from 'lodash/get';
import head from 'lodash/head';
import includes from 'lodash/includes';
import isEmpty from 'lodash/isEmpty';
import isString from 'lodash/isString';
import $ from 'jquery';
// Internal dependencies
import {
getOffsets,
} from './utils';
/**
* Get top / bottom limit attributes.
*
* @since 4.6.0
* @param {object} $selector
* @param limit
* @param {string}
* @returns {object}
* @returns {string} Object.limit.
* @returns {number} Object.height.
* @returns {number} Object.width.
* @return {object} object.offsets
* @return {number} object.offsets.top
* @return {number} object.offsets.right
* @return {number} object.offsets.bottom
* @return {number} object.offsets.left
*/
export const getLimit = ($selector, limit) => {
// @todo update valid limits based on selector
const validLimits = ['body', 'section', 'row', 'column'];
if (! includes(validLimits, limit)) {
return false;
}
// Limit selector
const $limitSelector = getLimitSelector($selector, limit);
if (! $limitSelector) {
return false;
}
const height = $limitSelector.outerHeight();
const width = $limitSelector.outerWidth();
return {
limit,
height,
width,
offsets: getOffsets($limitSelector, width, height),
};
};
/**
* Get top / bottom limit selector based on given name.
*
* @since 4.6.0
*
* @param {object} $selector
* @param {string} limit
*
* @returns {bool|object}
*/
export const getLimitSelector = ($selector, limit) => {
let parentSelector = false;
switch (limit) {
case 'body':
parentSelector = '.et_builder_inner_content';
break;
case 'section':
parentSelector = '.et_pb_section';
break;
case 'row':
parentSelector = '.et_pb_row';
break;
case 'column':
parentSelector = '.et_pb_column';
break;
default:
break;
}
return parentSelector ? $selector.closest(parentSelector) : false;
};
/**
* Filter invalid sticky modules
* 1. Sticky module inside another sticky module.
*
* @param {object} modules
* @param {object} currentModules
*
* @since 4.6.0
*/
export const filterInvalidModules = (modules, currentModules = {}) => {
const filteredModules = {};
forEach(modules, (module, key) => {
// If current sticky module is inside another sticky module, ignore current module
if ($(module.selector).parents('.et_pb_sticky_module').length > 0) {
return;
}
// Repopulate the module list
if (! isEmpty(currentModules) && currentModules[key]) {
// Keep props that isn't available on incoming modules intact
filteredModules[key] = {
...currentModules[key],
...module,
};
} else {
filteredModules[key] = module;
}
});
return filteredModules;
};
/**
* Get sticky style of given module by cloning, adding sticky state classname, appending DOM,
* retrieving value, then immediately the cloned DOM. This is needed for property that is most
* likely to be affected by transition if the sticky value is retrieved on the fly, thus it needs
* to be retrieved ahead its time by this approach.
*
* @since 4.6.0
*
* @param {string} id
* @param {object} $module
* @param {object} $placeholder
*
* @returns {object}
*/
export const getStickyStyles = (id, $module, $placeholder) => {
// Sticky state classname to be added; these will make cloned module to have fixed position and
// make sticky style take effect
const stickyStyleClassname = 'et_pb_sticky et_pb_sticky_style_dom';
// Cloned the module add sticky state classname; set the opacity to 0 and remove the transition
// so the dimension can be immediately retrieved
const $stickyStyleDom = $module.clone().addClass(stickyStyleClassname).attr({
'data-sticky-style-dom-id': id,
// Remove inline styles so on-page styles works. Especially needed if module is in sticky state
style: '',
}).css({
opacity: 0,
transition: 'none',
animation: 'none',
});
// Cloned module might contain image. However the image might take more than a milisecond to be
// loaded on the cloned module after the module is appended to the layout EVEN IF the image on
// the $module has been loaded. This might load to inaccurate sticky style calculation. To avoid
// it, recreate the image by getting actual width and height then recreate the image using SVG
$stickyStyleDom.find('img').each(function(index) {
const $img = $(this);
const $measuredImg = $module.find('img').eq(index);
const measuredWidth = get($measuredImg, [0, 'naturalWidth'], $module.find('img').eq(index).outerWidth());
const measuredHeight = get($measuredImg, [0, 'naturalHeight'], $module.find('img').eq(index).outerHeight());
$img.attr({
// Remove scrse to force DOM to use src
scrset: '',
// Recreate svg to use image's actual width so the image reacts appropriately when sticky
// style modifies image dimension (eg image has 100% and padding in sticky style is larger;
// this will resulting in image being smaller because the wrapper dimension is smaller)
src: `data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="${measuredWidth}" height="${measuredHeight}"><rect width="${measuredWidth}" height="${measuredHeight}" /></svg>`,
});
});
// Append the cloned DOM
$module.after($stickyStyleDom);
// Get inline margin style value that is substraction of sticky style - style due to position
// relative to fixed change
const getMarginStyle = corner => {
const marginPropName = `margin${corner}`;
const $normalModule = $module.hasClass('et_pb_sticky') ? $placeholder : $module;
return parseFloat($stickyStyleDom.css(marginPropName)) - parseFloat($normalModule.css(marginPropName));
};
// Measure sticky style DOM properties
const styles = {
height: $stickyStyleDom.outerHeight(),
width: $stickyStyleDom.outerWidth(),
marginRight: getMarginStyle('Right'),
marginLeft: getMarginStyle('Left'),
padding: $stickyStyleDom.css('padding'),
};
// Immediately remove the cloned DOM
$(`.et_pb_sticky_style_dom[data-sticky-style-dom-id="${id}"]`).remove();
return styles;
};
/**
* Remove given property's transition from transition property's value. To make some properties
* (eg. Width, top, left) transition smoothly when entering / leaving sticky state, its property
* and transition need to be removed then re-added 50ms later. This is mostly happened because the
* module positioning changed from relative to fixed when entering/leaving sticky state.
*
* @since 4.6.0
*
* @param {string} transitionValue
* @param {Array} trimmedProperties
*
* @returns {string}
*/
export const trimTransitionValue = (transitionValue, trimmedProperties) => {
// Make sure that transitionValue is string. Otherwise split will throw error
if (! isString(transitionValue)) {
transitionValue = '';
}
const transitions = transitionValue.split(', ');
const trimmedValue = filter(transitions, transition => ! includes(trimmedProperties, head(transition.split(' '))));
return isEmpty(trimmedValue) ? 'none' : trimmedValue.join(', ');
};
/**
* Calculate automatic offset that should be given based on sum of heights of all sticky modules
* that are currently in sticky state when window reaches $target's offset.
*
* @since 4.6.0
*
* @param {object} $target
*
* @returns {number}
*/
export const getClosestStickyModuleOffsetTop = $target => {
const offset = $target.offset();
offset.right = offset.left + $target.outerWidth();
let closestStickyElement = null;
let closestStickyOffsetTop = 0;
// Get all sticky module data from store. NOTE: this util might be used on various output build
// so it needs to get sticky store value via global object instead of importing it
const stickyModules = get(window.ET_FE, 'stores.sticky.modules', {});
// Loop sticky module data to get the closest sticky module to given y offset. Sticky module
// already has map of valid modules it needs to consider as automatic offset due to
// adjacent-column situation.
// @see https://github.com/elegantthemes/Divi/issues/19432
forEach(stickyModules, stickyModule => {
// Ignore sticky module if it is stuck to bottom
if (! includes(['top_bottom', 'top'], stickyModule.position)) {
return;
}
// Ignore if $target is sticky module (that sticks to top; stuck to bottom check above has
// made sure of it) - otherwise the auto-generate offset will subtract the element's offset
// and causing the scroll never reaches $target location.
// @see https://github.com/elegantthemes/Divi/issues/23240
if ($target.is(get(stickyModule, 'selector'))) {
return;
}
// Ignore if sticky module's right edge doesn't collide with target's left edge
if (get(stickyModule, 'offsets.right', 0) < offset.left) {
return;
}
// Ignore if sticky module's left edge doesn't collide with target's right edge
if (get(stickyModule, 'offsets.left', 0) > offset.right) {
return;
}
// Ignore sticky module if it is located below given y offset
if (get(stickyModule, 'offsets.top', 0) > offset.top) {
return;
}
// Ignore sticky module if its bottom limit is higher than given y offset
const bottomLimitBottom = get(stickyModule, 'bottomLimitSettings.offsets.bottom');
if (bottomLimitBottom && bottomLimitBottom < offset.top) {
return;
}
closestStickyElement = stickyModule;
});
// Once closest sticky module to given y offset has been found, loop its topOffsetModules, get
// each module's heightSticky and return the sum of their heights
if (get(closestStickyElement, 'topOffsetModules', false)) {
forEach(get(closestStickyElement, 'topOffsetModules', []), stickyId => {
// Get sticky module's height on sticky state; fallback to height just to be safe
const stickyModuleHeight = get(stickyModules, [stickyId, 'heightSticky'], get(stickyModules, [stickyId, 'height'], 0));
// Sum up top offset module's height
closestStickyOffsetTop += stickyModuleHeight;
});
// Get closest-to-y-offset's sticky module's height on sticky state;
const closestStickyElementHeight = get(stickyModules, [closestStickyElement.id, 'heightSticky'], get(stickyModules, [closestStickyElement.id, 'height'], 0));
// Sum up top offset module's height
closestStickyOffsetTop += closestStickyElementHeight;
}
return closestStickyOffsetTop;
};
/**
* Determine if the target is in sticky state.
*
* @since 4.9.5
*
* @param {object} $target
*
* @returns {bool}
*/
export const isTargetStickyState = $target => {
const stickyModules = get(window.ET_FE, 'stores.sticky.modules', {});
let isStickyState = false;
forEach(stickyModules, stickyModule => {
const isTarget = $target.is(get(stickyModule, 'selector'));
const {isSticky, isPaused} = stickyModule;
// If the target is in sticky state and not paused, set isStickyState to true and exit iteration.
// Elements can have a sticky limit (ex: section) in which case they can be sticky but paused.
if (isTarget && isSticky && !isPaused) {
isStickyState = true;
return false; // Exit iteration.
}
});
return isStickyState;
};

View File

@ -0,0 +1,306 @@
/**
* IMPORTANT: Keep external dependencies as low as possible since this utils might be
* imported by various frontend scripts; need to keep frontend script size low.
*/
// External dependencies
import includes from 'lodash/includes';
import get from 'lodash/get';
import $ from 'jquery';
// Internal dependencies
import { top_window } from '@core/admin/js/frame-helpers';
export const getBuilderUtilsParams = () => {
if (window.et_builder_utils_params) {
return window.et_builder_utils_params;
}
if (top_window.et_builder_utils_params) {
return top_window.et_builder_utils_params;
}
return {};
};
export const getBuilderType = () => get(getBuilderUtilsParams(), 'builderType', '');
/**
* Check current page's builder Type.
*
* @since 4.6.0
*
* @param {string} builderType Fe|vb|bfb|tb|lbb|lbp.
*
* @returns {bool}
*/
export const isBuilderType = (builderType) => builderType === getBuilderType();
/**
* Return condition value.
*
* @since 4.6.0
*
* @param {string} conditionName
*
* @returns {bool}
*/
export const is = conditionName => get(getBuilderUtilsParams(), `condition.${conditionName}`);
/**
* Is current page Frontend.
*
* @since 4.6.0
*
* @type {bool}
*/
export const isFE = isBuilderType('fe');
/**
* Is current page Visual Builder.
*
* @since 4.6.0
*
* @type {bool}
*/
export const isVB = isBuilderType('vb');
/**
* Is current page BFB / New Builder Experience.
*
* @since 4.6.0
*
* @type {bool}
*/
export const isBFB = isBuilderType('bfb');
/**
* Is current page Theme Builder.
*
* @since 4.6.0
*
* @type {bool}
*/
export const isTB = isBuilderType('tb');
/**
* Is current page Layout Block Builder.
*
* @type {bool}
*/
export const isLBB = isBuilderType('lbb');
/**
* Is current page uses Divi Theme.
*
* @since 4.6.0
*
* @type {bool}
*/
export const isDiviTheme = is('diviTheme');
/**
* Is current page uses Extra Theme.
*
* @since 4.6.0
*
* @type {bool}
*/
export const isExtraTheme = is('extraTheme');
/**
* Is current page Layout Block Preview.
*
* @since 4.6.0
*
* @type {bool}
*/
export const isLBP = isBuilderType('lbp');
/**
* Check if current window is block editor window (gutenberg editing page).
*
* @since 4.6.0
*
* @type {bool}
*/
export const isBlockEditor = 0 < $(top_window.document).find('.edit-post-layout__content').length;
/**
* Check if current window is builder window (VB, BFB, TB, LBB).
*
* @since 4.6.0
*
* @type {bool}
*/
export const isBuilder = includes(['vb', 'bfb', 'tb', 'lbb'], getBuilderType());
/**
* Get offsets value of all sides.
*
* @since 4.6.0
*
* @param {object} $selector JQuery selector instance.
* @param {number} height
* @param {number} width
*
* @returns {object}
*/
export const getOffsets = ($selector, width = 0, height = 0) => {
// Return previously saved offset if sticky tab is active; retrieving actual offset contain risk
// of incorrect offsets if sticky horizontal / vertical offset of relative position is modified.
const isStickyTabActive = isBuilder && $selector.hasClass('et_pb_sticky') && 'fixed' !== $selector.css('position');
const cachedOffsets = $selector.data('et-offsets');
const cachedDevice = $selector.data('et-offsets-device');
const currentDevice = get(window.ET_FE, 'stores.window.breakpoint', '');
// Only return cachedOffsets if sticky tab is active and cachedOffsets is not undefined and
// cachedDevice equal to currentDevice.
if (isStickyTabActive && cachedOffsets !== undefined && cachedDevice === currentDevice) {
return cachedOffsets;
}
// Get top & left offsets
const offsets = $selector.offset();
// If no offsets found, return empty object
if ('undefined' === typeof offsets) {
return {};
}
// FE sets the flag for sticky module which uses transform as classname on module wrapper while
// VB, BFB, TB, and LB sets the flag on CSS output's <style> element because it can't modify
// its parent. This compromises avoids the needs to extract transform rendering logic
const hasTransform = isBuilder
? $selector.children('.et-fb-custom-css-output[data-sticky-has-transform="on"]').length > 0
: $selector.hasClass('et_pb_sticky--has-transform');
let top = 'undefined' === typeof offsets.top ? 0 : offsets.top;
let left = 'undefined' === typeof offsets.left ? 0 : offsets.left;
// If module is sticky module that uses transform, its offset calculation needs to be adjusted
// because transform tends to modify the positioning of the module
if (hasTransform) {
// Calculate offset (relative to selector's parent) AFTER it is affected by transform
// NOTE: Can't use jQuery's position() because it considers margin-left `auto` which causes issue
// on row thus this manually calculate the difference between element and its parent's offset
// @see https://github.com/jquery/jquery/blob/1.12-stable/src/offset.js#L149-L155
const parentOffsets = $selector.parent().offset();
const transformedPosition = {
top: offsets.top - parentOffsets.top,
left: offsets.left - parentOffsets.left,
};
// Calculate offset (relative to selector's parent) BEFORE it is affected by transform
const preTransformedPosition = {
top: $selector[0].offsetTop,
left: $selector[0].offsetLeft,
};
// Update offset's top value
top += (preTransformedPosition.top - transformedPosition.top);
offsets.top = top;
// Update offset's left value
left += (preTransformedPosition.left - transformedPosition.left);
offsets.left = left;
}
// Manually calculate right & bottom offsets
offsets.right = left + width;
offsets.bottom = top + height;
// Save copy of the offset on element's .data() in case of scenario where retrieving actual
// offset value will lead to incorrect offset value (eg. sticky tab active with position offset)
$selector.data('et-offsets', offsets);
// Add current device to cache
if ('' !== currentDevice) {
$selector.data('et-offsets-device', offsets);
}
return offsets;
};
/**
* Increase EventEmitter's max listeners if lister count is about to surpass the max listeners limit
* IMPORTANT: Need to be placed BEFORE `.on()`.
*
* @since 4.6.0
* @param {EventEmitter} emitter
* @param eventName
* @param {string} EventName
*/
export const maybeIncreaseEmitterMaxListeners = (emitter, eventName) => {
const currentCount = emitter.listenerCount(eventName);
const maxListeners = emitter.getMaxListeners();
if (currentCount === maxListeners) {
emitter.setMaxListeners(maxListeners + 1);
}
};
/**
* Decrease EventEmitter's max listeners if listener count is less than max listener limit and above
* 10 (default max listener limit). If listener count is less than 10, max listener limit will
* remain at 10
* IMPORTANT: Need to be placed AFTER `.removeListener()`.
*
* @since 4.6.0
*
* @param {EventEmitter} emitter
* @param {string} eventName
*/
export const maybeDecreaseEmitterMaxListeners = (emitter, eventName) => {
const currentCount = emitter.listenerCount(eventName);
const maxListeners = emitter.getMaxListeners();
if (maxListeners > 10) {
emitter.setMaxListeners(currentCount);
}
};
/**
* Expose frontend (FE) component via global object so it can be accessed and reused externally
* Note: window.ET_Builder is for builder app's component; window.ET_FE is for frontend component.
*
* @since 4.6.0
*
* @param {string} type
* @param {string} name
* @param {mixed} component
*/
export const registerFrontendComponent = (type, name, component) => {
// Make sure that ET_FE is available
if ('undefined' === typeof window.ET_FE) {
window.ET_FE = {};
}
if ('object' !== typeof window.ET_FE[type]) {
window.ET_FE[type] = {};
}
window.ET_FE[type][name] = component;
};
/**
* Set inline style with !important tag. JQuery's .css() can't set value with `!important` tag so
* here it is.
*
* @since 4.6.2
*
* @param {object} $element
* @param {string} cssProp
* @param {string} value
*/
export const setImportantInlineValue = ($element, cssProp, value) => {
// Remove prop from current inline style in case the prop is already exist
$element.css(cssProp, '');
// Get current inline style
const inlineStyle = $element.attr('style');
// Re-insert inline style + property with important tag
$element.attr('style', `${inlineStyle} ${cssProp}: ${value} !important;`);
};