mirror of
https://github.com/tusen-ai/naive-ui.git
synced 2025-03-13 13:59:04 +08:00
refactor(transfer): move to legacy-transfer
This commit is contained in:
parent
039f5ef2e7
commit
9b260a3b16
@ -333,6 +333,11 @@ export const enComponentRoutes = [
|
||||
path: 'transfer',
|
||||
component: () => import('../../src/transfer/demos/enUS/index.demo-entry.md')
|
||||
},
|
||||
{
|
||||
path: 'legacy-transfer',
|
||||
component: () =>
|
||||
import('../../src/legacy-transfer/demos/enUS/index.demo-entry.md')
|
||||
},
|
||||
{
|
||||
path: 'spin',
|
||||
component: () => import('../../src/spin/demos/enUS/index.demo-entry.md')
|
||||
@ -701,6 +706,11 @@ export const zhComponentRoutes = [
|
||||
path: 'transfer',
|
||||
component: () => import('../../src/transfer/demos/zhCN/index.demo-entry.md')
|
||||
},
|
||||
{
|
||||
path: 'legacy-transfer',
|
||||
component: () =>
|
||||
import('../../src/legacy-transfer/demos/zhCN/index.demo-entry.md')
|
||||
},
|
||||
{
|
||||
path: 'spin',
|
||||
component: () => import('../../src/spin/demos/zhCN/index.demo-entry.md')
|
||||
|
@ -32,12 +32,6 @@ const appendCounts = (item) => {
|
||||
}
|
||||
}
|
||||
|
||||
// const createDebugDemos = (item, mode) => {
|
||||
// if (__DEV__ && mode === 'debug') {
|
||||
// return [item]
|
||||
// } else return []
|
||||
// }
|
||||
|
||||
function createItems (lang, theme, prefix, items) {
|
||||
const isZh = lang === 'zh-CN'
|
||||
const langKey = isZh ? 'zh' : 'en'
|
||||
@ -159,11 +153,6 @@ export function createDocumentationMenuOptions ({ lang, theme, mode }) {
|
||||
zh: '社区精选资源',
|
||||
path: '/community'
|
||||
}
|
||||
// {
|
||||
// en: 'Experimental Features',
|
||||
// zh: '试验性特性',
|
||||
// path: '/experimental-features'
|
||||
// }
|
||||
]
|
||||
},
|
||||
{
|
||||
@ -176,11 +165,6 @@ export function createDocumentationMenuOptions ({ lang, theme, mode }) {
|
||||
zh: '变更日志',
|
||||
path: '/changelog'
|
||||
}
|
||||
// {
|
||||
// en: 'Migrate From V1',
|
||||
// zh: '从 V1 升级',
|
||||
// path: '/from-v1'
|
||||
// }
|
||||
]
|
||||
}
|
||||
])
|
||||
@ -747,6 +731,19 @@ export function createComponentMenuOptions ({ lang, theme, mode }) {
|
||||
path: '/global-style'
|
||||
}
|
||||
]
|
||||
}),
|
||||
appendCounts({
|
||||
zh: '废弃的组件',
|
||||
en: 'Deprecated Components',
|
||||
type: 'group',
|
||||
children: [
|
||||
{
|
||||
en: 'Legacy Transfer',
|
||||
zh: '旧版穿梭框',
|
||||
enSuffix: true,
|
||||
path: '/legacy-transfer'
|
||||
}
|
||||
]
|
||||
})
|
||||
])
|
||||
}
|
||||
|
34
src/legacy-transfer/demos/enUS/basic.demo.vue
Normal file
34
src/legacy-transfer/demos/enUS/basic.demo.vue
Normal file
@ -0,0 +1,34 @@
|
||||
<markdown>
|
||||
# Basic
|
||||
|
||||
Basic example of the Transfer component. If you have tons of data, see below for virtualised lists.
|
||||
</markdown>
|
||||
|
||||
<template>
|
||||
<n-transfer ref="transfer" v-model:value="value" :options="options" />
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref } from 'vue'
|
||||
|
||||
function createOptions () {
|
||||
return Array.from({ length: 100 }).map((v, i) => ({
|
||||
label: 'Option ' + i,
|
||||
value: i,
|
||||
disabled: i % 5 === 0
|
||||
}))
|
||||
}
|
||||
|
||||
function createValues () {
|
||||
return Array.from({ length: 50 }).map((v, i) => i)
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
setup () {
|
||||
return {
|
||||
options: createOptions(),
|
||||
value: ref(createValues())
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
38
src/legacy-transfer/demos/enUS/filterable.demo.vue
Normal file
38
src/legacy-transfer/demos/enUS/filterable.demo.vue
Normal file
@ -0,0 +1,38 @@
|
||||
<markdown>
|
||||
# Filterable
|
||||
</markdown>
|
||||
|
||||
<template>
|
||||
<n-transfer
|
||||
ref="transfer"
|
||||
v-model:value="value"
|
||||
virtual-scroll
|
||||
:options="options"
|
||||
filterable
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref } from 'vue'
|
||||
|
||||
function createOptions () {
|
||||
return Array.from({ length: 100 }).map((v, i) => ({
|
||||
label: 'Option ' + i,
|
||||
value: i,
|
||||
disabled: i % 5 === 0
|
||||
}))
|
||||
}
|
||||
|
||||
function createValues () {
|
||||
return Array.from({ length: 50 }).map((v, i) => i)
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
setup () {
|
||||
return {
|
||||
options: createOptions(),
|
||||
value: ref(createValues())
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
46
src/legacy-transfer/demos/enUS/index.demo-entry.md
Normal file
46
src/legacy-transfer/demos/enUS/index.demo-entry.md
Normal file
@ -0,0 +1,46 @@
|
||||
# Legacy Transfer
|
||||
|
||||
<!--single-column-->
|
||||
|
||||
<n-alert title="Warning">
|
||||
The transfer component is deprecated and will be removed in the next major version.
|
||||
</n-alert>
|
||||
|
||||
Left, right, right, left... I'm a simple man, and I can play this all day.
|
||||
|
||||
## Demos
|
||||
|
||||
```demo
|
||||
basic.vue
|
||||
large-data.vue
|
||||
size.vue
|
||||
filterable.vue
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
### Transfer Props
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| default-value | `Array<string \| number> \| null` | `null` | Default value. |
|
||||
| disabled | `boolean` | `true` | Disabled state. |
|
||||
| filterable | `boolean` | `false` | Filterable state. |
|
||||
| filter | `function` | `(pattern: string, option: TransferOption, from: 'source' \| 'target') => boolean` | A basic label string match function. |
|
||||
| options | `Array<TransferOption>` | `[]` | For configuration options, see the TransferOption Type below. |
|
||||
| size | `'small' \| 'medium' \| 'large'` | `'medium'` | Size. |
|
||||
| source-filter-placeholder | `string` | `undefined` | Placeholder for the source items search box. |
|
||||
| source-title | `string` | `'Source'` | Source items title. |
|
||||
| target-filter-placeholder | `string` | `undefined` | Placeholder for the target items search box. |
|
||||
| target-title | `string` | `'Target'` | Target items title. |
|
||||
| value | `Array<string \| number> \| null` | `undefined` | Value when being set manually. |
|
||||
| on-update:value | `(value: Array<string \| number>) => void` | `undefined` | Callback when the value changes. |
|
||||
| virtual-scroll | `boolean` | `false` | Enable virtual scrolling. |
|
||||
|
||||
#### TransferOption Type
|
||||
|
||||
| Property | Type | Description |
|
||||
| -------- | ------------------ | ------------------------------ |
|
||||
| label | `string` | The option's label to display. |
|
||||
| value | `string \| number` | The option's unique value. |
|
||||
| disabled | `boolean` | The option's disabled state. |
|
39
src/legacy-transfer/demos/enUS/large-data.demo.vue
Normal file
39
src/legacy-transfer/demos/enUS/large-data.demo.vue
Normal file
@ -0,0 +1,39 @@
|
||||
<markdown>
|
||||
# Large Data
|
||||
|
||||
If you have tons of data, you may need to speed the transfer up! Set `virtual-scroll` on transfer to use a blazing fast transfer (which turns the ridiculous animation off).
|
||||
</markdown>
|
||||
|
||||
<template>
|
||||
<n-transfer
|
||||
ref="transfer"
|
||||
v-model:value="value"
|
||||
:options="options"
|
||||
virtual-scroll
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref } from 'vue'
|
||||
|
||||
function createOptions () {
|
||||
return Array.from({ length: 42000 }).map((v, i) => ({
|
||||
label: 'Option' + i,
|
||||
value: i,
|
||||
disabled: i % 5 === 0
|
||||
}))
|
||||
}
|
||||
|
||||
function createValues () {
|
||||
return Array.from({ length: 50 }).map((v, i) => i)
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
setup () {
|
||||
return {
|
||||
options: createOptions(),
|
||||
value: ref(createValues())
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
47
src/legacy-transfer/demos/enUS/size.demo.vue
Normal file
47
src/legacy-transfer/demos/enUS/size.demo.vue
Normal file
@ -0,0 +1,47 @@
|
||||
<markdown>
|
||||
# Size
|
||||
|
||||
Mixing sizes does not look harmonious.
|
||||
</markdown>
|
||||
|
||||
<template>
|
||||
<n-space vertical>
|
||||
<n-transfer
|
||||
ref="transfer"
|
||||
v-model:value="value"
|
||||
:options="options"
|
||||
size="small"
|
||||
/>
|
||||
<n-transfer
|
||||
ref="transfer"
|
||||
v-model:value="value"
|
||||
:options="options"
|
||||
size="large"
|
||||
/>
|
||||
</n-space>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref } from 'vue'
|
||||
|
||||
function createOptions () {
|
||||
return Array.from({ length: 100 }).map((v, i) => ({
|
||||
label: 'Option ' + i,
|
||||
value: i,
|
||||
disabled: i % 5 === 0
|
||||
}))
|
||||
}
|
||||
|
||||
function createValues () {
|
||||
return Array.from({ length: 50 }).map((v, i) => i)
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
setup () {
|
||||
return {
|
||||
options: createOptions(),
|
||||
value: ref(createValues())
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
34
src/legacy-transfer/demos/zhCN/basic.demo.vue
Normal file
34
src/legacy-transfer/demos/zhCN/basic.demo.vue
Normal file
@ -0,0 +1,34 @@
|
||||
<markdown>
|
||||
# 基础用法
|
||||
|
||||
穿梭框的基础用法。如果你有一大堆数据,看下一个例子。
|
||||
</markdown>
|
||||
|
||||
<template>
|
||||
<n-transfer ref="transfer" v-model:value="value" :options="options" />
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref } from 'vue'
|
||||
|
||||
function createOptions () {
|
||||
return Array.from({ length: 100 }).map((v, i) => ({
|
||||
label: 'Option ' + i,
|
||||
value: i,
|
||||
disabled: i % 5 === 0
|
||||
}))
|
||||
}
|
||||
|
||||
function createValues () {
|
||||
return Array.from({ length: 50 }).map((v, i) => i)
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
setup () {
|
||||
return {
|
||||
options: createOptions(),
|
||||
value: ref(createValues())
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
38
src/legacy-transfer/demos/zhCN/filterable.demo.vue
Normal file
38
src/legacy-transfer/demos/zhCN/filterable.demo.vue
Normal file
@ -0,0 +1,38 @@
|
||||
<markdown>
|
||||
# 可过滤
|
||||
</markdown>
|
||||
|
||||
<template>
|
||||
<n-transfer
|
||||
ref="transfer"
|
||||
v-model:value="value"
|
||||
virtual-scroll
|
||||
:options="options"
|
||||
filterable
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref } from 'vue'
|
||||
|
||||
function createOptions () {
|
||||
return Array.from({ length: 100 }).map((v, i) => ({
|
||||
label: 'Option ' + i,
|
||||
value: i,
|
||||
disabled: i % 5 === 0
|
||||
}))
|
||||
}
|
||||
|
||||
function createValues () {
|
||||
return Array.from({ length: 50 }).map((v, i) => i)
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
setup () {
|
||||
return {
|
||||
options: createOptions(),
|
||||
value: ref(createValues())
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
46
src/legacy-transfer/demos/zhCN/index.demo-entry.md
Normal file
46
src/legacy-transfer/demos/zhCN/index.demo-entry.md
Normal file
@ -0,0 +1,46 @@
|
||||
# 旧版穿梭框 Legacy Transfer
|
||||
|
||||
<!--single-column-->
|
||||
|
||||
<n-alert title="警告">
|
||||
这个穿梭框组件已经被废弃,并将在下一个大版本中彻底移除。
|
||||
</n-alert>
|
||||
|
||||
左、右、左、右...像我这么无聊的人能玩一整天。
|
||||
|
||||
## 演示
|
||||
|
||||
```demo
|
||||
basic.vue
|
||||
large-data.vue
|
||||
size.vue
|
||||
filterable.vue
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
### Transfer Props
|
||||
|
||||
| 名称 | 类型 | 默认值 | 说明 |
|
||||
| --- | --- | --- | --- |
|
||||
| default-value | `Array<string \| number> \| null` | `null` | 非受控模式下的默认值 |
|
||||
| disabled | `boolean` | `true` | 是否禁用 |
|
||||
| filterable | `boolean` | `false` | 是否可过滤 |
|
||||
| filter | `(pattern: string, option: TransferOption, from: 'source' \| 'target') => boolean` | 一个简单的标签字符串匹配函数 | 搜索时使用的过滤函数 |
|
||||
| options | `Array<TransferOption>` | `[]` | 配置选项内容,详情见 TransferOption Type |
|
||||
| size | `'small' \| 'medium' \| 'large'` | `'medium'` | 尺寸 |
|
||||
| source-filter-placeholder | `string` | `undefined` | 源项搜索框中的占位符 |
|
||||
| source-title | `string` | `'源项'` | 源项标题 |
|
||||
| target-filter-placeholder | `string` | `undefined` | 目标项搜索框中的占位符 |
|
||||
| target-title | `string` | `'目标项'` | 目标项标题 |
|
||||
| value | `Array<string \| number> \| null` | `undefined` | 受控模式下的值 |
|
||||
| on-update:value | `(value: Array<string \| number>) => void` | `undefined` | 值发生改变时的回调 |
|
||||
| virtual-scroll | `boolean` | `false` | 是否启用虚拟滚动 |
|
||||
|
||||
#### TransferOption Type
|
||||
|
||||
| 属性 | 类型 | 说明 |
|
||||
| -------- | ------------------ | ------------------------ |
|
||||
| label | `string` | 选项中用以页面显示的名称 |
|
||||
| value | `string \| number` | 所有选项中唯一的 `value` |
|
||||
| disabled | `boolean` | 是否禁用这个选项 |
|
39
src/legacy-transfer/demos/zhCN/large-data.demo.vue
Normal file
39
src/legacy-transfer/demos/zhCN/large-data.demo.vue
Normal file
@ -0,0 +1,39 @@
|
||||
<markdown>
|
||||
# 一大堆数据
|
||||
|
||||
如果你有一大堆数据,你可能想让它快一点。设定 `virtual-scroll` 来使用一个飞快的穿梭框(会关掉那个傻乎乎的动画)。
|
||||
</markdown>
|
||||
|
||||
<template>
|
||||
<n-transfer
|
||||
ref="transfer"
|
||||
v-model:value="value"
|
||||
:options="options"
|
||||
virtual-scroll
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref } from 'vue'
|
||||
|
||||
function createOptions () {
|
||||
return Array.from({ length: 42000 }).map((v, i) => ({
|
||||
label: 'Option ' + i,
|
||||
value: i,
|
||||
disabled: i % 5 === 0
|
||||
}))
|
||||
}
|
||||
|
||||
function createValues () {
|
||||
return Array.from({ length: 50 }).map((v, i) => i)
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
setup () {
|
||||
return {
|
||||
options: createOptions(),
|
||||
value: ref(createValues())
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
47
src/legacy-transfer/demos/zhCN/size.demo.vue
Normal file
47
src/legacy-transfer/demos/zhCN/size.demo.vue
Normal file
@ -0,0 +1,47 @@
|
||||
<markdown>
|
||||
# 尺寸
|
||||
|
||||
太小太大好像都不怎么好看。
|
||||
</markdown>
|
||||
|
||||
<template>
|
||||
<n-space vertical>
|
||||
<n-transfer
|
||||
ref="transfer"
|
||||
v-model:value="value"
|
||||
:options="options"
|
||||
size="small"
|
||||
/>
|
||||
<n-transfer
|
||||
ref="transfer"
|
||||
v-model:value="value"
|
||||
:options="options"
|
||||
size="large"
|
||||
/>
|
||||
</n-space>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref } from 'vue'
|
||||
|
||||
function createOptions () {
|
||||
return Array.from({ length: 100 }).map((v, i) => ({
|
||||
label: 'Option ' + i,
|
||||
value: i,
|
||||
disabled: i % 5 === 0
|
||||
}))
|
||||
}
|
||||
|
||||
function createValues () {
|
||||
return Array.from({ length: 50 }).map((v, i) => i)
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
setup () {
|
||||
return {
|
||||
options: createOptions(),
|
||||
value: ref(createValues())
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
6
src/legacy-transfer/index.ts
Normal file
6
src/legacy-transfer/index.ts
Normal file
@ -0,0 +1,6 @@
|
||||
export {
|
||||
default as NLegacyTransfer,
|
||||
transferProps as legacyTransferProps
|
||||
} from './src/Transfer'
|
||||
export type { transferProps as LegacyTransferProps } from './src/Transfer'
|
||||
export type { Option as LegacyTransferOption } from './src/interface'
|
402
src/legacy-transfer/src/Transfer.tsx
Normal file
402
src/legacy-transfer/src/Transfer.tsx
Normal file
@ -0,0 +1,402 @@
|
||||
import {
|
||||
computed,
|
||||
defineComponent,
|
||||
h,
|
||||
provide,
|
||||
PropType,
|
||||
CSSProperties
|
||||
} from 'vue'
|
||||
import { useIsMounted } from 'vooks'
|
||||
import { depx } from 'seemly'
|
||||
import { ChevronLeftIcon, ChevronRightIcon } from '../../_internal/icons'
|
||||
import { NBaseIcon } from '../../_internal'
|
||||
import { NButton } from '../../button'
|
||||
import { useLocale, useFormItem, useTheme, useConfig } from '../../_mixins'
|
||||
import type { ThemeProps } from '../../_mixins'
|
||||
import { createKey } from '../../_utils/cssr'
|
||||
import { warn, call, ExtractPublicPropTypes } from '../../_utils'
|
||||
import type { MaybeArray } from '../../_utils'
|
||||
import { transferLight } from '../styles'
|
||||
import type { TransferTheme } from '../styles'
|
||||
import NTransferHeader from './TransferHeader'
|
||||
import NTransferList from './TransferList'
|
||||
import NTransferFilter from './TransferFilter'
|
||||
import { useTransferData } from './use-transfer-data'
|
||||
import style from './styles/index.cssr'
|
||||
import {
|
||||
OptionValue,
|
||||
Option,
|
||||
Filter,
|
||||
OnUpdateValue,
|
||||
transferInjectionKey
|
||||
} from './interface'
|
||||
|
||||
export const transferProps = {
|
||||
...(useTheme.props as ThemeProps<TransferTheme>),
|
||||
value: Array as PropType<OptionValue[] | null>,
|
||||
defaultValue: {
|
||||
type: Array as PropType<OptionValue[] | null>,
|
||||
default: null
|
||||
},
|
||||
options: {
|
||||
type: Array as PropType<Option[]>,
|
||||
default: () => []
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean as PropType<boolean | undefined>,
|
||||
default: undefined
|
||||
},
|
||||
virtualScroll: Boolean,
|
||||
sourceTitle: String,
|
||||
targetTitle: String,
|
||||
filterable: Boolean,
|
||||
sourceFilterPlaceholder: String,
|
||||
targetFilterPlaceholder: String,
|
||||
filter: {
|
||||
type: Function as PropType<Filter>,
|
||||
default: (pattern: string, option: Option) => {
|
||||
if (!pattern) return true
|
||||
return ~('' + option.label)
|
||||
.toLowerCase()
|
||||
.indexOf(('' + pattern).toLowerCase())
|
||||
}
|
||||
},
|
||||
size: String as PropType<'small' | 'medium' | 'large'>,
|
||||
'onUpdate:value': [Function, Array] as PropType<MaybeArray<OnUpdateValue>>,
|
||||
onUpdateValue: [Function, Array] as PropType<MaybeArray<OnUpdateValue>>,
|
||||
onChange: {
|
||||
type: [Function, Array] as PropType<MaybeArray<OnUpdateValue>>,
|
||||
validator: () => {
|
||||
if (__DEV__) {
|
||||
warn(
|
||||
'transfer',
|
||||
'`on-change` is deprecated, please use `on-update:value` instead.'
|
||||
)
|
||||
}
|
||||
return true
|
||||
},
|
||||
default: undefined
|
||||
}
|
||||
} as const
|
||||
|
||||
export type TransferProps = ExtractPublicPropTypes<typeof transferProps>
|
||||
|
||||
export default defineComponent({
|
||||
name: 'Transfer',
|
||||
props: transferProps,
|
||||
setup (props) {
|
||||
const { mergedClsPrefixRef } = useConfig(props)
|
||||
const themeRef = useTheme(
|
||||
'Transfer',
|
||||
'-transfer',
|
||||
style,
|
||||
transferLight,
|
||||
props,
|
||||
mergedClsPrefixRef
|
||||
)
|
||||
const formItem = useFormItem(props)
|
||||
const { mergedSizeRef, mergedDisabledRef } = formItem
|
||||
const itemSizeRef = computed(() => {
|
||||
const { value: size } = mergedSizeRef
|
||||
const {
|
||||
self: { [createKey('itemHeight', size)]: itemSize }
|
||||
} = themeRef.value
|
||||
return depx(itemSize)
|
||||
})
|
||||
const {
|
||||
uncontrolledValue: uncontrolledValueRef,
|
||||
mergedValue: mergedValueRef,
|
||||
avlSrcValueSet: avlSrcValueSetRef,
|
||||
avlTgtValueSet: avlTgtValueSetRef,
|
||||
tgtOpts: tgtOptsRef,
|
||||
srcOpts: srcOptsRef,
|
||||
filteredSrcOpts: filteredSrcOptsRef,
|
||||
filteredTgtOpts: filteredTgtOptsRef,
|
||||
srcCheckedValues: srcCheckedValuesRef,
|
||||
tgtCheckedValues: tgtCheckedValuesRef,
|
||||
srcCheckedStatus: srcCheckedStatusRef,
|
||||
tgtCheckedStatus: tgtCheckedStatusRef,
|
||||
srcPattern: srcPatternRef,
|
||||
tgtPattern: tgtPatternRef,
|
||||
isInputing: isInputingRef,
|
||||
fromButtonDisabled: fromButtonDisabledRef,
|
||||
toButtonDisabled: toButtonDisabledRef,
|
||||
handleInputFocus,
|
||||
handleInputBlur,
|
||||
handleTgtFilterUpdateValue,
|
||||
handleSrcFilterUpdateValue
|
||||
} = useTransferData(props, mergedDisabledRef)
|
||||
function doUpdateValue (value: OptionValue[]): void {
|
||||
const {
|
||||
onUpdateValue,
|
||||
'onUpdate:value': _onUpdateValue,
|
||||
onChange
|
||||
} = props
|
||||
const { nTriggerFormInput, nTriggerFormChange } = formItem
|
||||
if (onUpdateValue) call(onUpdateValue, value)
|
||||
if (_onUpdateValue) call(_onUpdateValue, value)
|
||||
if (onChange) call(onChange, value)
|
||||
uncontrolledValueRef.value = value
|
||||
nTriggerFormInput()
|
||||
nTriggerFormChange()
|
||||
}
|
||||
function handleSrcHeaderCheck (value: boolean): void {
|
||||
const {
|
||||
value: { checked, indeterminate }
|
||||
} = srcCheckedStatusRef
|
||||
if (indeterminate || checked) {
|
||||
srcCheckedValuesRef.value = []
|
||||
} else {
|
||||
srcCheckedValuesRef.value = Array.from(avlSrcValueSetRef.value)
|
||||
}
|
||||
}
|
||||
function handleTgtHeaderCheck (): void {
|
||||
const {
|
||||
value: { checked, indeterminate }
|
||||
} = tgtCheckedStatusRef
|
||||
if (indeterminate || checked) {
|
||||
tgtCheckedValuesRef.value = []
|
||||
} else {
|
||||
tgtCheckedValuesRef.value = Array.from(avlTgtValueSetRef.value)
|
||||
}
|
||||
}
|
||||
function handleTgtCheckboxClick (
|
||||
checked: boolean,
|
||||
optionValue: OptionValue
|
||||
): void {
|
||||
if (checked) {
|
||||
tgtCheckedValuesRef.value.push(optionValue)
|
||||
} else {
|
||||
const index = tgtCheckedValuesRef.value.findIndex(
|
||||
(v) => v === optionValue
|
||||
)
|
||||
if (~index) {
|
||||
tgtCheckedValuesRef.value.splice(index, 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
function handleSrcCheckboxClick (
|
||||
checked: boolean,
|
||||
optionValue: OptionValue
|
||||
): void {
|
||||
if (checked) {
|
||||
srcCheckedValuesRef.value.push(optionValue)
|
||||
} else {
|
||||
const index = srcCheckedValuesRef.value.findIndex(
|
||||
(v) => v === optionValue
|
||||
)
|
||||
if (~index) {
|
||||
srcCheckedValuesRef.value.splice(index, 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
function handleToTgtClick (): void {
|
||||
doUpdateValue(
|
||||
srcCheckedValuesRef.value.concat(mergedValueRef.value || [])
|
||||
)
|
||||
srcCheckedValuesRef.value = []
|
||||
}
|
||||
function handleToSrcClick (): void {
|
||||
const tgtCheckedValueSet = new Set(tgtCheckedValuesRef.value)
|
||||
doUpdateValue(
|
||||
(mergedValueRef.value || []).filter((v) => !tgtCheckedValueSet.has(v))
|
||||
)
|
||||
tgtCheckedValuesRef.value = []
|
||||
}
|
||||
provide(transferInjectionKey, {
|
||||
mergedClsPrefixRef,
|
||||
mergedSizeRef,
|
||||
disabledRef: mergedDisabledRef,
|
||||
mergedThemeRef: themeRef,
|
||||
srcCheckedValuesRef,
|
||||
tgtCheckedValuesRef,
|
||||
srcOptsRef,
|
||||
tgtOptsRef,
|
||||
srcCheckedStatusRef,
|
||||
tgtCheckedStatusRef,
|
||||
handleSrcCheckboxClick,
|
||||
handleTgtCheckboxClick
|
||||
})
|
||||
const { localeRef } = useLocale('Transfer')
|
||||
return {
|
||||
locale: localeRef,
|
||||
mergedClsPrefix: mergedClsPrefixRef,
|
||||
mergedDisabled: mergedDisabledRef,
|
||||
itemSize: itemSizeRef,
|
||||
isMounted: useIsMounted(),
|
||||
isInputing: isInputingRef,
|
||||
mergedTheme: themeRef,
|
||||
filteredSrcOpts: filteredSrcOptsRef,
|
||||
filteredTgtOpts: filteredTgtOptsRef,
|
||||
srcPattern: srcPatternRef,
|
||||
tgtPattern: tgtPatternRef,
|
||||
toButtonDisabled: toButtonDisabledRef,
|
||||
fromButtonDisabled: fromButtonDisabledRef,
|
||||
handleSrcHeaderCheck,
|
||||
handleTgtHeaderCheck,
|
||||
handleToSrcClick,
|
||||
handleToTgtClick,
|
||||
handleInputFocus,
|
||||
handleInputBlur,
|
||||
handleTgtFilterUpdateValue,
|
||||
handleSrcFilterUpdateValue,
|
||||
cssVars: computed(() => {
|
||||
const { value: size } = mergedSizeRef
|
||||
const {
|
||||
common: {
|
||||
cubicBezierEaseInOut,
|
||||
cubicBezierEaseIn,
|
||||
cubicBezierEaseOut
|
||||
},
|
||||
self: {
|
||||
width,
|
||||
borderRadius,
|
||||
borderColor,
|
||||
listColor,
|
||||
headerColor,
|
||||
titleTextColor,
|
||||
titleTextColorDisabled,
|
||||
extraTextColor,
|
||||
filterDividerColor,
|
||||
itemTextColor,
|
||||
itemColorPending,
|
||||
itemTextColorDisabled,
|
||||
extraFontSize,
|
||||
titleFontWeight,
|
||||
iconColor,
|
||||
iconColorDisabled,
|
||||
[createKey('fontSize', size)]: fontSize,
|
||||
[createKey('itemHeight', size)]: itemHeight
|
||||
}
|
||||
} = themeRef.value
|
||||
return {
|
||||
'--n-bezier': cubicBezierEaseInOut,
|
||||
'--n-bezier-ease-in': cubicBezierEaseIn,
|
||||
'--n-bezier-ease-out': cubicBezierEaseOut,
|
||||
'--n-border-color': borderColor,
|
||||
'--n-border-radius': borderRadius,
|
||||
'--n-extra-font-size': extraFontSize,
|
||||
'--n-filter-divider-color': filterDividerColor,
|
||||
'--n-font-size': fontSize,
|
||||
'--n-header-color': headerColor,
|
||||
'--n-header-extra-text-color': extraTextColor,
|
||||
'--n-header-font-weight': titleFontWeight,
|
||||
'--n-header-text-color': titleTextColor,
|
||||
'--n-header-text-color-disabled': titleTextColorDisabled,
|
||||
'--n-item-color-pending': itemColorPending,
|
||||
'--n-item-height': itemHeight,
|
||||
'--n-item-text-color': itemTextColor,
|
||||
'--n-item-text-color-disabled': itemTextColorDisabled,
|
||||
'--n-list-color': listColor,
|
||||
'--n-width': width,
|
||||
'--n-icon-color': iconColor,
|
||||
'--n-icon-color-disabled': iconColorDisabled
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
render () {
|
||||
const { mergedClsPrefix } = this
|
||||
return (
|
||||
<div
|
||||
class={[
|
||||
`${mergedClsPrefix}-transfer`,
|
||||
this.mergedDisabled && `${mergedClsPrefix}-transfer--disabled`,
|
||||
this.filterable && `${mergedClsPrefix}-transfer--filterable`
|
||||
]}
|
||||
style={this.cssVars as CSSProperties}
|
||||
>
|
||||
<div class={`${mergedClsPrefix}-transfer-list`}>
|
||||
<NTransferHeader
|
||||
source
|
||||
onChange={this.handleSrcHeaderCheck}
|
||||
title={this.sourceTitle || this.locale.sourceTitle}
|
||||
/>
|
||||
<div class={`${mergedClsPrefix}-transfer-list-body`}>
|
||||
{this.filterable ? (
|
||||
<NTransferFilter
|
||||
onUpdateValue={this.handleSrcFilterUpdateValue}
|
||||
value={this.srcPattern}
|
||||
disabled={this.mergedDisabled}
|
||||
placeholder={this.sourceFilterPlaceholder}
|
||||
onFocus={this.handleInputFocus}
|
||||
onBlur={this.handleInputBlur}
|
||||
/>
|
||||
) : null}
|
||||
<div class={`${mergedClsPrefix}-transfer-list-flex-container`}>
|
||||
<NTransferList
|
||||
source
|
||||
options={this.filteredSrcOpts}
|
||||
disabled={this.mergedDisabled}
|
||||
virtualScroll={this.virtualScroll}
|
||||
isMounted={this.isMounted}
|
||||
isInputing={this.isInputing}
|
||||
itemSize={this.itemSize}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class={`${mergedClsPrefix}-transfer-list__border`} />
|
||||
</div>
|
||||
<div class={`${mergedClsPrefix}-transfer-gap`}>
|
||||
<NButton
|
||||
disabled={this.toButtonDisabled || this.mergedDisabled}
|
||||
theme={this.mergedTheme.peers.Button}
|
||||
themeOverrides={this.mergedTheme.peerOverrides.Button}
|
||||
onClick={this.handleToTgtClick}
|
||||
>
|
||||
{{
|
||||
icon: () => (
|
||||
<NBaseIcon clsPrefix={mergedClsPrefix}>
|
||||
{{ default: () => <ChevronRightIcon /> }}
|
||||
</NBaseIcon>
|
||||
)
|
||||
}}
|
||||
</NButton>
|
||||
<NButton
|
||||
disabled={this.fromButtonDisabled || this.mergedDisabled}
|
||||
theme={this.mergedTheme.peers.Button}
|
||||
themeOverrides={this.mergedTheme.peerOverrides.Button}
|
||||
onClick={this.handleToSrcClick}
|
||||
>
|
||||
{{
|
||||
icon: () => (
|
||||
<NBaseIcon clsPrefix={mergedClsPrefix}>
|
||||
{{ default: () => <ChevronLeftIcon /> }}
|
||||
</NBaseIcon>
|
||||
)
|
||||
}}
|
||||
</NButton>
|
||||
</div>
|
||||
<div class={`${mergedClsPrefix}-transfer-list`}>
|
||||
<NTransferHeader
|
||||
onChange={this.handleTgtHeaderCheck}
|
||||
title={this.targetTitle || this.locale.targetTitle}
|
||||
/>
|
||||
<div class={`${mergedClsPrefix}-transfer-list-body`}>
|
||||
{this.filterable ? (
|
||||
<NTransferFilter
|
||||
onUpdateValue={this.handleTgtFilterUpdateValue}
|
||||
value={this.tgtPattern}
|
||||
disabled={this.mergedDisabled}
|
||||
placeholder={this.targetFilterPlaceholder}
|
||||
onFocus={this.handleInputFocus}
|
||||
onBlur={this.handleInputBlur}
|
||||
/>
|
||||
) : null}
|
||||
<div class={`${mergedClsPrefix}-transfer-list-flex-container`}>
|
||||
<NTransferList
|
||||
options={this.filteredTgtOpts}
|
||||
disabled={this.mergedDisabled}
|
||||
virtualScroll={this.virtualScroll}
|
||||
isMounted={this.isMounted}
|
||||
isInputing={this.isInputing}
|
||||
itemSize={this.itemSize}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class={`${mergedClsPrefix}-transfer-list__border`} />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
})
|
64
src/legacy-transfer/src/TransferFilter.tsx
Normal file
64
src/legacy-transfer/src/TransferFilter.tsx
Normal file
@ -0,0 +1,64 @@
|
||||
import { h, defineComponent, inject, PropType } from 'vue'
|
||||
import { SearchIcon } from '../../_internal/icons'
|
||||
import { NBaseIcon } from '../../_internal'
|
||||
import { NInput } from '../../input'
|
||||
import { transferInjectionKey } from './interface'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'TransferFilter',
|
||||
props: {
|
||||
value: String,
|
||||
placeholder: String,
|
||||
disabled: Boolean,
|
||||
onFocus: {
|
||||
type: Function as PropType<() => void>,
|
||||
required: true
|
||||
},
|
||||
onBlur: {
|
||||
type: Function as PropType<() => void>,
|
||||
required: true
|
||||
},
|
||||
onUpdateValue: {
|
||||
type: Function as PropType<(value: string | null) => void>,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
setup () {
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const { mergedThemeRef, mergedClsPrefixRef } = inject(transferInjectionKey)!
|
||||
return {
|
||||
mergedClsPrefix: mergedClsPrefixRef,
|
||||
mergedTheme: mergedThemeRef
|
||||
}
|
||||
},
|
||||
render () {
|
||||
const { mergedTheme, mergedClsPrefix } = this
|
||||
return (
|
||||
<div class={`${mergedClsPrefix}-transfer-filter`}>
|
||||
<NInput
|
||||
value={this.value}
|
||||
onUpdateValue={this.onUpdateValue}
|
||||
disabled={this.disabled}
|
||||
theme={mergedTheme.peers.Input}
|
||||
themeOverrides={mergedTheme.peerOverrides.Input}
|
||||
clearable
|
||||
size="small"
|
||||
placeholder={this.placeholder}
|
||||
onFocus={this.onFocus}
|
||||
onBlur={this.onBlur}
|
||||
>
|
||||
{{
|
||||
'clear-icon-placeholder': () => (
|
||||
<NBaseIcon
|
||||
clsPrefix={mergedClsPrefix}
|
||||
class={`${mergedClsPrefix}-transfer-icon`}
|
||||
>
|
||||
{{ default: () => <SearchIcon /> }}
|
||||
</NBaseIcon>
|
||||
)
|
||||
}}
|
||||
</NInput>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
})
|
69
src/legacy-transfer/src/TransferHeader.tsx
Normal file
69
src/legacy-transfer/src/TransferHeader.tsx
Normal file
@ -0,0 +1,69 @@
|
||||
import { h, computed, defineComponent, inject, PropType } from 'vue'
|
||||
import { NCheckbox } from '../../checkbox'
|
||||
import { transferInjectionKey } from './interface'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'TransferHeader',
|
||||
props: {
|
||||
source: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
onChange: {
|
||||
type: Function as PropType<(value: boolean) => void>,
|
||||
required: true
|
||||
},
|
||||
title: String
|
||||
},
|
||||
setup (props) {
|
||||
const {
|
||||
srcOptsRef,
|
||||
tgtOptsRef,
|
||||
srcCheckedStatusRef,
|
||||
tgtCheckedStatusRef,
|
||||
srcCheckedValuesRef,
|
||||
tgtCheckedValuesRef,
|
||||
mergedThemeRef,
|
||||
disabledRef,
|
||||
mergedClsPrefixRef
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
} = inject(transferInjectionKey)!
|
||||
const checkboxPropsRef = computed(() => {
|
||||
const { source } = props
|
||||
if (source) {
|
||||
return srcCheckedStatusRef.value
|
||||
} else {
|
||||
return tgtCheckedStatusRef.value
|
||||
}
|
||||
})
|
||||
return () => {
|
||||
const { source } = props
|
||||
const { value: checkboxProps } = checkboxPropsRef
|
||||
const { value: mergedTheme } = mergedThemeRef
|
||||
const { value: mergedClsPrefix } = mergedClsPrefixRef
|
||||
return (
|
||||
<div class={`${mergedClsPrefix}-transfer-list-header`}>
|
||||
<div class={`${mergedClsPrefix}-transfer-list-header__checkbox`}>
|
||||
<NCheckbox
|
||||
theme={mergedTheme.peers.Checkbox}
|
||||
themeOverrides={mergedTheme.peerOverrides.Checkbox}
|
||||
checked={checkboxProps.checked}
|
||||
indeterminate={checkboxProps.indeterminate}
|
||||
disabled={checkboxProps.disabled || disabledRef.value}
|
||||
onUpdateChecked={props.onChange}
|
||||
/>
|
||||
</div>
|
||||
<div class={`${mergedClsPrefix}-transfer-list-header__header`}>
|
||||
{props.title}
|
||||
</div>
|
||||
<div class={`${mergedClsPrefix}-transfer-list-header__extra`}>
|
||||
{source
|
||||
? srcCheckedValuesRef.value.length
|
||||
: tgtCheckedValuesRef.value.length}
|
||||
/{source ? srcOptsRef.value.length : tgtOptsRef.value.length}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
163
src/legacy-transfer/src/TransferList.tsx
Normal file
163
src/legacy-transfer/src/TransferList.tsx
Normal file
@ -0,0 +1,163 @@
|
||||
import {
|
||||
h,
|
||||
defineComponent,
|
||||
ref,
|
||||
inject,
|
||||
PropType,
|
||||
TransitionGroup,
|
||||
Transition,
|
||||
Fragment
|
||||
} from 'vue'
|
||||
import { VirtualList, VirtualListInst } from 'vueuc'
|
||||
import { NEmpty } from '../../empty'
|
||||
import { NScrollbar, ScrollbarInst } from '../../_internal'
|
||||
import { Option, transferInjectionKey } from './interface'
|
||||
import NTransferListItem from './TransferListItem'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'TransferList',
|
||||
props: {
|
||||
virtualScroll: {
|
||||
type: Boolean,
|
||||
required: true
|
||||
},
|
||||
itemSize: {
|
||||
type: Number,
|
||||
required: true
|
||||
},
|
||||
options: {
|
||||
type: Array as PropType<Option[]>,
|
||||
required: true
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
required: true
|
||||
},
|
||||
isMounted: {
|
||||
type: Boolean,
|
||||
required: true
|
||||
},
|
||||
isInputing: {
|
||||
type: Boolean,
|
||||
required: true
|
||||
},
|
||||
source: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
setup () {
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const { mergedThemeRef, mergedClsPrefixRef } = inject(transferInjectionKey)!
|
||||
const scrollerInstRef = ref<ScrollbarInst | null>(null)
|
||||
const vlInstRef = ref<VirtualListInst | null>(null)
|
||||
function syncVLScroller (): void {
|
||||
scrollerInstRef.value?.sync()
|
||||
}
|
||||
function scrollContainer (): HTMLElement | null {
|
||||
const { value } = vlInstRef
|
||||
if (!value) return null
|
||||
const { listElRef } = value
|
||||
return listElRef
|
||||
}
|
||||
function scrollContent (): HTMLElement | null {
|
||||
const { value } = vlInstRef
|
||||
if (!value) return null
|
||||
const { itemsElRef } = value
|
||||
return itemsElRef
|
||||
}
|
||||
return {
|
||||
mergedTheme: mergedThemeRef,
|
||||
mergedClsPrefix: mergedClsPrefixRef,
|
||||
scrollerInstRef,
|
||||
vlInstRef,
|
||||
syncVLScroller,
|
||||
scrollContainer,
|
||||
scrollContent
|
||||
}
|
||||
},
|
||||
render () {
|
||||
const { mergedTheme, mergedClsPrefix, virtualScroll, syncVLScroller } = this
|
||||
return (
|
||||
<>
|
||||
<NScrollbar
|
||||
ref="scrollerInstRef"
|
||||
theme={mergedTheme.peers.Scrollbar}
|
||||
themeOverrides={mergedTheme.peerOverrides.Scrollbar}
|
||||
container={virtualScroll ? this.scrollContainer : undefined}
|
||||
content={virtualScroll ? this.scrollContent : undefined}
|
||||
>
|
||||
{{
|
||||
default: () =>
|
||||
virtualScroll ? (
|
||||
<VirtualList
|
||||
ref="vlInstRef"
|
||||
style={{ height: '100%' }}
|
||||
class={`${mergedClsPrefix}-transfer-list-content`}
|
||||
items={this.options}
|
||||
itemSize={this.itemSize}
|
||||
showScrollbar={false}
|
||||
onResize={syncVLScroller}
|
||||
onScroll={syncVLScroller}
|
||||
keyField="value"
|
||||
>
|
||||
{{
|
||||
default: ({ item }: { item: Option }) => {
|
||||
const { source, disabled } = this
|
||||
return (
|
||||
<NTransferListItem
|
||||
source={source}
|
||||
key={item.value}
|
||||
value={item.value}
|
||||
disabled={item.disabled || disabled}
|
||||
label={item.label}
|
||||
/>
|
||||
)
|
||||
}
|
||||
}}
|
||||
</VirtualList>
|
||||
) : (
|
||||
<div class={`${mergedClsPrefix}-transfer-list-content`}>
|
||||
<TransitionGroup
|
||||
name="item"
|
||||
appear={this.isMounted}
|
||||
css={!this.isInputing}
|
||||
>
|
||||
{{
|
||||
default: () => {
|
||||
const { source, disabled } = this
|
||||
return this.options.map((option) => (
|
||||
<NTransferListItem
|
||||
source={source}
|
||||
key={option.value}
|
||||
value={option.value}
|
||||
disabled={option.disabled || disabled}
|
||||
label={option.label}
|
||||
/>
|
||||
))
|
||||
}
|
||||
}}
|
||||
</TransitionGroup>
|
||||
</div>
|
||||
)
|
||||
}}
|
||||
</NScrollbar>
|
||||
<Transition
|
||||
name="fade-in-transition"
|
||||
appear={this.isMounted}
|
||||
css={!this.isInputing}
|
||||
>
|
||||
{{
|
||||
default: () =>
|
||||
this.options.length ? null : (
|
||||
<NEmpty
|
||||
theme={mergedTheme.peers.Empty}
|
||||
themeOverrides={mergedTheme.peerOverrides.Empty}
|
||||
/>
|
||||
)
|
||||
}}
|
||||
</Transition>
|
||||
</>
|
||||
)
|
||||
}
|
||||
})
|
90
src/legacy-transfer/src/TransferListItem.tsx
Normal file
90
src/legacy-transfer/src/TransferListItem.tsx
Normal file
@ -0,0 +1,90 @@
|
||||
import { h, inject, defineComponent } from 'vue'
|
||||
import { useMemo } from 'vooks'
|
||||
import { NCheckbox } from '../../checkbox'
|
||||
import { transferInjectionKey } from './interface'
|
||||
import { getTitleAttribute } from '../../_utils'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'NTransferListItem',
|
||||
props: {
|
||||
source: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
label: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
value: {
|
||||
type: [String, Number],
|
||||
required: true
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
setup (props) {
|
||||
const { source } = props
|
||||
const {
|
||||
mergedClsPrefixRef,
|
||||
mergedThemeRef,
|
||||
srcCheckedValuesRef,
|
||||
tgtCheckedValuesRef,
|
||||
handleSrcCheckboxClick,
|
||||
handleTgtCheckboxClick
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
} = inject(transferInjectionKey)!
|
||||
const checkedRef = source
|
||||
? useMemo(() => srcCheckedValuesRef.value.includes(props.value))
|
||||
: useMemo(() => tgtCheckedValuesRef.value.includes(props.value))
|
||||
const handleClick = source
|
||||
? () => {
|
||||
if (!props.disabled) {
|
||||
handleSrcCheckboxClick(!checkedRef.value, props.value)
|
||||
}
|
||||
}
|
||||
: () => {
|
||||
if (!props.disabled) {
|
||||
handleTgtCheckboxClick(!checkedRef.value, props.value)
|
||||
}
|
||||
}
|
||||
return {
|
||||
mergedClsPrefix: mergedClsPrefixRef,
|
||||
mergedTheme: mergedThemeRef,
|
||||
checked: checkedRef,
|
||||
handleClick
|
||||
}
|
||||
},
|
||||
render () {
|
||||
const { disabled, mergedTheme, mergedClsPrefix, label, checked, source } =
|
||||
this
|
||||
return (
|
||||
<div
|
||||
class={[
|
||||
`${mergedClsPrefix}-transfer-list-item`,
|
||||
disabled && `${mergedClsPrefix}-transfer-list-item--disabled`,
|
||||
source
|
||||
? `${mergedClsPrefix}-transfer-list-item--source`
|
||||
: `${mergedClsPrefix}-transfer-list-item--target`
|
||||
]}
|
||||
onClick={this.handleClick}
|
||||
>
|
||||
<div class={`${mergedClsPrefix}-transfer-list-item__checkbox`}>
|
||||
<NCheckbox
|
||||
theme={mergedTheme.peers.Checkbox}
|
||||
themeOverrides={mergedTheme.peerOverrides.Checkbox}
|
||||
disabled={disabled}
|
||||
checked={checked}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class={`${mergedClsPrefix}-transfer-list-item__label`}
|
||||
title={getTitleAttribute(label)}
|
||||
>
|
||||
{label}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
})
|
43
src/legacy-transfer/src/interface.ts
Normal file
43
src/legacy-transfer/src/interface.ts
Normal file
@ -0,0 +1,43 @@
|
||||
import { Ref } from 'vue'
|
||||
import type { MergedTheme } from '../../_mixins'
|
||||
import { createInjectionKey } from '../../_utils'
|
||||
import type { TransferTheme } from '../styles'
|
||||
|
||||
export type OptionValue = string | number
|
||||
export interface Option {
|
||||
label: string
|
||||
value: OptionValue
|
||||
disabled?: boolean
|
||||
}
|
||||
|
||||
export interface CheckedStatus {
|
||||
checked: boolean
|
||||
indeterminate: boolean
|
||||
disabled?: boolean
|
||||
}
|
||||
|
||||
export type Filter = (
|
||||
pattern: string,
|
||||
option: Option,
|
||||
from: 'source' | 'target'
|
||||
) => boolean
|
||||
|
||||
export interface TransferInjection {
|
||||
mergedClsPrefixRef: Ref<string>
|
||||
mergedSizeRef: Ref<'small' | 'medium' | 'large'>
|
||||
disabledRef: Ref<boolean>
|
||||
mergedThemeRef: Ref<MergedTheme<TransferTheme>>
|
||||
srcCheckedValuesRef: Ref<OptionValue[]>
|
||||
tgtCheckedValuesRef: Ref<OptionValue[]>
|
||||
srcOptsRef: Ref<Option[]>
|
||||
tgtOptsRef: Ref<Option[]>
|
||||
srcCheckedStatusRef: Ref<CheckedStatus>
|
||||
tgtCheckedStatusRef: Ref<CheckedStatus>
|
||||
handleSrcCheckboxClick: (checked: boolean, value: OptionValue) => void
|
||||
handleTgtCheckboxClick: (checked: boolean, value: OptionValue) => void
|
||||
}
|
||||
|
||||
export const transferInjectionKey =
|
||||
createInjectionKey<TransferInjection>('n-transfer')
|
||||
|
||||
export type OnUpdateValue = (value: OptionValue[]) => void
|
278
src/legacy-transfer/src/styles/index.cssr.ts
Normal file
278
src/legacy-transfer/src/styles/index.cssr.ts
Normal file
@ -0,0 +1,278 @@
|
||||
import { c, cB, cE, cM, cNotM } from '../../../_utils/cssr'
|
||||
import { fadeInTransition } from '../../../_styles/transitions/fade-in.cssr'
|
||||
|
||||
const animation = c([
|
||||
c('@keyframes transfer-slide-in-from-left', `
|
||||
0% {
|
||||
transform: translateX(-150%);
|
||||
}
|
||||
100% {
|
||||
transform: translateX(0);
|
||||
}
|
||||
`),
|
||||
c('@keyframes transfer-slide-out-to-right', `
|
||||
0% {
|
||||
transform: translateX(0);
|
||||
}
|
||||
100% {
|
||||
transform: translateX(150%);
|
||||
}
|
||||
`),
|
||||
c('@keyframes transfer-slide-in-from-right', `
|
||||
0% {
|
||||
transform: translateX(150%);
|
||||
}
|
||||
100% {
|
||||
transform: translateX(0);
|
||||
}
|
||||
`),
|
||||
c('@keyframes transfer-slide-out-to-left', `
|
||||
0% {
|
||||
transform: translateX(0);
|
||||
}
|
||||
100% {
|
||||
transform: translateX(-150%);
|
||||
}
|
||||
`),
|
||||
c('@keyframes transfer-height-collapse', `
|
||||
0% {
|
||||
max-height: var(--n-item-height);
|
||||
}
|
||||
100% {
|
||||
max-height: 0;
|
||||
}
|
||||
`),
|
||||
c('@keyframes transfer-height-expand', `
|
||||
0% {
|
||||
max-height: 0;
|
||||
}
|
||||
100% {
|
||||
max-height: var(--n-item-height);
|
||||
}
|
||||
`)
|
||||
])
|
||||
|
||||
export default c([
|
||||
cB('transfer', `
|
||||
display: flex;
|
||||
width: var(--n-width);
|
||||
font-size: var(--n-font-size);
|
||||
height: 240px;
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
`, [
|
||||
cB('transfer-icon', `
|
||||
color: var(--n-icon-color);
|
||||
transition: color .3s var(--n-bezier);
|
||||
`),
|
||||
cM('disabled', [
|
||||
cB('transfer-icon', {
|
||||
color: 'var(--n-icon-color-disabled)'
|
||||
})
|
||||
]),
|
||||
cB('transfer-list', `
|
||||
height: inherit;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-clip: padding-box;
|
||||
width: calc(50% - 36px);
|
||||
position: relative;
|
||||
transition: background-color .3s var(--n-bezier);
|
||||
border-radius: var(--n-border-radius);
|
||||
background-color: var(--n-list-color);
|
||||
`, [
|
||||
cE('border', `
|
||||
border: 1px solid var(--n-border-color);
|
||||
transition: border-color .3s var(--n-bezier);
|
||||
pointer-events: none;
|
||||
border-radius: inherit;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
`),
|
||||
cB('transfer-list-header', `
|
||||
height: calc(var(--n-item-height) + 4px);
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background-clip: padding-box;
|
||||
border-radius: inherit;
|
||||
border-bottom-left-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
background-color: var(--n-header-color);
|
||||
transition:
|
||||
border-color .3s var(--n-bezier),
|
||||
background-color .3s var(--n-bezier);
|
||||
`, [
|
||||
cE('checkbox', `
|
||||
display: flex;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
padding: 0 9px 0 14px;
|
||||
`),
|
||||
cE('header', `
|
||||
flex: 1;
|
||||
line-height: 1;
|
||||
font-weight: var(--n-header-font-weight);
|
||||
transition: color .3s var(--n-bezier);
|
||||
color: var(--n-header-text-color);
|
||||
`, [
|
||||
cM('disabled', {
|
||||
color: 'var(--n-header-text-color-disabled)'
|
||||
})
|
||||
]),
|
||||
cE('extra', `
|
||||
transition: color .3s var(--n-bezier);
|
||||
font-size: var(--n-extra-font-size);
|
||||
justify-self: flex-end;
|
||||
margin-right: 14px;
|
||||
white-space: nowrap;
|
||||
color: var(--n-header-extra-text-color);
|
||||
`)
|
||||
]),
|
||||
cB('transfer-list-body', `
|
||||
flex-basis: 0;
|
||||
flex-grow: 1;
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border-radius: inherit;
|
||||
border-top-left-radius: 0;
|
||||
border-top-right-radius: 0;
|
||||
`, [
|
||||
cB('transfer-filter', `
|
||||
padding: 0 8px 8px 8px;
|
||||
box-sizing: border-box;
|
||||
background-color: var(--n-header-color);
|
||||
transition:
|
||||
border-color .3s var(--n-bezier),
|
||||
background-color .3s var(--n-bezier);
|
||||
border-bottom: 1px solid var(--n-filter-divider-color);
|
||||
`),
|
||||
cB('transfer-list-flex-container', `
|
||||
flex: 1;
|
||||
position: relative;
|
||||
`, [
|
||||
cB('scrollbar', `
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
height: unset;
|
||||
`, [
|
||||
cB('scrollbar-content', {
|
||||
width: '100%'
|
||||
})
|
||||
]),
|
||||
cB('empty', `
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translateY(-50%) translateX(-50%);
|
||||
`, [
|
||||
fadeInTransition()
|
||||
]),
|
||||
cB('transfer-list-content', `
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
position: relative;
|
||||
`, [
|
||||
cM('transition-disabled', [
|
||||
cB('transfer-list-item', {
|
||||
animation: 'none !important'
|
||||
})
|
||||
]),
|
||||
cB('transfer-list-item', `
|
||||
height: var(--n-item-height);
|
||||
max-height: var(--n-item-height);
|
||||
transition:
|
||||
background-color .3s var(--n-bezier),
|
||||
color .3s var(--n-bezier);
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: var(--n-item-text-color);
|
||||
`, [
|
||||
cNotM('disabled', [
|
||||
c('&:hover', {
|
||||
backgroundColor: 'var(--n-item-color-pending)'
|
||||
})
|
||||
]),
|
||||
cE('extra', `
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
padding-right: 4px;
|
||||
`),
|
||||
cE('checkbox', `
|
||||
display: flex;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
padding: 0 9px 0 14px;
|
||||
`),
|
||||
cM('disabled', `
|
||||
cursor: not-allowed
|
||||
background-color: #0000;
|
||||
color: var(--n-item-text-color-disabled);
|
||||
`),
|
||||
cM('source', {
|
||||
animationFillMode: 'forwards'
|
||||
}, [
|
||||
c('&.item-enter-active', `
|
||||
transform: translateX(150%);
|
||||
animation-duration: .25s, .25s;
|
||||
animation-timing-function: var(--n-bezier), var(--n-bezier-ease-out);
|
||||
animation-delay: 0s, .25s;
|
||||
animation-name: transfer-height-expand, transfer-slide-in-from-right;
|
||||
`),
|
||||
c('&.item-leave-active', `
|
||||
transform: translateX(-150%);
|
||||
animation-duration: .25s, .25s;
|
||||
animation-timing-function: var(--n-bezier), var(--n-bezier-ease-in);
|
||||
animation-delay: .25s, 0s;
|
||||
animation-name: transfer-height-collapse, transfer-slide-out-to-right;
|
||||
`)
|
||||
]),
|
||||
cM('target', {
|
||||
animationFillMode: 'forwards'
|
||||
}, [
|
||||
c('&.item-enter-active', `
|
||||
transform: translateX(-150%);
|
||||
animation-duration: .25s, .25s;
|
||||
animation-timing-function: var(--n-bezier), var(--n-bezier-ease-out);
|
||||
animation-delay: 0s, .25s;
|
||||
animation-name: transfer-height-expand, transfer-slide-in-from-left;
|
||||
`),
|
||||
c('&.item-leave-active', `
|
||||
transform: translateX(150%);
|
||||
animation-duration: .25s, .25s;
|
||||
animation-timing-function: var(--n-bezier), var(--n-bezier-ease-in);
|
||||
animation-delay: .25s, 0s;
|
||||
animation-name: transfer-height-collapse, transfer-slide-out-to-left;
|
||||
`)
|
||||
])
|
||||
])
|
||||
])
|
||||
])
|
||||
])
|
||||
]),
|
||||
cB('transfer-gap', {
|
||||
width: '72px',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
flexDirection: 'column'
|
||||
}),
|
||||
cB('button', [
|
||||
c('&:first-child', {
|
||||
marginBottom: '12px'
|
||||
})
|
||||
])
|
||||
]),
|
||||
animation
|
||||
])
|
171
src/legacy-transfer/src/use-transfer-data.ts
Normal file
171
src/legacy-transfer/src/use-transfer-data.ts
Normal file
@ -0,0 +1,171 @@
|
||||
import { ref, computed, toRef, Ref } from 'vue'
|
||||
import { useMemo, useMergedState } from 'vooks'
|
||||
import type { Option, OptionValue, Filter, CheckedStatus } from './interface'
|
||||
|
||||
interface UseTransferDataProps {
|
||||
defaultValue: OptionValue[] | null
|
||||
value?: OptionValue[] | null
|
||||
options: Option[]
|
||||
filterable: boolean
|
||||
filter: Filter
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
||||
export function useTransferData (
|
||||
props: UseTransferDataProps,
|
||||
mergedDisabledRef: Ref<boolean>
|
||||
) {
|
||||
const uncontrolledValueRef = ref(props.defaultValue)
|
||||
const controlledValueRef = toRef(props, 'value')
|
||||
const mergedValueRef = useMergedState(
|
||||
controlledValueRef,
|
||||
uncontrolledValueRef
|
||||
)
|
||||
const optMapRef = computed(() => {
|
||||
const map = new Map()
|
||||
;(props.options || []).forEach((opt) => map.set(opt.value, opt))
|
||||
return map
|
||||
})
|
||||
const tgtValueSetRef = computed(() => new Set(mergedValueRef.value || []))
|
||||
const srcOptsRef = computed(() =>
|
||||
props.options.filter((option) => !tgtValueSetRef.value.has(option.value))
|
||||
)
|
||||
const tgtOptsRef = computed(() => {
|
||||
const optMap = optMapRef.value
|
||||
return (mergedValueRef.value || []).map((v) => optMap.get(v))
|
||||
})
|
||||
const srcPatternRef = ref('')
|
||||
const tgtPatternRef = ref('')
|
||||
const filteredSrcOptsRef = computed(() => {
|
||||
if (!props.filterable) return srcOptsRef.value
|
||||
const { filter } = props
|
||||
return srcOptsRef.value.filter((opt) =>
|
||||
filter(srcPatternRef.value, opt, 'source')
|
||||
)
|
||||
})
|
||||
const filteredTgtOptsRef = computed(() => {
|
||||
if (!props.filterable) return tgtOptsRef.value
|
||||
const { filter } = props
|
||||
return tgtOptsRef.value.filter((opt) =>
|
||||
filter(tgtPatternRef.value, opt, 'target')
|
||||
)
|
||||
})
|
||||
const avlSrcValueSetRef = computed(
|
||||
() =>
|
||||
new Set(
|
||||
filteredSrcOptsRef.value
|
||||
.filter((opt) => !opt.disabled)
|
||||
.map((opt) => opt.value)
|
||||
)
|
||||
)
|
||||
const avlTgtValueSetRef = computed(
|
||||
() =>
|
||||
new Set(
|
||||
filteredTgtOptsRef.value
|
||||
.filter((opt) => !opt.disabled)
|
||||
.map((opt) => opt.value)
|
||||
)
|
||||
)
|
||||
const srcCheckedValuesRef = ref<OptionValue[]>([])
|
||||
const tgtCheckedValuesRef = ref<OptionValue[]>([])
|
||||
const srcCheckedStatusRef = computed<CheckedStatus>(() => {
|
||||
const srcCheckedLength = srcCheckedValuesRef.value.filter((v) =>
|
||||
avlSrcValueSetRef.value.has(v)
|
||||
).length
|
||||
const avlValueCount = avlSrcValueSetRef.value.size
|
||||
if (avlValueCount === 0) {
|
||||
return {
|
||||
checked: false,
|
||||
indeterminate: false,
|
||||
disabled: true
|
||||
}
|
||||
} else if (srcCheckedLength === 0) {
|
||||
return {
|
||||
checked: false,
|
||||
indeterminate: false
|
||||
}
|
||||
} else if (srcCheckedLength === avlValueCount) {
|
||||
return {
|
||||
checked: true,
|
||||
indeterminate: false
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
checked: false,
|
||||
indeterminate: true
|
||||
}
|
||||
}
|
||||
})
|
||||
const tgtCheckedStatusRef = computed(() => {
|
||||
const tgtCheckedLength = tgtCheckedValuesRef.value.filter((v) =>
|
||||
avlTgtValueSetRef.value.has(v)
|
||||
).length
|
||||
const avlValueCount = avlTgtValueSetRef.value.size
|
||||
if (avlValueCount === 0) {
|
||||
return {
|
||||
checked: false,
|
||||
indeterminate: false,
|
||||
disabled: true
|
||||
}
|
||||
} else if (tgtCheckedLength === 0) {
|
||||
return {
|
||||
checked: false,
|
||||
indeterminate: false
|
||||
}
|
||||
} else if (tgtCheckedLength === avlValueCount) {
|
||||
return {
|
||||
checked: true,
|
||||
indeterminate: false
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
checked: false,
|
||||
indeterminate: true
|
||||
}
|
||||
}
|
||||
})
|
||||
const fromButtonDisabledRef = useMemo(() => {
|
||||
if (mergedDisabledRef.value) return true
|
||||
return tgtCheckedValuesRef.value.length === 0
|
||||
})
|
||||
const toButtonDisabledRef = useMemo(() => {
|
||||
if (mergedDisabledRef.value) return true
|
||||
return srcCheckedValuesRef.value.length === 0
|
||||
})
|
||||
const isInputingRef = ref(false)
|
||||
function handleInputFocus (): void {
|
||||
isInputingRef.value = true
|
||||
}
|
||||
function handleInputBlur (): void {
|
||||
isInputingRef.value = false
|
||||
}
|
||||
function handleSrcFilterUpdateValue (value: string | null): void {
|
||||
srcPatternRef.value = value ?? ''
|
||||
}
|
||||
function handleTgtFilterUpdateValue (value: string | null): void {
|
||||
tgtPatternRef.value = value ?? ''
|
||||
}
|
||||
return {
|
||||
uncontrolledValue: uncontrolledValueRef,
|
||||
mergedValue: mergedValueRef,
|
||||
avlSrcValueSet: avlSrcValueSetRef,
|
||||
avlTgtValueSet: avlTgtValueSetRef,
|
||||
tgtOpts: tgtOptsRef,
|
||||
srcOpts: srcOptsRef,
|
||||
filteredSrcOpts: filteredSrcOptsRef,
|
||||
filteredTgtOpts: filteredTgtOptsRef,
|
||||
srcCheckedValues: srcCheckedValuesRef,
|
||||
tgtCheckedValues: tgtCheckedValuesRef,
|
||||
srcCheckedStatus: srcCheckedStatusRef,
|
||||
tgtCheckedStatus: tgtCheckedStatusRef,
|
||||
srcPattern: srcPatternRef,
|
||||
tgtPattern: tgtPatternRef,
|
||||
isInputing: isInputingRef,
|
||||
fromButtonDisabled: fromButtonDisabledRef,
|
||||
toButtonDisabled: toButtonDisabledRef,
|
||||
handleInputFocus,
|
||||
handleInputBlur,
|
||||
handleTgtFilterUpdateValue,
|
||||
handleSrcFilterUpdateValue
|
||||
}
|
||||
}
|
4
src/legacy-transfer/styles/_common.ts
Normal file
4
src/legacy-transfer/styles/_common.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export default {
|
||||
extraFontSize: '12px',
|
||||
width: '440px'
|
||||
}
|
65
src/legacy-transfer/styles/dark.ts
Normal file
65
src/legacy-transfer/styles/dark.ts
Normal file
@ -0,0 +1,65 @@
|
||||
import commonVariables from './_common'
|
||||
import { checkboxDark } from '../../checkbox/styles'
|
||||
import { scrollbarDark } from '../../_internal/scrollbar/styles'
|
||||
import { inputDark } from '../../input/styles'
|
||||
import { commonDark } from '../../_styles/common'
|
||||
import { emptyDark } from '../../empty/styles'
|
||||
import { buttonDark } from '../../button/styles'
|
||||
import type { TransferTheme } from './light'
|
||||
|
||||
const transferDark: TransferTheme = {
|
||||
name: 'Transfer',
|
||||
common: commonDark,
|
||||
peers: {
|
||||
Checkbox: checkboxDark,
|
||||
Scrollbar: scrollbarDark,
|
||||
Input: inputDark,
|
||||
Empty: emptyDark,
|
||||
Button: buttonDark
|
||||
},
|
||||
self (vars) {
|
||||
const {
|
||||
iconColorDisabled,
|
||||
iconColor,
|
||||
fontWeight,
|
||||
fontSizeLarge,
|
||||
fontSizeMedium,
|
||||
fontSizeSmall,
|
||||
heightLarge,
|
||||
heightMedium,
|
||||
heightSmall,
|
||||
borderRadius,
|
||||
inputColor,
|
||||
tableHeaderColor,
|
||||
textColor1,
|
||||
textColorDisabled,
|
||||
textColor2,
|
||||
hoverColor
|
||||
} = vars
|
||||
return {
|
||||
...commonVariables,
|
||||
itemHeightSmall: heightSmall,
|
||||
itemHeightMedium: heightMedium,
|
||||
itemHeightLarge: heightLarge,
|
||||
fontSizeSmall,
|
||||
fontSizeMedium,
|
||||
fontSizeLarge,
|
||||
borderRadius,
|
||||
borderColor: '#0000',
|
||||
listColor: inputColor,
|
||||
headerColor: tableHeaderColor,
|
||||
titleTextColor: textColor1,
|
||||
titleTextColorDisabled: textColorDisabled,
|
||||
extraTextColor: textColor2,
|
||||
filterDividerColor: '#0000',
|
||||
itemTextColor: textColor2,
|
||||
itemTextColorDisabled: textColorDisabled,
|
||||
itemColorPending: hoverColor,
|
||||
titleFontWeight: fontWeight,
|
||||
iconColor,
|
||||
iconColorDisabled
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default transferDark
|
3
src/legacy-transfer/styles/index.ts
Normal file
3
src/legacy-transfer/styles/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export { default as transferDark } from './dark'
|
||||
export { default as transferLight } from './light'
|
||||
export type { TransferTheme, TransferThemeVars } from './light'
|
73
src/legacy-transfer/styles/light.ts
Normal file
73
src/legacy-transfer/styles/light.ts
Normal file
@ -0,0 +1,73 @@
|
||||
import commonVariables from './_common'
|
||||
import { composite } from 'seemly'
|
||||
import { checkboxLight } from '../../checkbox/styles'
|
||||
import { scrollbarLight } from '../../_internal/scrollbar/styles'
|
||||
import { inputLight } from '../../input/styles'
|
||||
import { commonLight } from '../../_styles/common'
|
||||
import type { ThemeCommonVars } from '../../_styles/common'
|
||||
import { emptyLight } from '../../empty/styles'
|
||||
import { buttonLight } from '../../button/styles'
|
||||
import { createTheme } from '../../_mixins'
|
||||
|
||||
const self = (vars: ThemeCommonVars) => {
|
||||
const {
|
||||
fontWeight,
|
||||
iconColorDisabled,
|
||||
iconColor,
|
||||
fontSizeLarge,
|
||||
fontSizeMedium,
|
||||
fontSizeSmall,
|
||||
heightLarge,
|
||||
heightMedium,
|
||||
heightSmall,
|
||||
borderRadius,
|
||||
cardColor,
|
||||
tableHeaderColor,
|
||||
textColor1,
|
||||
textColorDisabled,
|
||||
textColor2,
|
||||
borderColor,
|
||||
hoverColor
|
||||
} = vars
|
||||
return {
|
||||
...commonVariables,
|
||||
itemHeightSmall: heightSmall,
|
||||
itemHeightMedium: heightMedium,
|
||||
itemHeightLarge: heightLarge,
|
||||
fontSizeSmall,
|
||||
fontSizeMedium,
|
||||
fontSizeLarge,
|
||||
borderRadius,
|
||||
borderColor,
|
||||
listColor: cardColor,
|
||||
headerColor: composite(cardColor, tableHeaderColor),
|
||||
titleTextColor: textColor1,
|
||||
titleTextColorDisabled: textColorDisabled,
|
||||
extraTextColor: textColor2,
|
||||
filterDividerColor: borderColor,
|
||||
itemTextColor: textColor2,
|
||||
itemTextColorDisabled: textColorDisabled,
|
||||
itemColorPending: hoverColor,
|
||||
titleFontWeight: fontWeight,
|
||||
iconColor,
|
||||
iconColorDisabled
|
||||
}
|
||||
}
|
||||
|
||||
export type TransferThemeVars = ReturnType<typeof self>
|
||||
|
||||
const transferLight = createTheme({
|
||||
name: 'Transfer',
|
||||
common: commonLight,
|
||||
peers: {
|
||||
Checkbox: checkboxLight,
|
||||
Scrollbar: scrollbarLight,
|
||||
Input: inputLight,
|
||||
Empty: emptyLight,
|
||||
Button: buttonLight
|
||||
},
|
||||
self
|
||||
})
|
||||
|
||||
export default transferLight
|
||||
export type TransferTheme = typeof transferLight
|
79
src/legacy-transfer/tests/Transfer.spec.ts
Normal file
79
src/legacy-transfer/tests/Transfer.spec.ts
Normal file
@ -0,0 +1,79 @@
|
||||
import { mount } from '@vue/test-utils'
|
||||
import { sleep } from 'seemly'
|
||||
import { NTransfer } from '../index'
|
||||
|
||||
describe('n-transfer', () => {
|
||||
it('should work with import on demand', () => {
|
||||
mount(NTransfer)
|
||||
})
|
||||
|
||||
it('should work with `disabled` prop', () => {
|
||||
const wrapper = mount(NTransfer, { props: { disabled: true } })
|
||||
expect(wrapper.find('.n-transfer').attributes('class')).toContain(
|
||||
'n-transfer--disabled'
|
||||
)
|
||||
})
|
||||
|
||||
it('should work with `filterable` prop', () => {
|
||||
const wrapper = mount(NTransfer, { props: { filterable: true } })
|
||||
expect(wrapper.find('.n-transfer').attributes('class')).toContain(
|
||||
'n-transfer--filterable'
|
||||
)
|
||||
})
|
||||
|
||||
it('should work with `filter` prop', async () => {
|
||||
const options = [
|
||||
{
|
||||
label: 'test1',
|
||||
value: 'test1'
|
||||
}
|
||||
]
|
||||
const onFilter = jest.fn()
|
||||
const wrapper = mount(NTransfer, {
|
||||
props: { filterable: true, filter: onFilter, options: options }
|
||||
})
|
||||
await wrapper.find('input').setValue('1')
|
||||
await sleep(300)
|
||||
expect(onFilter).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should work with `size` prop', async () => {
|
||||
;(['small', 'medium', 'large'] as const).forEach((i) => {
|
||||
const wrapper = mount(NTransfer, {
|
||||
props: { size: i }
|
||||
})
|
||||
expect(wrapper.find('.n-transfer').attributes('style')).toMatchSnapshot()
|
||||
})
|
||||
})
|
||||
|
||||
it('should work with `source-filter-placeholder`、`target-filter-placeholder` props', async () => {
|
||||
const wrapper = mount(NTransfer, {
|
||||
props: {
|
||||
filterable: true,
|
||||
'source-filter-placeholder': 'test-source',
|
||||
'target-filter-placeholder': 'test-target'
|
||||
}
|
||||
})
|
||||
expect(wrapper.findAll('input')[0].attributes('placeholder')).toBe(
|
||||
'test-source'
|
||||
)
|
||||
expect(wrapper.findAll('input')[1].attributes('placeholder')).toBe(
|
||||
'test-target'
|
||||
)
|
||||
})
|
||||
|
||||
it('should work with `source-title`、`target-title` props', async () => {
|
||||
const wrapper = mount(NTransfer, {
|
||||
props: {
|
||||
'source-title': 'test-source',
|
||||
'target-title': 'test-target'
|
||||
}
|
||||
})
|
||||
expect(wrapper.findAll('.n-transfer-list-header__header')[0].text()).toBe(
|
||||
'test-source'
|
||||
)
|
||||
expect(wrapper.findAll('.n-transfer-list-header__header')[1].text()).toBe(
|
||||
'test-target'
|
||||
)
|
||||
})
|
||||
})
|
@ -0,0 +1,7 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`n-transfer should work with \`size\` prop 1`] = `"--n-bezier: cubic-bezier(.4, 0, .2, 1); --n-bezier-ease-in: cubic-bezier(.4, 0, 1, 1); --n-bezier-ease-out: cubic-bezier(0, 0, .2, 1); --n-border-color: rgb(224, 224, 230); --n-border-radius: 3px; --n-extra-font-size: 12px; --n-filter-divider-color: rgb(224, 224, 230); --n-font-size: 14px; --n-header-color: rgba(250, 250, 252, 1); --n-header-extra-text-color: rgb(51, 54, 57); --n-header-font-weight: 400; --n-header-text-color: rgb(31, 34, 37); --n-header-text-color-disabled: rgba(194, 194, 194, 1); --n-item-color-pending: rgb(243, 243, 245); --n-item-height: 28px; --n-item-text-color: rgb(51, 54, 57); --n-item-text-color-disabled: rgba(194, 194, 194, 1); --n-list-color: #fff; --n-width: 440px; --n-icon-color: rgba(194, 194, 194, 1); --n-icon-color-disabled: rgba(209, 209, 209, 1);"`;
|
||||
|
||||
exports[`n-transfer should work with \`size\` prop 2`] = `"--n-bezier: cubic-bezier(.4, 0, .2, 1); --n-bezier-ease-in: cubic-bezier(.4, 0, 1, 1); --n-bezier-ease-out: cubic-bezier(0, 0, .2, 1); --n-border-color: rgb(224, 224, 230); --n-border-radius: 3px; --n-extra-font-size: 12px; --n-filter-divider-color: rgb(224, 224, 230); --n-font-size: 14px; --n-header-color: rgba(250, 250, 252, 1); --n-header-extra-text-color: rgb(51, 54, 57); --n-header-font-weight: 400; --n-header-text-color: rgb(31, 34, 37); --n-header-text-color-disabled: rgba(194, 194, 194, 1); --n-item-color-pending: rgb(243, 243, 245); --n-item-height: 34px; --n-item-text-color: rgb(51, 54, 57); --n-item-text-color-disabled: rgba(194, 194, 194, 1); --n-list-color: #fff; --n-width: 440px; --n-icon-color: rgba(194, 194, 194, 1); --n-icon-color-disabled: rgba(209, 209, 209, 1);"`;
|
||||
|
||||
exports[`n-transfer should work with \`size\` prop 3`] = `"--n-bezier: cubic-bezier(.4, 0, .2, 1); --n-bezier-ease-in: cubic-bezier(.4, 0, 1, 1); --n-bezier-ease-out: cubic-bezier(0, 0, .2, 1); --n-border-color: rgb(224, 224, 230); --n-border-radius: 3px; --n-extra-font-size: 12px; --n-filter-divider-color: rgb(224, 224, 230); --n-font-size: 15px; --n-header-color: rgba(250, 250, 252, 1); --n-header-extra-text-color: rgb(51, 54, 57); --n-header-font-weight: 400; --n-header-text-color: rgb(31, 34, 37); --n-header-text-color-disabled: rgba(194, 194, 194, 1); --n-item-color-pending: rgb(243, 243, 245); --n-item-height: 40px; --n-item-text-color: rgb(51, 54, 57); --n-item-text-color-disabled: rgba(194, 194, 194, 1); --n-list-color: #fff; --n-width: 440px; --n-icon-color: rgba(194, 194, 194, 1); --n-icon-color-disabled: rgba(209, 209, 209, 1);"`;
|
19
src/legacy-transfer/tests/server.spec.tsx
Normal file
19
src/legacy-transfer/tests/server.spec.tsx
Normal file
@ -0,0 +1,19 @@
|
||||
/**
|
||||
* @jest-environment node
|
||||
*/
|
||||
import { h, createSSRApp } from 'vue'
|
||||
import { renderToString } from '@vue/server-renderer'
|
||||
import { setup } from '@css-render/vue3-ssr'
|
||||
import { NTransfer } from '../..'
|
||||
|
||||
describe('SSR', () => {
|
||||
it('works', async () => {
|
||||
const app = createSSRApp(() => <NTransfer />)
|
||||
setup(app)
|
||||
try {
|
||||
await renderToString(app)
|
||||
} catch (e) {
|
||||
expect(e).not.toBeTruthy()
|
||||
}
|
||||
})
|
||||
})
|
Loading…
Reference in New Issue
Block a user