Skip to content

Commit e926b6e

Browse files
committed
Use a flexbox-based layout
This change move away from floats and absolute positioning in favor of flexbox. Flexbox allows us to solve some of the more quirky issues we're having with elements (e.g, the input) being too big, causing "extra line breaks", vertical alignment of close buttons, etc... and simplified RTL support! I did need to introduce two new child elements to the `dropdown-toggle` element. These are used to group all of the selected tags and the input in one group. And the "actions" (clear button, dropdown indicator, and spinner) in another. Doing so has the added benefit of no longer allowing selected options from running "under" those other elements. NOTE: The large blocks of change are due to white space differences from indenting inside those new wrapper elements. View the diff ignoring white space to see a more accurate representation of the change here.
1 parent a0c8efe commit e926b6e

File tree

1 file changed

+104
-95
lines changed

1 file changed

+104
-95
lines changed

src/components/Select.vue

Lines changed: 104 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -3,42 +3,45 @@
33
position: relative;
44
font-family: inherit;
55
}
6-
76
.v-select,
87
.v-select * {
98
-webkit-box-sizing: border-box;
109
-moz-box-sizing: border-box;
1110
box-sizing: border-box;
1211
}
13-
/* Rtl support */
14-
.v-select.rtl .open-indicator {
15-
left: 10px;
16-
right: auto;
12+
13+
/* Rtl support - Because we're using a flexbox-based layout, the `dir="rtl"` HTML
14+
attribute does most of the work for us by rearranging the child elements visually.
15+
*/
16+
.v-select[dir="rtl"] .v-select__actions {
17+
padding: 0 3px 0 6px;
1718
}
18-
.v-select.rtl .selected-tag {
19-
float: right;
19+
.v-select[dir="rtl"] .dropdown-toggle .clear {
20+
margin-left: 6px;
21+
margin-right: 0;
22+
}
23+
.v-select[dir="rtl"] .selected-tag {
2024
margin-right: 3px;
2125
margin-left: 1px;
2226
}
23-
.v-select.rtl .dropdown-menu {
24-
text-align: right;
27+
.v-select[dir="rtl"] .selected-tag .close {
28+
margin-left: 0;
29+
margin-right: 2px;
2530
}
26-
.v-select.rtl .dropdown-toggle .clear {
27-
left: 30px;
28-
right: auto;
31+
.v-select[dir="rtl"] .dropdown-menu {
32+
text-align: right;
2933
}
34+
3035
/* Open Indicator */
3136
.v-select .open-indicator {
32-
position: absolute;
33-
bottom: 6px;
34-
right: 10px;
3537
display: inline-block;
3638
cursor: pointer;
3739
pointer-events: all;
3840
transition: all 150ms cubic-bezier(1.000, -0.115, 0.975, 0.855);
3941
transition-timing-function: cubic-bezier(1.000, -0.115, 0.975, 0.855);
4042
opacity: 1;
41-
height: 20px; width: 10px;
43+
height: 16px;
44+
width: 12px; /* To account for extra width from rotating. */
4245
}
4346
.v-select .open-indicator:before {
4447
border-color: rgba(60, 60, 60, .5);
@@ -48,7 +51,7 @@
4851
display: inline-block;
4952
height: 10px;
5053
width: 10px;
51-
vertical-align: top;
54+
vertical-align: text-top;
5255
transform: rotate(133deg);
5356
transition: all 150ms cubic-bezier(1.000, -0.115, 0.975, 0.855);
5457
transition-timing-function: cubic-bezier(1.000, -0.115, 0.975, 0.855);
@@ -64,16 +67,16 @@
6467
.v-select.open .open-indicator {
6568
bottom: 1px;
6669
}
70+
6771
/* Dropdown Toggle */
6872
.v-select .dropdown-toggle {
6973
-webkit-appearance: none;
7074
-moz-appearance: none;
7175
appearance: none;
72-
display: block;
76+
display: flex;
7377
padding: 0;
7478
background: none;
7579
border: 1px solid rgba(60, 60, 60, .26);
76-
min-height: 36px;
7780
border-radius: 4px;
7881
white-space: normal;
7982
}
@@ -85,12 +88,20 @@
8588
clear: both;
8689
height: 0;
8790
}
91+
.v-select .v-select__selected-options {
92+
display: flex;
93+
flex-basis: 100%;
94+
flex-grow: 1;
95+
flex-wrap: wrap;
96+
}
97+
.v-select .v-select__actions {
98+
display: flex;
99+
align-items: center;
100+
padding: 0 6px 0 3px;
101+
}
88102
89103
/* Clear Button */
90104
.v-select .dropdown-toggle .clear {
91-
position: absolute;
92-
bottom: 9px;
93-
right: 30px;
94105
font-size: 23px;
95106
font-weight: 700;
96107
line-height: 1;
@@ -99,6 +110,7 @@
99110
border: 0;
100111
background-color: transparent;
101112
cursor: pointer;
113+
margin-right: 6px;
102114
}
103115
104116
/* Dropdown Toggle States */
@@ -138,31 +150,27 @@
138150
}
139151
/* Selected Tags */
140152
.v-select .selected-tag {
153+
display: flex;
154+
align-items: baseline;
141155
color: #333;
142156
background-color: #f0f0f0;
143157
border: 1px solid #ccc;
144158
border-radius: 4px;
145159
height: 26px;
146160
margin: 4px 1px 0px 3px;
147161
padding: 1px 0.25em;
148-
float: left;
149162
line-height: 24px;
150163
}
151164
.v-select.single .selected-tag {
152165
background-color: transparent;
153166
border-color: transparent;
154167
}
155-
.v-select.single.open .selected-tag {
156-
position: absolute;
157-
opacity: .5;
158-
}
159-
.v-select.single.open.searching .selected-tag,
168+
.v-select.single.open .selected-tag,
160169
.v-select.single.loading .selected-tag {
161170
display: none;
162171
}
163172
.v-select .selected-tag .close {
164-
float: none;
165-
margin-right: 0;
173+
margin-left: 2px;
166174
font-size: 20px;
167175
appearance: none;
168176
padding: 0;
@@ -257,9 +265,6 @@
257265
/* Loading Spinner */
258266
.v-select .spinner {
259267
opacity: 0;
260-
position: absolute;
261-
top: 5px;
262-
right: 10px;
263268
font-size: 5px;
264269
text-indent: -9999em;
265270
overflow: hidden;
@@ -322,58 +327,62 @@
322327

323328
<template>
324329
<div :dir="dir" class="dropdown v-select" :class="dropdownClasses">
325-
<div ref="toggle" @mousedown.prevent="toggleDropdown" :class="['dropdown-toggle', 'clearfix']">
326-
327-
<slot v-for="option in valueAsArray" name="selected-option-container"
328-
:option="(typeof option === 'object')?option:{[label]: option}" :deselect="deselect" :multiple="multiple" :disabled="disabled">
329-
<span class="selected-tag" v-bind:key="option.index">
330-
<slot name="selected-option" v-bind="(typeof option === 'object')?option:{[label]: option}">
331-
{{ getOptionLabel(option) }}
332-
</slot>
333-
<button v-if="multiple" :disabled="disabled" @click="deselect(option)" type="button" class="close" aria-label="Remove option">
334-
<span aria-hidden="true">&times;</span>
335-
</button>
336-
</span>
337-
</slot>
338-
339-
<input
340-
ref="search"
341-
v-model="search"
342-
@keydown.delete="maybeDeleteValue"
343-
@keyup.esc="onEscape"
344-
@keydown.up.prevent="typeAheadUp"
345-
@keydown.down.prevent="typeAheadDown"
346-
@keydown.enter.prevent="typeAheadSelect"
347-
@blur="onSearchBlur"
348-
@focus="onSearchFocus"
349-
type="search"
350-
class="form-control"
351-
:class="inputClasses"
352-
autocomplete="off"
353-
:disabled="disabled"
354-
:placeholder="searchPlaceholder"
355-
:tabindex="tabindex"
356-
:readonly="!searchable"
357-
:id="inputId"
358-
aria-label="Search for option"
359-
>
360-
361-
<button
362-
v-show="showClearButton"
363-
:disabled="disabled"
364-
@click="clearSelection"
365-
type="button"
366-
class="clear"
367-
title="Clear selection"
368-
>
369-
<span aria-hidden="true">&times;</span>
370-
</button>
371-
372-
<i v-if="!noDrop" ref="openIndicator" role="presentation" class="open-indicator"></i>
373-
374-
<slot name="spinner">
375-
<div class="spinner" v-show="mutableLoading">Loading...</div>
376-
</slot>
330+
<div ref="toggle" @mousedown.prevent="toggleDropdown" class="dropdown-toggle clearfix">
331+
332+
<div class="v-select__selected-options">
333+
<slot v-for="option in valueAsArray" name="selected-option-container"
334+
:option="(typeof option === 'object')?option:{[label]: option}" :deselect="deselect" :multiple="multiple" :disabled="disabled">
335+
<span class="selected-tag" v-bind:key="option.index">
336+
<slot name="selected-option" v-bind="(typeof option === 'object')?option:{[label]: option}">
337+
{{ getOptionLabel(option) }}
338+
</slot>
339+
<button v-if="multiple" :disabled="disabled" @click="deselect(option)" type="button" class="close" aria-label="Remove option">
340+
<span aria-hidden="true">&times;</span>
341+
</button>
342+
</span>
343+
</slot>
344+
345+
<input
346+
ref="search"
347+
v-model="search"
348+
@keydown.delete="maybeDeleteValue"
349+
@keyup.esc="onEscape"
350+
@keydown.up.prevent="typeAheadUp"
351+
@keydown.down.prevent="typeAheadDown"
352+
@keydown.enter.prevent="typeAheadSelect"
353+
@blur="onSearchBlur"
354+
@focus="onSearchFocus"
355+
type="search"
356+
class="form-control"
357+
:class="inputClasses"
358+
autocomplete="off"
359+
:disabled="disabled"
360+
:placeholder="searchPlaceholder"
361+
:tabindex="tabindex"
362+
:readonly="!searchable"
363+
:id="inputId"
364+
aria-label="Search for option"
365+
>
366+
367+
</div>
368+
<div class="v-select__actions">
369+
<button
370+
v-show="showClearButton"
371+
:disabled="disabled"
372+
@click="clearSelection"
373+
type="button"
374+
class="clear"
375+
title="Clear selection"
376+
>
377+
<span aria-hidden="true">&times;</span>
378+
</button>
379+
380+
<i v-if="!noDrop" ref="openIndicator" role="presentation" class="open-indicator"></i>
381+
382+
<slot name="spinner">
383+
<div class="spinner" v-show="mutableLoading">Loading...</div>
384+
</slot>
385+
</div>
377386
</div>
378387

379388
<transition :name="transition">
@@ -706,12 +715,12 @@
706715
watch: {
707716
/**
708717
* When the value prop changes, update
709-
* the internal mutableValue.
718+
* the internal mutableValue.
710719
* @param {mixed} val
711720
* @return {void}
712721
*/
713722
value(val) {
714-
this.mutableValue = val
723+
this.mutableValue = val
715724
},
716725
717726
/**
@@ -720,7 +729,7 @@
720729
* @param {string|object} old
721730
* @return {void}
722731
*/
723-
mutableValue(val, old) {
732+
mutableValue(val, old) {
724733
if (this.multiple) {
725734
this.onChange ? this.onChange(val) : null
726735
} else {
@@ -739,24 +748,24 @@
739748
},
740749
741750
/**
742-
* Maybe reset the mutableValue
751+
* Maybe reset the mutableValue
743752
* when mutableOptions change.
744753
* @return {[type]} [description]
745754
*/
746755
mutableOptions() {
747756
if (!this.taggable && this.resetOnOptionsChange) {
748-
this.mutableValue = this.multiple ? [] : null
757+
this.mutableValue = this.multiple ? [] : null
749758
}
750759
},
751760
752761
/**
753-
* Always reset the mutableValue when
762+
* Always reset the mutableValue when
754763
* the multiple prop changes.
755764
* @param {Boolean} val
756765
* @return {void}
757766
*/
758767
multiple(val) {
759-
this.mutableValue = val ? [] : null
768+
this.mutableValue = val ? [] : null
760769
}
761770
},
762771
@@ -765,9 +774,9 @@
765774
* attach any event listeners.
766775
*/
767776
created() {
768-
this.mutableValue = this.value
777+
this.mutableValue = this.value
769778
this.mutableOptions = this.options.slice(0)
770-
this.mutableLoading = this.loading
779+
this.mutableLoading = this.loading
771780
772781
this.$on('option:created', this.maybePushTag)
773782
},
@@ -979,7 +988,7 @@
979988
searchable: this.searchable,
980989
unsearchable: !this.searchable,
981990
loading: this.mutableLoading,
982-
rtl: this.dir === 'rtl',
991+
rtl: this.dir === 'rtl', // This can be removed - styling is handled by `dir="rtl"` attribute
983992
disabled: this.disabled
984993
}
985994
},

0 commit comments

Comments
 (0)