16

In the following example, when I mouse over the 'X' button, the list-item hover style gets enabled as well, I do not want this to happen.

Is it possible to have a hover style on the button independent of the hover style on the list-group-item? Something like prevent the 'hover' propagation?

Is there any other way to achieve that? Maybe assembling all of this HTML/CSS/JS in a different way?

Working sample here

<ul class="list-group">
  <li class="list-group-item">
    Lalalalaiaia
                <button class="btn btn-default btn-xs pull-right remove-item">
      <span class="glyphicon glyphicon-remove"></span>
    </button>
  </li>
  <li class="list-group-item">
    Panananannaeue 
                <button class="btn btn-default btn-xs pull-right remove-item">
      <span class="glyphicon glyphicon-remove"></span>
    </button>
  </li>
</ul>

CSS

.list-group-item:hover {
  background: #fafafa;
  cursor: pointer;
}

JavaScript

  $('.list-group-item').on('click', function(){
    console.log('clicked item');
  });

  $('.remove-item').on('click', function(e){
    console.log('clicked remove-item btn');
    e.stopPropagation();
  });

UPDATE

The problem seems to be that when hovering the inner X button, the mouse actually doesn't leave the 'list-group-item' element, thus, it keeps the hover state.

I was able to solve it by manually dispatching mouseenter and mouseleave on the 'list-group-item' in the mouseleave and mouseenter event of the 'remove-item' button, respectively, without the need to use 'event.stopPropagation()' (except for the button click handler).

The drawback is that I need a mouseenter and a mouseleave event handler for both elements. Preferably I'd use only CSS, but that seems to be impossible.

I'm just not sure whether this is a clean solution, what do you think?

Working sample here

CSS

.list-group-item.mouseover {
  background: #fafafa;
  cursor: pointer;
}

.list-group-item .remove-item.mouseover {
  background: #aaf;
  cursor: pointer;
}

JavaScript

  // LIST-ITEM EVENT HANDLERS

  $('.list-group-item').on('mouseenter', function(e){
    $(this).addClass('mouseover');
  }).on('mouseleave', function(e){
    $(this).removeClass('mouseover');
  });

  $('.list-group-item').on('click', function(){
    console.log('clicked item');
  });

  // LIST-ITEM REMOVE BUTTON EVENT HANDLERS

  $('.remove-item').on('mouseenter', function(e){
    $(this).addClass('mouseover');
    $(this).parent().mouseleave();
  }).on('mouseleave', function(e){
    $(this).removeClass('mouseover');
    $(this).parent().mouseenter();
  });

  $('.remove-item').on('click', function(e){
    console.log('clicked remove-item btn');
    e.stopPropagation();
  });
zok
  • 6,065
  • 10
  • 43
  • 65
  • 3
    Just make a style for `.list-group-item:hover button` that un-does the hover style for the group item itself. – Pointy Aug 11 '14 at 15:07
  • 3
    @Pointy - But a rule on `.list-group-item:hover button` would change the CSS for the button, not the list item, which is what the OP wants to change. He wants it when you hover over the button, that the list hover doesn't activate (or doesn't look like it activates). – j08691 Aug 11 '14 at 15:15
  • @j08691 oh well I guessed that he didn't want the button to be affected by the inheritable styles on the list group, but looking at those I'm not sure why; the background probably won't change the button background. Maybe it's the cursor? – Pointy Aug 11 '14 at 15:19
  • @Pointy, it's the other way round, like j08691 wrote :) – zok Aug 11 '14 at 17:32
  • 1
    @zok Oh oh I see. So you want it to darken the item when you're over it, but then when you're over the button it should *not* keep the item darkened. That's hard/impossible with CSS. I kind-of wonder if it wouldn't look a little weird anyway; I mean, the cursor is still over the item, after all. – Pointy Aug 11 '14 at 18:02
  • @Pointy great point (no pun intended). I managed to do exactly what I wanted to (pelase see my update), but maybe that would actually confuse the user. As long as the click event on the button stops propagation, everything is fine and intuitive, right? – zok Aug 11 '14 at 18:14
  • ...what would basically make this question a non-issue :) – zok Aug 11 '14 at 18:18
  • @zok yes it's weird when problems start off seeming big and then somehow reality shifts around them :) – Pointy Aug 11 '14 at 18:24

4 Answers4

5

This is impossible to do with CSS only, except the not-so-clean way described by @Pointy. You can do this with javascript by using event.stopPropagation(). So your hover style should become a class that you toggle on mouseover.

This question is a duplicate of css :hover only affect top div of nest

Community
  • 1
  • 1
Issam Zoli
  • 2,724
  • 1
  • 21
  • 35
  • Yeah, I guess the only way is to have an event handler to handle the mouseover event. or to place the remove button outside the `li` but next to it somehow, but that doesn't seem to be a good idea – zok Aug 11 '14 at 17:41
4

Ok so there is actually a solution that only requires the use of CSS (no HTML or JS stuff)

The following selector will only select those elements with the class "parent" on hover, which do not have a child with the class "child" that is also being hovered on.

.parent:has(:not(.child:hover)):hover {}

The only problem I can see with the :has() selector/pseudo class is browser support (especially older versions) - so before you use it check the currerrent compatibility lists to see if it fits your requirements.

Lord-JulianXLII
  • 1,031
  • 1
  • 6
  • 16
  • I was reading about this last week! It seems it's only missing in Firefox for now (among the popular ones) – zok Oct 04 '22 at 08:42
  • 1
    @zok yeah but even in Firefox you can already activate it manually. So pretty sure it will be added there soonish. – Lord-JulianXLII Oct 04 '22 at 16:47
3

You can make a negation caluse like Pointy suggests but a more solid solution involves adding an extra node. The idea is that the row and the button become proper siblings since you can't style a TextNode.

<ul class="list-group">
  <li class="list-group-item">
    <div>Lalalalaiaia</div>
    <button class="btn btn-default btn-xs pull-right remove-item">
      <span class="glyphicon glyphicon-remove"></span>
    </button>
  </li>
  <li class="list-group-item">
    <div>Panananannaeue</div>
    <button class="btn btn-default btn-xs pull-right remove-item">
      <span class="glyphicon glyphicon-remove"></span>
    </button>
  </li>
</ul>

Now you can do:

.list-group-item div:hover {
  background: #fafafa;
  cursor: pointer;
}

You will need some extra trickery to get the button in the right place, like:

// untested
.list-group-item {
  position: relative;
}
.list-group-item button {
  position: absolute;
  top: 5px;
  left: 5px;
}
Halcyon
  • 57,230
  • 10
  • 89
  • 128
  • That's a good idea, and the button would be perfectly aligned by adding `display: inline` to the wrapping div, but the background color would be applied only to the text, not the whole list-item background.. – zok Aug 11 '14 at 17:39
  • Remove any padding from the list-item. It doesn't matter if the background is on the list-item or the div. – Halcyon Aug 12 '14 at 09:22
2

I could not find an answer that worked in all cases, and was also simple to implement. Sadly, there appears to be no consistent solution that is purely CSS and/or requires special arrangements of the HTML.

Here is a jQuery solution that seems to work in all cases.

Any element with .ui-hoverable will receive a .ui-hover class that does not propagate. So you can stack .ui-hoverable elements and only the top-most under the mouse will have the .ui-hover class.

$('.ui-hoverable').each(function() {
    var el = $(this);
    el.on('mousemove', function () {
        var parent = $(event.target).closest('.ui-hoverable');
        if(parent.length && parent[0] == el[0]) {
           el.addClass('ui-hover');
           return;
        }
        el.removeClass('ui-hover');
    });
    el.on('mouseleave', function () {
        el.removeClass('ui-hover');
    });
});

This works because the mousemove event searches for the closest .ui-hoverable and if it is not the current element the .ui-hover is removed. So the top most will receive the .ui-hover and an element under it will have it removed.

Enjoy, report any problems.

Thanks,

Reactgular
  • 52,335
  • 19
  • 158
  • 208