refactor(popselect): menu refactor & props refactor

This commit is contained in:
07akioni 2020-03-04 13:37:55 +08:00
parent cf737fa08e
commit 2072803d3c
18 changed files with 440 additions and 101 deletions

View File

@ -25,7 +25,7 @@ manual-position
|delay|`number`|`0`||
|duration|`number`|`300`||
|placement|`'top-start' \| 'top' \| 'top-end' \| 'right-start' \| 'right' \| 'right-end' \| 'bottom-start' \| 'bottom' \| 'bottom-end' \| 'left-start' \| 'left' \| 'left-end' \| `|`'bottom'`||
|arrow|`boolean`|`false`||
|show-arrow|`boolean`|`false`||
|raw|`boolean`|`false`||
|disabled|`boolean`|`false`||
|manuallyPositioned|`boolean`|`false`||

View File

@ -25,7 +25,7 @@ manual-position
|delay|`number`|`0`||
|duration|`number`|`300`||
|placement|`'top-start' \| 'top' \| 'top-end' \| 'right-start' \| 'right' \| 'right-end' \| 'bottom-start' \| 'bottom' \| 'bottom-end' \| 'left-start' \| 'left' \| 'left-end' \| `|`'bottom'`||
|arrow|`boolean`|`false`||
|show-arrow|`boolean`|`false`||
|raw|`boolean`|`false`||
|disabled|`boolean`|`false`||
|manuallyPositioned|`boolean`|`false`||

View File

@ -5,7 +5,8 @@ If you want select some options but don't want a picker, you can use popselect i
## Demos
```demo
basic
cancelable
size
scrollable
custom-width
multiple
```
@ -19,14 +20,15 @@ multiple
|Name|Type|Default|Description|
|-|-|-|-|
|value|`string \| number`|||
|options|`Array`|||
|width|`number \| string`|||
|multiple|`boolean`|||
|cancelable|`boolean`|||
|controller|`Object`|||
|arrow|`boolean`|||
|trigger|`'click' \| 'hover' \| 'manual'`|||
|value|`string \| number`|`null`||
|options|`Array<SelectOption \| SelectOptionGroup>`|`[]`||
|scrollable|`boolean`|`true`||
|multiple|`boolean`|`false`||
|size|`'small' \| 'medium' \| 'large'`|`'small'`||
For SelectOption & SelectOptionGroup, see [Select](n-select#SelectOption-Type)
For other props, see [Popover](n-popover#Props)
## Events
|Name|Parameters|Description|

View File

@ -0,0 +1,79 @@
# Unscrollable
```html
<n-popselect
v-model="value"
:options="options"
size="medium"
:scrollable="false"
@change="handleChange"
>
<n-tag style="margin-right: 8px;">{{ value || 'Popselect' }}</n-tag>
</n-popselect>
```
```js
export default {
data () {
return {
value: 'song1',
options: [
{
label: 'Drive My Car',
value: 'song1'
},
{
label: 'Norwegian Wood',
value: 'song2'
},
{
label: 'You Won\'t See',
value: 'song3',
disabled: true
},
{
label: 'Nowhere Man',
value: 'song4'
},
{
label: 'Think For Yourself',
value: 'song5'
},
{
label: 'The Word',
value: 'song6'
},
{
label: 'Michelle',
value: 'song7',
disabled: true
},
{
label: 'What goes on',
value: 'song8'
},
{
label: 'Girl',
value: 'song9'
},
{
label: 'I\'m looking through you',
value: 'song10'
},
{
label: 'In My Life',
value: 'song11'
},
{
label: 'Wait',
value: 'song12'
}
]
}
},
methods: {
handleChange (v) {
this.$NMessage.info('Value: ' + v)
}
}
}
```

View File

@ -0,0 +1,86 @@
# Size
```html
<n-popselect
v-model="value"
:options="options"
size="medium"
@change="handleChange"
>
<n-tag style="margin-right: 8px;">{{ value || 'Popselect' }}</n-tag>
</n-popselect>
<n-popselect
v-model="value"
:options="options"
size="large"
@change="handleChange"
>
<n-tag>{{ value || 'Popselect' }}</n-tag>
</n-popselect>
```
```js
export default {
data () {
return {
value: 'song1',
options: [
{
label: 'Drive My Car',
value: 'song1'
},
{
label: 'Norwegian Wood',
value: 'song2'
},
{
label: 'You Won\'t See',
value: 'song3',
disabled: true
},
{
label: 'Nowhere Man',
value: 'song4'
},
{
label: 'Think For Yourself',
value: 'song5'
},
{
label: 'The Word',
value: 'song6'
},
{
label: 'Michelle',
value: 'song7',
disabled: true
},
{
label: 'What goes on',
value: 'song8'
},
{
label: 'Girl',
value: 'song9'
},
{
label: 'I\'m looking through you',
value: 'song10'
},
{
label: 'In My Life',
value: 'song11'
},
{
label: 'Wait',
value: 'song12'
}
]
}
},
methods: {
handleChange (v) {
this.$NMessage.info('Value: ' + v)
}
}
}
```

View File

@ -5,7 +5,8 @@
## 演示
```demo
basic
cancelable
size
scrollable
custom-width
multiple
```
@ -18,14 +19,15 @@ multiple
|名称|类型|默认值|说明|
|-|-|-|-|
|value|`string \| number`|||
|options|`Array`|||
|width|`number \| string`|||
|multiple|`boolean`|||
|cancelable|`boolean`|||
|controller|`Object`|||
|arrow|`boolean`|||
|trigger|`'click' \| 'hover' \| 'manual'`|||
|value|`string \| number`|`null`||
|options|`Array<SelectOption \| SelectOptionGroup>`|`[]`||
|scrollable|`boolean`|`true`||
|multiple|`boolean`|`false`||
|size|`'small' \| 'medium' \| 'large'`|`'small'`||
对于 SelectOption & SelectOptionGroup参考 [Select](n-select#SelectOption-Type)
对于其他 props参考 [Popover](n-popover#Props)
## Events
|名称|参数|说明|

View File

@ -0,0 +1,79 @@
# 不让它滚动
```html
<n-popselect
v-model="value"
:options="options"
size="medium"
:scrollable="false"
@change="handleChange"
>
<n-tag style="margin-right: 8px;">{{ value || 'Popselect' }}</n-tag>
</n-popselect>
```
```js
export default {
data () {
return {
value: 'song1',
options: [
{
label: 'Drive My Car',
value: 'song1'
},
{
label: 'Norwegian Wood',
value: 'song2'
},
{
label: 'You Won\'t See',
value: 'song3',
disabled: true
},
{
label: 'Nowhere Man',
value: 'song4'
},
{
label: 'Think For Yourself',
value: 'song5'
},
{
label: 'The Word',
value: 'song6'
},
{
label: 'Michelle',
value: 'song7',
disabled: true
},
{
label: 'What goes on',
value: 'song8'
},
{
label: 'Girl',
value: 'song9'
},
{
label: 'I\'m looking through you',
value: 'song10'
},
{
label: 'In My Life',
value: 'song11'
},
{
label: 'Wait',
value: 'song12'
}
]
}
},
methods: {
handleChange (v) {
this.$NMessage.info('Value: ' + v)
}
}
}
```

View File

@ -0,0 +1,86 @@
# 尺寸
```html
<n-popselect
v-model="value"
:options="options"
size="medium"
@change="handleChange"
>
<n-tag style="margin-right: 8px;">{{ value || 'Popselect' }}</n-tag>
</n-popselect>
<n-popselect
v-model="value"
:options="options"
size="large"
@change="handleChange"
>
<n-tag>{{ value || 'Popselect' }}</n-tag>
</n-popselect>
```
```js
export default {
data () {
return {
value: 'song1',
options: [
{
label: 'Drive My Car',
value: 'song1'
},
{
label: 'Norwegian Wood',
value: 'song2'
},
{
label: 'You Won\'t See',
value: 'song3',
disabled: true
},
{
label: 'Nowhere Man',
value: 'song4'
},
{
label: 'Think For Yourself',
value: 'song5'
},
{
label: 'The Word',
value: 'song6'
},
{
label: 'Michelle',
value: 'song7',
disabled: true
},
{
label: 'What goes on',
value: 'song8'
},
{
label: 'Girl',
value: 'song9'
},
{
label: 'I\'m looking through you',
value: 'song10'
},
{
label: 'In My Life',
value: 'song11'
},
{
label: 'Wait',
value: 'song12'
}
]
}
},
methods: {
handleChange (v) {
this.$NMessage.info('Value: ' + v)
}
}
}
```

View File

@ -104,7 +104,7 @@ export default {
},
updateTrackingRectPosition: debounce(function (e) {
const trackingRect = this.$refs.trackingRect
trackingRect && trackingRect.updateLightBarTop(e.target)
trackingRect && trackingRect.updateTrackingRectTop(e.target)
}, 64),
handleOptionMouseLeave (e, option) {
this.$emit('option-mouseleave', e, option)
@ -114,7 +114,7 @@ export default {
},
handleMouseLeave: debounce(function (e) {
const trackingRect = this.$refs.trackingRect
trackingRect && trackingRect.hideLightBar()
trackingRect && trackingRect.hideTrackingRect()
}, 64),
handleOptionCheck (option) {
this.$emit('option-check', option.id)

View File

@ -198,7 +198,7 @@ export default {
ref: 'selectMenu',
props: {
virtualScroll: false,
withoutScrollbar: true,
scrollable: false,
options: this.selectOptions,
size: this.size,
isOptionSelected: () => false,

View File

@ -19,6 +19,7 @@ export default {
event: 'change'
},
props: {
...NPopover.props,
options: {
type: Array,
default: null
@ -29,10 +30,6 @@ export default {
},
default: null
},
width: {
type: Number,
default: null
},
multiple: {
type: Boolean,
default: false
@ -41,38 +38,24 @@ export default {
type: Boolean,
default: false
},
controller: {
type: Object,
default: null
size: {
validator (value) {
return ['small', 'medium', 'large'].includes(value)
},
default: 'small'
},
showArrow: {
scrollable: {
type: Boolean,
default: false
},
show: {
type: Boolean,
default: false
},
trigger: {
type: String,
default: 'click'
},
zIndex: {
type: Number,
default: undefined
}
},
methods: {
handleInput (v) {
this.$emit('input', v)
}
},
render (h, context) {
const slots = context.scopedSlots
const activator = slots.activator || slots.default
const controller = context.props.controller || {}
const onHide = context.listeners.hide || (() => {})
const onShow = context.listeners.show || (() => {})
const handleHide = context.listeners.hide || (() => {})
const handleShow = context.listeners.show || (() => {})
const handleChange = context.listeners.change || (() => {})
return h(NPopover, {
props: {
trigger: context.props.trigger,
@ -82,8 +65,8 @@ export default {
controller
},
on: {
show: onShow,
hide: onHide
show: handleShow,
hide: handleHide
},
scopedSlots: {
activator: () => activator(),
@ -93,7 +76,9 @@ export default {
...context.props,
controller
},
on: context.listeners
on: {
change: handleChange
}
})
}
})

View File

@ -3,11 +3,12 @@
:theme="syntheticTheme"
:multiple="multiple"
:options="options"
:loading="loading"
:size="size"
:is-option-selected="isSelected"
:width="width"
:virtual-scroll="false"
size="small"
:show-tracking-rect="false"
:scrollable="scrollable"
@menu-toggle-option="handleMenuToggleOption"
/>
</template>
@ -49,9 +50,13 @@ export default {
type: Object,
default: null
},
loading: {
size: {
type: String,
default: null
},
scrollable: {
type: Boolean,
default: false
default: true
}
},
watch: {
@ -59,11 +64,6 @@ export default {
this.$nextTick().then(() => {
this.controller && this.controller.updatePosition()
})
},
loading () {
this.$nextTick().then(() => {
this.controller && this.controller.updatePosition()
})
}
},
methods: {

View File

@ -1,5 +1,5 @@
<template>
<div v-if="withoutScrollbar" ref="scrollContent">
<div v-if="!scrollable" ref="scrollContent">
<slot />
</div>
<div
@ -99,9 +99,9 @@ export default {
type: Number,
default: 0
},
withoutScrollbar: {
scrollable: {
type: Boolean,
default: false
default: true
},
container: {
type: Function,
@ -279,7 +279,7 @@ export default {
}
},
scrollToElement (el, getTop = elm => elm.offsetTop, getHeight = elm => elm.offsetHeight) {
if (this.withoutScrollbar) return
if (!this.scrollable) return
const top = getTop(el)
const container = this._container()
if (top < container.scrollTop) {
@ -386,7 +386,7 @@ export default {
}
},
updateParameters () {
if (this.withoutScrollbar) return
if (!this.scrollable) return
this.updatePositionParameters()
this.updateScrollParameters()
},

View File

@ -623,34 +623,34 @@ export default {
const sourceLightBar = this.$refs.sourceLightBar
if (!sourceLightBar) return
if (this.virtualScroll) {
sourceLightBar.updateLightBarTop(true, () => index * this.itemSize)
sourceLightBar.updateTrackingRectTop(true, () => index * this.itemSize)
} else {
sourceLightBar.updateLightBarTop(e.target)
sourceLightBar.updateTrackingRectTop(e.target)
}
}, 64),
handleTargetOptionMouseEnter: debounce(function (e, index) {
const targetLightBar = this.$refs.targetLightBar
if (this.virtualScroll) {
targetLightBar.updateLightBarTop(true, () => index * this.itemSize)
targetLightBar.updateTrackingRectTop(true, () => index * this.itemSize)
} else {
targetLightBar.updateLightBarTop(e.target)
targetLightBar.updateTrackingRectTop(e.target)
}
}, 64),
handleSourceOptionMouseLeave: debounce(function (e) {
const sourceLightBar = this.$refs.sourceLightBar
sourceLightBar && sourceLightBar.hideLightBar()
sourceLightBar && sourceLightBar.hideTrackingRect()
}, 64),
handleTargetOptionMouseLeave: debounce(function (e) {
const targetLightBar = this.$refs.targetLightBar
targetLightBar && targetLightBar.hideLightBar()
targetLightBar && targetLightBar.hideTrackingRect()
}, 64),
handleSourceListMouseLeave: debounce(function () {
const sourceLightBar = this.$refs.sourceLightBar
sourceLightBar && sourceLightBar.hideLightBar()
sourceLightBar && sourceLightBar.hideTrackingRect()
}, 64),
handleTargetListMouseLeave: debounce(function () {
const targetLightBar = this.$refs.targetLightBar
targetLightBar && targetLightBar.hideLightBar()
targetLightBar && targetLightBar.hideTrackingRect()
}, 64)
}
}

View File

@ -4,6 +4,7 @@
:class="{
[`n-base-select-menu--${size}-size`]: true,
'n-base-select-menu--multiple': multiple,
[`n-base-select-menu--no-tracking-rect`]: !showTrackingRect,
[`n-${theme}-theme`]: theme
}"
:style="{
@ -17,7 +18,7 @@
v-show="!empty"
ref="scrollbar"
:theme="theme"
:without-scrollbar="withoutScrollbar"
:scrollable="scrollable"
:container="getScrollContainer"
:content="getScrollContent"
@scroll="handleMenuScroll"
@ -34,7 +35,12 @@
@visible="handleMenuVisible"
>
<template v-slot:before>
<n-base-tracking-rect ref="lightBar" :item-size="itemSize" :theme="theme" />
<n-base-tracking-rect
v-if="showTrackingRect"
ref="trackingRect"
:item-size="itemSize"
:theme="theme"
/>
</template>
<template v-slot="{ item: option }">
<n-select-option
@ -51,7 +57,12 @@
</template>
</recycle-scroller>
<template v-else>
<n-base-tracking-rect ref="lightBar" :item-size="itemSize" :theme="theme" />
<n-base-tracking-rect
v-if="showTrackingRect"
ref="trackingRect"
:item-size="itemSize"
:theme="theme"
/>
<template v-for="option in flattenedOptions">
<n-select-option
v-if="option.type === OPTION_TYPE.OPTION"
@ -129,13 +140,13 @@ export default {
type: String,
default: null
},
withoutLightBar: {
showTrackingRect: {
type: Boolean,
default: false
default: true
},
withoutScrollbar: {
scrollable: {
type: Boolean,
default: false
default: true
},
options: {
type: Array,
@ -225,7 +236,7 @@ export default {
watch: {
empty (value) {
if (value) {
this.hideLightBar(0)
this.hideTrackingRect(0)
}
},
flattenedOptions () {
@ -234,7 +245,7 @@ export default {
const firstAvailableOptionIndex = getNextAvailableIndex(this.flattenedOptions, null)
this.setPendingWrappedOptionIndex(firstAvailableOptionIndex)
} else {
this.hideLightBar()
this.hideTrackingRect()
this.pendingWrappedOption = null
}
})
@ -242,7 +253,7 @@ export default {
pendingWrappedOption (value) {
if (value === null) {
this.$nextTick().then(() => {
this.hideLightBar()
this.hideTrackingRect()
})
}
}
@ -278,7 +289,7 @@ export default {
this.$emit('menu-toggle-option', option)
},
handleMenuMouseLeave () {
this.hideLightBar()
this.hideTrackingRect()
this.pendingWrappedOption = null
},
/**
@ -316,7 +327,7 @@ export default {
this.pendingWrappedOption = this.flattenedOptions[index]
const itemSize = this.itemSize
const offsetTop = itemSize * index
this.updateLightBarTop({
this.updateTrackingRectTop({
offsetTop
})
doScroll && this.$refs.scrollbar.scrollToElement({}, () => offsetTop, () => itemSize)
@ -324,9 +335,9 @@ export default {
} else {
this.pendingWrappedOption = this.flattenedOptions[index]
const el = this.$el
const optionEl = el.querySelector(`[n-index="${index}"]`)
const optionEl = el.querySelector(`[n-option-index="${index}"]`)
const offsetTop = optionEl.offsetTop
this.updateLightBarTop({
this.updateTrackingRectTop({
offsetTop
})
doScroll && this.$refs.scrollbar.scrollToElement(optionEl)
@ -335,19 +346,19 @@ export default {
/**
* select option background related
*/
updateLightBarTop (el) {
if (this.$refs.lightBar) {
this.$refs.lightBar.updateLightBarTop(el)
updateTrackingRectTop (el) {
if (this.$refs.trackingRect) {
this.$refs.trackingRect.updateTrackingRectTop(el)
}
},
hideLightBar () {
if (this.$refs.lightBar) {
this.$refs.lightBar.hideLightBar()
hideTrackingRect () {
if (this.$refs.trackingRect) {
this.$refs.trackingRect.hideTrackingRect()
}
},
hideLightBarSync () {
if (this.$refs.lightBar) {
this.$refs.lightBar.hideLightBar(0)
hideTrackingRectSync () {
if (this.$refs.trackingRect) {
this.$refs.trackingRect.hideTrackingRect(0)
}
}
}

View File

@ -50,7 +50,7 @@ export default {
'n-base-select-option--disabled': data.disabled,
'n-base-select-option--grouped': this.grouped
},
attrs: { 'n-index': this.index },
attrs: { 'n-option-index': this.index },
style: data.style,
on: {
click: this.handleClick,

View File

@ -41,7 +41,7 @@ export default {
}
},
methods: {
hideLightBar (delay = 300) {
hideTrackingRect (delay = 300) {
this.vanishTimerId && window.clearTimeout(this.vanishTimerId)
if (!delay) {
this.vanishTimerId = null
@ -52,7 +52,7 @@ export default {
}, delay)
}
},
updateLightBarTop (el, getLightBarTop = elm => elm.offsetTop) {
updateTrackingRectTop (el, getLightBarTop = elm => elm.offsetTop) {
if (!el) return
this.vanishTimerId && window.clearTimeout(this.vanishTimerId)
this.vanishTimerId = null

View File

@ -109,6 +109,15 @@
border-top: 1px solid $--base-select-menu-action-divider-color;
color: $--base-select-menu-action-text-color;
}
@include m(no-tracking-rect) {
@include b(base-select-option) {
@include not-m(disabled) {
&:hover {
color: map-get($--base-select-menu-option-color, "selected");
}
}
}
}
@include m(multiple) {
@include b(base-select-option) {
@include once {