From 50bb0ec7e5f27bed66456839bd5827e07712214d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?"J.=20Rene=CC=81e=20Beach"?= <splendidnoise@gmail.com>
Date: Fri, 25 Jan 2013 16:01:37 -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

commit dc1c09c7a16deeaab705442bc073a56ba31ca627
Author: J. Renée Beach <splendidnoise@gmail.com>
Date:   Fri Jan 25 16:00:26 2013 -0500

    js improvements

    Signed-off-by: J. Renée Beach <splendidnoise@gmail.com>

commit a9a4abcd4193a071aaa52a98e8246b190005e357
Author: J. Renée Beach <splendidnoise@gmail.com>
Date:   Fri Jan 25 15:45:59 2013 -0500

    CSS updates.

    Signed-off-by: J. Renée Beach <splendidnoise@gmail.com>

commit a4a2dc7400a8f93a73611513846bf8ef60c40de7
Author: J. Renée Beach <splendidnoise@gmail.com>
Date:   Fri Jan 25 15:25:19 2013 -0500

    aria stuff

    Signed-off-by: J. Renée Beach <splendidnoise@gmail.com>

commit f1bbadd4b909ea75a35e797c9288a52667c4dd8c
Author: J. Renée Beach <splendidnoise@gmail.com>
Date:   Fri Jan 25 13:51:59 2013 -0500

    patch 60

    Signed-off-by: J. Renée Beach <splendidnoise@gmail.com>

Signed-off-by: J. Renée Beach <splendidnoise@gmail.com>
---
 core/modules/contextual/contextual.base-rtl.css  |    9 --
 core/modules/contextual/contextual.base.css      |   47 +++---
 core/modules/contextual/contextual.js            |  178 +++++++++++++++++-----
 core/modules/contextual/contextual.module        |    2 -
 core/modules/contextual/contextual.theme-rtl.css |   25 ++-
 core/modules/contextual/contextual.theme.css     |   61 ++++----
 6 files changed, 216 insertions(+), 106 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..bff2532 100644
--- a/core/modules/contextual/contextual.js
+++ b/core/modules/contextual/contextual.js
@@ -3,55 +3,159 @@
  * 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 = $('<a class="trigger" href="#" />').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')));
+    });
+  }
+};
+
+/**
+ * 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);
+
+  // 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) {
+    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}))
+    // 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 () {
-  $(this)
-    .find('.contextual-active').removeClass('contextual-active')
-    .find('.contextual-links').hide();
+Drupal.theme.contextualTrigger = function () {
+  return '<button class="trigger element-invisible element-focusable" type="button"></button>';
 };
 
-})(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' => '<div class="contextual">',
-    '#suffix' => '</div>',
     '#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..aca019c 100644
--- a/core/modules/contextual/contextual.theme.css
+++ b/core/modules/contextual/contextual.theme.css
@@ -6,58 +6,64 @@
 /**
  * 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;
 }
 
 /**
  * Contextual links.
+ *
+ * The following selectors are heavy to discourage theme overriding.
  */
-.contextual .contextual-links {
+.contextual-region .contextual .contextual-links {
   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. */
 .contextual-region .contextual .contextual-links li {
   background-color: #fff;
   border: none;
@@ -65,21 +71,22 @@
   list-style-image: none;
   margin: 0;
   padding: 0;
+  line-height: 100%;
 }
 .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;
   font-family: sans-serif;
   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,
-.contextual-region .contextual .contextual-links a:active,
-.contextual-region .contextual .contextual-links a:focus {
-  background-color: #fff;
-  color: #333;
+.contextual-region .contextual .contextual-links a:hover {
   text-decoration: none;
 }
 .no-touch .contextual-region .contextual .contextual-links li a:hover {
-- 
1.7.10.4

