PageSourceSearch

https://uniqsleep.com.ua/catalog/view/javascript/megamenu/jquery.menu-aim.js?v3

js uniqsleep.com.ua collected 2026-10-05 03:40:06 UTC 15,816 bytes, 386 lines download raw bytes

1;(function ( $, window, document, undefined ) {
2  var pluginName  = 'menuAim',
3      defaults    = {
4        triggerEvent:       "hover",  // A means of activating submenu - either 'hover' or 'click' or 'both
5        rowSelector:        "> li",   // Selector for identifying which elements in the menu are rows
6        handle:             "> a",    // Handle for triggering mouse clicks on menu item
7        submenuSelector:    "*",      // You may have some menu rows that aren't submenus and therefore
8                                      // shouldn't ever need to "activate." If so, filter submenu rows w/
9                                      // this selector. Defaults to "*" (all elements).
10        submenuDirection:   "right",  // Direction the submenu opens relative to the main menu. Can be
11                                      // left, right, above, or below. Defaults to "right".
12        openClassName:      "open",   // Class that will be applied to menu item when activated
13        tolerance:          75,       // Bigger = more tolerant for mouse movement precision when entering submenu
14        activationDelay:    0,      // Delay (ms) for first submenu opening
15        mouseLocsTracked:   3,        // Number of past mouse locations to track direction
16        defaultDelay:       700,      // Delay (ms) when user appears to be entering submenu
17        enterCallback:      $.noop,   // Function to call when mouse enters a menu row. Entering a row does not mean
18                                      // the row has been activated, as the user may be mousing over to a submenu.
19        activateCallback:   $.noop,   // Function to call when a row is purposefully activated. Use this
20                                      // to show a submenu's content for the activated row.
21        deactivateCallback: $.noop,   // Function to call when a row is deactivated.
22        exitCallback:       $.noop,   // Function to call when mouse exits a menu row.
23        exitMenuCallback:   $.noop    // Function to call when mouse exits whole menu - for autoclosing submenu
24      };
25  function Plugin( el, options ) {
26    this.el = el;
27    this.options = $.extend( {}, defaults, options);
28    this._defaults = defaults;
29    this._name = pluginName;
30    this.init();
31  }
32  Plugin.prototype = {
33    init: function () {
34      this.activeRow    = null,
35      this.mouseLocs    = [],
36      this.lastDelayLoc = null,
37      this.timeoutId    = null,
38      this.openDelayId  = null,
39      this.isOnClick    = $.inArray(this.options.triggerEvent, ['both', 'click']) > -1,
40      this.isOnHover    = $.inArray(this.options.triggerEvent, ['both', 'hover']) > -1;
41      /**
42       * Hook up initial menu events
43       */
44      if (this.isOnHover) {
45        this._hoverTriggerOn();
46      }
47      if (this.isOnClick) {
48        this._clickTriggerOn();
49      }
50    },
51    // Private methods start with underscore
52    /**
53     * Keep track of the last few locations of the mouse.
54     */
55    _mouseMoveDocument: function(e) {
56      obj = e.data.obj;
57      obj.mouseLocs.push({x: e.pageX, y: e.pageY});
58      if (obj.mouseLocs.length > obj.options.mouseLocsTracked) {
59        obj.mouseLocs.shift();
60      }
61    },
62    /**
63     * Cancel possible row activations when leaving the menu entirely
64     */
65    _mouseLeaveMenu: function(e) {
66      obj = e.data.obj;
67      if (obj.timeoutId) {
68        clearTimeout(obj.timeoutId);
69      }
70      if (obj.openDelayId) {
71        clearTimeout(obj.openDelayId);
72      }
73      obj._possiblyDeactivate(obj.activeRow);
74      obj.options.exitMenuCallback(this);
75    },
76    /**
77     * Trigger a possible row activation whenever entering a new row.
78     */
79    _mouseEnterRow: function(e) {
80      obj = e.data.obj;
81      if (obj.timeoutId) {
82        // Cancel any previous activation delays
83        clearTimeout(obj.timeoutId);
84      }
85      obj.options.enterCallback(this);
86      obj._possiblyActivate(this);
87    },
88    _mouseLeaveRow: function(e) {
89      e.data.obj.options.exitCallback(this);
90    },
91    /*
92     * Immediately activate a row if the user clicks on it.
93     */
94    _clickRow: function(e) {
95      obj = e.data.obj;
96      obj._activate(this);
97      // bind close event when submenu content is rendered
98      $(obj.el)
99        .find(obj.options.rowSelector)
100          .find(obj.options.handle)
101            .on('click', { obj: obj }, obj._clickRowHandle);
102    },
103    /*
104     * Close already opened submenu
105     */
106    _clickRowHandle: function(e) {
107      obj = e.data.obj;
108      if ($(this).closest('li').hasClass(obj.options.openClassName)) {
109        obj._deactivate();
110        e.stopPropagation();
111      }
112    },
113    /**
114     * Activate a menu row with possible delay
115     */
116    _activate: function(row) {
117      var obj = this;
118      if (row == this.activeRow) {
119        return;
120      }
121      if (this.openDelayId) {
122        clearTimeout(this.openDelayId);
123      }
124      // check if delay should be placed before activation
125      if (parseInt(obj.options.activationDelay, 0) > 0 && obj.isOnHover) {
126        if (obj.activeRow) {
127          obj._activateWithoutDelay(row);
128        } else {
129          this.openDelayId = setTimeout(function() {
130            obj._activateWithoutDelay(row);
131          },
132          obj.options.activationDelay);
133        }
134      } else {
135        obj._activateWithoutDelay(row);
136      }
137    },
138    _activateWithoutDelay: function(row) {
139      if (this.activeRow) {
140        this.options.deactivateCallback(this.activeRow);
141      }
142      this.options.activateCallback(row);
143      this.activeRow = row;
144    },
145    _deactivate: function() {
146      if (this.openDelayId) {
147        clearTimeout(this.openDelayId);
148      }
149      if (this.activeRow) {
150        this.options.deactivateCallback(this.activeRow);
151        this.activeRow = null;
152      }
153    },
154    /**
155     * Possibly activate a menu row. If mouse movement indicates that we
156     * shouldn't activate yet because user may be trying to enter
157     * a submenu's content, then delay and check again later.
158     */
159    _possiblyActivate: function(row) {
160      var delay = this._activationDelay(),
161          that  = this;
162      if (delay) {
163        this.timeoutId = setTimeout(function() {
164          that._possiblyActivate(row);
165        }, delay);
166      } else {
167        this._activate(row);
168      }
169    },
170    _possiblyDeactivate: function(row) {
171      var delay = this._activationDelay(),
172          that  = this;
173      if (delay) {
174        this.timeoutId = setTimeout(function() {
175          that._possiblyDeactivate(row);
176        }, delay)
177      } else {
178        this.options.deactivateCallback(row);
179        this.activeRow = null;
180      }
181    },
182    /**
183     * Return the amount of time that should be used as a delay before the
184     * currently hovered row is activated.
185     *
186     * Returns 0 if the activation should happen immediately. Otherwise,
187     * returns the number of milliseconds that should be delayed before
188     * checking again to see if the row should be activated.
189     */
190    _activationDelay: function() {
191      if (!this.activeRow || !$(this.activeRow).is(this.options.submenuSelector)) {
192        // If there is no other submenu row already active, then
193        // go ahead and activate immediately.
194        return 0;
195      }
196      var offset = $(this.el).offset(),
197          upperLeft = {
198            x: offset.left,
199            y: offset.top - this.options.tolerance
200          },
201          upperRight = {
202            x: offset.left + $(this.el).outerWidth(),
203            y: upperLeft.y
204          },
205          lowerLeft = {
206            x: offset.left,
207            y: offset.top + $(this.el).outerHeight() + this.options.tolerance
208          },
209          lowerRight = {
210            x: offset.left + $(this.el).outerWidth(),
211            y: lowerLeft.y
212          },
213          loc = this.mouseLocs[this.mouseLocs.length - 1],
214          prevLoc = this.mouseLocs[0];
215      if (!loc) {
216        return 0;
217      }
218      if (!prevLoc) {
219        prevLoc = loc;
220      }
221      if (prevLoc.x < offset.left || prevLoc.x > lowerRight.x ||
222          prevLoc.y < offset.top || prevLoc.y > lowerRight.y) {
223        // If the previous mouse location was outside of the entire
224        // menu's bounds, immediately activate.
225        return 0;
226      }
227      if (this.lastDelayLoc && loc.x == this.lastDelayLoc.x && loc.y == this.lastDelayLoc.y) {
228        // If the mouse hasn't moved since the last time we checked
229        // for activation status, immediately activate.
230        return 0;
231      }
232      // Detect if the user is moving towards the currently activated
233      // submenu.
234      //
235      // If the mouse is heading relatively clearly towards
236      // the submenu's content, we should wait and give the user more
237      // time before activating a new row. If the mouse is heading
238      // elsewhere, we can immediately activate a new row.
239      //
240      // We detect this by calculating the slope formed between the
241      // current mouse location and the upper/lower right points of
242      // the menu. We do the same for the previous mouse location.
243      // If the current mouse location's slopes are
244      // increasing/decreasing appropriately compared to the
245      // previous's, we know the user is moving toward the submenu.
246      //
247      // Note that since the y-axis increases as the cursor moves
248      // down the screen, we are looking for the slope between the
249      // cursor and the upper right corner to decrease over time, not
250      // increase (somewhat counterintuitively).
251      function slope(a, b) {
252        return (b.y - a.y) / (b.x - a.x);
253      };
254      var decreasingCorner = upperRight,
255          increasingCorner = lowerRight;
256      // Our expectations for decreasing or increasing slope values
257      // depends on which direction the submenu opens relative to the
258      // main menu. By default, if the menu opens on the right, we
259      // expect the slope between the cursor and the upper right
260      // corner to decrease over time, as explained above. If the
261      // submenu opens in a different direction, we change our slope
262      // expectations.
263      if (this.options.submenuDirection == "left") {
264        decreasingCorner = lowerLeft;
265        increasingCorner = upperLeft;
266      } else if (this.options.submenuDirection == "below") {
267        decreasingCorner = lowerRight;
268        increasingCorner = lowerLeft;
269      } else if (this.options.submenuDirection == "above") {
270        decreasingCorner = upperLeft;
271        increasingCorner = upperRight;
272      }
273      var decreasingSlope = slope(loc, decreasingCorner),
274          increasingSlope = slope(loc, increasingCorner),
275          prevDecreasingSlope = slope(prevLoc, decreasingCorner),
276          prevIncreasingSlope = slope(prevLoc, increasingCorner);
277      if (decreasingSlope < prevDecreasingSlope && increasingSlope > prevIncreasingSlope) {
278        // Mouse is moving from previous location towards the
279        // currently activated submenu. Delay before activating a
280        // new menu row, because user may be moving into submenu.
281        this.lastDelayLoc = loc;
282        return this.options.defaultDelay;
283      }
284      this.lastDelayLoc = null;
285      return 0;
286    },
287    // if the target isn't the container nor a descendant of the container
288    _outsideMenuClick: function(e) {
289      var obj = e.data.obj;
290      if ($(obj.el).not(e.target) && $(obj.el).has(e.target).length === 0) {
291        obj.options.deactivateCallback(obj.activeRow);
292        obj.activeRow = null;
293      }
294    },
295    _hoverTriggerOn: function() {
296      $(this.el).on('mouseleave', { obj: this}, this._mouseLeaveMenu )
297        .find(this.options.rowSelector)
298          .on('mouseenter', { obj: this}, this._mouseEnterRow)
299          .on('mouseleave', { obj: this}, this._mouseLeaveRow);
300      $(window).on('blur', { obj: this }, this._mouseLeaveMenu);
301      $(document).on('mousemove', { obj: this }, this._mouseMoveDocument);
302    },
303    _hoverTriggerOff: function() {
304      $(this.el).off('mouseleave', this._mouseLeaveMenu)
305        .find(this.options.rowSelector)
306          .off('mouseenter', this._mouseEnterRow)
307          .off('mouseleave', this._mouseLeaveRow)
308      $(window).off('blur', this._mouseLeaveMenu);
309      $(document).off('mousemove', { obj: this }, this._mouseMoveDocument);
310    },
311    _clickTriggerOn: function() {
312      $(this.el).find(this.options.rowSelector)
313        .on('click', { obj: this }, this._clickRow);
314      // hide menu if clicked elsewere
315      $(document).on('click', { obj: this }, this._outsideMenuClick);
316    },
317    _clickTriggerOff: function() {
318      $(this.el)
319        .find(this.options.rowSelector)
320          .off('click', this._clickRow);
321      $(document).off('click', this._outsideMenuClick);
322    },
323    // Public methods
324    switchToHover: function() {
325      this._clickTriggerOff();
326      this._hoverTriggerOn();
327      this.isOnHover = true;
328      this.isOnClick = false;
329    },
330    switchToClick: function() {
331      this._hoverTriggerOff();
332      this._clickTriggerOn();
333      this.isOnHover = false;
334      this.isOnClick = true;
335    }
336  };
337  // A lightweight plugin wrapper around the constructor,
338  // preventing against multiple instantiations and allowing any
339  // public function (ie. a function whose name doesn't start
340  // with an underscore) to be called via the jQuery plugin,
341  // e.g. $(element).defaultPluginName('functionName', arg1, arg2)
342  $.fn[pluginName] = function ( options ) {
343    var args = arguments;
344    // Is the first parameter an object (options), or was omitted,
345    // instantiate a new instance of the plugin.
346    if (options === undefined || typeof options === 'object') {
347      return this.each(function () {
348        // Only allow the plugin to be instantiated once,
349        // so we check that the element has no plugin instantiation yet
350        if (!$.data(this, 'plugin_' + pluginName)) {
351          // if it has no instance, create a new one,
352          // pass options to our plugin constructor,
353          // and store the plugin instance
354          // in the elements jQuery data object.
355          $.data(this, 'plugin_' + pluginName, new Plugin( this, options ));
356        }
357      });
358    // If the first parameter is a string and it doesn't start
359    // with an underscore or "contains" the `init`-function,
360    // treat this as a call to a public method.
361    } else if (typeof options === 'string' && options[0] !== '_' && options !== 'init') {
362      // Cache the method call
363      // to make it possible
364      // to return a value
365      var returns;
366      this.each(function () {
367        var instance = $.data(this, 'plugin_' + pluginName);
368        // Tests that there's already a plugin-instance
369        // and checks that the requested public method exists
370        if (instance instanceof Plugin && typeof instance[options] === 'function') {
371          // Call the method of our plugin instance,
372          // and pass it the supplied arguments.
373          returns = instance[options].apply( instance, Array.prototype.slice.call( args, 1 ) );
374        }
375        // Allow instances to be destroyed via the 'destroy' method
376        if (options === 'destroy') {
377			$.data(this, 'plugin_' + pluginName, null);
378        }
379      });
380      // If the earlier cached method
381      // gives a value back return the value,
382      // otherwise return this to preserve chainability.
383      return returns !== undefined ? returns : this;
384    }
385  };
386}(jQuery, window, document));

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.