Skip to content

Commit

Permalink
Merge pull request #5 from finnlabs/feature/make_context_menu_keyboar…
Browse files Browse the repository at this point in the history
…d_accessible

[Feature] Make context menu keyboard accessible
  • Loading branch information
tilt committed Aug 8, 2014
2 parents 0835494 + 6064db1 commit 9f4df2c
Show file tree
Hide file tree
Showing 7 changed files with 300 additions and 33 deletions.
3 changes: 2 additions & 1 deletion bower.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
"authors": [
"Ian Kennington Walter (http://ianvonwalter.com)",
"Briant Ford",
"Till Breuer (https://github.com/tilt)"
"Till Breuer (https://github.com/tilt)",
"finnlabs GmbH (http://finn.de)"
],
"license": "MIT",
"main": [
Expand Down
60 changes: 46 additions & 14 deletions dist/angular-context-menu.js
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,8 @@ angular.module('ng-context-menu', [])
'$injector',
'$window',
'$parse',
function($injector, $window, $parse) {
'$timeout',
function($injector, $window, $parse, $timeout) {
return {
restrict: 'A',
link: function(scope, element, attrs) {
Expand Down Expand Up @@ -149,44 +150,75 @@ angular.module('ng-context-menu', [])

function open(event) {
// set absolute position
contextMenu.open(locals, getCssPositionProperties(event));
var contextMenuPromise = contextMenu.open(locals, getCssPositionProperties(event));

contextMenuPromise.then(function(element) {
angular.element(element).trap();
});
}

function close() {
contextMenu.close();

if (openTarget) {
$timeout(function() {
openTarget.focus();
});
}
}

function getCssPositionProperties(event) {
return {
top: Math.max(event.pageY, 0) + 'px',
left: Math.max(event.pageX, 0) + 'px'
};
var position = { };

if (event.pageX && event.pageY) {
position.top = Math.max(event.pageY, 0) + 'px';
position.left = Math.max(event.pageX, 0) + 'px';
} else {
var bounding = angular.element(openTarget)[0].getBoundingClientRect();

position.top = Math.max(bounding.bottom, 0) + 'px';
position.left = Math.max(bounding.left, 0) + 'px';
}

return position;
}

element.bind(triggerOnEvent, function(event) {
function openContextMenu(event) {
openTarget = event.target;
event.preventDefault();
event.stopPropagation();

scope.$apply(function() {
open(event);
});
}

function closeContextMenu() {
scope.$apply(function() {
close();
});
}

element.bind(triggerOnEvent, function(event) {
openContextMenu(event);
});

win.bind('keyup', function(event) {
if (contextMenu.active() && event.keyCode === 27) {
scope.$apply(function() {
close();
});
closeContextMenu();
}

// Shift + C
if (event.keyCode === 67 && event.shiftKey) {
if (!contextMenu.active()) {
openContextMenu(event);
}
}
});

function handleWindowClickEvent(event) {
if (contextMenu.active() && openTarget && event.button !== 2) {

scope.$apply(function() {
close();
});
closeContextMenu();
}
}

Expand Down
2 changes: 1 addition & 1 deletion dist/angular-context-menu.min.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions gulpfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ var gulp = require('gulp'),
rename = require('gulp-rename');

gulp.task('default', function () {
gulp.run('lint', 'js');
gulp.start('lint', 'js');

gulp.watch('src/**/*.js', function (event) {
console.log('File ' + event.path + ' was ' + event.type + ', running tasks...');
gulp.run('lint', 'js');
gulp.start('lint', 'js');
});
});

Expand Down
202 changes: 202 additions & 0 deletions lib/jquery.trap.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
/*!
Copyright (c) 2011, 2012 Julien Wajsberg <felash@gmail.com>
All rights reserved.
Official repository: https://github.com/julienw/jquery-trap-input
License is there: https://github.com/julienw/jquery-trap-input/blob/master/LICENSE
This is version 1.2.0.
*/

(function( $, undefined ){

/*
(this comment is after the first line of code so that uglifyjs removes it)
Redistribution and use in source and binary forms, with or without
modification, are permitted without condition.
Although that's not an obligation, I would appreciate that you provide a
link to the official repository.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
IS" AND ANY EXPRESS OR IMPLIED WARRANTIES ARE DISCLAIMED.
*/

/*jshint boss: true, bitwise: true, curly: true, expr: true, newcap: true, noarg: true, nonew: true, latedef: true, regexdash: true */

var DATA_ISTRAPPING_KEY = "trap.isTrapping";

function onkeypress(e) {
if (e.keyCode === 9) {
var goReverse = !!(e.shiftKey);
if (processTab(this, e.target, goReverse)) {
e.preventDefault();
e.stopPropagation();
}
}
}

// will return true if we could process the tab event
// otherwise, return false
function processTab(container, elt, goReverse) {
var $focussable = getFocusableElementsInContainer(container),
curElt = elt,
index, nextIndex, prevIndex, lastIndex;

do {

index = $focussable.index(curElt);
nextIndex = index + 1;
prevIndex = index - 1;
lastIndex = $focussable.length - 1;

switch(index) {
case -1:
return false; // that's strange, let the browser do its job
case 0:
prevIndex = lastIndex;
break;
case lastIndex:
nextIndex = 0;
break;
}

if (goReverse) {
nextIndex = prevIndex;
}

curElt = $focussable.get(nextIndex);
// IE sometimes throws when an element is not visible
try {
curElt.focus();
} catch(e) {
}

} while (elt === elt.ownerDocument.activeElement);

return true;
}

function filterKeepSpeciallyFocusable() {
return this.tabIndex > 0;
}

function filterKeepNormalElements() {
return !this.tabIndex; // true if no tabIndex or tabIndex == 0
}

function sortFocusable(a, b) {
return (a.t - b.t) || (a.i - b.i);
}

function getFocusableElementsInContainer(container) {
var $container = $(container);
var result = [],
cnt = 0;

fixIndexSelector.enable && fixIndexSelector.enable();

// leaving away command and details for now
$container.find("a[href], link[href], [draggable=true], [contenteditable=true], :input:enabled, [tabindex=0]")
.filter(":visible")
.filter(filterKeepNormalElements)
.each(function(i, val) {
result.push({
v: val, // value
t: 0, // tabIndex
i: cnt++ // index for stable sort
});
});

$container
.find("[tabindex]")
.filter(":visible")
.filter(filterKeepSpeciallyFocusable)
.each(function(i, val) {
result.push({
v: val, // value
t: val.tabIndex, // tabIndex
i: cnt++ // index
});
});

fixIndexSelector.disable && fixIndexSelector.disable();

result = $.map(result.sort(sortFocusable), // needs stable sort
function(val) {
return val.v;
}
);


return $(result);

}

function trap() {
this.keydown(onkeypress);
this.data(DATA_ISTRAPPING_KEY, true);
return this;
}

function untrap() {
this.unbind('keydown', onkeypress);
this.removeData(DATA_ISTRAPPING_KEY);
return this;
}

function isTrapping() {
return !!this.data(DATA_ISTRAPPING_KEY);
}

$.fn.extend({
trap: trap,
untrap: untrap,
isTrapping: isTrapping
});

// jQuery 1.6.x tabindex attr hooks management
// this triggers problems for tabindex attribute
// selectors in IE7-
// see https://github.com/julienw/jquery-trap-input/issues/3

var fixIndexSelector = {};

if ($.find.find && $.find.attr !== $.attr) {
// jQuery uses Sizzle (this is jQuery >= 1.3)
// sizzle uses its own attribute handling (in jq 1.6.x and below)
(function() {
var tabindexKey = "tabindex";
var sizzleAttrHandle = $.expr.attrHandle;

// this function comes directly from jQuery 1.7.2 (propHooks.tabIndex.get)
// we have to put it here if we want to support jQuery < 1.6 which
// doesn't have an attrHooks object to reference.
function getTabindexAttr(elem) {
// elem.tabIndex doesn't always return the correct value when it hasn't been explicitly set
// http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/
var attributeNode = elem.getAttributeNode(tabindexKey);

return attributeNode && attributeNode.specified ?
parseInt( attributeNode.value, 10 ) :
undefined;
}

function fixSizzleAttrHook() {
// in jQ <= 1.6.x, we add to Sizzle the attrHook from jQuery's attr method
sizzleAttrHandle[tabindexKey] = sizzleAttrHandle.tabIndex = getTabindexAttr;
}

function unfixSizzleAttrHook() {
delete sizzleAttrHandle[tabindexKey];
delete sizzleAttrHandle.tabIndex;
}


fixIndexSelector = {
enable: fixSizzleAttrHook,
disable: unfixSizzleAttrHook
};
})();
}
})( jQuery );
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"description": "An AngularJS directive to display a context menu when a right-click event is triggered",
"main": "dist/angular-context-menu.min.js",
"devDependencies": {
"gulp": "~3.5.1",
"gulp": "~3.8.7",
"gulp-concat": "~2.1.7",
"gulp-uglify": "~0.2.0",
"gulp-jshint": "~1.3.4",
Expand Down
Loading

0 comments on commit 9f4df2c

Please sign in to comment.