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 document.querySelectorAll(`[data-offset-${edge}]`).forEach((el) => { 157 // If the element is not visible, do consider its dimensions. 158 if (el.style.display === 'none') { 159 return; 160 } 161 // If the offset data attribute contains a displacing value, use it. 162 let displacement = parseInt(el.getAttribute(`data-offset-${edge}`), 10); 163 // If the element's offset data attribute exits 164 // but is not a valid number then get the displacement 165 // dimensions directly from the element. 166 // eslint-disable-next-line no-restricted-globals 167 if (isNaN(displacement)) { 168 displacement = getRawOffset(el, edge); 169 } 170 // If the displacement value is larger than the current value for this 171 // edge, use the displacement value. 172 edgeOffset = Math.max(edgeOffset, displacement); 173 }); 174 175 return edgeOffset; 176 } 177 178 /** 179 * Informs listeners of the current offset dimensions. 180 * 181 * Corresponding CSS custom variables are also updated. 182 * Corresponding CSS custom variables names are: 183 * - `--drupal-displace-offset-top` 184 * - `--drupal-displace-offset-right` 185 * - `--drupal-displace-offset-bottom` 186 * - `--drupal-displace-offset-left` 187 * 188 * @function Drupal.displace 189 * 190 * @prop {Drupal~displaceOffset} offsets 191 * 192 * @param {boolean} [broadcast=true] 193 * When true, causes the recalculated offsets values to be 194 * broadcast to listeners. If none is given, defaults to true. 195 * 196 * @return {Drupal~displaceOffset} 197 * An object whose keys are the for sides an element -- top, right, bottom 198 * and left. The value of each key is the viewport displacement distance for 199 * that edge. 200 * 201 * @fires event:drupalViewportOffsetChange 202 */ 203 function displace(broadcast = true) { 204 const newOffsets = {}; 205 // Getting the offset and setting the offset needs to be separated because 206 // of performance concerns. Only do DOM/style reading happening here. 207 offsetKeys.forEach((edge) => { 208 newOffsets[edge] = calculateOffset(edge); 209 }); 210 // Once we have all the values, write to the DOM/style. 211 offsetKeys.forEach((edge) => { 212 // Updating the value in place also update Drupal.displace.offsets. 213 offsets[edge] = newOffsets[edge]; 214 }); 215 216 if (broadcast) { 217 $(document).trigger('drupalViewportOffsetChange', offsets); 218 } 219 return offsets; 220 } 221 222 /** 223 * Registers a resize handler on the window. 224 * 225 * @type {Drupal~behavior} 226 */ 227 Drupal.behaviors.drupalDisplace = { 228 attach() { 229 // Mark this behavior as processed on the first pass. 230 if (this.displaceProcessed) { 231 return; 232 } 233 this.displaceProcessed = true; 234 $(window).on('resize.drupalDisplace', debounce(displace, 200)); 235 }, 236 }; 237 238 /** 239 * Assign the displace function to a property of the Drupal global object. 240 * 241 * @ignore 242 */ 243 Drupal.displace = displace; 244 245 /**
246 * Expose offsets to other scripts to avoid having to recalculate offsets. 247 * 248 * @ignore 249 */ 250 Object.defineProperty(Drupal.displace, 'offsets', { 251 value: offsets, 252 // Make sure other scripts don't replace this object. 253 writable: false, 254 }); 255 256 /** 257 * Expose method to compute a single edge offsets. 258 * 259 * @ignore 260 */ 261 Drupal.displace.calculateOffset = calculateOffset; 262})(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.