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