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:
+99
-90
@@ -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">×</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">×</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">×</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">×</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
|
||||
}
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user