add select

This commit is contained in:
tangjinzhou 2018-02-07 18:56:58 +08:00
parent 493d37ad8e
commit ce7f9850dd
11 changed files with 2085 additions and 26 deletions

View File

@ -3,6 +3,11 @@ const hasProp = (instance, prop) => {
const propsData = $options.propsData || {}
return prop in propsData
}
const slotHasProp = (slot, prop) => {
const $options = slot.componentOptions || {}
const propsData = $options.propsData || {}
return prop in propsData
}
const filterProps = (props, propsData = {}) => {
const res = {}
Object.keys(props).forEach((k) => {
@ -12,7 +17,9 @@ const filterProps = (props, propsData = {}) => {
})
return res
}
const getSlotOptions = (slot) => {
return slot.componentOptions.Ctor.options
}
const getOptionProps = (instance) => {
const { $options = {}, $props = {}} = instance
return filterProps($props, $options.propsData)
@ -27,5 +34,9 @@ const getComponentFromProp = (instance, prop) => {
return instance.$slots[prop]
}
export { hasProp, filterProps, getOptionProps, getComponentFromProp }
const getPropsData = (ele) => {
return ele.componentOptions && ele.componentOptions.propsData
}
export { hasProp, filterProps, getOptionProps, getComponentFromProp, getSlotOptions, slotHasProp, getPropsData }
export default hasProp

View File

@ -123,6 +123,9 @@ export function filterEmpty (children = []) {
export function getPropsData (ele) {
return ele.componentOptions && ele.componentOptions.propsData
}
export function getValueByProp (ele, prop) {
return ele.componentOptions && ele.componentOptions.propsData[prop]
}
export function getEvents (child) {
let events = {}

View File

@ -1,6 +1,5 @@
<script>
import PropTypes from '../_util/vue-types'
import toArray from 'rc-util/lib/Children/toArray'
import Menu from '../vc-menu'
import scrollIntoView from 'dom-scroll-into-view'
import { getSelectKeys, preventDefaultEvent } from './util'
@ -99,6 +98,7 @@ export default {
firstActiveValue,
dropdownMenuStyle,
} = props
const { menuDeselect, menuSelect } = this.$listeners
if (menuItems && menuItems.length) {
const selectedKeys = getSelectKeys(menuItems, value)
const menuProps = {
@ -114,10 +114,10 @@ export default {
ref: 'menuRef',
}
if (multiple) {
menuProps.on.deselect = this.onMenuDeselect
menuProps.on.select = this.onMenuSelect
menuProps.on.deselect = menuDeselect
menuProps.on.select = menuSelect
} else {
menuProps.on.click = this.onMenuSelect
menuProps.on.click = menuSelect
}
const activeKeyProps = {}
@ -146,7 +146,7 @@ export default {
clonedMenuItems = menuItems.map(item => {
if (item.type.isMenuItemGroup) {
const children = toArray(item.props.children).map(clone)
const children = item.$slots.default.map(clone)
return cloneElement(item, {}, children)
}
return clone(item)
@ -167,27 +167,16 @@ export default {
}
return null
},
onPopupFocus (e) {
this.__emit('popupFocus', e)
},
onPopupScroll (e) {
this.__emit('popupScroll', e)
},
onMenuDeselect () {
this.__emit('menuDeselect', ...arguments)
},
onMenuSelect () {
this.__emit('menuSelect', ...arguments)
},
},
render () {
const renderMenu = this.renderMenu()
const { popupFocus, popupScroll } = this.$listeners
return renderMenu ? (
<div
style={{ overflow: 'auto' }}
onFocus={this.onPopupFocus}
onFocus={popupFocus}
onMousedown={preventDefaultEvent}
onScroll={this.onPopupScroll}
onScroll={popupScroll}
>
{renderMenu}
</div>

View File

@ -69,8 +69,8 @@ export const SelectPropTypes = {
placeholder: PropTypes.any,
// onDeselect: PropTypes.func,
labelInValue: PropTypes.bool,
value: valueType,
defaultValue: valueType,
value: PropTypes.any,
defaultValue: PropTypes.any,
dropdownStyle: PropTypes.object,
maxTagTextLength: PropTypes.number,
maxTagCount: PropTypes.number,

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,195 @@
<script>
import classnames from 'classnames'
import Trigger from '../trigger'
import PropTypes from '../_util/vue-types'
import DropdownMenu from './DropdownMenu'
import { isSingleMode } from './util'
import BaseMixin from '../_util/BaseMixin'
const BUILT_IN_PLACEMENTS = {
bottomLeft: {
points: ['tl', 'bl'],
offset: [0, 4],
overflow: {
adjustX: 0,
adjustY: 1,
},
},
topLeft: {
points: ['bl', 'tl'],
offset: [0, -4],
overflow: {
adjustX: 0,
adjustY: 1,
},
},
}
export default {
name: 'SelectTrigger',
mixins: [BaseMixin],
props: {
// onPopupFocus: PropTypes.func,
// onPopupScroll: PropTypes.func,
dropdownMatchSelectWidth: PropTypes.bool,
dropdownAlign: PropTypes.object,
visible: PropTypes.bool,
disabled: PropTypes.bool,
showSearch: PropTypes.bool,
dropdownClassName: PropTypes.string,
multiple: PropTypes.bool,
inputValue: PropTypes.string,
filterOption: PropTypes.any,
options: PropTypes.any,
prefixCls: PropTypes.string,
popupClassName: PropTypes.string,
// children: PropTypes.any,
showAction: PropTypes.arrayOf(PropTypes.string),
},
data () {
return {
dropdownWidth: null,
}
},
mounted () {
this.$nextTick(() => {
this.setDropdownWidth()
})
},
updated () {
this.$nextTick(() => {
this.setDropdownWidth()
})
},
methods: {
setDropdownWidth () {
const width = this.$el.offsetWidth
if (width !== this.dropdownWidth) {
this.setState({ dropdownWidth: width })
}
},
getInnerMenu () {
return this.$refs.dropdownMenuRef && this.$refs.dropdownMenuRef.$refs.menuRef
},
getPopupDOMNode () {
return this.$refs.triggerRef.getPopupDomNode()
},
getDropdownElement (newProps) {
const {
value, firstActiveValue, defaultActiveFirstOption,
dropdownMenuStyle, getDropdownPrefixCls,
} = this
const { menuSelect, menuDeselect, popupScroll } = this.$listeners
const dropdownMenuProps = {
props: {
...newProps.props,
prefixCls: getDropdownPrefixCls(),
value, firstActiveValue, defaultActiveFirstOption, dropdownMenuStyle,
},
on: {
...newProps.on,
menuSelect,
menuDeselect,
popupScroll,
},
ref: 'dropdownMenuRef',
}
return (
<DropdownMenu {...dropdownMenuProps} />
)
},
getDropdownTransitionName () {
const props = this.$props
let transitionName = props.transitionName
if (!transitionName && props.animation) {
transitionName = `${this.getDropdownPrefixCls()}-${props.animation}`
}
return transitionName
},
getDropdownPrefixCls () {
return `${this.prefixCls}-dropdown`
},
},
render () {
const { $props, $slots, $listeners } = this
const {
multiple,
visible,
inputValue,
dropdownAlign,
disabled,
showSearch,
dropdownClassName,
dropdownStyle,
dropdownMatchSelectWidth,
options,
getPopupContainer,
showAction,
} = $props
const dropdownPrefixCls = this.getDropdownPrefixCls()
const popupClassName = {
[dropdownClassName]: !!dropdownClassName,
[`${dropdownPrefixCls}--${multiple ? 'multiple' : 'single'}`]: 1,
}
const popupElement = this.getDropdownElement({
props: {
menuItems: options,
multiple,
inputValue,
visible,
}, on: {
popupFocus: $listeners.popupFocus,
},
})
let hideAction
if (disabled) {
hideAction = []
} else if (isSingleMode($props) && !showSearch) {
hideAction = ['click']
} else {
hideAction = ['blur']
}
const popupStyle = { ...dropdownStyle }
const widthProp = dropdownMatchSelectWidth ? 'width' : 'minWidth'
if (this.dropdownWidth) {
popupStyle[widthProp] = `${this.dropdownWidth}px`
}
const triggerProps = {
props: {
...$props,
showAction: disabled ? [] : showAction,
hideAction,
ref: 'triggerRef',
popupPlacement: 'bottomLeft',
builtinPlacements: BUILT_IN_PLACEMENTS,
prefixCls: dropdownPrefixCls,
popupTransitionName: this.getDropdownTransitionName(),
popup: popupElement,
popupAlign: dropdownAlign,
popupVisible: visible,
getPopupContainer,
popupClassName: classnames(popupClassName),
popupStyle,
},
on: {
popupVisibleChange: $listeners.dropdownVisibleChange,
},
ref: 'triggerRef',
}
return (
<Trigger {...triggerProps}>
{$slots.default}
</Trigger>
)
},
}
</script>

View File

@ -0,0 +1,502 @@
@selectPrefixCls: rc-select;
.effect() {
animation-duration: .3s;
animation-fill-mode: both;
transform-origin: 0 0;
}
.@{selectPrefixCls} {
box-sizing: border-box;
display: inline-block;
position: relative;
vertical-align: middle;
color: #666;
line-height: 28px;
&-allow-clear {
.@{selectPrefixCls}-selection--single .@{selectPrefixCls}-selection__rendered {
padding-right: 40px;
}
}
ul, li {
margin: 0;
padding: 0;
list-style: none;
}
> ul > li > a {
padding: 0;
background-color: #fff;
}
// arrow
&-arrow {
height: 26px;
position: absolute;
top: 1px;
right: 1px;
width: 20px;
outline: none;
b {
border-color: #999999 transparent transparent transparent;
border-style: solid;
border-width: 5px 4px 0 4px;
height: 0;
width: 0;
margin-left: -4px;
margin-top: -2px;
position: absolute;
top: 50%;
left: 50%;
}
}
&-selection {
outline: none;
user-select: none;
-webkit-user-select: none;
box-sizing: border-box;
display: block;
background-color: #fff;
border-radius: 6px;
border: 1px solid #d9d9d9;
&__placeholder {
position: absolute;
top: 0;
color: #aaa;
}
&__clear {
font-weight: bold;
position: absolute;
line-height: 28px;
&:after {
content: '×'
}
}
}
&-focused &-selection {
border-color: #23c0fa;
box-shadow: 0 0 2px fadeout(#2db7f5, 20%);
}
&-enabled &-selection {
&:hover {
border-color: #23c0fa;
box-shadow: 0 0 2px fadeout(#2db7f5, 20%);
}
&:active {
border-color: #2db7f5;
}
}
&-selection--single {
height: 28px;
line-height: 28px;
cursor: pointer;
position: relative;
.@{selectPrefixCls}-selection-selected-value {
position: absolute;
left: 0;
top: 0;
}
.@{selectPrefixCls}-selection__rendered {
height: 28px;
position: relative;
display: block;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
margin-left: 10px;
padding-right: 20px;
line-height: 28px;
}
.@{selectPrefixCls}-selection__clear {
top: 0;
right: 20px;
}
}
&-disabled {
color: #ccc;
cursor: not-allowed;
.@{selectPrefixCls}-selection--single,
.@{selectPrefixCls}-selection__choice__remove {
cursor: not-allowed;
color: #ccc;
&:hover {
cursor: not-allowed;
color: #ccc;
}
}
}
&-search__field__wrap {
display: inline-block;
}
&-search__field__placeholder {
position: absolute;
top: 0;
left: 3px;
color: #aaa;
}
&-search--inline {
width: 100%;
.@{selectPrefixCls}-search__field__wrap {
width: 100%;
}
.@{selectPrefixCls}-search__field {
border: none;
font-size: 100%;
//margin-top: 5px;
background: transparent;
outline: 0;
width: 100%;
&::-ms-clear {
display: none;
}
}
.@{selectPrefixCls}-search__field__mirror {
position: absolute;
top: -999px;
left: 0;
white-space: pre;
}
> i {
float: right;
}
}
&-enabled&-selection--multiple {
cursor: text;
}
&-selection--multiple {
min-height: 28px;
.@{selectPrefixCls}-search--inline {
float: left;
width: auto;
.@{selectPrefixCls}-search__field {
&__wrap {
width: auto;
}
width: 0.75em;
}
}
.@{selectPrefixCls}-search__field__placeholder {
top: 5px;
left: 8px;
}
.@{selectPrefixCls}-selection__rendered {
position: relative;
overflow: hidden;
text-overflow: ellipsis;
margin-left: 8px;
padding-bottom: 2px;
.@{selectPrefixCls}-selection__choice {
margin-top: 4px;
line-height: 20px;
}
}
.@{selectPrefixCls}-selection__clear {
top: 1px;
right: 8px;
}
}
&-enabled {
.@{selectPrefixCls}-selection__choice {
cursor: default;
&:hover {
.@{selectPrefixCls}-selection__choice__remove {
opacity: 1;
transform: scale(1);
}
.@{selectPrefixCls}-selection__choice__content {
margin-left: -8px;
margin-right: 8px;
}
}
}
.@{selectPrefixCls}-selection__choice__disabled {
cursor: not-allowed;
&:hover {
.@{selectPrefixCls}-selection__choice__content {
margin-left: 0;
margin-right: 0;
}
}
}
}
& &-selection__choice {
background-color: #f3f3f3;
border-radius: 4px;
float: left;
padding: 0 15px;
margin-right: 4px;
position: relative;
overflow: hidden;
transition: padding .3s cubic-bezier(0.6, -0.28, 0.735, 0.045), width .3s cubic-bezier(0.6, -0.28, 0.735, 0.045);
&__content {
margin-left: 0;
margin-right: 0;
transition: margin .3s cubic-bezier(0.165, 0.84, 0.44, 1);
}
&-zoom-enter, &-zoom-appear, &-zoom-leave {
.effect();
opacity: 0;
animation-play-state: paused;
animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1.275);
}
&-zoom-leave {
opacity: 1;
animation-timing-function: cubic-bezier(0.6, -0.28, 0.735, 0.045);
}
&-zoom-enter.@{selectPrefixCls}-selection__choice-zoom-enter-active,
&-zoom-appear.@{selectPrefixCls}-selection__choice-zoom-appear-active {
animation-play-state: running;
animation-name: rcSelectChoiceZoomIn;
}
&-zoom-leave.@{selectPrefixCls}-selection__choice-zoom-leave-active {
animation-play-state: running;
animation-name: rcSelectChoiceZoomOut;
}
@keyframes rcSelectChoiceZoomIn {
0% {
transform: scale(0.6);
opacity: 0;
}
100% {
transform: scale(1);
opacity: 1;
}
}
@keyframes rcSelectChoiceZoomOut {
to {
transform: scale(0);
opacity: 0;
}
}
&__remove {
color: #919191;
cursor: pointer;
font-weight: bold;
padding: 0 0 0 8px;
position: absolute;
opacity: 0;
transform: scale(0);
top: 0;
right: 2px;
transition: opacity .3s, transform .3s;
&:before {
content: '×'
}
&:hover {
color: #333;
}
}
}
&-dropdown {
background-color: white;
border: 1px solid #d9d9d9;
box-shadow: 0 0px 4px #d9d9d9;
border-radius: 4px;
box-sizing: border-box;
z-index: 100;
left: -9999px;
top: -9999px;
position: absolute;
outline: none;
&:empty,
&-hidden {
display: none;
}
&-menu {
outline: none;
margin: 0;
padding: 0;
list-style: none;
z-index: 9999;
> li {
margin: 0;
padding: 0;
}
&-item-group-list {
margin: 0;
padding: 0;
> li.@{selectPrefixCls}-menu-item {
padding-left: 20px;
}
}
&-item-group-title {
color: #999;
line-height: 1.5;
padding: 8px 10px;
border-bottom: 1px solid #dedede;
}
li&-item {
margin: 0;
position: relative;
display: block;
padding: 7px 10px;
font-weight: normal;
color: #666;
white-space: nowrap;
&-disabled {
color: #ccc;
cursor: not-allowed;
}
&-selected {
color: #666;
background-color: #ddd;
}
&-active {
background-color: #5897fb;
color: white;
cursor: pointer;
}
&-divider {
height: 1px;
margin: 1px 0;
overflow: hidden;
background-color: #e5e5e5;
line-height: 0;
}
}
}
&-slide-up-enter, &-slide-up-appear {
.effect();
opacity: 0;
animation-timing-function: cubic-bezier(0.08, 0.82, 0.17, 1);
animation-play-state: paused;
}
&-slide-up-leave {
.effect();
opacity: 1;
animation-timing-function: cubic-bezier(0.6, 0.04, 0.98, 0.34);
animation-play-state: paused;
}
&-slide-up-enter&-slide-up-enter-active&-placement-bottomLeft, &-slide-up-appear&-slide-up-appear-active&-placement-bottomLeft {
animation-name: rcSelectDropdownSlideUpIn;
animation-play-state: running;
}
&-slide-up-leave&-slide-up-leave-active&-placement-bottomLeft {
animation-name: rcSelectDropdownSlideUpOut;
animation-play-state: running;
}
&-slide-up-enter&-slide-up-enter-active&-placement-topLeft, &-slide-up-appear&-slide-up-appear-active&-placement-topLeft {
animation-name: rcSelectDropdownSlideDownIn;
animation-play-state: running;
}
&-slide-up-leave&-slide-up-leave-active&-placement-topLeft {
animation-name: rcSelectDropdownSlideDownOut;
animation-play-state: running;
}
@keyframes rcSelectDropdownSlideUpIn {
0% {
opacity: 0;
transform-origin: 0% 0%;
transform: scaleY(0);
}
100% {
opacity: 1;
transform-origin: 0% 0%;
transform: scaleY(1);
}
}
@keyframes rcSelectDropdownSlideUpOut {
0% {
opacity: 1;
transform-origin: 0% 0%;
transform: scaleY(1);
}
100% {
opacity: 0;
transform-origin: 0% 0%;
transform: scaleY(0);
}
}
@keyframes rcSelectDropdownSlideDownIn {
0% {
opacity: 0;
transform-origin: 0% 100%;
transform: scaleY(0);
}
100% {
opacity: 1;
transform-origin: 0% 100%;
transform: scaleY(1);
}
}
@keyframes rcSelectDropdownSlideDownOut {
0% {
opacity: 1;
transform-origin: 0% 100%;
transform: scaleY(1);
}
100% {
opacity: 0;
transform-origin: 0% 100%;
transform: scaleY(0);
}
}
}
&-open {
.@{selectPrefixCls}-arrow b {
border-color: transparent transparent #888 transparent;
border-width: 0 4px 5px 4px;
}
}
}

View File

@ -0,0 +1,63 @@
<script>
import Select, { Option } from '../index'
import '../assets/index.less'
export default {
data () {
return {
disabled: false,
value: '',
}
},
methods: {
onChange (value) {
this.value = value
},
onKeyDown (e) {
if (e.keyCode === 13) {
console.log('onEnter', this.value)
}
},
onSelect (v) {
console.log('onSelect', v)
},
toggleDisabled () {
this.disabled = !this.disabled
},
},
render () {
return (<div>
<h2>combobox</h2>
<p>
<button onClick={this.toggleDisabled}>toggle disabled</button>
</p>
<div style={{ width: 300 }}>
<Select
disabled={this.disabled}
style={{ width: 500 }}
onChange={this.onChange}
onSelect={this.onSelect}
onInputKeydown={this.onKeyDown}
notFoundContent=''
allowClear
placeholder='please select'
value={this.value}
combobox
backfill
>
<Option value='jack'>
<b style={{ color: 'red' }}>jack</b>
</Option>
<Option value='lucy'>lucy</Option>
<Option value='disabled' disabled>disabled</Option>
<Option value='yiminghe'>yiminghe</Option>
</Select>
</div>
</div>)
},
}
</script>

View File

@ -1,12 +1,13 @@
import { getPropsData, getSlotOptions } from '../_util/props-util'
export function getValuePropValue (child) {
const props = child.props
const props = getPropsData(child)
if ('value' in props) {
return props.value
}
if (child.key) {
return child.key
}
if (child.type && child.type.isSelectOptGroup && props.label) {
if (getSlotOptions(child).isSelectOptGroup && props.label) {
return props.label
}
throw new Error(

View File

@ -3,7 +3,7 @@ const AsyncComp = () => {
const hashs = window.location.hash.split('/')
const d = hashs[hashs.length - 1]
return {
component: import(`../components/message/demo/${d}.md`),
component: import(`../components/vc-select/demo/${d}.vue`),
}
}
export default [

View File

@ -85,6 +85,7 @@
},
"dependencies": {
"add-dom-event-listener": "^1.0.2",
"classnames": "^2.2.5",
"component-classes": "^1.2.6",
"css-animation": "^1.4.1",
"dom-align": "^1.6.7",