f66812a0791dd11c23b677fc167c30a4aff065f8Luke SmithYUI.add('event-contextmenu', function(Y) {
f66812a0791dd11c23b677fc167c30a4aff065f8Luke Smith
f66812a0791dd11c23b677fc167c30a4aff065f8Luke Smith/**
f66812a0791dd11c23b677fc167c30a4aff065f8Luke Smith * Provides extended keyboard support for the "contextmenu" event such that:
f66812a0791dd11c23b677fc167c30a4aff065f8Luke Smith * <ul>
2b57c2557a233b760f9178d792804d2c402f2d9cLuke Smith * <li>The browser's default context menu is suppressed regardless of how the event is triggered.</li>
2b57c2557a233b760f9178d792804d2c402f2d9cLuke Smith * <li>On Windows the "contextmenu" event is fired consistently regardless of whether the user pressed the Menu key or Shift + F10.</li>
f66812a0791dd11c23b677fc167c30a4aff065f8Luke Smith * <li>When the "contextmenu" event is fired via the keyboard, the pageX, pageY, clientX and clientY properties reference the center of the event target. This makes it easy for "contextmenu" event listeners to position an overlay in response to the event by not having to worry about special handling of the x and y coordinates based on the device that fired the event.</li>
2b57c2557a233b760f9178d792804d2c402f2d9cLuke Smith * <li>For Webkit and Gecko on the Mac it enables the use of the Shift + Control + Option + M keyboard shortcut to fire the "contextmenu" event, which (by default) is only available when VoiceOver (the screen reader on the Mac) is enabled.</li>
2b57c2557a233b760f9178d792804d2c402f2d9cLuke Smith * <li>For Opera on the Mac it ensures the "contextmenu" event is fired when the user presses Shift + Command + M (Opera's context menu keyboard shortcut).</li>
f66812a0791dd11c23b677fc167c30a4aff065f8Luke Smith * </ul>
f66812a0791dd11c23b677fc167c30a4aff065f8Luke Smith * @module event-contextmenu
f66812a0791dd11c23b677fc167c30a4aff065f8Luke Smith * @requires event
f66812a0791dd11c23b677fc167c30a4aff065f8Luke Smith */
f66812a0791dd11c23b677fc167c30a4aff065f8Luke Smith
f66812a0791dd11c23b677fc167c30a4aff065f8Luke Smithvar Event = Y.Event,
f66812a0791dd11c23b677fc167c30a4aff065f8Luke Smith DOM = Y.DOM,
f66812a0791dd11c23b677fc167c30a4aff065f8Luke Smith UA = Y.UA,
f66812a0791dd11c23b677fc167c30a4aff065f8Luke Smith OS = Y.UA.os,
2b57c2557a233b760f9178d792804d2c402f2d9cLuke Smith
f66812a0791dd11c23b677fc167c30a4aff065f8Luke Smith ie = UA.ie,
f66812a0791dd11c23b677fc167c30a4aff065f8Luke Smith gecko = UA.gecko,
f66812a0791dd11c23b677fc167c30a4aff065f8Luke Smith webkit = UA.webkit,
2b57c2557a233b760f9178d792804d2c402f2d9cLuke Smith opera = UA.opera,
f66812a0791dd11c23b677fc167c30a4aff065f8Luke Smith
f66812a0791dd11c23b677fc167c30a4aff065f8Luke Smith isWin = (OS === "windows"),
f66812a0791dd11c23b677fc167c30a4aff065f8Luke Smith isMac = (OS === "macintosh"),
f66812a0791dd11c23b677fc167c30a4aff065f8Luke Smith
f66812a0791dd11c23b677fc167c30a4aff065f8Luke Smith eventData = {},
f66812a0791dd11c23b677fc167c30a4aff065f8Luke Smith
f66812a0791dd11c23b677fc167c30a4aff065f8Luke Smith conf = {
f66812a0791dd11c23b677fc167c30a4aff065f8Luke Smith
f66812a0791dd11c23b677fc167c30a4aff065f8Luke Smith on: function (node, subscription, notifier, filter) {
2b57c2557a233b760f9178d792804d2c402f2d9cLuke Smith
f66812a0791dd11c23b677fc167c30a4aff065f8Luke Smith var handles = [];
2b57c2557a233b760f9178d792804d2c402f2d9cLuke Smith
f66812a0791dd11c23b677fc167c30a4aff065f8Luke Smith handles.push(Event._attach(["contextmenu", function (e) {
f66812a0791dd11c23b677fc167c30a4aff065f8Luke Smith
2b57c2557a233b760f9178d792804d2c402f2d9cLuke Smith // Any developer listening for the "contextmenu" event is likely
f66812a0791dd11c23b677fc167c30a4aff065f8Luke Smith // going to call preventDefault() to prevent the display of
f66812a0791dd11c23b677fc167c30a4aff065f8Luke Smith // the browser's context menu. So, you know, save them a step.
f66812a0791dd11c23b677fc167c30a4aff065f8Luke Smith e.preventDefault();
2b57c2557a233b760f9178d792804d2c402f2d9cLuke Smith
f66812a0791dd11c23b677fc167c30a4aff065f8Luke Smith var id = Y.stamp(node),
f66812a0791dd11c23b677fc167c30a4aff065f8Luke Smith data = eventData[id];
2b57c2557a233b760f9178d792804d2c402f2d9cLuke Smith
f66812a0791dd11c23b677fc167c30a4aff065f8Luke Smith if (data) {
f66812a0791dd11c23b677fc167c30a4aff065f8Luke Smith e.clientX = data.clientX;
f66812a0791dd11c23b677fc167c30a4aff065f8Luke Smith e.clientY = data.clientY;
f66812a0791dd11c23b677fc167c30a4aff065f8Luke Smith e.pageX = data.pageX;
f66812a0791dd11c23b677fc167c30a4aff065f8Luke Smith e.pageY = data.pageY;
f66812a0791dd11c23b677fc167c30a4aff065f8Luke Smith delete eventData[id];
f66812a0791dd11c23b677fc167c30a4aff065f8Luke Smith }
2b57c2557a233b760f9178d792804d2c402f2d9cLuke Smith
f66812a0791dd11c23b677fc167c30a4aff065f8Luke Smith notifier.fire(e);
2b57c2557a233b760f9178d792804d2c402f2d9cLuke Smith
f66812a0791dd11c23b677fc167c30a4aff065f8Luke Smith }, node]));
2b57c2557a233b760f9178d792804d2c402f2d9cLuke Smith
2b57c2557a233b760f9178d792804d2c402f2d9cLuke Smith
f66812a0791dd11c23b677fc167c30a4aff065f8Luke Smith handles.push(node[filter ? "delegate" : "on"]("keydown", function (e) {
2b57c2557a233b760f9178d792804d2c402f2d9cLuke Smith
f66812a0791dd11c23b677fc167c30a4aff065f8Luke Smith var target = this.getDOMNode(),
f66812a0791dd11c23b677fc167c30a4aff065f8Luke Smith shiftKey = e.shiftKey,
f66812a0791dd11c23b677fc167c30a4aff065f8Luke Smith keyCode = e.keyCode,
f66812a0791dd11c23b677fc167c30a4aff065f8Luke Smith shiftF10 = (shiftKey && keyCode == 121),
f66812a0791dd11c23b677fc167c30a4aff065f8Luke Smith menuKey = (isWin && keyCode == 93),
2b57c2557a233b760f9178d792804d2c402f2d9cLuke Smith ctrlKey = e.ctrlKey,
2b57c2557a233b760f9178d792804d2c402f2d9cLuke Smith mKey = (keyCode === 77),
2b57c2557a233b760f9178d792804d2c402f2d9cLuke Smith macWebkitAndGeckoShortcut = (isMac && (webkit || gecko) && ctrlKey && shiftKey && e.altKey && mKey),
2b57c2557a233b760f9178d792804d2c402f2d9cLuke Smith
2b57c2557a233b760f9178d792804d2c402f2d9cLuke Smith // Note: The context menu keyboard shortcut for Opera on the Mac is Shift + Cmd (metaKey) + M,
2b57c2557a233b760f9178d792804d2c402f2d9cLuke Smith // but e.metaKey is false for Opera, and Opera sets e.ctrlKey to true instead.
2b57c2557a233b760f9178d792804d2c402f2d9cLuke Smith macOperaShortcut = (isMac && opera && ctrlKey && shiftKey && mKey),
2b57c2557a233b760f9178d792804d2c402f2d9cLuke Smith
f66812a0791dd11c23b677fc167c30a4aff065f8Luke Smith clientX = 0,
f66812a0791dd11c23b677fc167c30a4aff065f8Luke Smith clientY = 0,
f66812a0791dd11c23b677fc167c30a4aff065f8Luke Smith scrollX,
f66812a0791dd11c23b677fc167c30a4aff065f8Luke Smith scrollY,
f66812a0791dd11c23b677fc167c30a4aff065f8Luke Smith pageX,
f66812a0791dd11c23b677fc167c30a4aff065f8Luke Smith pageY,
f66812a0791dd11c23b677fc167c30a4aff065f8Luke Smith xy,
f66812a0791dd11c23b677fc167c30a4aff065f8Luke Smith x,
f66812a0791dd11c23b677fc167c30a4aff065f8Luke Smith y;
2b57c2557a233b760f9178d792804d2c402f2d9cLuke Smith
2b57c2557a233b760f9178d792804d2c402f2d9cLuke Smith
f66812a0791dd11c23b677fc167c30a4aff065f8Luke Smith if ((isWin && (shiftF10 || menuKey)) ||
2b57c2557a233b760f9178d792804d2c402f2d9cLuke Smith (macWebkitAndGeckoShortcut || macOperaShortcut)) {
2b57c2557a233b760f9178d792804d2c402f2d9cLuke Smith
f66812a0791dd11c23b677fc167c30a4aff065f8Luke Smith // Need to call preventDefault() here b/c:
2b57c2557a233b760f9178d792804d2c402f2d9cLuke Smith // 1) To prevent IE's menubar from gaining focus when the
f66812a0791dd11c23b677fc167c30a4aff065f8Luke Smith // user presses Shift + F10
2b57c2557a233b760f9178d792804d2c402f2d9cLuke Smith // 2) In Firefox and Opera for Win, Shift + F10 will display a
2b57c2557a233b760f9178d792804d2c402f2d9cLuke Smith // context menu, but won't fire the "contextmenu" event. So, need
2b57c2557a233b760f9178d792804d2c402f2d9cLuke Smith // to call preventDefault() to prevent the display of the
2b57c2557a233b760f9178d792804d2c402f2d9cLuke Smith // browser's context menu
2b57c2557a233b760f9178d792804d2c402f2d9cLuke Smith // 3) For Opera on the Mac the context menu keyboard shortcut
2b57c2557a233b760f9178d792804d2c402f2d9cLuke Smith // (Shift + Cmd + M) will display a context menu, but like Firefox
2b57c2557a233b760f9178d792804d2c402f2d9cLuke Smith // and Opera on windows, Opera doesn't fire a "contextmenu" event,
2b57c2557a233b760f9178d792804d2c402f2d9cLuke Smith // so preventDefault() is just used to supress Opera's
2b57c2557a233b760f9178d792804d2c402f2d9cLuke Smith // default context menu.
2b57c2557a233b760f9178d792804d2c402f2d9cLuke Smith if (((ie || (isWin && (gecko || opera))) && shiftF10) || macOperaShortcut) {
f66812a0791dd11c23b677fc167c30a4aff065f8Luke Smith e.preventDefault();
f66812a0791dd11c23b677fc167c30a4aff065f8Luke Smith }
2b57c2557a233b760f9178d792804d2c402f2d9cLuke Smith
f66812a0791dd11c23b677fc167c30a4aff065f8Luke Smith xy = DOM.getXY(target);
f66812a0791dd11c23b677fc167c30a4aff065f8Luke Smith x = xy[0];
f66812a0791dd11c23b677fc167c30a4aff065f8Luke Smith y = xy[1];
f66812a0791dd11c23b677fc167c30a4aff065f8Luke Smith scrollX = DOM.docScrollX();
f66812a0791dd11c23b677fc167c30a4aff065f8Luke Smith scrollY = DOM.docScrollY();
2b57c2557a233b760f9178d792804d2c402f2d9cLuke Smith
2b57c2557a233b760f9178d792804d2c402f2d9cLuke Smith // Protect against instances where xy and might not be returned,
f66812a0791dd11c23b677fc167c30a4aff065f8Luke Smith // for example if the target is the document.
f66812a0791dd11c23b677fc167c30a4aff065f8Luke Smith if (!Y.Lang.isUndefined(x)) {
f66812a0791dd11c23b677fc167c30a4aff065f8Luke Smith clientX = (x + (target.offsetWidth/2)) - scrollX;
f66812a0791dd11c23b677fc167c30a4aff065f8Luke Smith clientY = (y + (target.offsetHeight/2)) - scrollY;
f66812a0791dd11c23b677fc167c30a4aff065f8Luke Smith }
2b57c2557a233b760f9178d792804d2c402f2d9cLuke Smith
f66812a0791dd11c23b677fc167c30a4aff065f8Luke Smith pageX = clientX + scrollX;
f66812a0791dd11c23b677fc167c30a4aff065f8Luke Smith pageY = clientY + scrollY;
f66812a0791dd11c23b677fc167c30a4aff065f8Luke Smith
2b57c2557a233b760f9178d792804d2c402f2d9cLuke Smith // When the "contextmenu" event is fired from the keyboard
f66812a0791dd11c23b677fc167c30a4aff065f8Luke Smith // clientX, clientY, pageX or pageY aren't set to useful
2b57c2557a233b760f9178d792804d2c402f2d9cLuke Smith // values. So, we follow Safari's model here of setting
f66812a0791dd11c23b677fc167c30a4aff065f8Luke Smith // the x & x coords to the center of the event target.
f66812a0791dd11c23b677fc167c30a4aff065f8Luke Smith
f66812a0791dd11c23b677fc167c30a4aff065f8Luke Smith if (menuKey || (isWin && webkit && shiftF10)) {
f66812a0791dd11c23b677fc167c30a4aff065f8Luke Smith eventData[Y.stamp(node)] = {
f66812a0791dd11c23b677fc167c30a4aff065f8Luke Smith clientX: clientX,
f66812a0791dd11c23b677fc167c30a4aff065f8Luke Smith clientY: clientY,
f66812a0791dd11c23b677fc167c30a4aff065f8Luke Smith pageX: pageX,
f66812a0791dd11c23b677fc167c30a4aff065f8Luke Smith pageY: pageY
f66812a0791dd11c23b677fc167c30a4aff065f8Luke Smith };
f66812a0791dd11c23b677fc167c30a4aff065f8Luke Smith }
2b57c2557a233b760f9178d792804d2c402f2d9cLuke Smith
f66812a0791dd11c23b677fc167c30a4aff065f8Luke Smith // Don't need to call notifier.fire(e) when the Menu key
2b57c2557a233b760f9178d792804d2c402f2d9cLuke Smith // is pressed as it fires the "contextmenu" event by default.
2b57c2557a233b760f9178d792804d2c402f2d9cLuke Smith //
f66812a0791dd11c23b677fc167c30a4aff065f8Luke Smith // In IE the call to preventDefault() for Shift + F10
2b57c2557a233b760f9178d792804d2c402f2d9cLuke Smith // prevents the "contextmenu" event from firing, so we need
f66812a0791dd11c23b677fc167c30a4aff065f8Luke Smith // to call notifier.fire(e)
f66812a0791dd11c23b677fc167c30a4aff065f8Luke Smith //
2b57c2557a233b760f9178d792804d2c402f2d9cLuke Smith // Need to also call notifier.fire(e) for Gecko and Opera since
2b57c2557a233b760f9178d792804d2c402f2d9cLuke Smith // neither Shift + F10 or Shift + Cmd + M fire the "contextmenu" event.
2b57c2557a233b760f9178d792804d2c402f2d9cLuke Smith //
2b57c2557a233b760f9178d792804d2c402f2d9cLuke Smith // Lastly, also need to call notifier.fire(e) for all Mac browsers
2b57c2557a233b760f9178d792804d2c402f2d9cLuke Smith // since neither Shift + Ctrl + Option + M (Webkit and Gecko) or
2b57c2557a233b760f9178d792804d2c402f2d9cLuke Smith // Shift + Command + M (Opera) fire the "contextmenu" event.
f66812a0791dd11c23b677fc167c30a4aff065f8Luke Smith
2b57c2557a233b760f9178d792804d2c402f2d9cLuke Smith if (((ie || (isWin && (gecko || opera))) && shiftF10) || isMac) {
f66812a0791dd11c23b677fc167c30a4aff065f8Luke Smith
f66812a0791dd11c23b677fc167c30a4aff065f8Luke Smith e.clientX = clientX;
f66812a0791dd11c23b677fc167c30a4aff065f8Luke Smith e.clientY = clientY;
f66812a0791dd11c23b677fc167c30a4aff065f8Luke Smith e.pageX = pageX;
f66812a0791dd11c23b677fc167c30a4aff065f8Luke Smith e.pageY = pageY;
2b57c2557a233b760f9178d792804d2c402f2d9cLuke Smith
f66812a0791dd11c23b677fc167c30a4aff065f8Luke Smith notifier.fire(e);
f66812a0791dd11c23b677fc167c30a4aff065f8Luke Smith }
f66812a0791dd11c23b677fc167c30a4aff065f8Luke Smith
f66812a0791dd11c23b677fc167c30a4aff065f8Luke Smith }
2b57c2557a233b760f9178d792804d2c402f2d9cLuke Smith
f66812a0791dd11c23b677fc167c30a4aff065f8Luke Smith }, filter));
2b57c2557a233b760f9178d792804d2c402f2d9cLuke Smith
f66812a0791dd11c23b677fc167c30a4aff065f8Luke Smith subscription._handles = handles;
2b57c2557a233b760f9178d792804d2c402f2d9cLuke Smith
f66812a0791dd11c23b677fc167c30a4aff065f8Luke Smith },
2b57c2557a233b760f9178d792804d2c402f2d9cLuke Smith
f66812a0791dd11c23b677fc167c30a4aff065f8Luke Smith detach: function (node, subscription, notifier) {
2b57c2557a233b760f9178d792804d2c402f2d9cLuke Smith
f66812a0791dd11c23b677fc167c30a4aff065f8Luke Smith Y.each(subscription._handles, function (handle) {
f66812a0791dd11c23b677fc167c30a4aff065f8Luke Smith handle.detach();
f66812a0791dd11c23b677fc167c30a4aff065f8Luke Smith });
2b57c2557a233b760f9178d792804d2c402f2d9cLuke Smith
f66812a0791dd11c23b677fc167c30a4aff065f8Luke Smith }
2b57c2557a233b760f9178d792804d2c402f2d9cLuke Smith
f66812a0791dd11c23b677fc167c30a4aff065f8Luke Smith };
f66812a0791dd11c23b677fc167c30a4aff065f8Luke Smith
f66812a0791dd11c23b677fc167c30a4aff065f8Luke Smith
f66812a0791dd11c23b677fc167c30a4aff065f8Luke Smithconf.delegate = conf.on;
f66812a0791dd11c23b677fc167c30a4aff065f8Luke Smithconf.detachDelegate = conf.detach;
f66812a0791dd11c23b677fc167c30a4aff065f8Luke Smith
f66812a0791dd11c23b677fc167c30a4aff065f8Luke Smith
f66812a0791dd11c23b677fc167c30a4aff065f8Luke SmithEvent.define("contextmenu", conf, true);
f66812a0791dd11c23b677fc167c30a4aff065f8Luke Smith
f66812a0791dd11c23b677fc167c30a4aff065f8Luke Smith
f66812a0791dd11c23b677fc167c30a4aff065f8Luke Smith}, '@VERSION@' ,{requires:['event-synthetic', 'dom-screen']});