239 lines
5.2 KiB
JavaScript
239 lines
5.2 KiB
JavaScript
|
// 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;
|