2
0
mirror of https://github.com/tenrok/vue-select.git synced 2026-06-01 05:54:03 +03:00

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.
This commit is contained in:
Steven Harman
2018-07-17 22:59:44 -04:00
parent a0c8efe0e5
commit e926b6e007
+99 -90
View File
@@ -3,42 +3,45 @@
position: relative;
font-family: inherit;
}
.v-select,
.v-select * {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
/* Rtl support */
.v-select.rtl .open-indicator {
left: 10px;
right: auto;
/* Rtl support - Because we're using a flexbox-based layout, the `dir="rtl"` HTML
attribute does most of the work for us by rearranging the child elements visually.
*/
.v-select[dir="rtl"] .v-select__actions {
padding: 0 3px 0 6px;
}
.v-select.rtl .selected-tag {
float: right;
.v-select[dir="rtl"] .dropdown-toggle .clear {
margin-left: 6px;
margin-right: 0;
}
.v-select[dir="rtl"] .selected-tag {
margin-right: 3px;
margin-left: 1px;
}
.v-select.rtl .dropdown-menu {
.v-select[dir="rtl"] .selected-tag .close {
margin-left: 0;
margin-right: 2px;
}
.v-select[dir="rtl"] .dropdown-menu {
text-align: right;
}
.v-select.rtl .dropdown-toggle .clear {
left: 30px;
right: auto;
}
/* Open Indicator */
.v-select .open-indicator {
position: absolute;
bottom: 6px;
right: 10px;
display: inline-block;
cursor: pointer;
pointer-events: all;
transition: all 150ms cubic-bezier(1.000, -0.115, 0.975, 0.855);
transition-timing-function: cubic-bezier(1.000, -0.115, 0.975, 0.855);
opacity: 1;
height: 20px; width: 10px;
height: 16px;
width: 12px; /* To account for extra width from rotating. */
}
.v-select .open-indicator:before {
border-color: rgba(60, 60, 60, .5);
@@ -48,7 +51,7 @@
display: inline-block;
height: 10px;
width: 10px;
vertical-align: top;
vertical-align: text-top;
transform: rotate(133deg);
transition: all 150ms cubic-bezier(1.000, -0.115, 0.975, 0.855);
transition-timing-function: cubic-bezier(1.000, -0.115, 0.975, 0.855);
@@ -64,16 +67,16 @@
.v-select.open .open-indicator {
bottom: 1px;
}
/* Dropdown Toggle */
.v-select .dropdown-toggle {
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
display: block;
display: flex;
padding: 0;
background: none;
border: 1px solid rgba(60, 60, 60, .26);
min-height: 36px;
border-radius: 4px;
white-space: normal;
}
@@ -85,12 +88,20 @@
clear: both;
height: 0;
}
.v-select .v-select__selected-options {
display: flex;
flex-basis: 100%;
flex-grow: 1;
flex-wrap: wrap;
}
.v-select .v-select__actions {
display: flex;
align-items: center;
padding: 0 6px 0 3px;
}
/* Clear Button */
.v-select .dropdown-toggle .clear {
position: absolute;
bottom: 9px;
right: 30px;
font-size: 23px;
font-weight: 700;
line-height: 1;
@@ -99,6 +110,7 @@
border: 0;
background-color: transparent;
cursor: pointer;
margin-right: 6px;
}
/* Dropdown Toggle States */
@@ -138,6 +150,8 @@
}
/* Selected Tags */
.v-select .selected-tag {
display: flex;
align-items: baseline;
color: #333;
background-color: #f0f0f0;
border: 1px solid #ccc;
@@ -145,24 +159,18 @@
height: 26px;
margin: 4px 1px 0px 3px;
padding: 1px 0.25em;
float: left;
line-height: 24px;
}
.v-select.single .selected-tag {
background-color: transparent;
border-color: transparent;
}
.v-select.single.open .selected-tag {
position: absolute;
opacity: .5;
}
.v-select.single.open.searching .selected-tag,
.v-select.single.open .selected-tag,
.v-select.single.loading .selected-tag {
display: none;
}
.v-select .selected-tag .close {
float: none;
margin-right: 0;
margin-left: 2px;
font-size: 20px;
appearance: none;
padding: 0;
@@ -257,9 +265,6 @@
/* Loading Spinner */
.v-select .spinner {
opacity: 0;
position: absolute;
top: 5px;
right: 10px;
font-size: 5px;
text-indent: -9999em;
overflow: hidden;
@@ -322,58 +327,62 @@
<template>
<div :dir="dir" class="dropdown v-select" :class="dropdownClasses">
<div ref="toggle" @mousedown.prevent="toggleDropdown" :class="['dropdown-toggle', 'clearfix']">
<div ref="toggle" @mousedown.prevent="toggleDropdown" class="dropdown-toggle clearfix">
<slot v-for="option in valueAsArray" name="selected-option-container"
:option="(typeof option === 'object')?option:{[label]: option}" :deselect="deselect" :multiple="multiple" :disabled="disabled">
<span class="selected-tag" v-bind:key="option.index">
<slot name="selected-option" v-bind="(typeof option === 'object')?option:{[label]: option}">
{{ getOptionLabel(option) }}
</slot>
<button v-if="multiple" :disabled="disabled" @click="deselect(option)" type="button" class="close" aria-label="Remove option">
<span aria-hidden="true">&times;</span>
</button>
</span>
</slot>
<div class="v-select__selected-options">
<slot v-for="option in valueAsArray" name="selected-option-container"
:option="(typeof option === 'object')?option:{[label]: option}" :deselect="deselect" :multiple="multiple" :disabled="disabled">
<span class="selected-tag" v-bind:key="option.index">
<slot name="selected-option" v-bind="(typeof option === 'object')?option:{[label]: option}">
{{ getOptionLabel(option) }}
</slot>
<button v-if="multiple" :disabled="disabled" @click="deselect(option)" type="button" class="close" aria-label="Remove option">
<span aria-hidden="true">&times;</span>
</button>
</span>
</slot>
<input
ref="search"
v-model="search"
@keydown.delete="maybeDeleteValue"
@keyup.esc="onEscape"
@keydown.up.prevent="typeAheadUp"
@keydown.down.prevent="typeAheadDown"
@keydown.enter.prevent="typeAheadSelect"
@blur="onSearchBlur"
@focus="onSearchFocus"
type="search"
class="form-control"
:class="inputClasses"
autocomplete="off"
:disabled="disabled"
:placeholder="searchPlaceholder"
:tabindex="tabindex"
:readonly="!searchable"
:id="inputId"
aria-label="Search for option"
>
<input
ref="search"
v-model="search"
@keydown.delete="maybeDeleteValue"
@keyup.esc="onEscape"
@keydown.up.prevent="typeAheadUp"
@keydown.down.prevent="typeAheadDown"
@keydown.enter.prevent="typeAheadSelect"
@blur="onSearchBlur"
@focus="onSearchFocus"
type="search"
class="form-control"
:class="inputClasses"
autocomplete="off"
:disabled="disabled"
:placeholder="searchPlaceholder"
:tabindex="tabindex"
:readonly="!searchable"
:id="inputId"
aria-label="Search for option"
>
<button
v-show="showClearButton"
:disabled="disabled"
@click="clearSelection"
type="button"
class="clear"
title="Clear selection"
>
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="v-select__actions">
<button
v-show="showClearButton"
:disabled="disabled"
@click="clearSelection"
type="button"
class="clear"
title="Clear selection"
>
<span aria-hidden="true">&times;</span>
</button>
<i v-if="!noDrop" ref="openIndicator" role="presentation" class="open-indicator"></i>
<i v-if="!noDrop" ref="openIndicator" role="presentation" class="open-indicator"></i>
<slot name="spinner">
<div class="spinner" v-show="mutableLoading">Loading...</div>
</slot>
<slot name="spinner">
<div class="spinner" v-show="mutableLoading">Loading...</div>
</slot>
</div>
</div>
<transition :name="transition">
@@ -706,12 +715,12 @@
watch: {
/**
* When the value prop changes, update
* the internal mutableValue.
* the internal mutableValue.
* @param {mixed} val
* @return {void}
*/
value(val) {
this.mutableValue = val
this.mutableValue = val
},
/**
@@ -720,7 +729,7 @@
* @param {string|object} old
* @return {void}
*/
mutableValue(val, old) {
mutableValue(val, old) {
if (this.multiple) {
this.onChange ? this.onChange(val) : null
} else {
@@ -739,24 +748,24 @@
},
/**
* Maybe reset the mutableValue
* Maybe reset the mutableValue
* when mutableOptions change.
* @return {[type]} [description]
*/
mutableOptions() {
if (!this.taggable && this.resetOnOptionsChange) {
this.mutableValue = this.multiple ? [] : null
this.mutableValue = this.multiple ? [] : null
}
},
/**
* Always reset the mutableValue when
* Always reset the mutableValue when
* the multiple prop changes.
* @param {Boolean} val
* @return {void}
*/
multiple(val) {
this.mutableValue = val ? [] : null
this.mutableValue = val ? [] : null
}
},
@@ -765,9 +774,9 @@
* attach any event listeners.
*/
created() {
this.mutableValue = this.value
this.mutableValue = this.value
this.mutableOptions = this.options.slice(0)
this.mutableLoading = this.loading
this.mutableLoading = this.loading
this.$on('option:created', this.maybePushTag)
},
@@ -979,7 +988,7 @@
searchable: this.searchable,
unsearchable: !this.searchable,
loading: this.mutableLoading,
rtl: this.dir === 'rtl',
rtl: this.dir === 'rtl', // This can be removed - styling is handled by `dir="rtl"` attribute
disabled: this.disabled
}
},