1/** 2 * Explorer Loading Stability â frontend. 3 * 4 * Kills the layout jump when the explorer swaps property cards for loading 5 * skeletons. Two causes, two fixes: 6 * 7 * 1. The plugin renders at most 8 skeletons regardless of per-page 8 * (Math.min(perPage, 8) "to prevent the page from becoming too long"), 9 * so a 12-card grid collapses to 8 placeholders on every refilter. 10 * Fix: top the skeletons up by cloning until they match the card count 11 * that was just on screen. The plugin's own success handler consumes 12 * skeletons FIFO and removes any leftovers, so clones are absorbed by 13 * its normal flow. 14 * 15 * 2. Skeleton cards are not the same height as real cards, so even a 16 * matched count shifts the page a little. Fix: pin the grid's 17 * min-height to its settled (pre-loading) height for the duration of 18 * the load, released when real cards land. 19 * 20 * The grid is flex-wrap with default align-content, so the min-height pin 21 * alone makes flex distribute the pinned height across the skeleton rows â 22 * on a 100-per-page grid every skeleton stretched ~2.6x (293x305 â 23 * 293x797). The pin therefore comes with align-content: flex-start for 24 * its duration: skeletons keep their natural height and the excess pinned 25 * space stays empty below them. 26 * 27 * The `loading` class toggles synchronously around the card swap, so the 28 * settled height/count are tracked on every load COMPLETION (the mutation 29 * callback runs after the swap â measuring at loading-start would already 30 * see the skeleton state). 31 */ 32(function () { 33 'use strict'; 34 35 var MAX_SKELETONS = 48; // sanity cap on top-up cloning 36 37 function grids( root ) { 38 return root.querySelectorAll( '.hfse-properties' ); 39 } 40 41 function watch( root ) { 42 var settledHeight = 0; 43 var settledCards = 0; 44 45 function measure() { 46 var grid = grids( root )[ 0 ]; 47 if ( ! grid ) { 48 return; 49 } 50 var cards = grid.querySelectorAll( '.property-item:not(.property-loading)' ).length; 51 if ( cards > 0 ) { 52 settledCards = cards; 53 settledHeight = grid.offsetHeight; 54 } 55 } 56 57 function onLoadingStart() { 58 var grid = grids( root )[ 0 ]; 59 if ( ! grid ) { 60 return; 61 } 62 63 if ( settledHeight > 0 ) { 64 grid.style.minHeight = settledHeight + 'px'; 65 grid.style.alignContent = 'flex-start'; 66 } 67 68 var skeletons = grid.querySelectorAll( '.property-loading' ); 69 var want = Math.min( settledCards, MAX_SKELETONS ); 70 var template = skeletons[ skeletons.length - 1 ]; 71 if ( ! template || skeletons.length >= want ) { 72 return; 73 } 74 for ( var i = skeletons.length; i < want; i++ ) { 75 grid.appendChild( template.cloneNode( true ) ); 76 } 77 } 78 79 function onLoadingEnd() { 80 var grid = grids( root )[ 0 ]; 81 if ( grid ) { 82 grid.style.minHeight = ''; 83 grid.style.alignContent = ''; 84 } 85 measure(); 86 } 87 88 var wasLoading = root.classList.contains( 'loading' ); 89 90 new MutationObserver( function () { 91 var isLoading = root.classList.contains( 'loading' ); 92 if ( isLoading === wasLoading ) { 93 return; 94 } 95 wasLoading = isLoading; 96 if ( isLoading ) { 97 onLoadingStart(); 98 } else { 99 onLoadingEnd(); 100 } 101 } ).observe( root, { attributes: true, attributeFilter: [ 'class' ] } ); 102 103 // Settled baseline for the first refilter (initial load may still be 104 // in flight; measure() ignores empty grids and the loading-end hook 105 // re-measures after every completed load). 106 measure(); 107 } 108 109 function start() { 110 if ( ! window.MutationObserver ) { 111 return; 112 } 113 document.querySelectorAll( '.hfse' ).forEach( watch ); 114 } 115 116 if ( 'loading' === document.readyState ) { 117 document.addEventListener( 'DOMContentLoaded', start ); 118 } else { 119 start(); 120 } 121})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.