smock 0.1.213 → 0.1.214
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/Gemfile.lock +1 -1
- data/Rakefile +0 -2
- data/app/assets/javascripts/core/namespace.coffee +1 -0
- data/app/assets/javascripts/modules/category_dropdown.coffee +39 -0
- data/app/assets/javascripts/smock.coffee +2 -1
- data/app/assets/javascripts/vendor/jquery.js +10346 -0
- data/app/assets/javascripts/vendor/jquery.menuaim.js +323 -0
- data/app/assets/stylesheets/modules/_categories_dropdown.sass +3 -2
- data/examples/pages/homepage.html +19 -16
- data/package.json +1 -1
- data/webpack.config.js +11 -3
- metadata +8 -5
- data/webpack.config.min.js +0 -18
@@ -0,0 +1,323 @@
|
|
1
|
+
/**
|
2
|
+
* menu-aim is a jQuery plugin for dropdown menus that can differentiate
|
3
|
+
* between a user trying hover over a dropdown item vs trying to navigate into
|
4
|
+
* a submenu's contents.
|
5
|
+
*
|
6
|
+
* menu-aim assumes that you have are using a menu with submenus that expand
|
7
|
+
* to the menu's right. It will fire events when the user's mouse enters a new
|
8
|
+
* dropdown item *and* when that item is being intentionally hovered over.
|
9
|
+
*
|
10
|
+
* __________________________
|
11
|
+
* | Monkeys >| Gorilla |
|
12
|
+
* | Gorillas >| Content |
|
13
|
+
* | Chimps >| Here |
|
14
|
+
* |___________|____________|
|
15
|
+
*
|
16
|
+
* In the above example, "Gorillas" is selected and its submenu content is
|
17
|
+
* being shown on the right. Imagine that the user's cursor is hovering over
|
18
|
+
* "Gorillas." When they move their mouse into the "Gorilla Content" area, they
|
19
|
+
* may briefly hover over "Chimps." This shouldn't close the "Gorilla Content"
|
20
|
+
* area.
|
21
|
+
*
|
22
|
+
* This problem is normally solved using timeouts and delays. menu-aim tries to
|
23
|
+
* solve this by detecting the direction of the user's mouse movement. This can
|
24
|
+
* make for quicker transitions when navigating up and down the menu. The
|
25
|
+
* experience is hopefully similar to amazon.com/'s "Shop by Department"
|
26
|
+
* dropdown.
|
27
|
+
*
|
28
|
+
* Use like so:
|
29
|
+
*
|
30
|
+
* $("#menu").menuAim({
|
31
|
+
* activate: $.noop, // fired on row activation
|
32
|
+
* deactivate: $.noop // fired on row deactivation
|
33
|
+
* });
|
34
|
+
*
|
35
|
+
* ...to receive events when a menu's row has been purposefully (de)activated.
|
36
|
+
*
|
37
|
+
* The following options can be passed to menuAim. All functions execute with
|
38
|
+
* the relevant row's HTML element as the execution context ('this'):
|
39
|
+
*
|
40
|
+
* .menuAim({
|
41
|
+
* // Function to call when a row is purposefully activated. Use this
|
42
|
+
* // to show a submenu's content for the activated row.
|
43
|
+
* activate: function() {},
|
44
|
+
*
|
45
|
+
* // Function to call when a row is deactivated.
|
46
|
+
* deactivate: function() {},
|
47
|
+
*
|
48
|
+
* // Function to call when mouse enters a menu row. Entering a row
|
49
|
+
* // does not mean the row has been activated, as the user may be
|
50
|
+
* // mousing over to a submenu.
|
51
|
+
* enter: function() {},
|
52
|
+
*
|
53
|
+
* // Function to call when mouse exits a menu row.
|
54
|
+
* exit: function() {},
|
55
|
+
*
|
56
|
+
* // Selector for identifying which elements in the menu are rows
|
57
|
+
* // that can trigger the above events. Defaults to "> li".
|
58
|
+
* rowSelector: "> li",
|
59
|
+
*
|
60
|
+
* // You may have some menu rows that aren't submenus and therefore
|
61
|
+
* // shouldn't ever need to "activate." If so, filter submenu rows w/
|
62
|
+
* // this selector. Defaults to "*" (all elements).
|
63
|
+
* submenuSelector: "*",
|
64
|
+
*
|
65
|
+
* // Direction the submenu opens relative to the main menu. Can be
|
66
|
+
* // left, right, above, or below. Defaults to "right".
|
67
|
+
* submenuDirection: "right"
|
68
|
+
* });
|
69
|
+
*
|
70
|
+
* https://github.com/kamens/jQuery-menu-aim
|
71
|
+
*/
|
72
|
+
(function($) {
|
73
|
+
|
74
|
+
$.fn.menuAim = function(opts) {
|
75
|
+
// Initialize menu-aim for all elements in jQuery collection
|
76
|
+
this.each(function() {
|
77
|
+
init.call(this, opts);
|
78
|
+
});
|
79
|
+
|
80
|
+
return this;
|
81
|
+
};
|
82
|
+
|
83
|
+
function init(opts) {
|
84
|
+
var $menu = $(this),
|
85
|
+
activeRow = null,
|
86
|
+
mouseLocs = [],
|
87
|
+
lastDelayLoc = null,
|
88
|
+
timeoutId = null,
|
89
|
+
options = $.extend({
|
90
|
+
rowSelector: "> li",
|
91
|
+
submenuSelector: "*",
|
92
|
+
submenuDirection: "right",
|
93
|
+
tolerance: 75, // bigger = more forgivey when entering submenu
|
94
|
+
enter: $.noop,
|
95
|
+
exit: $.noop,
|
96
|
+
activate: $.noop,
|
97
|
+
deactivate: $.noop,
|
98
|
+
exitMenu: $.noop
|
99
|
+
}, opts);
|
100
|
+
|
101
|
+
var MOUSE_LOCS_TRACKED = 3, // number of past mouse locations to track
|
102
|
+
DELAY = 300; // ms delay when user appears to be entering submenu
|
103
|
+
|
104
|
+
/**
|
105
|
+
* Keep track of the last few locations of the mouse.
|
106
|
+
*/
|
107
|
+
var mousemoveDocument = function(e) {
|
108
|
+
mouseLocs.push({x: e.pageX, y: e.pageY});
|
109
|
+
|
110
|
+
if (mouseLocs.length > MOUSE_LOCS_TRACKED) {
|
111
|
+
mouseLocs.shift();
|
112
|
+
}
|
113
|
+
};
|
114
|
+
|
115
|
+
/**
|
116
|
+
* Cancel possible row activations when leaving the menu entirely
|
117
|
+
*/
|
118
|
+
var mouseleaveMenu = function() {
|
119
|
+
if (timeoutId) {
|
120
|
+
clearTimeout(timeoutId);
|
121
|
+
}
|
122
|
+
|
123
|
+
// If exitMenu is supplied and returns true, deactivate the
|
124
|
+
// currently active row on menu exit.
|
125
|
+
if (options.exitMenu(this)) {
|
126
|
+
if (activeRow) {
|
127
|
+
options.deactivate(activeRow);
|
128
|
+
}
|
129
|
+
|
130
|
+
activeRow = null;
|
131
|
+
}
|
132
|
+
};
|
133
|
+
|
134
|
+
/**
|
135
|
+
* Trigger a possible row activation whenever entering a new row.
|
136
|
+
*/
|
137
|
+
var mouseenterRow = function() {
|
138
|
+
if (timeoutId) {
|
139
|
+
// Cancel any previous activation delays
|
140
|
+
clearTimeout(timeoutId);
|
141
|
+
}
|
142
|
+
|
143
|
+
options.enter(this);
|
144
|
+
possiblyActivate(this);
|
145
|
+
},
|
146
|
+
mouseleaveRow = function() {
|
147
|
+
options.exit(this);
|
148
|
+
};
|
149
|
+
|
150
|
+
/*
|
151
|
+
* Immediately activate a row if the user clicks on it.
|
152
|
+
*/
|
153
|
+
var clickRow = function() {
|
154
|
+
activate(this);
|
155
|
+
};
|
156
|
+
|
157
|
+
/**
|
158
|
+
* Activate a menu row.
|
159
|
+
*/
|
160
|
+
var activate = function(row) {
|
161
|
+
if (row == activeRow) {
|
162
|
+
return;
|
163
|
+
}
|
164
|
+
|
165
|
+
if (activeRow) {
|
166
|
+
options.deactivate(activeRow);
|
167
|
+
}
|
168
|
+
|
169
|
+
options.activate(row);
|
170
|
+
activeRow = row;
|
171
|
+
};
|
172
|
+
|
173
|
+
/**
|
174
|
+
* Possibly activate a menu row. If mouse movement indicates that we
|
175
|
+
* shouldn't activate yet because user may be trying to enter
|
176
|
+
* a submenu's content, then delay and check again later.
|
177
|
+
*/
|
178
|
+
var possiblyActivate = function(row) {
|
179
|
+
var delay = activationDelay();
|
180
|
+
|
181
|
+
if (delay) {
|
182
|
+
timeoutId = setTimeout(function() {
|
183
|
+
possiblyActivate(row);
|
184
|
+
}, delay);
|
185
|
+
} else {
|
186
|
+
activate(row);
|
187
|
+
}
|
188
|
+
};
|
189
|
+
|
190
|
+
/**
|
191
|
+
* Return the amount of time that should be used as a delay before the
|
192
|
+
* currently hovered row is activated.
|
193
|
+
*
|
194
|
+
* Returns 0 if the activation should happen immediately. Otherwise,
|
195
|
+
* returns the number of milliseconds that should be delayed before
|
196
|
+
* checking again to see if the row should be activated.
|
197
|
+
*/
|
198
|
+
var activationDelay = function() {
|
199
|
+
if (!activeRow || !$(activeRow).is(options.submenuSelector)) {
|
200
|
+
// If there is no other submenu row already active, then
|
201
|
+
// go ahead and activate immediately.
|
202
|
+
return 0;
|
203
|
+
}
|
204
|
+
|
205
|
+
var offset = $menu.offset(),
|
206
|
+
upperLeft = {
|
207
|
+
x: offset.left,
|
208
|
+
y: offset.top - options.tolerance
|
209
|
+
},
|
210
|
+
upperRight = {
|
211
|
+
x: offset.left + $menu.outerWidth(),
|
212
|
+
y: upperLeft.y
|
213
|
+
},
|
214
|
+
lowerLeft = {
|
215
|
+
x: offset.left,
|
216
|
+
y: offset.top + $menu.outerHeight() + options.tolerance
|
217
|
+
},
|
218
|
+
lowerRight = {
|
219
|
+
x: offset.left + $menu.outerWidth(),
|
220
|
+
y: lowerLeft.y
|
221
|
+
},
|
222
|
+
loc = mouseLocs[mouseLocs.length - 1],
|
223
|
+
prevLoc = mouseLocs[0];
|
224
|
+
|
225
|
+
if (!loc) {
|
226
|
+
return 0;
|
227
|
+
}
|
228
|
+
|
229
|
+
if (!prevLoc) {
|
230
|
+
prevLoc = loc;
|
231
|
+
}
|
232
|
+
|
233
|
+
if (prevLoc.x < offset.left || prevLoc.x > lowerRight.x ||
|
234
|
+
prevLoc.y < offset.top || prevLoc.y > lowerRight.y) {
|
235
|
+
// If the previous mouse location was outside of the entire
|
236
|
+
// menu's bounds, immediately activate.
|
237
|
+
return 0;
|
238
|
+
}
|
239
|
+
|
240
|
+
if (lastDelayLoc &&
|
241
|
+
loc.x == lastDelayLoc.x && loc.y == lastDelayLoc.y) {
|
242
|
+
// If the mouse hasn't moved since the last time we checked
|
243
|
+
// for activation status, immediately activate.
|
244
|
+
return 0;
|
245
|
+
}
|
246
|
+
|
247
|
+
// Detect if the user is moving towards the currently activated
|
248
|
+
// submenu.
|
249
|
+
//
|
250
|
+
// If the mouse is heading relatively clearly towards
|
251
|
+
// the submenu's content, we should wait and give the user more
|
252
|
+
// time before activating a new row. If the mouse is heading
|
253
|
+
// elsewhere, we can immediately activate a new row.
|
254
|
+
//
|
255
|
+
// We detect this by calculating the slope formed between the
|
256
|
+
// current mouse location and the upper/lower right points of
|
257
|
+
// the menu. We do the same for the previous mouse location.
|
258
|
+
// If the current mouse location's slopes are
|
259
|
+
// increasing/decreasing appropriately compared to the
|
260
|
+
// previous's, we know the user is moving toward the submenu.
|
261
|
+
//
|
262
|
+
// Note that since the y-axis increases as the cursor moves
|
263
|
+
// down the screen, we are looking for the slope between the
|
264
|
+
// cursor and the upper right corner to decrease over time, not
|
265
|
+
// increase (somewhat counterintuitively).
|
266
|
+
function slope(a, b) {
|
267
|
+
return (b.y - a.y) / (b.x - a.x);
|
268
|
+
};
|
269
|
+
|
270
|
+
var decreasingCorner = upperRight,
|
271
|
+
increasingCorner = lowerRight;
|
272
|
+
|
273
|
+
// Our expectations for decreasing or increasing slope values
|
274
|
+
// depends on which direction the submenu opens relative to the
|
275
|
+
// main menu. By default, if the menu opens on the right, we
|
276
|
+
// expect the slope between the cursor and the upper right
|
277
|
+
// corner to decrease over time, as explained above. If the
|
278
|
+
// submenu opens in a different direction, we change our slope
|
279
|
+
// expectations.
|
280
|
+
if (options.submenuDirection == "left") {
|
281
|
+
decreasingCorner = lowerLeft;
|
282
|
+
increasingCorner = upperLeft;
|
283
|
+
} else if (options.submenuDirection == "below") {
|
284
|
+
decreasingCorner = lowerRight;
|
285
|
+
increasingCorner = lowerLeft;
|
286
|
+
} else if (options.submenuDirection == "above") {
|
287
|
+
decreasingCorner = upperLeft;
|
288
|
+
increasingCorner = upperRight;
|
289
|
+
}
|
290
|
+
|
291
|
+
var decreasingSlope = slope(loc, decreasingCorner),
|
292
|
+
increasingSlope = slope(loc, increasingCorner),
|
293
|
+
prevDecreasingSlope = slope(prevLoc, decreasingCorner),
|
294
|
+
prevIncreasingSlope = slope(prevLoc, increasingCorner);
|
295
|
+
|
296
|
+
if (decreasingSlope < prevDecreasingSlope &&
|
297
|
+
increasingSlope > prevIncreasingSlope) {
|
298
|
+
// Mouse is moving from previous location towards the
|
299
|
+
// currently activated submenu. Delay before activating a
|
300
|
+
// new menu row, because user may be moving into submenu.
|
301
|
+
lastDelayLoc = loc;
|
302
|
+
return DELAY;
|
303
|
+
}
|
304
|
+
|
305
|
+
lastDelayLoc = null;
|
306
|
+
return 0;
|
307
|
+
};
|
308
|
+
|
309
|
+
/**
|
310
|
+
* Hook up initial menu events
|
311
|
+
*/
|
312
|
+
$menu
|
313
|
+
.mouseleave(mouseleaveMenu)
|
314
|
+
.find(options.rowSelector)
|
315
|
+
.mouseenter(mouseenterRow)
|
316
|
+
.mouseleave(mouseleaveRow)
|
317
|
+
.click(clickRow);
|
318
|
+
|
319
|
+
$(document).mousemove(mousemoveDocument);
|
320
|
+
|
321
|
+
};
|
322
|
+
})(jQuery);
|
323
|
+
|
@@ -21,8 +21,8 @@
|
|
21
21
|
overflow: hidden
|
22
22
|
+transition( height 0.05s ease-in)
|
23
23
|
|
24
|
-
.category-
|
25
|
-
|
24
|
+
.category-dropdown__categories--expanded
|
25
|
+
width: 590px
|
26
26
|
|
27
27
|
.category-dropdown__categories__list
|
28
28
|
+space(min-height, 9)
|
@@ -47,3 +47,4 @@
|
|
47
47
|
+space(top)
|
48
48
|
right: 0
|
49
49
|
width: 45%
|
50
|
+
height: 100%
|
@@ -34,9 +34,9 @@
|
|
34
34
|
<a class="category-dropdown__control th-double-line th-inverse" href="#">Browse Categories</a>
|
35
35
|
<div class="category-dropdown__categories th-size-normal ch-white-background">
|
36
36
|
<ul class="category-dropdown__categories__list">
|
37
|
-
<li>
|
37
|
+
<li data-submenu-id="1">
|
38
38
|
<a href="#" class="th-size-medium th-double-line">Business & Marketing</a>
|
39
|
-
<ul class="category-dropdown__categories__item__subcategories show-me">
|
39
|
+
<ul id="1" class="category-dropdown__categories__item__subcategories show-me">
|
40
40
|
<li><a href="#">Inforgraphic Design</a></li>
|
41
41
|
<li><a href="#">Caricatures & Cartoon Design</a></li>
|
42
42
|
<li><a href="#">eBooks & Digital Publishing</a></li>
|
@@ -44,57 +44,57 @@
|
|
44
44
|
<li><a href="#">Website Customization & Updates</a></li>
|
45
45
|
</ul>
|
46
46
|
</li>
|
47
|
-
<li
|
47
|
+
<li data-submenu-id="2">
|
48
48
|
<a href="#" class="th-size-medium th-double-line">Mobile & Apps</a>
|
49
|
-
<ul class="category-dropdown__categories__item__subcategories">
|
49
|
+
<ul id="2" class="category-dropdown__categories__item__subcategories">
|
50
50
|
<li class="category-dropdown__categories__item__subcategories__item"><a href="#">Inforgraphic Design</a></li>
|
51
51
|
<li class="category-dropdown__categories__item__subcategories__item"><a href="#">Inforgraphic Design</a></li>
|
52
52
|
<li class="category-dropdown__categories__item__subcategories__item"><a href="#">Inforgraphic Design</a></li>
|
53
53
|
</ul>
|
54
54
|
</li>
|
55
|
-
<li
|
55
|
+
<li data-submenu-id="3">
|
56
56
|
<a href="#" class="th-size-medium th-double-line">Ecommerce & CMS Development</a>
|
57
|
-
<ul class="category-dropdown__categories__item__subcategories">
|
57
|
+
<ul id="3" class="category-dropdown__categories__item__subcategories">
|
58
58
|
<li class="category-dropdown__categories__item__subcategories__item"><a href="#">Inforgraphic Design</a></li>
|
59
59
|
<li class="category-dropdown__categories__item__subcategories__item"><a href="#">Inforgraphic Design</a></li>
|
60
60
|
<li class="category-dropdown__categories__item__subcategories__item"><a href="#">Inforgraphic Design</a></li>
|
61
61
|
</ul>
|
62
62
|
</li>
|
63
|
-
<li
|
63
|
+
<li data-submenu-id="4">
|
64
64
|
<a href="#" class="th-size-medium th-double-line">Websites & Programming</a>
|
65
|
-
<ul class="category-dropdown__categories__item__subcategories">
|
65
|
+
<ul id="4" class="category-dropdown__categories__item__subcategories">
|
66
66
|
<li class="category-dropdown__categories__item__subcategories__item"><a href="#">Inforgraphic Design</a></li>
|
67
67
|
<li class="category-dropdown__categories__item__subcategories__item"><a href="#">Inforgraphic Design</a></li>
|
68
68
|
<li class="category-dropdown__categories__item__subcategories__item"><a href="#">Inforgraphic Design</a></li>
|
69
69
|
</ul>
|
70
70
|
</li>
|
71
|
-
<li
|
71
|
+
<li data-submenu-id="5">
|
72
72
|
<a href="#" class="th-size-medium th-double-line">Design & Graphics</a>
|
73
|
-
<ul class="category-dropdown__categories__item__subcategories">
|
73
|
+
<ul id="5" class="category-dropdown__categories__item__subcategories">
|
74
74
|
<li class="category-dropdown__categories__item__subcategories__item"><a href="#">Inforgraphic Design</a></li>
|
75
75
|
<li class="category-dropdown__categories__item__subcategories__item"><a href="#">Inforgraphic Design</a></li>
|
76
76
|
<li class="category-dropdown__categories__item__subcategories__item"><a href="#">Inforgraphic Design</a></li>
|
77
77
|
</ul>
|
78
78
|
</li>
|
79
|
-
<li
|
79
|
+
<li data-submenu-id="6">
|
80
80
|
<a href="#" class="th-size-medium th-double-line">Logos & Branding</a>
|
81
|
-
<ul class="category-dropdown__categories__item__subcategories">
|
81
|
+
<ul id="6" class="category-dropdown__categories__item__subcategories">
|
82
82
|
<li class="category-dropdown__categories__item__subcategories__item"><a href="#">Inforgraphic Design</a></li>
|
83
83
|
<li class="category-dropdown__categories__item__subcategories__item"><a href="#">Inforgraphic Design</a></li>
|
84
84
|
<li class="category-dropdown__categories__item__subcategories__item"><a href="#">Inforgraphic Design</a></li>
|
85
85
|
</ul>
|
86
86
|
</li>
|
87
|
-
<li
|
87
|
+
<li data-submenu-id="7">
|
88
88
|
<a href="#" class="th-size-medium th-double-line">WordPress</a>
|
89
|
-
<ul class="category-dropdown__categories__item__subcategories">
|
89
|
+
<ul id="7" class="category-dropdown__categories__item__subcategories">
|
90
90
|
<li class="category-dropdown__categories__item__subcategories__item"><a href="#">Inforgraphic Design</a></li>
|
91
91
|
<li class="category-dropdown__categories__item__subcategories__item"><a href="#">Inforgraphic Design</a></li>
|
92
92
|
<li class="category-dropdown__categories__item__subcategories__item"><a href="#">Inforgraphic Design</a></li>
|
93
93
|
</ul>
|
94
94
|
</li>
|
95
|
-
<li
|
95
|
+
<li data-submenu-id="8">
|
96
96
|
<a href="#" class="th-size-medium th-double-line">Video & Animation</a>
|
97
|
-
<ul class="category-dropdown__categories__item__subcategories">
|
97
|
+
<ul id="8" class="category-dropdown__categories__item__subcategories">
|
98
98
|
<li class="category-dropdown__categories__item__subcategories__item"><a href="#">Inforgraphic Design</a></li>
|
99
99
|
<li class="category-dropdown__categories__item__subcategories__item"><a href="#">Inforgraphic Design</a></li>
|
100
100
|
<li class="category-dropdown__categories__item__subcategories__item"><a href="#">Inforgraphic Design</a></li>
|
@@ -578,5 +578,8 @@
|
|
578
578
|
</footer>
|
579
579
|
|
580
580
|
<script src="../../smock.js"></script>
|
581
|
+
<script type="text/javascript" charset="utf-8">
|
582
|
+
var dropdown = new Smock.CategoryDropdown().ready();
|
583
|
+
</script>
|
581
584
|
</body>
|
582
585
|
</html>
|
data/package.json
CHANGED
data/webpack.config.js
CHANGED
@@ -2,15 +2,23 @@ var webpack = require("webpack");
|
|
2
2
|
var path = require("path");
|
3
3
|
|
4
4
|
module.exports = {
|
5
|
-
entry:
|
5
|
+
entry: {
|
6
|
+
"smock": "./app/assets/javascripts/smock.coffee"
|
7
|
+
},
|
6
8
|
devtool: "source-map",
|
7
9
|
output: {
|
8
|
-
filename: "
|
10
|
+
filename: "[name].js"
|
9
11
|
},
|
10
12
|
module: {
|
11
13
|
loaders: [
|
12
14
|
{ test: /\.coffee$/, loader: "coffee-loader" },
|
13
15
|
]
|
14
|
-
}
|
16
|
+
},
|
17
|
+
plugins: [
|
18
|
+
new webpack.ProvidePlugin({
|
19
|
+
"$": "./jquery.js",
|
20
|
+
"jQuery": "./jquery.js"
|
21
|
+
})
|
22
|
+
]
|
15
23
|
};
|
16
24
|
|