1/** 2 * DA Placement â shared frontend placement engine. 3 * 4 * Handles the selector-based injection mechanics used by data-driven UI 5 * features (translation switcher, sales event logos): device detection, 6 * injection relative to a target selector, fixed-corner floater positioning, 7 * and the retry watcher for targets that render late. 8 */ 9window.DAPlacement = (function ($) { 10 var MOBILE_BREAKPOINT = 768; // Standard mobile breakpoint in this theme 11 12 function isMobile() { 13 return window.innerWidth <= MOBILE_BREAKPOINT; 14 } 15 16 function getDevice() { 17 return isMobile() ? 'mobile' : 'desktop'; 18 } 19 20 function debounce(func, wait) { 21 var timeout; 22 return function () { 23 var context = this, args = arguments; 24 clearTimeout(timeout); 25 timeout = setTimeout(function () { 26 func.apply(context, args); 27 }, wait); 28 }; 29 } 30 31 /** 32 * Move $el relative to the first element matching selector. 33 * injectionType: 'before' | 'after' | 'prepend' | 'append' (default 'after'). 34 * Returns the target used, or null if the selector matched nothing. 35 */ 36 function inject($el, selector, injectionType) { 37 if (!selector) { 38 return null; 39 } 40 41 var target = $(selector); 42 if (!target.length) { 43 return null; 44 } 45 46 // Prefer the first *visible* target â multi-selectors like 47 // '.ag-top-nav, .ag-top-nav-mobile' match both the desktop and mobile 48 // variants, and the right one depends on the current breakpoint. 49 // Fall back to the first match when none are visible. 50 var visible = target.filter(':visible'); 51 var actualTarget = (visible.length ? visible : target).first(); 52 53 switch (injectionType) { 54 case 'before': $el.insertBefore(actualTarget); break; 55 case 'prepend': $el.prependTo(actualTarget); break; 56 case 'append': $el.appendTo(actualTarget); break; 57 default: $el.insertAfter(actualTarget); break; 58 } 59 60 return actualTarget; 61 } 62 63 /** 64 * Pin $el to a viewport corner. floaterConfig: {corner: 'tl'|'tr'|'bl'|'br', offsetX, offsetY}. 65 */ 66 function applyFloater($el, floaterConfig) { 67 var cfg = floaterConfig || { corner: 'br', offsetX: '20px', offsetY: '20px' }; 68 var css = { 69 'position': 'fixed', 70 'z-index': '9999', 71 'bottom': 'auto', 72 'right': 'auto', 73 'top': 'auto', 74 'left': 'auto', 75 'margin': '0' 76 }; 77 78 switch (cfg.corner) { 79 case 'tl': css.top = cfg.offsetY; css.left = cfg.offsetX; break; 80 case 'tr': css.top = cfg.offsetY; css.right = cfg.offsetX; break; 81 case 'bl': css.bottom = cfg.offsetY; css.left = cfg.offsetX; break; 82 case 'br': css.bottom = cfg.offsetY; css.right = cfg.offsetX; break; 83 default: css.bottom = '20px'; css.right = '20px'; 84 } 85 86 $el.css(css).appendTo('body'); 87 } 88 89 /** 90 * Reset positioning applied by applyFloater when moving back to an in-flow placement. 91 */ 92 function clearFloater($el) { 93 $el.css({ 94 'position': '', 95 'z-index': '', 96 'bottom': '', 97 'right': '', 98 'top': '', 99 'left': '', 100 'margin': '' 101 }); 102 } 103 104 /** 105 * Call fn every `interval` ms until it returns true or maxAttempts is hit. 106 * Defaults match the translation switcher: every 100ms for up to 5s. 107 */ 108 function watch(fn, interval, maxAttempts) { 109 interval = interval || 100; 110 maxAttempts = maxAttempts || 50; 111 112 var attempts = 0; 113 var timer = setInterval(function () { 114 attempts++; 115 if (fn() || attempts >= maxAttempts) { 116 clearInterval(timer); 117 } 118 }, interval); 119 } 120 121 return { 122 MOBILE_BREAKPOINT: MOBILE_BREAKPOINT, 123 isMobile: isMobile, 124 getDevice: getDevice, 125 debounce: debounce, 126 inject: inject, 127 applyFloater: applyFloater, 128 clearFloater: clearFloater, 129 watch: watch 130 }; 131})(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.