Commit 8eefa571 authored by webchick's avatar webchick

Issue #849926 by jessebeach, Jacine, mgifford, casey, Mirabuck: Fixed links...

Issue #849926 by jessebeach, Jacine, mgifford, casey, Mirabuck: Fixed links wrapped in .contextual-links-wrapper divs are not accessible at all via keyboard alone also problems with screen readers.
parent 5c90f1b8
/**
* @file
* RTL base styles for the Contextual module.
*/
.contextual .trigger {
text-align: left;
}
...@@ -4,35 +4,36 @@ ...@@ -4,35 +4,36 @@
* Generic base styles for contextual module. * Generic base styles for contextual module.
*/ */
/** .contextual-region {
* Contextual links behavior. position: relative;
*/ }
.contextual, .touch .contextual .trigger {
.contextual .contextual-links, display: block;
.contextual .trigger { }
.contextual .contextual-links {
display: none; display: none;
} }
.touch .contextual, .contextual-links-active .contextual-links {
.touch .contextual .trigger,
.no-touch .contextual-region:hover .contextual,
.no-touch .contextual-region:hover .contextual-links-trigger-active,
.contextual-active .contextual-links {
display: block; display: block;
} }
/** /**
* Contextual links structure. * The .element-focusable class extends the .element-invisible class to allow
* the element to be focusable when navigated to via the keyboard.
*
* Add support for hover.
*/ */
.contextual-region { .touch .contextual-region .element-invisible.element-focusable,
position: relative; .contextual-region:hover .element-invisible.element-focusable {
clip: auto;
overflow: visible;
height: auto;
} }
.contextual { /* Override the position for contextual links. */
position: absolute; .contextual-region .element-invisible.element-focusable:active,
z-index: 999; .contextual-region .element-invisible.element-focusable:focus,
} .contextual-region:hover .element-invisible.element-focusable,
.contextual .trigger { .contextual-region-active .element-invisible.element-focusable,
overflow: hidden; .touch .contextual-region .element-invisible.element-focusable {
position: relative; position: relative !important;
text-align: right; /* LTR */
z-index: 1;
} }
...@@ -3,55 +3,159 @@ ...@@ -3,55 +3,159 @@
* Attaches behaviors for the Contextual module. * Attaches behaviors for the Contextual module.
*/ */
(function ($) { (function ($, Drupal) {
"use strict"; "use strict";
Drupal.contextualLinks = Drupal.contextualLinks || {};
/** /**
* Attaches outline behavior for regions associated with contextual links. * Attaches outline behavior for regions associated with contextual links.
*/ */
Drupal.behaviors.contextualLinks = { Drupal.behaviors.contextual = {
attach: function (context) { attach: function (context) {
$(context).find('div.contextual').once('contextual-links', function () { $('ul.contextual-links', context).once('contextual', function () {
var $wrapper = $(this); var $this = $(this);
var $region = $wrapper.closest('.contextual-region'); $this.data('drupal-contextual', new Drupal.contextual($this, $this.closest('.contextual-region')));
var $links = $wrapper.find('ul'); });
var $trigger = $('<a class="trigger" href="#" />').text(Drupal.t('Configure')).click( }
function (e) { };
e.preventDefault();
e.stopPropagation(); /**
$links.stop(true, true).slideToggle(100); * Contextual links object.
$wrapper.toggleClass('contextual-active'); */
} Drupal.contextual = function($links, $region) {
); this.$links = $links;
// Attach hover behavior to trigger and ul.contextual-links, for non touch devices only. this.$region = $region;
if(!Modernizr.touch) {
$trigger.add($links).hover( this.init();
function () { $region.addClass('contextual-region-active'); }, };
function () { $region.removeClass('contextual-region-active'); }
); /**
} * Initiates a contextual links object.
// Hide the contextual links when user clicks a link or rolls out of the .contextual-region. */
$region.bind('mouseleave click', Drupal.contextualLinks.mouseleave); Drupal.contextual.prototype.init = function() {
$region.hover( // Wrap the links to provide positioning and behavior attachment context.
function() { $trigger.addClass('contextual-links-trigger-active'); }, this.$wrapper = $(Drupal.theme.contextualWrapper())
function() { $trigger.removeClass('contextual-links-trigger-active'); } .insertBefore(this.$links)
); .append(this.$links);
// Prepend the trigger.
$wrapper.prepend($trigger); // Mark the links as hidden. Use aria-role form so that the number of items
// in the list is spoken.
this.$links
.attr({
'hidden': 'hidden',
'role': 'form'
}); });
// Create and append the contextual links trigger.
var action = Drupal.t('Open');
this.$trigger = $(Drupal.theme.contextualTrigger())
.text(Drupal.t('@action configuration options', {'@action': action}))
// Set the aria-pressed state.
.attr('aria-pressed', false)
.prependTo(this.$wrapper);
// Bind behaviors through delegation.
var highlightRegion = $.proxy(this.highlightRegion, this);
this.$region
.on('click.contextual', '.contextual .trigger', $.proxy(this.triggerClickHandler, this))
.on('mouseenter.contextual', {highlight: true}, highlightRegion)
.on('mouseleave.contextual', {highlight: false}, highlightRegion)
.on('mouseleave.contextual', '.contextual', {show: false}, $.proxy(this.triggerLeaveHandler, this))
.on('focus.contextual', '.contextual-links a, .contextual .trigger', {highlight: true}, highlightRegion)
.on('blur.contextual', '.contextual-links a, .contextual .trigger', {highlight: false}, highlightRegion);
};
/**
* Toggles the highlighting of a contextual region.
*
* @param {Object} event
* jQuery Event object.
*/
Drupal.contextual.prototype.highlightRegion = function(event) {
// Set up a timeout to delay the dismissal of the region highlight state.
if (!event.data.highlight && this.timer === undefined) {
return this.timer = window.setTimeout($.proxy($.fn.trigger, $(event.target), 'mouseleave.contextual'), 100);
}
// Clear the timeout to prevent an infinite loop of mouseleave being
// triggered.
if (this.timer) {
window.clearTimeout(this.timer);
delete this.timer;
} }
// Toggle active state of the contextual region based on the highlight value.
this.$region.toggleClass('contextual-region-active', event.data.highlight);
// Hide the links if the contextual region is inactive.
var state = this.$region.hasClass('contextual-region-active');
if (!state) {
this.showLinks(state);
}
};
/**
* Handles click on the contextual links trigger.
*
* @param {Object} event
* jQuery Event object.
*/
Drupal.contextual.prototype.triggerClickHandler = function (event) {
event.preventDefault();
this.showLinks();
};
/**
* Handles mouseleave on the contextual links trigger.
*
* @param {Object} event
* jQuery Event object.
*/
Drupal.contextual.prototype.triggerLeaveHandler = function (event) {
var show = event && event.data && event.data.show;
this.showLinks(show);
};
/**
* Toggles the active state of the contextual links.
*
* @param {Boolean} show
* (optional) True if the links should be shown. False is the links should be
* hidden.
*/
Drupal.contextual.prototype.showLinks = function(show) {
this.$wrapper.toggleClass('contextual-links-active', show);
var isOpen = this.$wrapper.hasClass('contextual-links-active');
var action = (isOpen) ? Drupal.t('Close') : Drupal.t('Open');
this.$trigger
.text(Drupal.t('@action configuration options', {'@action': action}))
// Set the aria-pressed state.
.attr('aria-pressed', isOpen);
// Mark the links as hidden if they are.
if (isOpen) {
this.$links.removeAttr('hidden');
}
else {
this.$links.attr('hidden', 'hidden');
}
};
/**
* Wraps contextual links.
*
* @return {String}
* A string representing a DOM fragment.
*/
Drupal.theme.contextualWrapper = function () {
return '<div class="contextual" />';
}; };
/** /**
* Disables outline for the region contextual links are associated with. * A trigger is an interactive element often bound to a click handler.
*
* @return {String}
* A string representing a DOM fragment.
*/ */
Drupal.contextualLinks.mouseleave = function () { Drupal.theme.contextualTrigger = function () {
$(this) return '<button class="trigger element-invisible element-focusable" type="button"></button>';
.find('.contextual-active').removeClass('contextual-active')
.find('.contextual-links').hide();
}; };
})(jQuery); })(jQuery, Drupal);
...@@ -69,8 +69,6 @@ function contextual_element_info() { ...@@ -69,8 +69,6 @@ function contextual_element_info() {
'#pre_render' => array('contextual_pre_render_links'), '#pre_render' => array('contextual_pre_render_links'),
'#theme' => 'links__contextual', '#theme' => 'links__contextual',
'#links' => array(), '#links' => array(),
'#prefix' => '<div class="contextual">',
'#suffix' => '</div>',
'#attributes' => array('class' => array('contextual-links')), '#attributes' => array('class' => array('contextual-links')),
'#attached' => array( '#attached' => array(
'library' => array( 'library' => array(
......
...@@ -3,17 +3,26 @@ ...@@ -3,17 +3,26 @@
* RTL styling for contextual module. * RTL styling for contextual module.
*/ */
/**
* Contextual links wrappers.
*/
.contextual { .contextual {
left: 5px;
right: auto;
}
.contextual .contextual-links {
border-radius: 0 4px 4px 4px;
left: 0; left: 0;
right: auto; right: auto;
} }
.contextual-region .contextual .contextual-links a { /**
text-align: right; * Contextual trigger.
padding: 0.4em 0.6em 0.4em 0.8em; */
.contextual .trigger {
float: left;
}
/**
* Contextual links.
*/
.contextual .contextual-links {
border-radius: 0 4px 4px 4px;
float: left;
text-align: right;
} }
...@@ -6,58 +6,64 @@ ...@@ -6,58 +6,64 @@
/** /**
* Contextual links wrappers. * Contextual links wrappers.
*/ */
.contextual {
position: absolute;
right: 0; /* LTR */
top: 2px;
z-index: 999;
}
.contextual-region-active { .contextual-region-active {
outline: 1px dashed #d6d6d6; outline: 1px dashed #d6d6d6;
outline-offset: 1px; outline-offset: 1px;
} }
.contextual {
right: 2px; /* LTR */
top: 2px;
}
/** /**
* Contextual trigger. * Contextual trigger.
*/ */
.contextual .trigger { .contextual .trigger {
background: transparent url(images/gear-select.png) no-repeat 2px 0; background: transparent url("images/gear-select.png") no-repeat 2px 0;
border: 1px solid transparent; border: 1px solid transparent;
height: 18px; border-radius: 4px 4px 0 0;
/* Override the .element-focusable height: auto */
height: 18px !important;
float: right; /* LTR */
margin: 0; margin: 0;
outline: none;
overflow: hidden; overflow: hidden;
padding: 0 2px; padding: 0 2px;
text-indent: 34px; position: relative;
width: 28px; width: 34px;
text-indent: -9999px;
z-index: 2;
} }
.no-touch .contextual .trigger:hover, .no-touch .contextual .trigger:hover,
.contextual-active .trigger { .contextual-links-active .trigger {
background-position: 2px -18px; background-position: 2px -18px;
} }
.contextual-active .trigger { .contextual-links-active .trigger {
background-color: #ffffff; background-color: #fff;
border-bottom: none; border-bottom: none;
border-color: #d6d6d6; border-color: #d6d6d6;
border-radius: 4px 4px 0 0;
position: relative;
z-index: 1;
} }
/** /**
* Contextual links. * Contextual links.
*
* The following selectors are heavy to discourage theme overriding.
*/ */
.contextual .contextual-links { .contextual-region .contextual .contextual-links {
background-color: #fff; background-color: #fff;
border: 1px solid #d6d6d6; border: 1px solid #d6d6d6;
border-radius: 4px 0 4px 4px; /* LTR */ border-radius: 4px 0 4px 4px; /* LTR */
clear: both;
float: right; /* LTR */
margin: 0; margin: 0;
padding: 0.25em 0; padding: 0.25em 0;
position: absolute; position: relative;
right: 0; /* LTR */ text-align: left; /* LTR */
text-align: left; top: -1px;
top: 18px;
white-space: nowrap; white-space: nowrap;
z-index: 1;
} }
/* Reset the li to prevent accidential overrides by a theme. */
.contextual-region .contextual .contextual-links li { .contextual-region .contextual .contextual-links li {
background-color: #fff; background-color: #fff;
border: none; border: none;
...@@ -65,21 +71,22 @@ ...@@ -65,21 +71,22 @@
list-style-image: none; list-style-image: none;
margin: 0; margin: 0;
padding: 0; padding: 0;
line-height: 100%;
} }
.contextual-region .contextual .contextual-links a { .contextual-region .contextual .contextual-links a {
background-color: #fff;
/* This is an unforetunately necessary use of !important to prevent white
* links on a white background or some similar illegible combination. */
color: #333 !important;
display: block; display: block;
font-family: sans-serif; font-family: sans-serif;
font-size: small; font-size: small;
line-height: 0.8em; line-height: 0.8em;
margin: 0.25em 0; margin: 0.25em 0;
padding: 0.4em 0.8em 0.4em 0.6em; /* LTR */ padding: 0.4em 0.6em;
} }
.contextual-region .contextual .contextual-links a, .contextual-region .contextual .contextual-links a,
.no-touch .contextual-region .contextual .contextual-links a:hover, .contextual-region .contextual .contextual-links a:hover {
.contextual-region .contextual .contextual-links a:active,
.contextual-region .contextual .contextual-links a:focus {
background-color: #fff;
color: #333;
text-decoration: none; text-decoration: none;
} }
.no-touch .contextual-region .contextual .contextual-links li a:hover { .no-touch .contextual-region .contextual .contextual-links li a:hover {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment