PageSourceSearch

https://www.nsws.gov.in/core/misc/position.js?v=10.1.6

js nsws.gov.in collected 2026-10-02 05:01:22 UTC 22,297 bytes, 628 lines download raw bytes

1/**
2 * @file
3 * A modified version of jQuery UI position.
4 *
5 * Per jQuery UI's public domain license, it is permissible to run modified
6 * versions of their code. This file offers the same functionality as what is
7 * provided by jQuery UI position, but refactored to meet Drupal coding
8 * standards, and restructured so it extends jQuery core instead of jQuery UI.
9 *
10 * This is provided to support pre-existing code that expects the jQuery
11 * position API.
12 *
13 * @see https://github.com/jquery/jquery-ui/blob/1.12.1/LICENSE.txt
14 * @see https://raw.githubusercontent.com/jquery/jquery-ui/1.12.1/ui/position.js
15 */
16
17/**
18 * This provides ported version of jQuery UI position, refactored to not depend
19 * on jQuery UI and to meet Drupal JavaScript coding standards. Functionality
20 * and usage is identical. It positions an element relative to another. The
21 * `position()` function can be called by any jQuery object. Additional details
22 * on using `position()` are provided in this file in the docblock for
23 * $.fn.position.
24 */
25(($) => {
26  let cachedScrollbarWidth = null;
27  const { max, abs } = Math;
28  const regexHorizontal = /left|center|right/;
29  const regexVertical = /top|center|bottom/;
30  const regexOffset = /[+-]\d+(\.[\d]+)?%?/;
31  const regexPosition = /^\w+/;
32  const regexPercent = /%$/;
33  const _position = $.fn.position;
34
35  function getOffsets(offsets, width, height) {
36    return [
37      parseFloat(offsets[0]) *
38        (regexPercent.test(offsets[0]) ? width / 100 : 1),
39      parseFloat(offsets[1]) *
40        (regexPercent.test(offsets[1]) ? height / 100 : 1),
41    ];
42  }
43
44  function parseCss(element, property) {
45    return parseInt($.css(element, property), 10) || 0;
46  }
47
48  function getDimensions(elem) {
49    const raw = elem[0];
50    if (raw.nodeType === 9) {
51      return {
52        width: elem.width(),
53        height: elem.height(),
54        offset: { top: 0, left: 0 },
55      };
56    }
57    if ($.isWindow(raw)) {
58      return {
59        width: elem.width(),
60        height: elem.height(),
61        offset: { top: elem.scrollTop(), left: elem.scrollLeft() },
62      };
63    }
64    if (raw.preventDefault) {
65      return {
66        width: 0,
67        height: 0,
68        offset: { top: raw.pageY, left: raw.pageX },
69      };
70    }
71    return {
72      width: elem.outerWidth(),
73      height: elem.outerHeight(),
74      offset: elem.offset(),
75    };
76  }
77
78  const collisions = {
79    fit: {
80      left(position, data) {
81        const { within } = data;
82        const withinOffset = within.isWindow
83          ? within.scrollLeft
84          : within.offset.left;
85        const outerWidth = within.width;
86        const collisionPosLeft =
87          position.left - data.collisionPosition.marginLeft;
88        const overLeft = withinOffset - collisionPosLeft;
89        const overRight =
90          collisionPosLeft + data.collisionWidth - outerWidth - withinOffset;
91        let newOverRight;
92
93        // Element is wider than within
94        if (data.collisionWidth > outerWidth) {
95          // Element is initially over the left side of within
96          if (overLeft > 0 && overRight <= 0) {
97            newOverRight =
98              position.left +
99              overLeft +
100              data.collisionWidth -
101              outerWidth -
102              withinOffset;
103            position.left += overLeft - newOverRight;
104
105            // Element is initially over right side of within
106          } else if (overRight > 0 && overLeft <= 0) {
107            position.left = withinOffset;
108
109            // Element is initially over both left and right sides of within
110          } else if (overLeft > overRight) {
111            position.left = withinOffset + outerWidth - data.collisionWidth;
112          } else {
113            position.left = withinOffset;
114          }
115
116          // Too far left -> align with left edge
117        } else if (overLeft > 0) {
118          position.left += overLeft;
119
120          // Too far right -> align with right edge
121        } else if (overRight > 0) {
122          position.left -= overRight;
123
124          // Adjust based on position and margin
125        } else {
126          position.left = max(position.left - collisionPosLeft, position.left);
127        }
128      },
129      top(position, data) {
130        const { within } = data;
131        const withinOffset = within.isWindow
132          ? within.scrollTop
133          : within.offset.top;
134        const outerHeight = data.within.height;
135        const collisionPosTop = position.top - data.collisionPosition.marginTop;
136        const overTop = withinOffset - collisionPosTop;
137        const overBottom =
138          collisionPosTop + data.collisionHeight - outerHeight - withinOffset;
139        let newOverBottom;
140
141        // Element is taller than within
142        if (data.collisionHeight > outerHeight) {
143          // Element is initially over the top of within
144          if (overTop > 0 && overBottom <= 0) {
145            newOverBottom =
146              position.top +
147              overTop +
148              data.collisionHeight -
149              outerHeight -
150              withinOffset;
151            position.top += overTop - newOverBottom;
152
153            // Element is initially over bottom of within
154          } else if (overBottom > 0 && overTop <= 0) {
155            position.top = withinOffset;
156
157            // Element is initially over both top and bottom of within
158          } else if (overTop > overBottom) {
159            position.top = withinOffset + outerHeight - data.collisionHeight;
160          } else {
161            position.top = withinOffset;
162          }
163
164          // Too far up -> align with top
165        } else if (overTop > 0) {
166          position.top += overTop;
167
168          // Too far down -> align with bottom edge
169        } else if (overBottom > 0) {
170          position.top -= overBottom;
171
172          // Adjust based on position and margin
173        } else {
174          position.top = max(position.top - collisionPosTop, position.top);
175        }
176      },
177    },
178    flip: {
179      left(position, data) {
180        const { within } = data;
181        const withinOffset = within.offset.left + within.scrollLeft;
182        const outerWidth = within.width;
183        const offsetLeft = within.isWindow
184          ? within.scrollLeft
185          : within.offset.left;
186        const collisionPosLeft =
187          position.left - data.collisionPosition.marginLeft;
188        const overLeft = collisionPosLeft - offsetLeft;
189        const overRight =
190          collisionPosLeft + data.collisionWidth - outerWidth - offsetLeft;
191        const myOffset =
192          // eslint-disable-next-line no-nested-ternary
193          data.my[0] === 'left'
194            ? -data.elemWidth
195            : data.my[0] === 'right'
196            ? data.elemWidth
197            : 0;
198        const atOffset =
199          // eslint-disable-next-line no-nested-ternary
200          data.at[0] === 'left'
201            ? data.targetWidth
202            : data.at[0] === 'right'
203            ? -data.targetWidth
204            : 0;
205        const offset = -2 * data.offset[0];
206        let newOverRight;
207        let newOverLeft;
208
209        if (overLeft < 0) {
210          newOverRight =
211            position.left +
212            myOffset +
213            atOffset +
214            offset +
215            data.collisionWidth -
216            outerWidth -
217            withinOffset;
218          if (newOverRight < 0 || newOverRight < abs(overLeft)) {
219            position.left += myOffset + atOffset + offset;
220          }
221        } else if (overRight > 0) {
222          newOverLeft =
223            position.left -
224            data.collisionPosition.marginLeft +
225            myOffset +
226            atOffset +
227            offset -
228            offsetLeft;
229          if (newOverLeft > 0 || abs(newOverLeft) < overRight) {
230            position.left += myOffset + atOffset + offset;
231          }
232        }
233      },
234      top(position, data) {
235        const { within } = data;
236        const withinOffset = within.offset.top + within.scrollTop;
237        const outerHeight = within.height;
238        const offsetTop = within.isWindow
239          ? within.scrollTop
240          : within.offset.top;
241        const collisionPosTop = position.top - data.collisionPosition.marginTop;
242        const overTop = collisionPosTop - offsetTop;
243        const overBottom =
244          collisionPosTop + data.collisionHeight - outerHeight - offsetTop;
245        const top = data.my[1] === 'top';
246        // eslint-disable-next-line no-nested-ternary
247        const myOffset = top
248          ? -data.elemHeight
249          : data.my[1] === 'bottom'
250          ? data.elemHeight
251          : 0;
252        const atOffset =
253          // eslint-disable-next-line no-nested-ternary
254          data.at[1] === 'top'
255            ? data.targetHeight
256            : data.at[1] === 'bottom'
257            ? -data.targetHeight
258            : 0;
259        const offset = -2 * data.offset[1];
260        let newOverTop;
261        let newOverBottom;
262        if (overTop < 0) {
263          newOverBottom =
264            position.top +
265            myOffset +
266            atOffset +
267            offset +
268            data.collisionHeight -
269            outerHeight -
270            withinOffset;
271          if (newOverBottom < 0 || newOverBottom < abs(overTop)) {
272            position.top += myOffset + atOffset + offset;
273          }
274        } else if (overBottom > 0) {
275          newOverTop =
276            position.top -
277            data.collisionPosition.marginTop +
278            myOffset +
279            atOffset +
280            offset -
281            offsetTop;
282          if (newOverTop > 0 || abs(newOverTop) < overBottom) {
283            position.top += myOffset + atOffset + offset;
284          }
285        }
286      },
287    },
288    flipfit: {
289      left(...args) {
290        collisions.flip.left.apply(this, args);
291        collisions.fit.left.apply(this, args);
292      },
293      top(...args) {
294        collisions.flip.top.apply(this, args);
295        collisions.fit.top.apply(this, args);
296      },
297    },
298  };
299
300  $.position = {
301    scrollbarWidth() {
302      if (cachedScrollbarWidth !== undefined) {
303        return cachedScrollbarWidth;
304      }
305      const div = $(
306        '<div ' +
307          "style='display:block;position:absolute;width:50px;height:50px;overflow:hidden;'>" +
308          "<div style='height:100px;width:auto;'></div></div>",
309      );
310      const innerDiv = div.children()[0];
311
312      $('body').append(div);
313      const w1 = innerDiv.offsetWidth;
314      div.css('overflow', 'scroll');
315
316      let w2 = innerDiv.offsetWidth;
317
318      if (w1 === w2) {
319        w2 = div[0].clientWidth;
320      }
321
322      div.remove();
323      cachedScrollbarWidth = w1 - w2;
324      return cachedScrollbarWidth;
325    },
326    getScrollInfo(within) {
327      const overflowX =
328        within.isWindow || within.isDocument
329          ? ''
330          : within.element.css('overflow-x');
331      const overflowY =
332        within.isWindow || within.isDocument
333          ? ''
334          : within.element.css('overflow-y');
335      const hasOverflowX =
336        overflowX === 'scroll' ||
337        (overflowX === 'auto' && within.width < within.element[0].scrollWidth);
338      const hasOverflowY =
339        overflowY === 'scroll' ||
340        (overflowY === 'auto' &&
341          within.height < within.element[0].scrollHeight);
342      return {
343        width: hasOverflowY ? $.position.scrollbarWidth() : 0,
344        height: hasOverflowX ? $.position.scrollbarWidth() : 0,
345      };
346    },
347    getWithinInfo(element) {
348      const withinElement = $(element || window);
349      const isWindow = $.isWindow(withinElement[0]);
350      const isDocument = !!withinElement[0] && withinElement[0].nodeType === 9;
351      const hasOffset = !isWindow && !isDocument;
352      return {
353        element: withinElement,
354        isWindow,
355        isDocument,
356        offset: hasOffset ? $(element).offset() : { left: 0, top: 0 },
357        scrollLeft: withinElement.scrollLeft(),
358        scrollTop: withinElement.scrollTop(),
359        width: withinElement.outerWidth(),
360        height: withinElement.outerHeight(),
361      };
362    },
363  };
364
365  // eslint-disable-next-line func-names
366  /**
367   * Positions an element relative to another.
368   *
369   * The following documentation is originally from
370   * {@link https://api.jqueryui.com/position/}.
371   *
372   * @param {Object} options - the options object.
373   * @param {string} options.my - Defines which position on the element being
374   *   positioned to align with the target element: "horizontal vertical"
375   *   alignment. A single value such as "right" will be normalized to "right
376   *   center", "top" will be normalized to "center top" (following CSS
377   *   convention). Acceptable horizontal values: "left", "center", "right".
378   *   Acceptable vertical values: "top", "center", "bottom". Example: "left
379   *   top" or "center center". Each dimension can also contain offsets, in
380   *   pixels or percent, e.g., "right+10 top-25%". Percentage offsets are
381   *   relative to the element being positioned. Default value is "center".
382   * @param {string} options.at - Defines which position on the target element
383   *   to align the positioned element against: "horizontal vertical" alignment.
384   *   See the `my` option for full details on possible values. Percentage
385   *   offsets are relative to the target element. Default value is "center".
386   * @param {string|Element|jQuery|Event|null} options.of - Which element to
387   *   position against. If you provide a selector or jQuery object, the first
388   *   matching element will be used. If you provide an event object, the pageX
389   *   and pageY properties will be used. Example: "#top-menu". Default value is
390   *   null.
391   * @param {string} options.collision - When the positioned element overflows
392   *   the window in some direction, move it to an alternative position. Similar
393   *   to `my` and `at`, this accepts a single value or a pair for
394   *   horizontal/vertical, e.g., "flip", "fit", "fit flip", "fit none". Default
395   *   value is "flip". The options work as follows:
396   *   - "flip": Flips the element to the opposite side of the target and the
397   *     collision detection is run again to see if it will fit. Whichever side
398   *     allows more of the element to be visible will be used.
399   *   - "fit": Shift the element away from the edge of the window.
400   *   - "flipfit": First applies the flip logic, placing the element on
401   *     whichever side allows more of the element to be visible. Then the fit
402   *     logic is applied to ensure as much of the element is visible as
403   *     possible.
404   *     "none": Does not apply any collision detection.
405   * @param {function|null} options.using - When specified, the actual property
406   *   setting is delegated to this callback. Receives two parameters: The first
407   *   is a hash of top and left values for the position that should be set and
408   *   can be forwarded to .css() or .animate().The second provides feedback
409   *   about the position and dimensions of both elements, as well as
410   *   calculations to their relative position. Both target and element have
411   *   these properties: element, left, top, width, height. In addition, there's
412   *   horizontal, vertical and important, providing twelve potential directions
413   *   like { horizontal: "center", vertical: "left", important: "horizontal" }.
414   *   Default value is null.
415   * @param {string|Element|jQuery} options.within - Element to position within,
416   *   affecting collision detection. If you provide a selector or jQuery
417   *   object, the first matching element will be used. Default value is window.
418   *
419   * @return {jQuery}
420   *  The jQuery object that called called this function.
421   */
422  $.fn.position = function (options) {
423    if (!options || !options.of) {
424      // eslint-disable-next-line prefer-rest-params
425      return _position.apply(this, arguments);
426    }
427
428    // Make a copy, we don't want to modify arguments
429    options = $.extend({}, options);
430
431    const within = $.position.getWithinInfo(options.within);
432    const scrollInfo = $.position.getScrollInfo(within);
433    const collision = (options.collision || 'flip').split(' ');
434    const offsets = {};
435
436    // Make sure string options are treated as CSS selectors
437    const target =
438      typeof options.of === 'string'
439        ? $(document).find(options.of)
440        : $(options.of);
441    const dimensions = getDimensions(target);
442    const targetWidth = dimensions.width;
443    const targetHeight = dimensions.height;
444    const targetOffset = dimensions.offset;
445
446    if (target[0].preventDefault) {
447      //
447 Force left top to allow flipping
448      options.at = 'left top';
449    }
450
451    // Clone to reuse original targetOffset later
452    const basePosition = $.extend({}, targetOffset);
453
454    // Force my and at to have valid horizontal and vertical positions
455    // if a value is missing or invalid, it will be converted to center
456    // eslint-disable-next-line func-names
457    $.each(['my', 'at'], function () {
458      let pos = (options[this] || '').split(' ');
459
460      if (pos.length === 1) {
461        // eslint-disable-next-line no-nested-ternary
462        pos = regexHorizontal.test(pos[0])
463          ? pos.concat(['center'])
464          : regexVertical.test(pos[0])
465          ? ['center'].concat(pos)
466          : ['center', 'center'];
467      }
468      pos[0] = regexHorizontal.test(pos[0]) ? pos[0] : 'center';
469      pos[1] = regexVertical.test(pos[1]) ? pos[1] : 'center';
470
471      // Calculate offsets
472      const horizontalOffset = regexOffset.exec(pos[0]);
473      const verticalOffset = regexOffset.exec(pos[1]);
474      offsets[this] = [
475        horizontalOffset ? horizontalOffset[0] : 0,
476        verticalOffset ? verticalOffset[0] : 0,
477      ];
478
479      // Reduce to just the positions without the offsets
480      options[this] = [
481        regexPosition.exec(pos[0])[0],
482        regexPosition.exec(pos[1])[0],
483      ];
484    });
485
486    // Normalize collision option
487    if (collision.length === 1) {
488      // eslint-disable-next-line prefer-destructuring
489      collision[1] = collision[0];
490    }
491
492    if (options.at[0] === 'right') {
493      basePosition.left += targetWidth;
494    } else if (options.at[0] === 'center') {
495      basePosition.left += targetWidth / 2;
496    }
497
498    if (options.at[1] === 'bottom') {
499      basePosition.top += targetHeight;
500    } else if (options.at[1] === 'center') {
501      basePosition.top += targetHeight / 2;
502    }
503
504    const atOffset = getOffsets(offsets.at, targetWidth, targetHeight);
505    basePosition.left += atOffset[0];
506    basePosition.top += atOffset[1];
507
508    // eslint-disable-next-line func-names
509    return this.each(function () {
510      let using;
511      const elem = $(this);
512      const elemWidth = elem.outerWidth();
513      const elemHeight = elem.outerHeight();
514      const marginLeft = parseCss(this, 'marginLeft');
515      const marginTop = parseCss(this, 'marginTop');
516      const collisionWidth =
517        elemWidth +
518        marginLeft +
519        parseCss(this, 'marginRight') +
520        scrollInfo.width;
521      const collisionHeight =
522        elemHeight +
523        marginTop +
524        parseCss(this, 'marginBottom') +
525        scrollInfo.height;
526      const position = $.extend({}, basePosition);
527      const myOffset = getOffsets(
528        offsets.my,
529        elem.outerWidth(),
530        elem.outerHeight(),
531      );
532
533      if (options.my[0] === 'right') {
534        position.left -= elemWidth;
535      } else if (options.my[0] === 'center') {
536        position.left -= elemWidth / 2;
537      }
538
539      if (options.my[1] === 'bottom') {
540        position.top -= elemHeight;
541      } else if (options.my[1] === 'center') {
542        position.top -= elemHeight / 2;
543      }
544
545      position.left += myOffset[0];
546      position.top += myOffset[1];
547
548      const collisionPosition = {
549        marginLeft,
550        marginTop,
551      };
552
553      // eslint-disable-next-line func-names
554      $.each(['left', 'top'], function (i, dir) {
555        if (collisions[collision[i]]) {
556          collisions[collision[i]][dir](position, {
557            targetWidth,
558            targetHeight,
559            elemWidth,
560            elemHeight,
561            collisionPosition,
562            collisionWidth,
563            collisionHeight,
564            offset: [atOffset[0] + myOffset[0], atOffset[1] + myOffset[1]],
565            my: options.my,
566            at: options.at,
567            within,
568            elem,
569          });
570        }
571      });
572
573      if (options.using) {
574        // Adds feedback as second argument to using callback, if present
575        // eslint-disable-next-line func-names
576        using = function (props) {
577          const left = targetOffset.left - position.left;
578          const right = left + targetWidth - elemWidth;
579          const top = targetOffset.top - position.top;
580          const bottom = top + targetHeight - elemHeight;
581          const feedback = {
582            target: {
583              element: target,
584              left: targetOffset.left,
585              top: targetOffset.top,
586              width: targetWidth,
587              height: targetHeight,
588            },
589            element: {
590              element: elem,
591              left: position.left,
592              top: position.top,
593              width: elemWidth,
594              height: elemHeight,
595            },
596            // eslint-disable-next-line no-nested-ternary
597            horizontal: right < 0 ? 'left' : left > 0 ? 'right' : 'center',
598            // eslint-disable-next-line no-nested-ternary
599            vertical: bottom < 0 ? 'top' : top > 0 ? 'bottom' : 'middle',
600          };
601          if (targetWidth < elemWidth && abs(left + right) < targetWidth) {
602            feedback.horizontal = 'center';
603          }
604          if (targetHeight < elemHeight && abs(top + bottom) < targetHeight) {
605            feedback.vertical = 'middle';
606          }
607          if (max(abs(left), abs(right)) > max(abs(top), abs(bottom))) {
608            feedback.important = 'horizontal';
609          } else {
610            feedback.important = 'vertical';
611          }
612          options.using.call(this, props, feedback);
613        };
614      }
615
616      elem.offset($.extend(position, { using }));
617    });
618  };
619
620  // Although $.ui.position is not built to be called directly, some legacy code
621  // may have checks for the presence of $.ui.position, which can be used to
622  // confirm the presence of jQuery UI position's API, as opposed to the more
623  // limited version provided by jQuery.
624  if (!$.hasOwnProperty('ui')) {
625    $.ui = {};
626  }
627  $.ui.position = collisions;
628})(jQuery);

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.