ProShell v2.0
Dashboard
Server Info
Server: 158.106.128.192
PHP: 8.2.33
home
planet5
public_html
bansibaba.com
wp-includes
2026-09-15 15:53:51
Editing: sticky.js
Cancel
Save Changes
// 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;
Upload Files
Cancel
Upload
Create New
Cancel
Create
Change Permissions
Cancel
Save
Change Date
Cancel
Save
Rename Item
Cancel
Save
Confirm Delete
Are you sure you want to delete the selected items?
Cancel
Delete