I have created a flexbox container that acts as a form control with two input fields, representing a range between two dates. If the user has entered something into any of the two fields a cross appears next to that field. The user can click on this cross to clear the field.
The cross is an :after
pseudo-element, created and interacted with, with some nifty css and JavaScript, largely based on this answer on Stack Overflow. Here's my implementation (somewhat simplified, but not much):
$('.field.clearable').on('change input', 'input', function($e) {
$(this).parent().toggleClass('non-empty', !!this.value);
})
.on('mousemove', 'span.non-empty', function($e) {
var $this = $(this);
$this.toggleClass('onX', $this.width() < $e.clientX - $this.offset().left);
$this.hasClass('onX') ? $this.attr('title', 'Clear field') : $this.removeAttr('title');
})
.on('mouseleave', 'span.non-empty', function($e) {
var $this = $(this);
$this.removeClass('onX')
.removeAttr('title');
})
.on('click', 'span.non-empty.onX', function($e) {
$e.preventDefault();
var $this = $(this);
$this.removeClass('non-empty onX')
.removeAttr('title')
.children('input')
.val('')
.trigger('clear');
});
/* reset and cosmetic css */
* {
margin: 0;
padding: 0;
color: inherit;
font-family: inherit;
font-size: inherit;
}
::-webkit-input-placeholder {
color: #ccc;
font-size: 80%;
opacity: .8;
}
:-moz-placeholder {
color: #ccc;
font-size: 80%;
opacity: .8;
}
::-moz-placeholder {
color: #ccc;
font-size: 80%;
opacity: .8;
}
:-ms-input-placeholder {
color: #ccc;
font-size: 80%;
opacity: .8;
}
:focus::-webkit-input-placeholder {
opacity: .5;
}
:focus:-moz-placeholder {
opacity: .5;
}
:focus::-moz-placeholder {
opacity: .5;
}
:focus:-ms-input-placeholder {
opacity: .5;
}
html {
font-family: Arial;
font-size: 10px;
font-weight: normal;
text-align: left;
height: 100%;
}
body {
margin: 4em auto;
width: 400px;
color: #2c5ba0;
font-size: 1.5rem;
line-height: 1.5em;
background-color: #fff;
}
/* relevant css */
div.field {
display: inline-block;
margin: .4em 0;
}
div.field>label {
display: block;
margin: 0 0 .3em .2em;
font-size: 80%;
line-height: 1em;
}
div.field>span {
position: relative;
display: -webkit-flex;
display: flex;
-webkit-align-items: center;
align-items: center;
-webkit-justify-content: space-between;
justify-content: space-between;
box-sizing: border-box;
height: 2em;
border: .1em solid #ddd;
background-color: #fff;
}
div.field>span>span {
-webkit-flex: 1;
flex: 1;
}
div.field>span>span>input {
width: 100%;
border: none;
}
div.field.range>span input {
text-align: center;
}
div.field.range>span label {
-webkit-flex: 0;
flex: 0;
margin: 0 .2em;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
div.field.clearable span.non-empty {
position: relative;
padding-right: 2em;
}
div.field.clearable span.non-empty:after {
position: absolute;
right: 0;
top: 0;
content: 'x';
display: inline-block;
width: 2em;
height: 2em;
color: #ddd;
vertical-align: middle !important;
text-align: center !important;
font-style: normal !important;
font-weight: normal !important;
text-transform: none !important;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
cursor: pointer;
}
div.field.clearable span.non-empty.onX:after {
color: #f37e31;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="field clearable range">
<label for="start">created</label>
<span>
<span>
<input type="text" id="start" placeholder="from">
</span>
<label for="end">–</label>
<span>
<input type="text" id="end" placeholder="until">
</span>
</span>
</div>
When both <input>
fields contain values and thus have a cross next to them, their width-ratios cancel each other out nicely and nothing is wrong. However, when only one of the <input>
fields contains a value and the cross appears next to only that <input>
field, the cross / <input>
field nudges the other content aside.
This is not supposed to happen. I suspect the <input>
fields are the culprit. Even though the container div.field
should keep its display: block
flexibility, the ratios of the inner content should stay constant.
Here are the relevant bits I thought I could achieve this with:
/* the span that acts as the flexbox container */
div.field>span {
position: relative;
display: -webkit-flex;
display: flex;
-webkit-align-items: center;
align-items: center;
-webkit-justify-content: space-between;
justify-content: space-between;
box-sizing: border-box;
height: 2em;
border: .1em solid #ddd;
}
/* let the spans, containing the <input> fields, take available space */
div.field>span>span {
-webkit-flex: 1;
flex: 1;
}
/*
let <input> fields take 100% content space of span
I thought this should take into account any padding on its parent
*/
div.field>span>span>input {
width: 100%;
border: none;
}
/* let center label (the dash) not be flexible */
div.field.range>span label {
-webkit-flex: 0;
flex: 0;
margin: 0 .2em;
}
/* if .non-empty class was set by JavaScript */
/*
make room for :after by setting padding-right accordingly
but this is the issue:
I thought this would also make the <input> shrink accordingly,
but it appears it doesn't shrink the amount I anticipated
*/
div.field.clearable span.non-empty {
position: relative;
padding-right: 2em;
}
/* show :after pseudo-element */
div.field.clearable span.non-empty:after {
position: absolute;
right: 0;
top: 0;
content: 'x';
display: inline-block;
width: 2em;
height: 2em;
/* etc. */
}
Do you know how I can let the complete control element maintain its flexibility, including its :after
trickery, but have the <input>
s shrink accordingly, when one of the crosses appear?
Or put more simply, I guess: is there a way to let two flex-items, with flex: 1
, both take up an equal amount of space, at any given time?
The reason I'm using :after
elements and not background-image
like in the original answer I took the inspiration from, is because I'm using a custom icon font and not icon images.