mirror of
https://github.com/tusen-ai/naive-ui.git
synced 2025-01-30 12:52:43 +08:00
perf(transfer): use many tricks to improve performance
This commit is contained in:
parent
3a35c4eaff
commit
4deba40870
@ -11,23 +11,24 @@
|
|||||||
<n-button @click="regenValues">
|
<n-button @click="regenValues">
|
||||||
Regen Values
|
Regen Values
|
||||||
</n-button>
|
</n-button>
|
||||||
<pre class="n-doc-section__inspect">{{ JSON.stringify(value) }}</pre>
|
<!-- <pre class="n-doc-section__inspect">{{ JSON.stringify(value) }}</pre>
|
||||||
<pre class="n-doc-section__inspect">{{ $refs.transfer ? $refs.transfer._data : null }}</pre>
|
<pre class="n-doc-section__inspect">{{ $refs.transfer ? $refs.transfer.memorizedSourceOptions.map(option => option.value) : null }}</pre>
|
||||||
|
<pre class="n-doc-section__inspect">{{ $refs.transfer ? $refs.transfer.targetOptions.map(option => option.value) : null }}</pre> -->
|
||||||
```
|
```
|
||||||
```js
|
```js
|
||||||
let prefix = null
|
let prefix = null
|
||||||
|
|
||||||
function genOptions () {
|
function genOptions () {
|
||||||
prefix = Math.random().toString(36).slice(2, 5)
|
prefix = Math.random().toString(36).slice(2, 5)
|
||||||
return Array.apply(null, { length: 20 }).map((v, i) => ({
|
return Array.apply(null, { length: 1000 }).map((v, i) => ({
|
||||||
label: prefix + 'Option' + i,
|
label: prefix + 'Option' + i,
|
||||||
value: prefix + i,
|
value: prefix + i,
|
||||||
disabled: i % 3 === 0
|
disabled: i % 5 === 0
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
function genValues () {
|
function genValues () {
|
||||||
return Array.apply(null, { length: 5 }).map((v, i) => prefix + i)
|
return Array.apply(null, { length: 500 }).map((v, i) => prefix + i)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
@ -58,7 +58,7 @@ export default {
|
|||||||
],
|
],
|
||||||
model: {
|
model: {
|
||||||
prop: 'checked',
|
prop: 'checked',
|
||||||
event: 'input'
|
event: 'change'
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
value: {
|
value: {
|
||||||
@ -103,8 +103,7 @@ export default {
|
|||||||
if (this.NCheckboxGroup) {
|
if (this.NCheckboxGroup) {
|
||||||
this.NCheckboxGroup.toggleCheckbox(!this.synthesizedChecked, this.value)
|
this.NCheckboxGroup.toggleCheckbox(!this.synthesizedChecked, this.value)
|
||||||
} else {
|
} else {
|
||||||
this.$emit('input', !this.synthesizedChecked)
|
this.$emit('change', !this.synthesizedChecked, this.synthesizedChecked)
|
||||||
this.$emit('change', !this.synthesizedChecked, this.value)
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
handleClick (e) {
|
handleClick (e) {
|
||||||
|
85
packages/common/Checkbox/src/SimpleCheckbox.vue
Normal file
85
packages/common/Checkbox/src/SimpleCheckbox.vue
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class="n-checkbox"
|
||||||
|
:class="{
|
||||||
|
'n-checkbox--checked': checked,
|
||||||
|
'n-checkbox--disabled': disabled,
|
||||||
|
'n-checkbox--indeterminate': indeterminate,
|
||||||
|
[`n-${theme}-theme`]: theme
|
||||||
|
}"
|
||||||
|
:tabindex="disabled ? false : 0"
|
||||||
|
@keyup.enter="handleKeyUpEnter"
|
||||||
|
@keyup.space="handleKeyUpSpace"
|
||||||
|
@keydown.space="handleKeyDownSpace"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="n-checkbox-box"
|
||||||
|
@click="handleClick"
|
||||||
|
>
|
||||||
|
<check-mark class="n-checkbox-box__check-mark" />
|
||||||
|
<line-mark class="n-checkbox-box__line-mark" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import CheckMark from './CheckMark'
|
||||||
|
import LineMark from './LineMark'
|
||||||
|
import createValidator from '../../../utils/validateProp'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'NSimpleCheckbox',
|
||||||
|
components: {
|
||||||
|
CheckMark,
|
||||||
|
LineMark
|
||||||
|
},
|
||||||
|
model: {
|
||||||
|
prop: 'checked',
|
||||||
|
event: 'chanage'
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
value: {
|
||||||
|
validator: createValidator(['number', 'boolean', 'string']),
|
||||||
|
default: null
|
||||||
|
},
|
||||||
|
checked: {
|
||||||
|
validator: createValidator(['boolean']),
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
disabled: {
|
||||||
|
validator: createValidator(['boolean']),
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
indeterminate: {
|
||||||
|
validator: createValidator(['boolean']),
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
theme: {
|
||||||
|
validator: createValidator(['string']),
|
||||||
|
default: null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
handleClick (e) {
|
||||||
|
this.$emit('click', e)
|
||||||
|
if (!this.disabled) {
|
||||||
|
this.toggle()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handleKeyUpEnter (e) {
|
||||||
|
if (!this.disabled) {
|
||||||
|
this.toggle()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
toggle () {
|
||||||
|
this.$emit('change', !this.checked, this.checked)
|
||||||
|
},
|
||||||
|
handleKeyDownSpace (e) {
|
||||||
|
e.preventDefault()
|
||||||
|
},
|
||||||
|
handleKeyUpSpace (e) {
|
||||||
|
this.handleKeyUpEnter()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
@ -1,7 +1,9 @@
|
|||||||
/* istanbul ignore file */
|
/* istanbul ignore file */
|
||||||
import Transfer from './src/Transfer.vue'
|
import Transfer from './src/Transfer.vue'
|
||||||
|
import installPropsUnsafeTransition from '../../utils/installPropsUnsafeTransition'
|
||||||
|
|
||||||
Transfer.install = function (Vue) {
|
Transfer.install = function (Vue) {
|
||||||
|
installPropsUnsafeTransition(Vue)
|
||||||
Vue.component(Transfer.name, Transfer)
|
Vue.component(Transfer.name, Transfer)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,68 +8,44 @@
|
|||||||
<div class="n-transfer-list">
|
<div class="n-transfer-list">
|
||||||
<div class="n-transfer-list-header">
|
<div class="n-transfer-list-header">
|
||||||
<div class="n-transfer-list-header__checkbox">
|
<div class="n-transfer-list-header__checkbox">
|
||||||
<div class="n-transfer-list-light-bar" />
|
<n-transfer-header-checkbox :source="true" :theme="synthesizedTheme" @change="handleSourceHeaderCheckboxChange" />
|
||||||
<n-checkbox
|
|
||||||
:checked="sourceHeaderCheckboxChecked"
|
|
||||||
:indeterminate="sourceHeaderCheckboxIndeterminate"
|
|
||||||
:disabled="sourceHeaderCheckboxDisabled"
|
|
||||||
@input="handleSourceHeaderCheckboxInput"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="n-transfer-list-header__header">
|
<div class="n-transfer-list-header__header">
|
||||||
Source
|
Source
|
||||||
</div>
|
</div>
|
||||||
<div class="n-transfer-list-header__extra">
|
<n-transfer-header-extra :source="true" />
|
||||||
{{ sourceCheckedValueSet.size }} / {{ sourceOptions.length }}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="n-transfer-list-body"
|
class="n-transfer-list-body"
|
||||||
@mouseleave="handleSourceListMouseLeave"
|
@mouseleave="handleSourceListMouseLeave"
|
||||||
>
|
>
|
||||||
<n-scrollbar ref="leftScrollbar">
|
<n-scrollbar @scroll="handleSourceListScroll">
|
||||||
<ul class="n-transfer-list-content">
|
<ul ref="sourceList" class="n-transfer-list-content n-transfer-list-content--animation-disabled">
|
||||||
<transition name="n-transfer-list-light-bar--transition">
|
<n-transfer-light-bar ref="lightBar" />
|
||||||
<div
|
<n-transfer-source-list-item
|
||||||
v-if="showLightBar"
|
v-for="(option, index) in memorizedSourceOptions"
|
||||||
class="n-transfer-list-light-bar"
|
ref="sourceListItems"
|
||||||
:style="{
|
|
||||||
top: lightBarStyleTop
|
|
||||||
}"
|
|
||||||
/>
|
|
||||||
</transition>
|
|
||||||
<n-transfer-list-item
|
|
||||||
v-for="option in memorizedSourceOptions"
|
|
||||||
:key="option.value"
|
:key="option.value"
|
||||||
:show="sourceValueSet.has(option.value)"
|
:index="index"
|
||||||
:value="option.value"
|
:value="option.value"
|
||||||
:checked="sourceCheckedValueSet.has(option.value)"
|
:disabled="!!option.disabled"
|
||||||
:disabled="option.disabled"
|
:label="option.label"
|
||||||
source
|
@click="handleSourceCheckboxClick"
|
||||||
:title="option.label"
|
|
||||||
@click="handleSourceCheckboxInput(
|
|
||||||
!sourceCheckedValueSet.has(option.value),
|
|
||||||
option.value
|
|
||||||
)"
|
|
||||||
@mouseenter="handleSourceOptionMouseEnter"
|
@mouseenter="handleSourceOptionMouseEnter"
|
||||||
@mouseleave="handleSourceOptionMouseLeave"
|
@mouseleave="handleSourceOptionMouseLeave"
|
||||||
>
|
/>
|
||||||
{{ option.label }}
|
|
||||||
</n-transfer-list-item>
|
|
||||||
</ul>
|
</ul>
|
||||||
</n-scrollbar>
|
</n-scrollbar>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="n-transfer-gap">
|
<div class="n-transfer-gap">
|
||||||
<n-transfer-button
|
<n-transfer-button
|
||||||
to
|
:to="true"
|
||||||
:disabled="toTargetButtonDisabled"
|
|
||||||
@click="handleToTargetClick"
|
@click="handleToTargetClick"
|
||||||
>
|
>
|
||||||
To Target
|
To Target
|
||||||
</n-transfer-button>
|
</n-transfer-button>
|
||||||
<n-transfer-button
|
<n-transfer-button
|
||||||
:disabled="toSourceButtonDisabled"
|
|
||||||
@click="handleToSourceClick"
|
@click="handleToSourceClick"
|
||||||
>
|
>
|
||||||
To Source
|
To Source
|
||||||
@ -78,53 +54,32 @@
|
|||||||
<div class="n-transfer-list">
|
<div class="n-transfer-list">
|
||||||
<div class="n-transfer-list-header">
|
<div class="n-transfer-list-header">
|
||||||
<div class="n-transfer-list-header__checkbox">
|
<div class="n-transfer-list-header__checkbox">
|
||||||
<n-checkbox
|
<n-transfer-header-checkbox :theme="synthesizedTheme" @change="handleTargetHeaderCheckboxChange" />
|
||||||
:checked="targetHeaderCheckboxChecked"
|
|
||||||
:indeterminate="targetHeaderCheckboxIndeterminate"
|
|
||||||
:disabled="targetHeaderCheckboxDisabled"
|
|
||||||
@input="handleTargetHeaderCheckboxInput"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="n-transfer-list-header__header">
|
<div class="n-transfer-list-header__header">
|
||||||
Target
|
Target
|
||||||
</div>
|
</div>
|
||||||
<div class="n-transfer-list-header__extra">
|
<n-transfer-header-extra />
|
||||||
{{ targetCheckedValueSet.size }} / {{ targetOptions.length }}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="n-transfer-list-body"
|
class="n-transfer-list-body"
|
||||||
@mouseleave="handleTargetListMouseLeave"
|
@mouseleave="handleTargetListMouseLeave"
|
||||||
>
|
>
|
||||||
<n-scrollbar ref="rightScrollbar">
|
<n-scrollbar ref="rightScrollbar" @scroll="handleTargetListScroll">
|
||||||
<ul class="n-transfer-list-content">
|
<ul ref="targetList" class="n-transfer-list-content n-transfer-list-content--animation-disabled">
|
||||||
<transition name="n-transfer-list-light-bar--transition">
|
<n-transfer-light-bar ref="secondLightBar" />
|
||||||
<div
|
<n-transfer-target-list-item
|
||||||
v-if="showSecondLightBar"
|
v-for="(option, index) in targetOptions"
|
||||||
class="n-transfer-list-light-bar"
|
ref="targetListItems"
|
||||||
:style="{
|
|
||||||
top: secondLightBarStyleTop
|
|
||||||
}"
|
|
||||||
/>
|
|
||||||
</transition>
|
|
||||||
<n-transfer-list-item
|
|
||||||
v-for="option in memorizedTargetOptions"
|
|
||||||
:key="option.value"
|
:key="option.value"
|
||||||
:show="targetValueSet.has(option.value)"
|
:index="index"
|
||||||
:value="option.value"
|
:value="option.value"
|
||||||
:checked="targetCheckedValueSet.has(option.value)"
|
:disabled="!!option.disabled"
|
||||||
:disabled="option.disabled"
|
:label="option.label"
|
||||||
target
|
@click="handleTargetCheckboxClick"
|
||||||
:title="option.label"
|
|
||||||
@click="handleTargetCheckboxInput(
|
|
||||||
!targetCheckedValueSet.has(option.value),
|
|
||||||
option.value
|
|
||||||
)"
|
|
||||||
@mouseenter="handleTargetOptionMouseEnter"
|
@mouseenter="handleTargetOptionMouseEnter"
|
||||||
@mouseleave="handleTargetOptionMouseLeave"
|
@mouseleave="handleTargetOptionMouseLeave"
|
||||||
>
|
/>
|
||||||
{{ option.label }}
|
|
||||||
</n-transfer-list-item>
|
|
||||||
</ul>
|
</ul>
|
||||||
</n-scrollbar>
|
</n-scrollbar>
|
||||||
</div>
|
</div>
|
||||||
@ -133,26 +88,37 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import NCheckbox from '../../Checkbox'
|
|
||||||
import NScrollbar from '../../Scrollbar'
|
import NScrollbar from '../../Scrollbar'
|
||||||
import NTransferListItem from './TransferListItem'
|
import NTransferHeaderCheckbox from './TransferHeaderCheckbox'
|
||||||
|
import NTransferHeaderExtra from './TransferHeaderExtra'
|
||||||
|
import NTransferSourceListItem from './TransferSourceListItem'
|
||||||
|
import NTransferTargetListItem from './TransferTargetListItem'
|
||||||
import NTransferButton from './TransferButton'
|
import NTransferButton from './TransferButton'
|
||||||
import cloneDeep from 'lodash/cloneDeep'
|
import cloneDeep from 'lodash/cloneDeep'
|
||||||
import withlightbar from '../../../mixins/withlightbar'
|
|
||||||
import withsecondlightbar from '../../../mixins/withsecondlightbar'
|
|
||||||
import asformitem from '../../../mixins/asformitem'
|
import asformitem from '../../../mixins/asformitem'
|
||||||
import withapp from '../../../mixins/withapp'
|
import withapp from '../../../mixins/withapp'
|
||||||
import themeable from '../../../mixins/themeable'
|
import themeable from '../../../mixins/themeable'
|
||||||
|
import NTransferLightBar from './TransferLightBar'
|
||||||
|
import debounce from 'lodash-es/debounce'
|
||||||
|
|
||||||
|
const SCROLL_VISIBLE_BUFFER = 1200
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'NTransfer',
|
name: 'NTransfer',
|
||||||
components: {
|
components: {
|
||||||
NCheckbox,
|
NTransferHeaderCheckbox,
|
||||||
|
NTransferHeaderExtra,
|
||||||
NScrollbar,
|
NScrollbar,
|
||||||
NTransferListItem,
|
NTransferSourceListItem,
|
||||||
NTransferButton
|
NTransferTargetListItem,
|
||||||
|
NTransferButton,
|
||||||
|
NTransferLightBar
|
||||||
|
},
|
||||||
|
mixins: [withapp, themeable, asformitem()],
|
||||||
|
model: {
|
||||||
|
prop: 'value',
|
||||||
|
event: 'change'
|
||||||
},
|
},
|
||||||
mixins: [withapp, themeable, asformitem(), withlightbar, withsecondlightbar],
|
|
||||||
props: {
|
props: {
|
||||||
value: {
|
value: {
|
||||||
type: Array,
|
type: Array,
|
||||||
@ -167,204 +133,280 @@ export default {
|
|||||||
default: false
|
default: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
provide () {
|
||||||
|
return {
|
||||||
|
NTransfer: this
|
||||||
|
}
|
||||||
|
},
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
sourceCheckedValues: [],
|
sourceCheckedValues: [],
|
||||||
targetCheckedValues: [],
|
targetCheckedValues: [],
|
||||||
memorizedSourceOptions: null,
|
memorizedSourceOptions: null,
|
||||||
memorizedTargetOptions: null,
|
sourceListVisibleMinIndex: -SCROLL_VISIBLE_BUFFER / 34,
|
||||||
init: true,
|
sourceListVisibleMaxIndex: SCROLL_VISIBLE_BUFFER / 34,
|
||||||
active: true
|
targetListVisibleMinIndex: -SCROLL_VISIBLE_BUFFER / 34,
|
||||||
|
targetListVisibleMaxIndex: SCROLL_VISIBLE_BUFFER / 34,
|
||||||
|
initialized: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
toTargetButtonDisabled () {
|
mergedDisabledStatus () {
|
||||||
return this.disabled || this.sourceCheckedValueSet.size === 0
|
return {
|
||||||
},
|
source: this.memorizedSourceOptions.every(option => option.disabled),
|
||||||
toSourceButtonDisabled () {
|
target: this.targetOptions.every(option => option.disabled)
|
||||||
return this.disabled || this.targetCheckedValueSet.size === 0
|
}
|
||||||
},
|
|
||||||
sourceEnabledOptions () {
|
|
||||||
return this.sourceOptions.filter(option => !option.disabled)
|
|
||||||
},
|
|
||||||
targetEnabledOptions () {
|
|
||||||
return this.targetOptions.filter(option => !option.disabled)
|
|
||||||
},
|
},
|
||||||
sourceHeaderCheckboxDisabled () {
|
sourceHeaderCheckboxDisabled () {
|
||||||
return this.disabled || !this.sourceEnabledOptions.length
|
return this.disabled || this.mergedDisabledStatus.source
|
||||||
},
|
},
|
||||||
targetHeaderCheckboxDisabled () {
|
targetHeaderCheckboxDisabled () {
|
||||||
return this.disabled || !this.targetEnabledOptions.length
|
return this.disabled || this.mergedDisabledStatus.target
|
||||||
},
|
},
|
||||||
sourceHeaderCheckboxChecked () {
|
sourceHeaderCheckboxChecked () {
|
||||||
return this.sourceCheckedValueSet.size === this.sourceOptions.length && !!this.sourceOptions.length
|
return this.sourceCheckedValues.length === this.memorizedSourceOptions.length && !!this.memorizedSourceOptions.length
|
||||||
},
|
},
|
||||||
targetHeaderCheckboxChecked () {
|
targetHeaderCheckboxChecked () {
|
||||||
return this.targetCheckedValueSet.size === this.targetOptions.length && !!this.targetOptions.length
|
return this.targetCheckedValues.length === this.targetOptions.length && !!this.targetOptions.length
|
||||||
},
|
},
|
||||||
valueToOptionMap () {
|
valueToOptionMap () {
|
||||||
const map = new Map()
|
const map = new Map()
|
||||||
this.options.forEach(option => {
|
this.options.forEach(option => {
|
||||||
map.set(option.value, option)
|
map.set(option.value, { ...option })
|
||||||
})
|
})
|
||||||
return map
|
return map
|
||||||
},
|
},
|
||||||
sourceHeaderCheckboxIndeterminate () {
|
sourceHeaderCheckboxIndeterminate () {
|
||||||
return this.sourceCheckedValueSet.size !== 0 && this.sourceCheckedValueSet.size < this.sourceOptions.length
|
return this.sourceCheckedValues.length !== 0 && this.sourceCheckedValues.length < this.memorizedSourceOptions.length
|
||||||
},
|
},
|
||||||
targetHeaderCheckboxIndeterminate () {
|
targetHeaderCheckboxIndeterminate () {
|
||||||
return this.targetCheckedValueSet.size !== 0 && this.targetCheckedValueSet.size < this.targetOptions.length
|
return this.targetCheckedValues.length !== 0 && this.targetCheckedValues.length < this.targetOptions.length
|
||||||
},
|
|
||||||
sourceValueSet () {
|
|
||||||
return new Set(this.sourceOptions.map(option => option.value))
|
|
||||||
},
|
|
||||||
targetValueSet () {
|
|
||||||
return new Set(this.targetOptions.map(option => option.value))
|
|
||||||
},
|
},
|
||||||
sourceCheckedValueSet () {
|
sourceCheckedValueSet () {
|
||||||
return new Set(this.sourceCheckedValues.filter(value => this.valueToOptionMap.has(value)))
|
const valueToOptionMap = this.valueToOptionMap
|
||||||
|
return new Set(this.sourceCheckedValues.filter(value => valueToOptionMap.has(value)))
|
||||||
},
|
},
|
||||||
targetCheckedValueSet () {
|
targetCheckedValueSet () {
|
||||||
return new Set(this.targetCheckedValues.filter(value => this.valueToOptionMap.has(value)))
|
const valueToOptionMap = this.valueToOptionMap
|
||||||
|
return new Set(this.targetCheckedValues.filter(value => valueToOptionMap.has(value)))
|
||||||
},
|
},
|
||||||
sourceOptions () {
|
valueSet () {
|
||||||
const valueSet = Array.isArray(this.value) ? new Set(this.value) : new Set()
|
return Array.isArray(this.value) ? new Set(this.value) : new Set()
|
||||||
return this.options.filter(option => !valueSet.has(option.value))
|
},
|
||||||
|
sourceValueSet () {
|
||||||
|
return this.mergedValueSet.sourceValueSet
|
||||||
|
},
|
||||||
|
targetValueSet () {
|
||||||
|
return this.mergedValueSet.targetValueSet
|
||||||
|
},
|
||||||
|
targetOptionsWithShowStatus () {
|
||||||
|
return this.targetOptions.map((option, index) => {
|
||||||
|
const show = true
|
||||||
|
option.show = show
|
||||||
|
option.index = index
|
||||||
|
return option
|
||||||
|
})
|
||||||
|
},
|
||||||
|
mergedValueSet () {
|
||||||
|
const valueSet = this.valueSet
|
||||||
|
const sourceValueSet = new Set()
|
||||||
|
const targetValueSet = new Set()
|
||||||
|
this.options.forEach(option => {
|
||||||
|
if (valueSet.has(option.value)) {
|
||||||
|
targetValueSet.add(option.value)
|
||||||
|
} else {
|
||||||
|
sourceValueSet.add(option.value)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return {
|
||||||
|
sourceValueSet,
|
||||||
|
targetValueSet
|
||||||
|
}
|
||||||
},
|
},
|
||||||
targetOptions () {
|
targetOptions () {
|
||||||
const valueSet = Array.isArray(this.value) ? new Set(this.value) : new Set()
|
const vModelValue = Array.isArray(this.value) ? this.value : []
|
||||||
return this.options.filter(option => valueSet.has(option.value))
|
const valueMap = this.valueToOptionMap
|
||||||
},
|
const targetOptions = []
|
||||||
orderedOptions () {
|
vModelValue.forEach(value => {
|
||||||
return this.sourceOptions.concat(this.targetOptions)
|
const option = valueMap.get(value)
|
||||||
|
if (option !== undefined) targetOptions.push(option)
|
||||||
|
})
|
||||||
|
return targetOptions
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
options (newOptions) {
|
options (newOptions) {
|
||||||
this.init = true
|
this.initialized = false
|
||||||
this.$nextTick().then(() => {
|
const valueSet = this.valueSet
|
||||||
this.memorizedSourceOptions = cloneDeep(newOptions)
|
this.memorizedSourceOptions = cloneDeep(this.options.filter(option => !valueSet.has(option.value)))
|
||||||
this.memorizedTargetOptions = cloneDeep(newOptions)
|
|
||||||
this.sourceCheckedValues = []
|
this.sourceCheckedValues = []
|
||||||
this.targetCheckedValues = []
|
this.targetCheckedValues = []
|
||||||
return this.$nextTick()
|
this.$nextTick().then(() => {
|
||||||
}).then(() => {
|
this.initialized = true
|
||||||
this.init = false
|
|
||||||
})
|
})
|
||||||
},
|
|
||||||
value (value, oldValue) {
|
|
||||||
this.$emit('change', value, oldValue)
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted () {
|
mounted () {
|
||||||
this.$nextTick().then(() => {
|
this.initialized = true
|
||||||
this.init = false
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
created () {
|
created () {
|
||||||
this.memorizedSourceOptions = cloneDeep(this.options)
|
const valueSet = this.valueSet
|
||||||
this.memorizedTargetOptions = cloneDeep(this.options)
|
this.memorizedSourceOptions = cloneDeep(this.options.filter(option => !valueSet.has(option.value)))
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
emitInputEvent (value) {
|
handleSourceListScroll: debounce(function (_, container) {
|
||||||
|
const scrollTop = container.scrollTop
|
||||||
|
this.sourceListVisibleMinIndex = (scrollTop - SCROLL_VISIBLE_BUFFER) / 34
|
||||||
|
this.sourceListVisibleMaxIndex = (scrollTop + SCROLL_VISIBLE_BUFFER) / 34
|
||||||
|
}, 128),
|
||||||
|
handleTargetListScroll: debounce(function (_, container) {
|
||||||
|
const scrollTop = container.scrollTop
|
||||||
|
this.targetListVisibleMinIndex = (scrollTop - SCROLL_VISIBLE_BUFFER) / 34
|
||||||
|
this.targetListVisibleMaxIndex = (scrollTop + SCROLL_VISIBLE_BUFFER) / 34
|
||||||
|
}, 128),
|
||||||
|
emitChangeEvent (value) {
|
||||||
const newValue = this.cleanValue(value)
|
const newValue = this.cleanValue(value)
|
||||||
this.$emit('input', newValue)
|
this.$emit('change', newValue)
|
||||||
},
|
},
|
||||||
cleanValue (value) {
|
cleanValue (value) {
|
||||||
if (Array.isArray(value)) {
|
if (Array.isArray(value)) {
|
||||||
return value.filter((v) => this.valueToOptionMap.has(v))
|
return value.filter((v) => this.valueToOptionMap.has(v))
|
||||||
} else return null
|
} else return null
|
||||||
},
|
},
|
||||||
handleSourceHeaderCheckboxInput (value) {
|
handleSourceHeaderCheckboxChange (value) {
|
||||||
if (this.sourceHeaderCheckboxIndeterminate) {
|
if (this.sourceHeaderCheckboxIndeterminate) {
|
||||||
|
(this.$refs.sourceListItems || []).forEach(listItem => listItem.setChecked(false))
|
||||||
this.sourceCheckedValues = []
|
this.sourceCheckedValues = []
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (value) {
|
if (value) {
|
||||||
const newValues = this.sourceOptions.filter(option => !option.disabled).map(option => option.value).concat(this.sourceCheckedValues)
|
(this.$refs.sourceListItems || []).forEach(listItem => listItem.setChecked(true))
|
||||||
|
const newValues = this.memorizedSourceOptions.filter(option => !option.disabled).map(option => option.value).concat(this.sourceCheckedValues)
|
||||||
this.sourceCheckedValues = Array.from(new Set(newValues))
|
this.sourceCheckedValues = Array.from(new Set(newValues))
|
||||||
} else {
|
} else {
|
||||||
|
(this.$refs.sourceListItems || []).forEach(listItem => listItem.setChecked(false))
|
||||||
this.sourceCheckedValues = []
|
this.sourceCheckedValues = []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
handleTargetHeaderCheckboxInput (value) {
|
handleTargetHeaderCheckboxChange (value) {
|
||||||
if (this.targetHeaderCheckboxIndeterminate) {
|
if (this.targetHeaderCheckboxIndeterminate) {
|
||||||
|
(this.$refs.targetListItems || []).forEach(listItem => listItem.setChecked(false))
|
||||||
this.targetCheckedValues = []
|
this.targetCheckedValues = []
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (value) {
|
if (value) {
|
||||||
|
(this.$refs.targetListItems || []).forEach(listItem => listItem.setChecked(true))
|
||||||
const newValues = this.targetOptions.filter(option => !option.disabled).map(option => option.value).concat(this.targetCheckedValues)
|
const newValues = this.targetOptions.filter(option => !option.disabled).map(option => option.value).concat(this.targetCheckedValues)
|
||||||
this.targetCheckedValues = Array.from(new Set(newValues))
|
this.targetCheckedValues = Array.from(new Set(newValues))
|
||||||
} else {
|
} else {
|
||||||
|
(this.$refs.targetListItems || []).forEach(listItem => listItem.setChecked(false))
|
||||||
this.targetCheckedValues = []
|
this.targetCheckedValues = []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
handleTargetCheckboxInput (checked, value) {
|
handleTargetCheckboxClick (checked, optionValue) {
|
||||||
if (checked) {
|
if (checked) {
|
||||||
this.targetCheckedValues.push(value)
|
this.targetCheckedValues.push(optionValue)
|
||||||
} else {
|
} else {
|
||||||
const index = this.targetCheckedValues.findIndex(v => v === value)
|
const index = this.targetCheckedValues.findIndex(v => v === optionValue)
|
||||||
if (~index) this.targetCheckedValues.splice(index, 1)
|
if (~index) {
|
||||||
|
this.targetCheckedValues.splice(index, 1)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
handleSourceCheckboxInput (checked, value) {
|
handleSourceCheckboxClick (checked, optionValue) {
|
||||||
if (checked) {
|
if (checked) {
|
||||||
this.sourceCheckedValues.push(value)
|
this.sourceCheckedValues.push(optionValue)
|
||||||
} else {
|
} else {
|
||||||
const index = this.sourceCheckedValues.findIndex(v => v === value)
|
const index = this.sourceCheckedValues.findIndex(v => v === optionValue)
|
||||||
if (~index) this.sourceCheckedValues.splice(index, 1)
|
if (~index) {
|
||||||
|
this.sourceCheckedValues.splice(index, 1)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
handleToTargetClick () {
|
handleToTargetClick () {
|
||||||
|
const enteredItemEls = Array.from(this.$el.getElementsByClassName('n-transfer-list-item--enter'))
|
||||||
|
const length = enteredItemEls.length
|
||||||
|
for (let i = 0; i < length; ++i) {
|
||||||
|
enteredItemEls[i].classList.remove('n-transfer-list-item--enter')
|
||||||
|
}
|
||||||
|
this.$refs.sourceList.classList.remove('n-transfer-list-content--animation-disabled')
|
||||||
|
this.$refs.targetList.classList.remove('n-transfer-list-content--animation-disabled')
|
||||||
|
const sourceCheckedValues = this.sourceCheckedValues
|
||||||
|
/** create new value */
|
||||||
let newValue = Array.isArray(this.value) ? this.value : []
|
let newValue = Array.isArray(this.value) ? this.value : []
|
||||||
newValue = this.sourceCheckedValues.concat(newValue)
|
newValue = sourceCheckedValues.concat(newValue)
|
||||||
const headTargetOptions = this.sourceCheckedValues.map(value => this.valueToOptionMap.get(value)).map(option => ({
|
/** play source leave animation */
|
||||||
...option
|
const sourceCheckedValueSet = this.sourceCheckedValueSet
|
||||||
}))
|
this.$refs.sourceListItems.forEach(listItem => {
|
||||||
const tailTargetOptions = this.memorizedTargetOptions.filter(option => !this.sourceCheckedValueSet.has(option.value)).map(option => ({
|
if (sourceCheckedValueSet.has(listItem.value)) {
|
||||||
...option
|
listItem.leave()
|
||||||
}))
|
}
|
||||||
const reorderedTargetOptions = headTargetOptions.concat(tailTargetOptions)
|
|
||||||
this.memorizedTargetOptions = reorderedTargetOptions
|
|
||||||
this.$nextTick().then(() => {
|
|
||||||
this.emitInputEvent(newValue)
|
|
||||||
})
|
})
|
||||||
|
window.setTimeout(() => {
|
||||||
|
/** disable animation before apply dom change */
|
||||||
|
this.$refs.sourceList.classList.add('n-transfer-list-content--animation-disabled')
|
||||||
|
/** after animation is done change memorized source options to remove dom */
|
||||||
|
this.memorizedSourceOptions = this.memorizedSourceOptions.filter(option => !sourceCheckedValueSet.has(option.value))
|
||||||
|
}, 300)
|
||||||
|
/** clear check */
|
||||||
|
;(this.$refs.sourceListItems || []).forEach(listItem => listItem.setChecked(false))
|
||||||
this.sourceCheckedValues = []
|
this.sourceCheckedValues = []
|
||||||
|
/** emit new value */
|
||||||
|
/** auto play target options enter animation */
|
||||||
|
this.$emit('change', newValue)
|
||||||
},
|
},
|
||||||
handleToSourceClick () {
|
handleToSourceClick () {
|
||||||
|
const enteredItemEls = Array.from(this.$el.getElementsByClassName('n-transfer-list-item--enter'))
|
||||||
|
const length = enteredItemEls.length
|
||||||
|
for (let i = 0; i < length; ++i) {
|
||||||
|
enteredItemEls[i].classList.remove('n-transfer-list-item--enter')
|
||||||
|
}
|
||||||
|
this.$refs.sourceList.classList.remove('n-transfer-list-content--animation-disabled')
|
||||||
|
this.$refs.targetList.classList.remove('n-transfer-list-content--animation-disabled')
|
||||||
|
/** create new value */
|
||||||
let newValue = Array.isArray(this.value) ? this.value : []
|
let newValue = Array.isArray(this.value) ? this.value : []
|
||||||
newValue = newValue.filter(value => !this.targetCheckedValueSet.has(value))
|
const targetValueSet = this.targetCheckedValueSet
|
||||||
const headSourceOptions = this.targetCheckedValues.map(value => this.valueToOptionMap.get(value)).map(option => ({
|
/** play target leave animation */
|
||||||
...option
|
this.$refs.targetListItems.forEach(listItem => {
|
||||||
}))
|
if (targetValueSet.has(listItem.value)) {
|
||||||
const tailSourceOptions = this.memorizedSourceOptions.filter(option => !this.targetCheckedValueSet.has(option.value)).map(option => ({
|
listItem.leave()
|
||||||
...option
|
}
|
||||||
}))
|
|
||||||
const reorderedSourceOptions = headSourceOptions.concat(tailSourceOptions)
|
|
||||||
this.memorizedSourceOptions = reorderedSourceOptions
|
|
||||||
this.$nextTick().then(() => {
|
|
||||||
this.emitInputEvent(newValue)
|
|
||||||
})
|
})
|
||||||
|
window.setTimeout(() => {
|
||||||
|
this.$refs.targetList.classList.add('n-transfer-list-content--animation-disabled')
|
||||||
|
/** disable animation before apply dom change */
|
||||||
|
this.targetAnimationDisabled = true
|
||||||
|
/** after animation is done change value to remove dom */
|
||||||
|
newValue = newValue.filter(value => !targetValueSet.has(value))
|
||||||
|
/** emit new value */
|
||||||
|
this.emitChangeEvent(newValue)
|
||||||
|
}, 300)
|
||||||
|
/** change memorized source options */
|
||||||
|
const valueToOptionMap = this.valueToOptionMap
|
||||||
|
const newSourceOptions = this.targetCheckedValues.map(value => valueToOptionMap.get(value))
|
||||||
|
this.memorizedSourceOptions = newSourceOptions.concat(this.memorizedSourceOptions)
|
||||||
|
/** clear check */
|
||||||
|
;(this.$refs.targetListItems || []).forEach(listItem => listItem.setChecked(false))
|
||||||
this.targetCheckedValues = []
|
this.targetCheckedValues = []
|
||||||
},
|
},
|
||||||
handleSourceOptionMouseEnter (e) {
|
handleSourceOptionMouseEnter: debounce(function (e) {
|
||||||
this.updateLightBarPosition(e.target)
|
this.$refs.lightBar.updateLightBarPosition(e.target)
|
||||||
},
|
}, 128),
|
||||||
handleTargetOptionMouseEnter (e) {
|
handleTargetOptionMouseEnter: debounce(function (e) {
|
||||||
this.updateSecondLightBarPosition(e.target)
|
this.$refs.secondLightBar.updateLightBarPosition(e.target)
|
||||||
},
|
}, 128),
|
||||||
handleSourceOptionMouseLeave (e) {
|
handleSourceOptionMouseLeave: debounce(function (e) {
|
||||||
this.hideLightBar()
|
this.$refs.lightBar.hideLightBar()
|
||||||
},
|
}, 128),
|
||||||
handleTargetOptionMouseLeave (e) {
|
handleTargetOptionMouseLeave: debounce(function (e) {
|
||||||
this.hideSecondLightBar()
|
this.$refs.secondLightBar.hideLightBar()
|
||||||
},
|
}, 128),
|
||||||
handleSourceListMouseLeave () {
|
handleSourceListMouseLeave: debounce(function () {
|
||||||
this.hideLightBar()
|
this.$refs.lightBar.hideLightBar()
|
||||||
},
|
}, 128),
|
||||||
handleTargetListMouseLeave () {
|
handleTargetListMouseLeave: debounce(function () {
|
||||||
this.hideSecondLightBar()
|
this.$refs.secondLightBar.hideLightBar()
|
||||||
}
|
}, 128)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
@ -21,15 +21,25 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import createValidator from '../../../utils/validateProp'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: {
|
props: {
|
||||||
to: {
|
to: {
|
||||||
type: Boolean,
|
validator: createValidator(['boolean']),
|
||||||
default: false
|
default: false
|
||||||
|
}
|
||||||
},
|
},
|
||||||
disabled: {
|
inject: {
|
||||||
type: Boolean,
|
NTransfer: {
|
||||||
default: false
|
default: null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
disabled () {
|
||||||
|
if (this.NTransfer.disabled) return true
|
||||||
|
if (this.to) return this.NTransfer.sourceCheckedValues.length === 0
|
||||||
|
else return this.NTransfer.targetCheckedValues.length === 0
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
55
packages/common/Transfer/src/TransferHeaderCheckbox.vue
Normal file
55
packages/common/Transfer/src/TransferHeaderCheckbox.vue
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
<template>
|
||||||
|
<n-simple-checkbox
|
||||||
|
:theme="theme"
|
||||||
|
:checked="checked"
|
||||||
|
:indeterminate="indeterminate"
|
||||||
|
:disabled="disabled"
|
||||||
|
@change="handleChange"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import NSimpleCheckbox from '../../Checkbox/src/SimpleCheckbox'
|
||||||
|
import createValidator from '../../../utils/validateProp'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'NTransferHeaderCheckbox',
|
||||||
|
components: {
|
||||||
|
NSimpleCheckbox
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
theme: {
|
||||||
|
validator: createValidator(['string']),
|
||||||
|
default: null
|
||||||
|
},
|
||||||
|
source: {
|
||||||
|
validator: createValidator(['boolean']),
|
||||||
|
default: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
inject: {
|
||||||
|
NTransfer: {
|
||||||
|
default: null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
checked () {
|
||||||
|
if (this.source) return this.NTransfer.sourceHeaderCheckboxChecked
|
||||||
|
return this.NTransfer.targetHeaderCheckboxChecked
|
||||||
|
},
|
||||||
|
disabled () {
|
||||||
|
if (this.source) return this.NTransfer.sourceHeaderCheckboxDisabled
|
||||||
|
return this.NTransfer.targetHeaderCheckboxDisabled
|
||||||
|
},
|
||||||
|
indeterminate () {
|
||||||
|
if (this.source) return this.NTransfer.sourceHeaderCheckboxIndeterminate
|
||||||
|
return this.NTransfer.targetHeaderCheckboxIndeterminate
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
handleChange (value) {
|
||||||
|
this.$emit('change', value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
32
packages/common/Transfer/src/TransferHeaderExtra.vue
Normal file
32
packages/common/Transfer/src/TransferHeaderExtra.vue
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
<template>
|
||||||
|
<div class="n-transfer-list-header__extra">
|
||||||
|
{{
|
||||||
|
source ?
|
||||||
|
NTransfer.sourceCheckedValues.length :
|
||||||
|
NTransfer.targetCheckedValues.length
|
||||||
|
}} / {{
|
||||||
|
source ?
|
||||||
|
NTransfer.memorizedSourceOptions.length :
|
||||||
|
NTransfer.targetOptions.length
|
||||||
|
}}
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import createValidator from '../../../utils/validateProp'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'NTransferHeaderExtra',
|
||||||
|
props: {
|
||||||
|
source: {
|
||||||
|
validator: createValidator(['boolean']),
|
||||||
|
default: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
inject: {
|
||||||
|
NTransfer: {
|
||||||
|
default: null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
39
packages/common/Transfer/src/TransferLightBar.vue
Normal file
39
packages/common/Transfer/src/TransferLightBar.vue
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
<template>
|
||||||
|
<!-- <div style="position: absolute; left: 0; right: 0; top: 0; bottom: 0;"> -->
|
||||||
|
<transition name="n-transfer-list-light-bar--transition">
|
||||||
|
<div
|
||||||
|
v-show="show"
|
||||||
|
class="n-transfer-list-light-bar"
|
||||||
|
:style="{
|
||||||
|
top: styleTop
|
||||||
|
}"
|
||||||
|
/>
|
||||||
|
</transition>
|
||||||
|
<!-- </div> -->
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
show: false,
|
||||||
|
styleTop: null,
|
||||||
|
vanishTimerId: null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
hideLightBar (delay = 300) {
|
||||||
|
this.vanishTimerId && window.clearTimeout(this.vanishTimerId)
|
||||||
|
this.vanishTimerId = window.setTimeout(() => {
|
||||||
|
this.show = false
|
||||||
|
}, delay)
|
||||||
|
},
|
||||||
|
updateLightBarPosition (el) {
|
||||||
|
this.vanishTimerId && window.clearTimeout(this.vanishTimerId)
|
||||||
|
this.vanishTimerId = null
|
||||||
|
this.show = true
|
||||||
|
this.styleTop = el.offsetTop + 'px'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
@ -1,100 +0,0 @@
|
|||||||
<template>
|
|
||||||
<transition
|
|
||||||
:name="transitionName"
|
|
||||||
>
|
|
||||||
<li
|
|
||||||
v-show="show"
|
|
||||||
class="n-transfer-list-item"
|
|
||||||
:class="{
|
|
||||||
'n-transfer-list-item--disabled': disabled
|
|
||||||
}"
|
|
||||||
@click="handleClick"
|
|
||||||
@mouseenter="handleMouseEnter"
|
|
||||||
@mouseleave="handleMouseLeave"
|
|
||||||
>
|
|
||||||
<div class="n-transfer-list-item__checkbox">
|
|
||||||
<n-checkbox
|
|
||||||
:disabled="disabled"
|
|
||||||
:checked="checked"
|
|
||||||
@input="handleInput"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="n-transfer-list-item__label"
|
|
||||||
:title="title"
|
|
||||||
>
|
|
||||||
<slot />
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
</transition>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import NCheckbox from '../../Checkbox'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'NTransferListItem',
|
|
||||||
components: {
|
|
||||||
NCheckbox
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
checked: {
|
|
||||||
type: Boolean,
|
|
||||||
required: true
|
|
||||||
},
|
|
||||||
value: {
|
|
||||||
validator () {
|
|
||||||
return true
|
|
||||||
},
|
|
||||||
required: true
|
|
||||||
},
|
|
||||||
disabled: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false
|
|
||||||
},
|
|
||||||
show: {
|
|
||||||
type: Boolean,
|
|
||||||
default: true
|
|
||||||
},
|
|
||||||
source: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false
|
|
||||||
},
|
|
||||||
target: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false
|
|
||||||
},
|
|
||||||
title: {
|
|
||||||
type: String,
|
|
||||||
required: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
transitionName () {
|
|
||||||
return this.source ? 'n-transfer-list-item-source--transition' : 'n-transfer-list-item-target--transition'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
handleClick () {
|
|
||||||
if (!this.disabled) {
|
|
||||||
this.$emit('click')
|
|
||||||
}
|
|
||||||
},
|
|
||||||
handleInput (checked) {
|
|
||||||
if (!this.disabled) {
|
|
||||||
this.$emit('input', checked, this.value)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
handleMouseEnter (e) {
|
|
||||||
if (!this.disabled) {
|
|
||||||
this.$emit('mouseenter', e)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
handleMouseLeave (e) {
|
|
||||||
if (!this.disabled) {
|
|
||||||
this.$emit('mouseleave', e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
104
packages/common/Transfer/src/TransferSourceListItem.vue
Normal file
104
packages/common/Transfer/src/TransferSourceListItem.vue
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
<template>
|
||||||
|
<li
|
||||||
|
class="n-transfer-list-item n-transfer-list-item--source"
|
||||||
|
:class="{
|
||||||
|
'n-transfer-list-item--disabled': disabled,
|
||||||
|
'n-transfer-list-item--enter': enableEnterAnimation
|
||||||
|
}"
|
||||||
|
@click="handleClick"
|
||||||
|
@mouseenter="handleMouseEnter"
|
||||||
|
@mouseleave="handleMouseLeave"
|
||||||
|
>
|
||||||
|
<div v-if="visible" class="n-transfer-list-item__checkbox">
|
||||||
|
<n-simple-checkbox
|
||||||
|
:theme="NTransfer.synthesizedTheme"
|
||||||
|
:disabled="disabled"
|
||||||
|
:checked="checked"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="visible"
|
||||||
|
class="n-transfer-list-item__label"
|
||||||
|
>
|
||||||
|
{{ label }}
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import NSimpleCheckbox from '../../Checkbox/src/SimpleCheckbox'
|
||||||
|
import createValidator from '../../../utils/validateProp'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'NTransferListItem',
|
||||||
|
components: {
|
||||||
|
NSimpleCheckbox
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
label: {
|
||||||
|
validator: createValidator(['string']),
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
value: {
|
||||||
|
validator: createValidator(['string', 'number']),
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
disabled: {
|
||||||
|
validator: createValidator(['boolean']),
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
index: {
|
||||||
|
validator: createValidator(['number']),
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
inject: {
|
||||||
|
NTransfer: {
|
||||||
|
default: null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
checked: false,
|
||||||
|
enableEnterAnimation: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
visible () {
|
||||||
|
return this.NTransfer.sourceListVisibleMinIndex < this.index && this.index < this.NTransfer.sourceListVisibleMaxIndex
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created () {
|
||||||
|
if (this.NTransfer.initialized) {
|
||||||
|
this.enableEnterAnimation = true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
setChecked (checked) {
|
||||||
|
if (!this.disabled && this.checked !== checked) {
|
||||||
|
this.checked = checked
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handleClick () {
|
||||||
|
if (!this.disabled) {
|
||||||
|
const newCheckedStatus = !this.checked
|
||||||
|
this.checked = newCheckedStatus
|
||||||
|
this.$emit('click', newCheckedStatus, this.value)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handleMouseEnter (e) {
|
||||||
|
if (!this.disabled) {
|
||||||
|
this.$emit('mouseenter', e)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handleMouseLeave (e) {
|
||||||
|
if (!this.disabled) {
|
||||||
|
this.$emit('mouseleave', e)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
leave () {
|
||||||
|
this.$el.classList.add('n-transfer-list-item--leave')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
104
packages/common/Transfer/src/TransferTargetListItem.vue
Normal file
104
packages/common/Transfer/src/TransferTargetListItem.vue
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
<template>
|
||||||
|
<li
|
||||||
|
class="n-transfer-list-item n-transfer-list-item--target"
|
||||||
|
:class="{
|
||||||
|
'n-transfer-list-item--disabled': disabled,
|
||||||
|
'n-transfer-list-item--enter': enableEnterAnimation
|
||||||
|
}"
|
||||||
|
@click="handleClick"
|
||||||
|
@mouseenter="handleMouseEnter"
|
||||||
|
@mouseleave="handleMouseLeave"
|
||||||
|
>
|
||||||
|
<div v-if="visible" class="n-transfer-list-item__checkbox">
|
||||||
|
<n-simple-checkbox
|
||||||
|
:theme="NTransfer.synthesizedTheme"
|
||||||
|
:disabled="disabled"
|
||||||
|
:checked="checked"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="visible"
|
||||||
|
class="n-transfer-list-item__label"
|
||||||
|
>
|
||||||
|
{{ label }}
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import NSimpleCheckbox from '../../Checkbox/src/SimpleCheckbox'
|
||||||
|
import createValidator from '../../../utils/validateProp'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'NTransferListItem',
|
||||||
|
components: {
|
||||||
|
NSimpleCheckbox
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
label: {
|
||||||
|
validator: createValidator(['string']),
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
value: {
|
||||||
|
validator: createValidator(['string', 'number']),
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
disabled: {
|
||||||
|
validator: createValidator(['boolean']),
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
index: {
|
||||||
|
validator: createValidator(['number']),
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
inject: {
|
||||||
|
NTransfer: {
|
||||||
|
default: null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
checked: false,
|
||||||
|
enableEnterAnimation: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
visible () {
|
||||||
|
return this.NTransfer.targetListVisibleMinIndex < this.index && this.index < this.NTransfer.targetListVisibleMaxIndex
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created () {
|
||||||
|
if (this.NTransfer.initialized) {
|
||||||
|
this.enableEnterAnimation = true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
setChecked (checked) {
|
||||||
|
if (!this.disabled && this.checked !== checked) {
|
||||||
|
this.checked = checked
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handleClick () {
|
||||||
|
if (!this.disabled) {
|
||||||
|
const newCheckedStatus = !this.checked
|
||||||
|
this.checked = newCheckedStatus
|
||||||
|
this.$emit('click', newCheckedStatus, this.value)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handleMouseEnter (e) {
|
||||||
|
if (!this.disabled) {
|
||||||
|
this.$emit('mouseenter', e)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handleMouseLeave (e) {
|
||||||
|
if (!this.disabled) {
|
||||||
|
this.$emit('mouseleave', e)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
leave () {
|
||||||
|
this.$el.classList.add('n-transfer-list-item--leave')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
15
packages/utils/installPropsUnsafeTransition.js
Normal file
15
packages/utils/installPropsUnsafeTransition.js
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
export default function (Vue) {
|
||||||
|
if (!Vue.options.components.PropsUnsafeTransition) {
|
||||||
|
const PropsUnsafeTransition = { ...(Vue.options.components.Transition) }
|
||||||
|
PropsUnsafeTransition.name = 'PropsUnsafeTransition'
|
||||||
|
PropsUnsafeTransition.props = {
|
||||||
|
name: {
|
||||||
|
validator: () => true
|
||||||
|
},
|
||||||
|
appear: {
|
||||||
|
validator: () => true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Vue.component(PropsUnsafeTransition.name, PropsUnsafeTransition)
|
||||||
|
}
|
||||||
|
}
|
11
packages/utils/validateProp.js
Normal file
11
packages/utils/validateProp.js
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
function createValidator (types) {
|
||||||
|
return value => {
|
||||||
|
for (let i = 0; i < types.length; ++i) {
|
||||||
|
// eslint-disable-next-line valid-typeof
|
||||||
|
if (typeof value === types[i]) return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default createValidator
|
@ -1,6 +1,76 @@
|
|||||||
@import './mixins/mixins.scss';
|
@import './mixins/mixins.scss';
|
||||||
@import './themes/vars.scss';
|
@import './themes/vars.scss';
|
||||||
|
|
||||||
|
@keyframes slide-in-from-left {
|
||||||
|
0% {
|
||||||
|
max-height: 0;
|
||||||
|
transform: translateX(-100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
50% {
|
||||||
|
max-height: 34px;
|
||||||
|
transform: translateX(-100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
max-height: 34px;
|
||||||
|
transform: translateX(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slide-out-to-right {
|
||||||
|
0% {
|
||||||
|
max-height: 34px;
|
||||||
|
transform: translateX(0%);
|
||||||
|
}
|
||||||
|
|
||||||
|
50% {
|
||||||
|
max-height: 34px;
|
||||||
|
transform: translateX(100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
max-height: 0px;
|
||||||
|
transform: translateX(100%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slide-in-from-right {
|
||||||
|
0% {
|
||||||
|
max-height: 0;
|
||||||
|
transform: translateX(100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
50% {
|
||||||
|
max-height: 34px;
|
||||||
|
transform: translateX(100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
max-height: 34px;
|
||||||
|
transform: translateX(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slide-out-to-left {
|
||||||
|
0% {
|
||||||
|
max-height: 34px;
|
||||||
|
transform: translateX(0%);
|
||||||
|
}
|
||||||
|
|
||||||
|
50% {
|
||||||
|
max-height: 34px;
|
||||||
|
transform: translateX(-100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
max-height: 0px;
|
||||||
|
transform: translateX(-100%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* There are some theme related hard codes in transfer.
|
* There are some theme related hard codes in transfer.
|
||||||
* Emmm, when I am writing these code I can't figure out a better solution.
|
* Emmm, when I am writing these code I can't figure out a better solution.
|
||||||
@ -96,6 +166,11 @@
|
|||||||
padding: 0;
|
padding: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
@include m(animation-disabled) {
|
||||||
|
@include b(transfer-list-item) {
|
||||||
|
animation: none !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@include b(transfer-list-light-bar) {
|
@include b(transfer-list-light-bar) {
|
||||||
@include once {
|
@include once {
|
||||||
@ -109,8 +184,6 @@
|
|||||||
}
|
}
|
||||||
@include b(transfer-list-item) {
|
@include b(transfer-list-item) {
|
||||||
@include once {
|
@include once {
|
||||||
@include slide-right-transition(transfer-list-item-source);
|
|
||||||
@include slide-left-transition(transfer-list-item-target);
|
|
||||||
transition: color .3s $--n-ease-in-out-cubic-bezier;
|
transition: color .3s $--n-ease-in-out-cubic-bezier;
|
||||||
position: relative;
|
position: relative;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
@ -125,6 +198,24 @@
|
|||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
padding-right: 4px;
|
padding-right: 4px;
|
||||||
}
|
}
|
||||||
|
@include m(source) {
|
||||||
|
animation-fill-mode: forwards;
|
||||||
|
@include m(enter) {
|
||||||
|
animation: .3s slide-in-from-right;
|
||||||
|
}
|
||||||
|
@include m(leave) {
|
||||||
|
animation: .3s slide-out-to-right;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@include m(target) {
|
||||||
|
animation-fill-mode: forwards;
|
||||||
|
@include m(enter) {
|
||||||
|
animation: .3s slide-in-from-left;
|
||||||
|
}
|
||||||
|
@include m(leave) {
|
||||||
|
animation: .3s slide-out-to-left;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
color: map-get($--transfer-item-text-color, 'default');
|
color: map-get($--transfer-item-text-color, 'default');
|
||||||
@include e(checkbox) {
|
@include e(checkbox) {
|
||||||
|
Loading…
Reference in New Issue
Block a user