I am making a custom element dialog box. It is NOT modal, so needs to detect a click outside of itself to close, but I should not close when the click is inside (unless the property closeOnClick is set). It uses a default <slot> for the contents of the dialog box. The html for it is like this (I use lit-element but that is not relevant to this question).
<style>
:host {
display: block;
}
dialog {
position: fixed;
padding: 10px;
margin:0;
border: none;
border-radius: 2px;
box-shadow: 0 0 40px rgba(0,0,0,0.1), 0 0 10px rgba(0,0,0,0.25);
}
</style>
<dialog
id="dialog"
#close="${this._dialogClosed}"
#keys-pressed="${this._keysPressed}">
<slot></slot>
</dialog>
I manage multiple instances by holding a Map of instances with the z-index as the value. When there is at least one open dialog box I have an event listener on window for the click event. When I receive a click I use the following code to see whether to close the dialog (this.sizingTarget is the dialog element within the shadowRoot of my element).
const keys = [...openDialogs.keys()];
const self = keys[keys.length -1];
if (self.clickReady && (self.closeOnClick || !self.sizingTarget.contains(e.composedPath()[0]))) {
//click was outside dialog (or we close on click), so close
self.close('click');
}
The problem is the test !self.sizingTarget.contains(e.composedPath()[0]) does not appear to work when the element clicked on is itself a custom element with some <slot> elements in it. I found a long discussion on github about this issue and it appears they resolved it by adding Node.isConnected property. But I can't work out how you are supposed to use it.
Can someone please explain how I should go about seeing if the element clicked is actually within the finally distributed nodes in my tree of elements and their (all open) shadowRoots.
It appears that !e.composedPath().includes(self.sizingTarget) works - at least it does were it worked before and the case I was struggling with above.
Related
I am in the process of finding a rich text editor to add to my application and came across TipTap. It looks great so I followed a tutorial I found on YouTube however no styles are being loaded on my site.
I have been searching for a solution for ages but can't find a replication of this problem.
No styles are being loaded on render so the editor is no more than a line of text indicating the menu buttons and a blank box (which is editable) for the input area.
All of the buttons behave as expected but there are no styles.
I noticed that if I try to render a simple HTML button on another page it also comes without default styling. Is there something obvious I'm missing here? I have been searching for a solution for a few hours now.
This is how my editor looks
This is how it should look
This is a HTML button also showing without a default style I tried to render on another page
<button type="button">Where is the style?</button>
I tried deleting the cache on Chrome however nothing changed
If anyone is in the same boat I have found a solution, each element on the page needs to be defined explicitly within the css file. Default behaviour wasn't working as expected so for the buttons I added to the style sheet:
button {
border: 2px solid black;
padding: 3px;
border-radius: 2px;
}
and styles appeared.
Adding .ProseMirror to each element had no effect on its styling so I had to style elements globally. This affected other components I had made such as a Button component, to overcome this I added editor to the class name of each button in the Editor component
<button
//
className={editor.isActive('underline') ? 'is-active' : 'editor'} //default is '' but 'editor' added
>
This allowed me to style the Editor buttons separately
button.editor {
border: 2px solid black;
padding: 6px;
width: 80x;
border-radius: 8px;
background-color: ##C1BCAC;
}
I try to make a select box whose entries are opened after clicking into the input box. After selecting one of the items, the dropdown should be closed again.
I want to achieve the open/close part of the dropdown without the use of javascript.
The html looks like this:
<div id="outer">
<input type="text" id="input">
<div id="results">
<div>Test 1 </div>
<div>Test 2 </div>
<div>Test 3 </div>
<div>Test 4 </div>
</div>
</div>
<div id="label">
</div>
After clicking onto an item, the selected value should appear below the #outer div (just for demonstration purposes).
The Javascript for assigning click events to the dropdown values:
document.querySelectorAll("#results div").forEach(setClick);
function setClick(node) {
node.addEventListener("click", setText.bind(null, node.innerHTML))
}
function setText(t) {
document.getElementById("label").innerHTML = t;
}
Now I will show you my first draft of css code:
#outer {
width: 200px;
position: relative;
}
#input {
width: 100%;
}
#results {
position: absolute;
width: 100%;
display: block;
visibility: hidden;
background-color: white;
}
#results > div:hover {
background-color: lightblue;
cursor: pointer;
}
#outer:focus-within #results, #results:hover {
visibility: visible;
}
This works like a charm but fails in one point:
After clicking an item, the dropdown is not closed. This is because of the #results:hover selector which is needed to keep the dropdown open after clicking onto an item. The click takes the focus out of the input field, thus the focus-within selector is not applied anymore. As the focus is removed from the input before the click occurs, the dropdown is hidden when the final click arrives in the document (this is my understanding of the problem).
Thus I use the hover selector which forces the div to keep open as long as the mouse is above the div.
You can test this here:
https://jsfiddle.net/hcetz1og/3/
My solution for this was a transition that hides the dropdown after the focus has been taken away:
#outer:not(:focus-within) #results:hover {
visibility: hidden;
transition-property: visibility;
/*use 10 ms and the clicked value in the drop down won't be shown */
transition-delay: 100ms;
transition-timing-function: step-end;
}
This works on my machine when I use 100ms as a delay. If I use 10ms, I have the same problem again. It seems that the click event is triggered "very" late.
Feel free to test it here:
https://jsfiddle.net/hcetz1og/2
Question:
How long will it take until the click event arrives at the document? Is there a fixed time span I have to wait or can the delay depend on every machine?
If so, I am forced to not use plain CSS but must use javascript for this I think.
Edit:
Feel free to post an alternative solution using plain css. But please be aware that I mainly want to focus on getting an answer to this question, not alternative solutions.
As #Mark Baijens said in the comments, using timeouts is a bad practice, so here is a pretty clean solution.
I used JavaScript to render the dropdown, not the CSS, because the CSS is where Your issue is coming from.
I don't know why would You want to set the innerHTML, but not some other property, like style.visibility for example. It just doesn't make sense to me, so with that in mind, let's get our hands on this :)
Working demo >> HERE <<.
Step 1 - remove the #outer...:hover parts of CSS
So, You are left with this:
#outer {
width: 200px;
position: relative;
}
#input {
width: 100%;
}
#results {
position: absolute;
width: 100%;
display: block;
visibility: hidden;
background-color: white;
}
#results > div:hover {
background-color: lightblue;
cursor: pointer;
}
Step 2 - add the onfocus event to the input field
Just assign a function call to the onfocus attribute of the input. Everything else in the HTML stays the same.
<div id="outer">
<input type="text" id="input" onfocus="showElements()">
<div id="results">
<div>Test 1 </div>
<div>Test 2 </div>
<div>Test 3 </div>
<div>Test 4 </div>
</div>
</div>
<div id="label">
</div>
Step 3 - create the showElements and hideElements function:
function showElements() {
document.getElementById("results").style.visibility = 'visible';
}
function hideElements() {
document.getElementById("results").style.visibility = 'hidden';
}
Step 4 - call the hideElements() when clicked outside the input element
There are two cases for the click outside the input element:
Case 1 - we clicked on one of the divs inside the #results wrapper
Case 2 - clicking outside the input field, but not on one of the divs inside the #results wrapper
In the first case, we will modify the assignment of the onclick handler like this:
document.querySelectorAll("#results div").forEach(setClick);
function setClick(node) {
node.addEventListener("click", setTextAndHideElements.bind(null, node.innerHTML));
}
So, the setText function now becomes setTextAndHideElements and looks like this:
function setTextAndHideElements(t) {
document.getElementById("label").innerHTML = t;
hideElements();
}
For the second case (clicking outside the input field, but not on one of the divs inside the #results wrapper), we must watch for the click on the whole page (document element), and respond to the action like this:
document.onclick = function(e) {
if (e.target.id !== 'input'){
hideElements();
}
}
Note: this will override any previously assigned onclick events assigned to the document element.
As mentioned in the beginning, working demo is >> HERE (codepen.io) <<.
I tried another solution which requires no setting of additional JS events.
See: https://jsfiddle.net/hcetz1og/4/
I gave every result item a tabindex of "0" to ensure, those items can be focusable.
Then i removed the #outer:not() part from the css and replaced the hover selector with this: #results:focus-within. Additional I called node.blur() on the node after clicking onto them.
Summary:
Change in HTML:
<div tabindex="0">Test 1 </div>
Change in JS:
function setText(t, node) {
document.getElementById("label").innerHTML = t;
node.blur();
}
Change in CSS:
#outer:focus-within #results, #results:focus-within {
visibility: visible;
}
What do you think about this one? Should be stable I think because the focus onto the #results div is set before the click event is triggered onto the result item.
Event order should be (based on my observation):
input focus -> input blur -> item focus -> item click
Not sure if the step between blur and focus can lead to a visible problem. Theoretically, the results div must be hidden and shown again in a very small amount of time.
But I investigated this with chrome's performance timeline and did not recognize a new render between both events. One can see, that the result item is focused (outline is set onto it) and then it disappears as expected.
I have set a popup to open at mouse over on an element. The popup should close when the cursor is off it.
For some reason the popup window closes when the cursor is right off its opener button, instead.
You can see an example here:
http://www.friends.wwz.co.il/Lab/Gefen/Generali/es/popup.html
Please try to hover with the mouse on the "lee mas" button. A popup will open. It should close at hovering off it. But instead it closes at hovering off the lee mas button, so it closes immediately.
Any idea where do I go wrong?
Many thanks in advance for you advice
The main problem is you are attaching hover events to the button. Once you hover out of the button element, it fires the hoverOut button.
So the ideal course of action can be:
Bind hover event to the button.
Once hovered, a pop-up is dynamically added.
Then, a hover event is bounded to the pop.
And the code of dissolving the pop-up is attached in the hover-out function.
So that when actually the cursor is hovered out of the pop-up it dissolves.
Apart from that, Just have a look at this fiddle. It has two hyperlinks for hover. The first is the one you are facing. The second one is the one you are looking for. :D
Code for it:
$(document).ready(function() {
$("#toggleSwitch_j").hover(
function() {
$("#theBox_3").slideDown(500);
}, function() {
$("#theBox_3").slideUp(500);
});
$("#StayOpen").hover(
function() {
$("#theBox_2").slideDown(500);
}, function() {
$("#theBox_2").slideUp(500);
});
});
body {
background-color: #eef;
}
#theBox_3,
#theBox_2 {
display: none;
border: 1px solid #000;
width: 200px;
height: 100px;
background-color: #ddf;
}
#toggleSwitch_j,
#StayOpen {
background-color: #cacaca;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
This layout will only keep the hidden div visible as long as you point to the link
<br>You'll never be able to reach anything inside the div
<br>jQuery Hover
<div id="theBox_3">Peek-a-boo!</div>
<hr>This layout puts the link and hidden div inside a wrapper - hovering anywhere inside the wrapper expands the hidden div, so you can reach content inside it. This would be handy if you need to put links or form elements inside the hidden div, instead of
just text to read.
<div id="StayOpen">
jQuery Hover
<div id="theBox_2">Peek-a-boo!</div>
</div>
Web development isn't my strongest point, so apologies if this is foolish, but I want to have a div that is tappable on mobile so I can intercept the tap and do things.
I'm attaching a touchdown event listener to the div in JavaScript, and when I tap, I color the div to indicate it's been selected.
However, if there's an anchor tag within the div with some text in it (totally valid in this case), I don't want the div to be highlighted when you're just clicking the link.
I'm not sure the term in web development, but is there a way to make the anchor tag intercept touches so they don't get sent to the div if the anchor tag is selected?
You can use a conditional in the event handler to test if the event target is an anchor. I used the click event in my example, but it should work with touchdown also.
var tapDiv = document.querySelector('#tap');
tapDiv.addEventListener('click', function(e) {
//conditional ensures the clicked element isn't an <a>
if (e.target.tagName.toLowerCase() !== 'a') {
e.currentTarget.style.backgroundColor = 'orange';
}
});
#tap {
padding: 8px;
background-color: black;
}
#tap a {
display: inline-block;
padding: 8px;
background-color: white;
}
<div id="tap">This is the anchor</div>
I'm having a problem where the left two pixels of a Font-Awesome icon I've placed inside of a button element do not trigger the click event of the button.
Here's an example button:
<button class="btn btn-mini">
<i class="icon-edit"></i>
</button>
And here's what it looks like with bootstrap
Any ideas for why those left two pixels don't trigger a click event?
Edit: Here's a test site where I've managed to recreate the issue: http://ace.cwserve.com
I know this post is 4 years old but it might help people understand why a font-awesome "icon" inside a button prevents the click event.
When rendered, the icon class adds a ::before pseudo-element to the icon tag that prevents the button's click event.
Given this situation, we should definitly take a look at the CSS pointer-events Property
The pointer-events property defines whether or not an element reacts
to pointer events.
So we just need to add this css declaration for the "icon" which is inside a button:
button > i {
pointer-events: none;
}
Outline
The outline isn't part of the CSS box, which means it won't fire click events. This is perhaps slightly counter-intuitive, but that's how it works ...
Your page sets an outline on .btn:focus, but this doesn't seem to be the problem, since it has an offset of -2 (meaning it's displayed inside the box, rather than outside of it).
Moving the box on :active
You can move the box on :active, which can cause neat effect, but first the box is moved, and then will the click event be fired, which will use the moved position.
You can see this in action by keeping the mouse button pressed; the box will move, but the event won't be fired until you release the button. So if you move your box to the right by then pixels, then the left 10 pixels won't do anything.
This is according to spec, from the DOM spec:
click
The click event occurs when the pointing device button is clicked over an element. A click is defined as a mousedown and mouseup
over the same screen location. The sequence of these events is:
mousedown
mouseup
click
This seems to be the problem, this following CSS seems to solve it:
button.btn:active {
left: 1px;
top: 1px;
}
Example
Here's a script to demonstrate both issues:
<!DOCTYPE html>
<html><head><style>
body { margin-left: 30px; }
div {
background-color: red;
border: 20px solid green;
outline: 20px solid blue;
width: 100px;
height: 100px;
position: relative;
}
div:active {
left: 20px;
top: 20px;
}
</style></head> <body>
<div></div>
<script src="http://code.jquery.com/jquery-1.11.0.min.js"></script>
<script>
$('div').on('click', function(e) {
alert('click!');
});
</script></body></html>