1/** 2 * @file 3 * Manages elements that can offset the size of the viewport. 4 * 5 * Measures and reports viewport offset dimensions from elements like the 6 * toolbar that can potentially displace the positioning of other elements. 7 */ 8 9/** 10 * @typedef {object} Drupal~displaceOffset 11 * 12 * @prop {number} top 13 * @prop {number} left 14 * @prop {number} right 15 * @prop {number} bottom 16 */ 17 18/** 19 * Triggers when layout of the page changes. 20 * 21 * This is used to position fixed element on the page during page resize and 22 * Toolbar toggling. 23 * 24 * @event drupalViewportOffsetChange 25 */ 26(function ($, Drupal, debounce) { 27 /** 28 * 29 * @type {Drupal~displaceOffset} 30 */ 31 const cache = { 32 right: 0, 33 left: 0, 34 bottom: 0, 35 top: 0, 36 }; 37 /** 38 * The prefix used for the css custom variable name. 39 * 40 * @type {string} 41 */ 42 const cssVarPrefix = '--drupal-displace-offset'; 43 const documentStyle = document.documentElement.style; 44 const offsetKeys = Object.keys(cache); 45 /** 46 * The object with accessors that update the CSS variable on value update. 47 * 48 * @type {Drupal~displaceOffset} 49 */ 50 const offsetProps = {}; 51 offsetKeys.forEach((edge) => { 52 offsetProps[edge] = { 53 // Show this property when using Object.keys(). 54 enumerable: true, 55 get() { 56 return cache[edge]; 57 }, 58 set(value) { 59 // Only update the CSS custom variable when the value changed. 60 if (value !== cache[edge]) { 61 documentStyle.setProperty(`${cssVarPrefix}-${edge}`, `${value}px`); 62 } 63 cache[edge] = value; 64 }, 65 }; 66 }); 67 68 /** 69 * Current value of the size of margins on the page. 70 * 71 * This property is read-only and the object is sealed to prevent key name 72 * modifications since key names are used to dynamically construct CSS custom 73 * variable names. 74 * 75 * @name Drupal.displace.offsets 76 * 77 * @type {Drupal~displaceOffset} 78 */ 79 const offsets = Object.seal(Object.defineProperties({}, offsetProps)); 80 81 /** 82 * Calculates displacement for element based on its dimensions and placement. 83 * 84 * @param {HTMLElement} el 85 * The element whose dimensions and placement will be measured. 86 * 87 * @param {string} edge 88 * The name of the edge of the viewport that the element is associated 89 * with. 90 * 91 * @return {number} 92 * The viewport displacement distance for the requested edge. 93 */ 94 function getRawOffset(el, edge) { 95 const $el = $(el); 96 const documentElement = document.documentElement; 97 let displacement = 0; 98 const horizontal = edge === 'left' || edge === 'right'; 99 // Get the offset of the element itself. 100 let placement = $el.offset()[horizontal ? 'left' : 'top']; 101 // Subtract scroll distance from placement to get the distance 102 // to the edge of the viewport. 103 placement -= 104 window[`scroll${horizontal ? 'X' : 'Y'}`] || 105 document.documentElement[`scroll${horizontal ? 'Left' : 'Top'}`] || 106 0; 107 // Find the displacement value according to the edge. 108 switch (edge) { 109 // Left and top elements displace as a sum of their own offset value 110 // plus their size. 111 case 'top': 112 // Total displacement is the sum of the elements placement and size. 113 displacement = placement + $el.outerHeight(); 114 break; 115 116 case 'left': 117 // Total displacement is the sum of the elements placement and size. 118 displacement = placement + $el.outerWidth(); 119 break; 120 121 // Right and bottom elements displace according to their left and 122 // top offset. Their size isn't important. 123 case 'bottom': 124 displacement = documentElement.clientHeight - placement; 125 break; 126 127 case 'right': 128 displacement = documentElement.clientWidth - placement; 129 break; 130 131 default: 132 displacement = 0; 133 } 134 return displacement; 135 } 136 137 /** 138 * Gets a specific edge's offset. 139 * 140 * Any element with the attribute data-offset-{edge} e.g. data-offset-top will 141 * be considered in the viewport offset calculations. If the attribute has a 142 * numeric value, that value will be used. If no value is provided, one will 143 * be calculated using the element's dimensions and placement. 144 * 145 * @function Drupal.displace.calculateOffset 146 * 147 * @param {string} edge 148 * The name of the edge to calculate. Can be 'top', 'right', 149 * 'bottom' or 'left'. 150 * 151 * @return {number} 152 * The viewport displacement distance for the requested edge. 153 */ 154 function calculateOffset(edge) { 155 let edgeOffset = 0; 156 const displacingElements = document.querySelectorAll( 157 `[data-offset-${edge}]`, 158 ); 159 const n = displacingElements.length; 160 for (let i = 0; i < n; i++) { 161 const el = displacingElements[i]; 162 // If the element is not visible, do consider its dimensions. 163 if (el.style.display === 'none') { 164 continue; 165 } 166 // If the offset data attribute contains a displacing value, use it. 167 let displacement = parseInt(el.getAttribute(`data-offset-${edge}`), 10); 168 // If the element's offset data attribute exits 169 // but is not a valid number then get the displacement 170 // dimensions directly from the element. 171 // eslint-disable-next-line no-restricted-globals 172 if (isNaN(displacement)) { 173 displacement = getRawOffset(el, edge); 174 } 175 // If the displacement value is larger than the current value for this 176 // edge, use the displacement value. 177 edgeOffset = Math.max(edgeOffset, displacement); 178 } 179 180 return edgeOffset; 181 } 182 183 /** 184 * Informs listeners of the current offset dimensions. 185 * 186 * Corresponding CSS custom variables are also updated. 187 * Corresponding CSS custom variables names are: 188 * - `--drupal-displace-offset-top` 189 * - `--drupal-displace-offset-right` 190 * - `--drupal-displace-offset-bottom` 191 * - `--drupal-displace-offset-left` 192 * 193 * @function Drupal.displace 194 * 195 * @prop {Drupal~displaceOffset} offsets 196 * 197 * @param {boolean} [broadcast=true] 198 * When true, causes the recalculated offsets values to be 199 * broadcast to listeners. If none is given, defaults to true. 200 * 201 * @return {Drupal~displaceOffset} 202 * An object whose keys are the for sides an element -- top, right, bottom 203 * and left. The value of each key is the viewport displacement distance for 204 * that edge. 205 * 206 * @fires event:drupalViewportOffsetChange 207 */ 208 function displace(broadcast = true) { 209 const newOffsets = {}; 210 // Getting the offset and setting the offset needs to be separated because 211 // of performance concerns. Only do DOM/style reading happening here. 212 offsetKeys.forEach((edge) => { 213 newOffsets[edge] = calculateOffset(edge); 214 }); 215 // Once we have all the values, write to the DOM/style. 216 offsetKeys.forEach((edge) => { 217 // Updating the value in place also update Drupal.displace.offsets. 218 offsets[edge] = newOffsets[edge]; 219 }); 220 221 if (broadcast) { 222 $(document).trigger('drupalViewportOffsetChange', offsets); 223 } 224 return offsets; 225 } 226 227 /** 228 * Registers a resize handler on the window. 229 * 230 * @type {Drupal~behavior} 231 */ 232 Drupal.behaviors.drupalDisplace = { 233 attach() { 234 // Mark this behavior as processed on the first pass. 235 if (this.displaceProcessed) { 236 return; 237 } 238 this.displaceProcessed = true; 239 $(window).on('resize.drupalDisplace', debounce(displace, 200)); 240 }, 241 }; 242 243 /** 244 * Assign the displace function to a property of the Drupal global object. 245 * 246 * @ignore 247 */ 248 Drupal.displace = displace; 249 250 /**
251 * Expose offsets to other scripts to avoid having to recalculate offsets. 252 * 253 * @ignore 254 */ 255 Object.defineProperty(Drupal.displace, 'offsets', { 256 value: offsets, 257 // Make sure other scripts don't replace this object. 258 writable: false, 259 }); 260 261 /** 262 * Expose method to compute a single edge offsets. 263 * 264 * @ignore 265 */ 266 Drupal.displace.calculateOffset = calculateOffset; 267})(jQuery, Drupal, Drupal.debounce);
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.