Updated to Drupal 8.5. Core Media not yet in use.
[yaffs-website] / web / core / misc / displace.es6.js
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
27 (function ($, Drupal, debounce) {
28   /**
29    * @name Drupal.displace.offsets
30    *
31    * @type {Drupal~displaceOffset}
32    */
33   let offsets = {
34     top: 0,
35     right: 0,
36     bottom: 0,
37     left: 0,
38   };
39
40   /**
41    * Registers a resize handler on the window.
42    *
43    * @type {Drupal~behavior}
44    */
45   Drupal.behaviors.drupalDisplace = {
46     attach() {
47       // Mark this behavior as processed on the first pass.
48       if (this.displaceProcessed) {
49         return;
50       }
51       this.displaceProcessed = true;
52
53       $(window).on('resize.drupalDisplace', debounce(displace, 200));
54     },
55   };
56
57   /**
58    * Informs listeners of the current offset dimensions.
59    *
60    * @function Drupal.displace
61    *
62    * @prop {Drupal~displaceOffset} offsets
63    *
64    * @param {bool} [broadcast]
65    *   When true or undefined, causes the recalculated offsets values to be
66    *   broadcast to listeners.
67    *
68    * @return {Drupal~displaceOffset}
69    *   An object whose keys are the for sides an element -- top, right, bottom
70    *   and left. The value of each key is the viewport displacement distance for
71    *   that edge.
72    *
73    * @fires event:drupalViewportOffsetChange
74    */
75   function displace(broadcast) {
76     offsets = calculateOffsets();
77     Drupal.displace.offsets = offsets;
78     if (typeof broadcast === 'undefined' || broadcast) {
79       $(document).trigger('drupalViewportOffsetChange', offsets);
80     }
81     return offsets;
82   }
83
84   /**
85    * Determines the viewport offsets.
86    *
87    * @return {Drupal~displaceOffset}
88    *   An object whose keys are the for sides an element -- top, right, bottom
89    *   and left. The value of each key is the viewport displacement distance for
90    *   that edge.
91    */
92   function calculateOffsets() {
93     return {
94       top: calculateOffset('top'),
95       right: calculateOffset('right'),
96       bottom: calculateOffset('bottom'),
97       left: calculateOffset('left'),
98     };
99   }
100
101   /**
102    * Gets a specific edge's offset.
103    *
104    * Any element with the attribute data-offset-{edge} e.g. data-offset-top will
105    * be considered in the viewport offset calculations. If the attribute has a
106    * numeric value, that value will be used. If no value is provided, one will
107    * be calculated using the element's dimensions and placement.
108    *
109    * @function Drupal.displace.calculateOffset
110    *
111    * @param {string} edge
112    *   The name of the edge to calculate. Can be 'top', 'right',
113    *   'bottom' or 'left'.
114    *
115    * @return {number}
116    *   The viewport displacement distance for the requested edge.
117    */
118   function calculateOffset(edge) {
119     let edgeOffset = 0;
120     const displacingElements = document.querySelectorAll(`[data-offset-${edge}]`);
121     const n = displacingElements.length;
122     for (let i = 0; i < n; i++) {
123       const el = displacingElements[i];
124       // If the element is not visible, do consider its dimensions.
125       if (el.style.display === 'none') {
126         continue;
127       }
128       // If the offset data attribute contains a displacing value, use it.
129       let displacement = parseInt(el.getAttribute(`data-offset-${edge}`), 10);
130       // If the element's offset data attribute exits
131       // but is not a valid number then get the displacement
132       // dimensions directly from the element.
133       if (isNaN(displacement)) {
134         displacement = getRawOffset(el, edge);
135       }
136       // If the displacement value is larger than the current value for this
137       // edge, use the displacement value.
138       edgeOffset = Math.max(edgeOffset, displacement);
139     }
140
141     return edgeOffset;
142   }
143
144   /**
145    * Calculates displacement for element based on its dimensions and placement.
146    *
147    * @param {HTMLElement} el
148    *   The jQuery element whose dimensions and placement will be measured.
149    *
150    * @param {string} edge
151    *   The name of the edge of the viewport that the element is associated
152    *   with.
153    *
154    * @return {number}
155    *   The viewport displacement distance for the requested edge.
156    */
157   function getRawOffset(el, edge) {
158     const $el = $(el);
159     const documentElement = document.documentElement;
160     let displacement = 0;
161     const horizontal = (edge === 'left' || edge === 'right');
162     // Get the offset of the element itself.
163     let placement = $el.offset()[horizontal ? 'left' : 'top'];
164     // Subtract scroll distance from placement to get the distance
165     // to the edge of the viewport.
166     placement -= window[`scroll${horizontal ? 'X' : 'Y'}`] || document.documentElement[`scroll${horizontal ? 'Left' : 'Top'}`] || 0;
167     // Find the displacement value according to the edge.
168     switch (edge) {
169       // Left and top elements displace as a sum of their own offset value
170       // plus their size.
171       case 'top':
172         // Total displacement is the sum of the elements placement and size.
173         displacement = placement + $el.outerHeight();
174         break;
175
176       case 'left':
177         // Total displacement is the sum of the elements placement and size.
178         displacement = placement + $el.outerWidth();
179         break;
180
181       // Right and bottom elements displace according to their left and
182       // top offset. Their size isn't important.
183       case 'bottom':
184         displacement = documentElement.clientHeight - placement;
185         break;
186
187       case 'right':
188         displacement = documentElement.clientWidth - placement;
189         break;
190
191       default:
192         displacement = 0;
193     }
194     return displacement;
195   }
196
197   /**
198    * Assign the displace function to a property of the Drupal global object.
199    *
200    * @ignore
201    */
202   Drupal.displace = displace;
203   $.extend(Drupal.displace, {
204
205     /**
206      * Expose offsets to other scripts to avoid having to recalculate offsets.
207      *
208      * @ignore
209      */
210     offsets,
211
212     /**
213      * Expose method to compute a single edge offsets.
214      *
215      * @ignore
216      */
217     calculateOffset,
218   });
219 }(jQuery, Drupal, Drupal.debounce));