diff --git a/core/modules/contextual/contextual.base.css b/core/modules/contextual/contextual.base.css index 5c20c8a..379f334 100644 --- a/core/modules/contextual/contextual.base.css +++ b/core/modules/contextual/contextual.base.css @@ -7,14 +7,11 @@ /** * Contextual links behavior. */ -.contextual, -.contextual .contextual-links, -.contextual .trigger { +.contextual .contextual-links { display: none; } -.contextual-region:hover .contextual, -.contextual-region:hover .contextual-links-trigger-active, -.contextual-active .contextual-links { +.contextual .trigger, +.contextual-region:hover .contextual { display: block; } @@ -27,6 +24,7 @@ .contextual { position: absolute; z-index: 999; + display: block; } .contextual .trigger { overflow: hidden; diff --git a/core/modules/contextual/contextual.js b/core/modules/contextual/contextual.js index 3b7aeeb..2f14d1b 100644 --- a/core/modules/contextual/contextual.js +++ b/core/modules/contextual/contextual.js @@ -7,48 +7,132 @@ "use strict"; -Drupal.contextualLinks = Drupal.contextualLinks || {}; - /** * Attaches outline behavior for regions associated with contextual links. */ Drupal.behaviors.contextualLinks = { 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 () { - $links.stop(true, true).slideToggle(100); - $wrapper.toggleClass('contextual-active'); - return false; - } - ); - // Attach hover behavior to trigger and ul.contextual-links. - $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-links', function () { + var $this = $(this); + $this.data('drupal-contextual', new Drupal.contextualLinks(this, $this.closest('.contextual-region'))); }); } }; /** - * Disables outline for the region contextual links are associated with. + * Contextual links object. */ -Drupal.contextualLinks.mouseleave = function () { - $(this) - .find('.contextual-active').removeClass('contextual-active') - .find('.contextual-links').hide(); +Drupal.contextualLinks = function(links, region) { + this.links = links; + this.region = region; + + this.isHighlighted = false; + this.triggerHighlighted = false; + this.isOpen = false; + + this.init(); +}; + +Drupal.contextualLinks.prototype.init = function() { + var self = this; + + this.wrapper = $('
') + .insertBefore(this.links) + .append(this.links) + // Only store a reference to the DOM element itself to preserve memory. + [0]; + + this.region.mouseenter(function () { + self.highlightTrigger(true); + }) + .mouseleave(function () { + self.highlightTrigger(false); + }); + + this.trigger = $('') + .text(Drupal.t('Configure')) + .click(function () { + self.showLinks(); + return false; + }) + .focus(function () { + self.highlightTrigger(true); + }) + .focusout(function () { + self.highlightTrigger(false); + }) + .prependTo(this.wrapper); + + this.trigger + .add(this.links) + .mouseenter(function () { + self.highlightRegion(true); + }) + .mouseleave(function () { + self.highlightRegion(false); + }); + + // Hide the contextual links when mouse is moved outside of its region. + $(this.region).mouseleave(function () { + self.showLinks(false); + }); + + // Hide the contextual links when focus is moved outside of contextual links + // wrapper, for example when user uses keyboard (TAB) navigation. + $(document).bind('focusin', function (e) { + if (self.isOpen && !$(e.target).closest('div.contextual', self.region).length) { + self.highlightRegion(false); + self.highlightTrigger(false); + self.showLinks(false); + } + }); +}; + +Drupal.contextualLinks.prototype.highlightTrigger = function(highlight) { + if (highlight === undefined) { + highlight = !this.isHighlighted; + } + + if (this.isHighlighted && !highlight) { + $(this.trigger).removeClass('contextual-links-trigger-active'); + this.isHighlighted = false; + } + else if (!this.isHighlighted && highlight) { + $(this.trigger).addClass('contextual-links-trigger-active'); + this.isHighlighted = true; + } +}; + +Drupal.contextualLinks.prototype.highlightRegion = function(highlight) { + if (highlight === undefined) { + highlight = !this.triggerHighlighted; + } + + if (this.triggerHighlighted && !highlight) { + $(this.region).removeClass('contextual-region-active'); + this.triggerHighlighted = false; + } + else if (!this.triggerHighlighted && highlight) { + $(this.region).addClass('contextual-region-active'); + this.triggerHighlighted = true; + } +}; + +Drupal.contextualLinks.prototype.showLinks = function(show) { + if (show === undefined) { + show = !this.isOpen; + } + + if (this.isOpen && !show) { + $(this.links).stop(true, true).slideUp(100); + $(this.wrapper).removeClass('contextual-links-active'); + this.isOpen = false; + } + else if (!this.isOpen && show) { + $(this.links).stop(true, true).slideDown(100); + $(this.wrapper).addClass('contextual-links-active'); + this.isOpen = true; + } }; })(jQuery); diff --git a/core/modules/contextual/contextual.module b/core/modules/contextual/contextual.module index 844e9ee..4c3daa3 100644 --- a/core/modules/contextual/contextual.module +++ b/core/modules/contextual/contextual.module @@ -63,8 +63,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.css b/core/modules/contextual/contextual.theme.css index 48d8e83..fb9fc2e 100644 --- a/core/modules/contextual/contextual.theme.css +++ b/core/modules/contextual/contextual.theme.css @@ -12,8 +12,9 @@ outline-offset: 1px; } .contextual { - right: 2px; /* LTR */ + right: 5px; /* LTR */ top: 2px; + font-size: 90%; } /** @@ -29,18 +30,21 @@ padding: 0 2px; text-indent: 34px; width: 28px; + position: absolute; + right: 0; + clip: rect(1px 1px 1px 1px); /* IE6, IE7 */ + clip: rect(1px, 1px, 1px, 1px); } -.contextual .trigger:hover, -.contextual-active .trigger { +.contextual-links-trigger-active { background-position: 2px -18px; + clip: auto !important; } -.contextual-active .trigger { - background-color: #ffffff; +.contextual-links-active .trigger { + background-color: #fff; border-bottom: none; border-color: #d6d6d6; -moz-border-radius: 4px 4px 0 0; /* FF3.6 */ border-radius: 4px 4px 0 0; - position: relative; z-index: 1; } @@ -68,6 +72,7 @@ list-style-image: none; margin: 0; padding: 0; + line-height: 100%; } .contextual-region .contextual .contextual-links a { display: block; @@ -87,4 +92,5 @@ } .contextual-region .contextual .contextual-links li a:hover { background-color: #bfdcee; + outline: none; }