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