|
1 /* |
|
2 YUI 3.10.3 (build 2fb5187) |
|
3 Copyright 2013 Yahoo! Inc. All rights reserved. |
|
4 Licensed under the BSD License. |
|
5 http://yuilibrary.com/license/ |
|
6 */ |
|
7 |
|
8 YUI.add('event-contextmenu', function (Y, NAME) { |
|
9 |
|
10 /** |
|
11 * Provides extended keyboard support for the "contextmenu" event such that: |
|
12 * <ul> |
|
13 * <li>The browser's default context menu is suppressed regardless of how the event is triggered.</li> |
|
14 * <li>On Windows the "contextmenu" event is fired consistently regardless of whether the user |
|
15 * pressed the Menu key or Shift + F10.</li> |
|
16 * <li>When the "contextmenu" event is fired via the keyboard, the pageX, pageY, clientX and clientY |
|
17 * properties reference the center of the event target. This makes it easy for "contextmenu" event listeners |
|
18 * to position an overlay in response to the event by not having to worry about special handling of the x |
|
19 * and y coordinates based on the device that fired the event.</li> |
|
20 * <li>For Webkit and Gecko on the Mac it enables the use of the Shift + Control + Option + M keyboard |
|
21 * shortcut to fire the "contextmenu" event, which (by default) is only available when VoiceOver |
|
22 * (the screen reader on the Mac) is enabled.</li> |
|
23 * <li>For Opera on the Mac it ensures the "contextmenu" event is fired when the user presses |
|
24 * Shift + Command + M (Opera's context menu keyboard shortcut).</li> |
|
25 * </ul> |
|
26 * @module event-contextmenu |
|
27 * @requires event |
|
28 */ |
|
29 |
|
30 var Event = Y.Event, |
|
31 DOM = Y.DOM, |
|
32 UA = Y.UA, |
|
33 OS = Y.UA.os, |
|
34 |
|
35 ie = UA.ie, |
|
36 gecko = UA.gecko, |
|
37 webkit = UA.webkit, |
|
38 opera = UA.opera, |
|
39 |
|
40 isWin = (OS === "windows"), |
|
41 isMac = (OS === "macintosh"), |
|
42 |
|
43 eventData = {}, |
|
44 |
|
45 conf = { |
|
46 |
|
47 on: function (node, subscription, notifier, filter) { |
|
48 |
|
49 var handles = []; |
|
50 |
|
51 handles.push(Event._attach(["contextmenu", function (e) { |
|
52 |
|
53 // Any developer listening for the "contextmenu" event is likely |
|
54 // going to call preventDefault() to prevent the display of |
|
55 // the browser's context menu. So, you know, save them a step. |
|
56 e.preventDefault(); |
|
57 |
|
58 var id = Y.stamp(node), |
|
59 data = eventData[id]; |
|
60 |
|
61 if (data) { |
|
62 e.clientX = data.clientX; |
|
63 e.clientY = data.clientY; |
|
64 e.pageX = data.pageX; |
|
65 e.pageY = data.pageY; |
|
66 delete eventData[id]; |
|
67 } |
|
68 |
|
69 notifier.fire(e); |
|
70 |
|
71 }, node])); |
|
72 |
|
73 |
|
74 handles.push(node[filter ? "delegate" : "on"]("keydown", function (e) { |
|
75 |
|
76 var target = this.getDOMNode(), |
|
77 shiftKey = e.shiftKey, |
|
78 keyCode = e.keyCode, |
|
79 shiftF10 = (shiftKey && keyCode == 121), |
|
80 menuKey = (isWin && keyCode == 93), |
|
81 ctrlKey = e.ctrlKey, |
|
82 mKey = (keyCode === 77), |
|
83 macWebkitAndGeckoShortcut = (isMac && (webkit || gecko) && ctrlKey && shiftKey && e.altKey && mKey), |
|
84 |
|
85 // Note: The context menu keyboard shortcut for Opera on the Mac is Shift + Cmd (metaKey) + M, |
|
86 // but e.metaKey is false for Opera, and Opera sets e.ctrlKey to true instead. |
|
87 macOperaShortcut = (isMac && opera && ctrlKey && shiftKey && mKey), |
|
88 |
|
89 clientX = 0, |
|
90 clientY = 0, |
|
91 scrollX, |
|
92 scrollY, |
|
93 pageX, |
|
94 pageY, |
|
95 xy, |
|
96 x, |
|
97 y; |
|
98 |
|
99 |
|
100 if ((isWin && (shiftF10 || menuKey)) || |
|
101 (macWebkitAndGeckoShortcut || macOperaShortcut)) { |
|
102 |
|
103 // Need to call preventDefault() here b/c: |
|
104 // 1) To prevent IE's menubar from gaining focus when the |
|
105 // user presses Shift + F10 |
|
106 // 2) In Firefox and Opera for Win, Shift + F10 will display a |
|
107 // context menu, but won't fire the "contextmenu" event. So, need |
|
108 // to call preventDefault() to prevent the display of the |
|
109 // browser's context menu |
|
110 // 3) For Opera on the Mac the context menu keyboard shortcut |
|
111 // (Shift + Cmd + M) will display a context menu, but like Firefox |
|
112 // and Opera on windows, Opera doesn't fire a "contextmenu" event, |
|
113 // so preventDefault() is just used to supress Opera's |
|
114 // default context menu. |
|
115 if (((ie || (isWin && (gecko || opera))) && shiftF10) || macOperaShortcut) { |
|
116 e.preventDefault(); |
|
117 } |
|
118 |
|
119 xy = DOM.getXY(target); |
|
120 x = xy[0]; |
|
121 y = xy[1]; |
|
122 scrollX = DOM.docScrollX(); |
|
123 scrollY = DOM.docScrollY(); |
|
124 |
|
125 // Protect against instances where xy and might not be returned, |
|
126 // for example if the target is the document. |
|
127 if (!Y.Lang.isUndefined(x)) { |
|
128 clientX = (x + (target.offsetWidth/2)) - scrollX; |
|
129 clientY = (y + (target.offsetHeight/2)) - scrollY; |
|
130 } |
|
131 |
|
132 pageX = clientX + scrollX; |
|
133 pageY = clientY + scrollY; |
|
134 |
|
135 // When the "contextmenu" event is fired from the keyboard |
|
136 // clientX, clientY, pageX or pageY aren't set to useful |
|
137 // values. So, we follow Safari's model here of setting |
|
138 // the x & x coords to the center of the event target. |
|
139 |
|
140 if (menuKey || (isWin && webkit && shiftF10)) { |
|
141 eventData[Y.stamp(node)] = { |
|
142 clientX: clientX, |
|
143 clientY: clientY, |
|
144 pageX: pageX, |
|
145 pageY: pageY |
|
146 }; |
|
147 } |
|
148 |
|
149 // Don't need to call notifier.fire(e) when the Menu key |
|
150 // is pressed as it fires the "contextmenu" event by default. |
|
151 // |
|
152 // In IE the call to preventDefault() for Shift + F10 |
|
153 // prevents the "contextmenu" event from firing, so we need |
|
154 // to call notifier.fire(e) |
|
155 // |
|
156 // Need to also call notifier.fire(e) for Gecko and Opera since |
|
157 // neither Shift + F10 or Shift + Cmd + M fire the "contextmenu" event. |
|
158 // |
|
159 // Lastly, also need to call notifier.fire(e) for all Mac browsers |
|
160 // since neither Shift + Ctrl + Option + M (Webkit and Gecko) or |
|
161 // Shift + Command + M (Opera) fire the "contextmenu" event. |
|
162 |
|
163 if (((ie || (isWin && (gecko || opera))) && shiftF10) || isMac) { |
|
164 |
|
165 e.clientX = clientX; |
|
166 e.clientY = clientY; |
|
167 e.pageX = pageX; |
|
168 e.pageY = pageY; |
|
169 |
|
170 notifier.fire(e); |
|
171 } |
|
172 |
|
173 } |
|
174 |
|
175 }, filter)); |
|
176 |
|
177 subscription._handles = handles; |
|
178 |
|
179 }, |
|
180 |
|
181 detach: function (node, subscription, notifier) { |
|
182 |
|
183 Y.each(subscription._handles, function (handle) { |
|
184 handle.detach(); |
|
185 }); |
|
186 |
|
187 } |
|
188 |
|
189 }; |
|
190 |
|
191 |
|
192 conf.delegate = conf.on; |
|
193 conf.detachDelegate = conf.detach; |
|
194 |
|
195 |
|
196 Event.define("contextmenu", conf, true); |
|
197 |
|
198 |
|
199 }, '3.10.3', {"requires": ["event-synthetic", "dom-screen"]}); |