From ebcd0aab342c4af3ac44ab12990cfc6edc07236c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?"J.=20Rene=CC=81e=20Beach"?= Date: Mon, 24 Dec 2012 16:13:34 -0500 Subject: [PATCH] Issue #849926 by casey, Everett Zufelt, Jacine, Mirabuck, mgifford, jessebeach: links wrapped in .contextual-links-wrapper divs are not accessible at all via keyboard alone also problems with screen readers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: J. ReneĢe Beach --- core/modules/contextual/contextual.base-rtl.css | 9 -- core/modules/contextual/contextual.base.css | 47 +++---- core/modules/contextual/contextual.js | 160 +++++++++++++++++----- core/modules/contextual/contextual.module | 2 - core/modules/contextual/contextual.theme-rtl.css | 25 ++-- core/modules/contextual/contextual.theme.css | 48 ++++--- 6 files changed, 191 insertions(+), 100 deletions(-) delete mode 100644 core/modules/contextual/contextual.base-rtl.css diff --git a/core/modules/contextual/contextual.base-rtl.css b/core/modules/contextual/contextual.base-rtl.css deleted file mode 100644 index 147a567..0000000 --- a/core/modules/contextual/contextual.base-rtl.css +++ /dev/null @@ -1,9 +0,0 @@ - -/** - * @file - * RTL base styles for the Contextual module. - */ - -.contextual .trigger { - text-align: left; -} diff --git a/core/modules/contextual/contextual.base.css b/core/modules/contextual/contextual.base.css index cbec804..8452f52 100644 --- a/core/modules/contextual/contextual.base.css +++ b/core/modules/contextual/contextual.base.css @@ -4,35 +4,36 @@ * Generic base styles for contextual module. */ -/** - * Contextual links behavior. - */ -.contextual, -.contextual .contextual-links, -.contextual .trigger { +.contextual-region { + position: relative; +} +.touch .contextual .trigger { + display: block; +} +.contextual .contextual-links { display: none; } -.touch .contextual, -.touch .contextual .trigger, -.no-touch .contextual-region:hover .contextual, -.no-touch .contextual-region:hover .contextual-links-trigger-active, -.contextual-active .contextual-links { +.contextual-links-active .contextual-links { 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 { - position: relative; +.touch .contextual-region .element-invisible.element-focusable, +.contextual-region:hover .element-invisible.element-focusable { + clip: auto; + overflow: visible; + height: auto; } -.contextual { - position: absolute; - z-index: 999; -} -.contextual .trigger { - overflow: hidden; - position: relative; - text-align: right; /* LTR */ - z-index: 1; +/* Override the position for contextual links. */ +.contextual-region .element-invisible.element-focusable:active, +.contextual-region .element-invisible.element-focusable:focus, +.contextual-region:hover .element-invisible.element-focusable, +.contextual-region-active .element-invisible.element-focusable, +.touch .contextual-region .element-invisible.element-focusable { + position: relative !important; } diff --git a/core/modules/contextual/contextual.js b/core/modules/contextual/contextual.js index d8a4742..b0842f8 100644 --- a/core/modules/contextual/contextual.js +++ b/core/modules/contextual/contextual.js @@ -3,55 +3,141 @@ * Attaches behaviors for the Contextual module. */ -(function ($) { +(function ($, Drupal) { "use strict"; -Drupal.contextualLinks = Drupal.contextualLinks || {}; - /** * Attaches outline behavior for regions associated with contextual links. */ -Drupal.behaviors.contextualLinks = { +Drupal.behaviors.contextual = { attach: function (context) { - $(context).find('div.contextual').once('contextual-links', function () { - var $wrapper = $(this); - var $region = $wrapper.closest('.contextual-region'); - var $links = $wrapper.find('ul'); - var $trigger = $('').text(Drupal.t('Configure')).click( - function (e) { - e.preventDefault(); - e.stopPropagation(); - $links.stop(true, true).slideToggle(100); - $wrapper.toggleClass('contextual-active'); - } - ); - // Attach hover behavior to trigger and ul.contextual-links, for non touch devices only. - if(!Modernizr.touch) { - $trigger.add($links).hover( - function () { $region.addClass('contextual-region-active'); }, - function () { $region.removeClass('contextual-region-active'); } - ); - } - // Hide the contextual links when user clicks a link or rolls out of the .contextual-region. - $region.bind('mouseleave click', Drupal.contextualLinks.mouseleave); - $region.hover( - function() { $trigger.addClass('contextual-links-trigger-active'); }, - function() { $trigger.removeClass('contextual-links-trigger-active'); } - ); - // Prepend the trigger. - $wrapper.prepend($trigger); + $('ul.contextual-links', context).once('contextual', function () { + var $this = $(this); + $this.data('drupal-contextual', new Drupal.contextual(this, $this.closest('.contextual-region'))); }); } }; /** - * Disables outline for the region contextual links are associated with. + * Contextual links object. + */ +Drupal.contextual = function($links, $region) { + this.$links = $links; + this.$region = $region; + this.timer = null; + + this.init(); +}; + +/** + * Initiates a contextual links object. + */ +Drupal.contextual.prototype.init = function() { + // Wrap the links to provide positioning and behavior attachment context. + this.$wrapper = $(Drupal.theme.contextualWrapper()) + .insertBefore(this.$links) + .append(this.$links); + + // 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})) + .prependTo(this.$wrapper); + + // Bind behaviors through delegation. + var highlightRegion = $.proxy(this.highlightRegion, this); + this.$region + .on('click.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('focusin.contextual', '.contextual a, .trigger', {highlight: true}, highlightRegion) + .on('focusout.contextual', '.contextual a, .trigger', {highlight: false}, highlightRegion); +}; + +/** + * Toggles the highlighting of a contextual region. + * + * If the state of a contextual region is toggled to inactive, the links + * + * @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) { + return this.timer = window.setTimeout($.proxy($.fn.trigger, $(event.target), 'mouseleave.contextual'), 100); + } + // Clear the timeout if the region should be highlighted and a timer exists. + if (event.data.highlight && this.timer) { + window.clearTimeout(this.timer); + } + 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); + } + // Clear the timeout. + this.timer = null; +}; + +/** + * 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})); +}; + +/** + * Wraps contextual links. + * + * @return {String} + * A string representing a DOM fragment. + */ +Drupal.theme.contextualWrapper = function () { + return '
'; +}; + +/** + * A trigger is an interactive element often bound to a click handler. + * + * @return {String} + * A string representing a DOM fragment. */ -Drupal.contextualLinks.mouseleave = function () { - $(this) - .find('.contextual-active').removeClass('contextual-active') - .find('.contextual-links').hide(); +Drupal.theme.contextualTrigger = function () { + return ''; }; -})(jQuery); +})(jQuery, Drupal); diff --git a/core/modules/contextual/contextual.module b/core/modules/contextual/contextual.module index bbb00e8..30dcacb 100644 --- a/core/modules/contextual/contextual.module +++ b/core/modules/contextual/contextual.module @@ -69,8 +69,6 @@ function contextual_element_info() { '#pre_render' => array('contextual_pre_render_links'), '#theme' => 'links__contextual', '#links' => array(), - '#prefix' => '
', - '#suffix' => '
', '#attributes' => array('class' => array('contextual-links')), '#attached' => array( 'library' => array( diff --git a/core/modules/contextual/contextual.theme-rtl.css b/core/modules/contextual/contextual.theme-rtl.css index f558ffa..ed48367 100644 --- a/core/modules/contextual/contextual.theme-rtl.css +++ b/core/modules/contextual/contextual.theme-rtl.css @@ -3,17 +3,26 @@ * RTL styling for contextual module. */ +/** + * Contextual links wrappers. + */ .contextual { - left: 5px; - right: auto; -} -.contextual .contextual-links { - border-radius: 0 4px 4px 4px; left: 0; right: auto; } -.contextual-region .contextual .contextual-links a { - text-align: right; - padding: 0.4em 0.6em 0.4em 0.8em; +/** + * Contextual trigger. + */ +.contextual .trigger { + float: left; +} + +/** + * Contextual links. + */ +.contextual .contextual-links { + border-radius: 0 4px 4px 4px; + float: left; + text-align: right; } diff --git a/core/modules/contextual/contextual.theme.css b/core/modules/contextual/contextual.theme.css index 8b5956a..2855dca 100644 --- a/core/modules/contextual/contextual.theme.css +++ b/core/modules/contextual/contextual.theme.css @@ -6,40 +6,43 @@ /** * Contextual links wrappers. */ +.contextual { + position: absolute; + right: 0; /* LTR */ + top: 2px; + z-index: 999; +} .contextual-region-active { outline: 1px dashed #d6d6d6; outline-offset: 1px; } -.contextual { - right: 2px; /* LTR */ - top: 2px; -} /** * 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; - height: 18px; + border-radius: 4px 4px 0 0; + /* Override the .element-focusable height: auto */ + height: 18px !important; + float: right; /* LTR */ margin: 0; - outline: none; overflow: hidden; padding: 0 2px; - text-indent: 34px; - width: 28px; + position: relative; + width: 34px; + text-indent: -9999px; + z-index: 2; } .no-touch .contextual .trigger:hover, -.contextual-active .trigger { +.contextual-links-active .trigger { background-position: 2px -18px; } -.contextual-active .trigger { - background-color: #ffffff; +.contextual-links-active .trigger { + background-color: #fff; border-bottom: none; border-color: #d6d6d6; - border-radius: 4px 4px 0 0; - position: relative; - z-index: 1; } /** @@ -49,15 +52,17 @@ background-color: #fff; border: 1px solid #d6d6d6; border-radius: 4px 0 4px 4px; /* LTR */ + clear: both; + float: right; /* LTR */ margin: 0; padding: 0.25em 0; - position: absolute; - right: 0; /* LTR */ - text-align: left; - top: 18px; + position: relative; + text-align: left; /* LTR */ + top: -1px; white-space: nowrap; + z-index: 1; } -/* Reset the li to prevent accidential overrides by a theme. */ +/* Reset the li to prevent accidental overrides by a theme. */ .contextual-region .contextual .contextual-links li { background-color: #fff; border: none; @@ -65,6 +70,7 @@ list-style-image: none; margin: 0; padding: 0; + line-height: 100%; } .contextual-region .contextual .contextual-links a { display: block; @@ -72,7 +78,7 @@ font-size: small; line-height: 0.8em; 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, .no-touch .contextual-region .contextual .contextual-links a:hover, -- 1.7.10.4