2
0
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:
Steven Harman
2018-07-17 22:59:44 -04:00
parent a0c8efe0e5
commit e926b6e007
+99 -90
View File
@@ -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">&times;</span> <button v-if="multiple" :disabled="disabled" @click="deselect(option)" type="button" class="close" aria-label="Remove option">
</button> <span aria-hidden="true">&times;</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">&times;</span> title="Clear selection"
</button> >
<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"> <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
} }
}, },