diff --git a/packages/components/checkbox/src/checkbox.ts b/packages/components/checkbox/src/checkbox.ts index 6129dd49aa..18c4d42711 100644 --- a/packages/components/checkbox/src/checkbox.ts +++ b/packages/components/checkbox/src/checkbox.ts @@ -309,7 +309,7 @@ const useEvent = ( } } -type CheckboxValueType = string | number | boolean +export type CheckboxValueType = string | number | boolean export const checkboxEmits = { [UPDATE_MODEL_EVENT]: (val: CheckboxValueType) => diff --git a/packages/components/transfer/__tests__/transfer.test.ts b/packages/components/transfer/__tests__/transfer.test.ts index 630a5f0588..92813fc53b 100644 --- a/packages/components/transfer/__tests__/transfer.test.ts +++ b/packages/components/transfer/__tests__/transfer.test.ts @@ -2,7 +2,7 @@ import { nextTick } from 'vue' import { mount } from '@vue/test-utils' import { describe, expect, it } from 'vitest' -import Transfer from '../src/index.vue' +import Transfer from '../src/transfer.vue' describe('Transfer', () => { const getTestData = () => { diff --git a/packages/components/transfer/index.ts b/packages/components/transfer/index.ts index a306838e6d..6207d34d66 100644 --- a/packages/components/transfer/index.ts +++ b/packages/components/transfer/index.ts @@ -1,15 +1,8 @@ -import Transfer from './src/index.vue' +import { withInstall } from '@element-plus/utils' -import type { App } from 'vue' -import type { SFCWithInstall } from '@element-plus/utils' +import Transfer from './src/transfer.vue' -Transfer.install = (app: App): void => { - app.component(Transfer.name, Transfer) -} - -const _Transfer = Transfer as SFCWithInstall - -export default _Transfer -export const ElTransfer = _Transfer +export const ElTransfer = withInstall(Transfer) +export default ElTransfer export * from './src/transfer' diff --git a/packages/components/transfer/src/composables/index.ts b/packages/components/transfer/src/composables/index.ts new file mode 100644 index 0000000000..731781a447 --- /dev/null +++ b/packages/components/transfer/src/composables/index.ts @@ -0,0 +1,5 @@ +export * from './use-check' +export * from './use-checked-change' +export * from './use-computed-data' +export * from './use-move' +export * from './use-props-alias' diff --git a/packages/components/transfer/src/useCheck.ts b/packages/components/transfer/src/composables/use-check.ts similarity index 66% rename from packages/components/transfer/src/useCheck.ts rename to packages/components/transfer/src/composables/use-check.ts index 1522d6413d..51a4b4eeb5 100644 --- a/packages/components/transfer/src/useCheck.ts +++ b/packages/components/transfer/src/composables/use-check.ts @@ -1,54 +1,40 @@ -// @ts-nocheck -import { computed, getCurrentInstance, watch } from 'vue' +import { computed, watch } from 'vue' +import { isFunction } from '@element-plus/utils' +import { CHECKED_CHANGE_EVENT } from '../transfer-panel' +import { usePropsAlias } from './use-props-alias' -import type { ExtractPropTypes } from 'vue' -import type { Key, TransferPanelState } from './transfer' - -export const CHECKED_CHANGE_EVENT = 'checked-change' - -export const useCheckProps = { - data: { - type: Array, - default() { - return [] - }, - }, - optionRender: Function, - placeholder: String, - title: String, - filterable: Boolean, - format: Object, - filterMethod: Function, - defaultChecked: Array, - props: Object, -} +import type { SetupContext } from 'vue' +import type { CheckboxValueType } from '@element-plus/components/checkbox' +import type { TransferKey } from '../transfer' +import type { + TransferPanelEmits, + TransferPanelProps, + TransferPanelState, +} from '../transfer-panel' export const useCheck = ( - props: ExtractPropTypes, - panelState: TransferPanelState + props: TransferPanelProps, + panelState: TransferPanelState, + emit: SetupContext['emit'] ) => { - const { emit } = getCurrentInstance() - - const labelProp = computed(() => props.props.label || 'label') - - const keyProp = computed(() => props.props.key || 'key') - - const disabledProp = computed(() => props.props.disabled || 'disabled') + const propsAlias = usePropsAlias(props) const filteredData = computed(() => { return props.data.filter((item) => { - if (typeof props.filterMethod === 'function') { + if (isFunction(props.filterMethod)) { return props.filterMethod(panelState.query, item) } else { - const label = item[labelProp.value] || item[keyProp.value].toString() + const label = String( + item[propsAlias.value.label] || item[propsAlias.value.key] + ) return label.toLowerCase().includes(panelState.query.toLowerCase()) } }) }) - const checkableData = computed(() => { - return filteredData.value.filter((item) => !item[disabledProp.value]) - }) + const checkableData = computed(() => + filteredData.value.filter((item) => !item[propsAlias.value.disabled]) + ) const checkedSummary = computed(() => { const checkedLength = panelState.checked.length @@ -73,16 +59,16 @@ export const useCheck = ( const updateAllChecked = () => { const checkableDataKeys = checkableData.value.map( - (item) => item[keyProp.value] + (item) => item[propsAlias.value.key] ) panelState.allChecked = checkableDataKeys.length > 0 && checkableDataKeys.every((item) => panelState.checked.includes(item)) } - const handleAllCheckedChange = (value: Key[]) => { + const handleAllCheckedChange = (value: CheckboxValueType) => { panelState.checked = value - ? checkableData.value.map((item) => item[keyProp.value]) + ? checkableData.value.map((item) => item[propsAlias.value.key]) : [] } @@ -110,9 +96,9 @@ export const useCheck = ( watch( () => props.data, () => { - const checked = [] + const checked: TransferKey[] = [] const filteredDataKeys = filteredData.value.map( - (item) => item[keyProp.value] + (item) => item[propsAlias.value.key] ) panelState.checked.forEach((item) => { if (filteredDataKeys.includes(item)) { @@ -134,9 +120,9 @@ export const useCheck = ( ) return - const checked = [] + const checked: TransferKey[] = [] const checkableDataKeys = checkableData.value.map( - (item) => item[keyProp.value] + (item) => item[propsAlias.value.key] ) val.forEach((item) => { @@ -153,9 +139,6 @@ export const useCheck = ( ) return { - labelProp, - keyProp, - disabledProp, filteredData, checkableData, checkedSummary, diff --git a/packages/components/transfer/src/composables/use-checked-change.ts b/packages/components/transfer/src/composables/use-checked-change.ts new file mode 100644 index 0000000000..2d134386d4 --- /dev/null +++ b/packages/components/transfer/src/composables/use-checked-change.ts @@ -0,0 +1,36 @@ +import { LEFT_CHECK_CHANGE_EVENT, RIGHT_CHECK_CHANGE_EVENT } from '../transfer' + +import type { SetupContext } from 'vue' +import type { + TransferCheckedState, + TransferEmits, + TransferKey, +} from '../transfer' + +export const useCheckedChange = ( + checkedState: TransferCheckedState, + emit: SetupContext['emit'] +) => { + const onSourceCheckedChange = ( + val: TransferKey[], + movedKeys?: TransferKey[] + ) => { + checkedState.leftChecked = val + if (!movedKeys) return + emit(LEFT_CHECK_CHANGE_EVENT, val, movedKeys) + } + + const onTargetCheckedChange = ( + val: TransferKey[], + movedKeys?: TransferKey[] + ) => { + checkedState.rightChecked = val + if (!movedKeys) return + emit(RIGHT_CHECK_CHANGE_EVENT, val, movedKeys) + } + + return { + onSourceCheckedChange, + onTargetCheckedChange, + } +} diff --git a/packages/components/transfer/src/composables/use-computed-data.ts b/packages/components/transfer/src/composables/use-computed-data.ts new file mode 100644 index 0000000000..109e5bb7c5 --- /dev/null +++ b/packages/components/transfer/src/composables/use-computed-data.ts @@ -0,0 +1,42 @@ +import { computed } from 'vue' +import { usePropsAlias } from './use-props-alias' + +import type { TransferDataItem, TransferKey, TransferProps } from '../transfer' + +export const useComputedData = (props: TransferProps) => { + const propsAlias = usePropsAlias(props) + + const dataObj = computed(() => + props.data.reduce((o, cur) => (o[cur[propsAlias.value.key]] = cur) && o, {}) + ) + + const sourceData = computed(() => + props.data.filter( + (item) => !props.modelValue.includes(item[propsAlias.value.key]) + ) + ) + + const targetData = computed(() => { + if (props.targetOrder === 'original') { + return props.data.filter((item) => + props.modelValue.includes(item[propsAlias.value.key]) + ) + } else { + return props.modelValue.reduce( + (arr: TransferDataItem[], cur: TransferKey) => { + const val = dataObj.value[cur] + if (val) { + arr.push(val) + } + return arr + }, + [] + ) + } + }) + + return { + sourceData, + targetData, + } +} diff --git a/packages/components/transfer/src/useMove.ts b/packages/components/transfer/src/composables/use-move.ts similarity index 62% rename from packages/components/transfer/src/useMove.ts rename to packages/components/transfer/src/composables/use-move.ts index b0306475de..f7aa124c95 100644 --- a/packages/components/transfer/src/useMove.ts +++ b/packages/components/transfer/src/composables/use-move.ts @@ -1,23 +1,30 @@ -// @ts-nocheck import { CHANGE_EVENT, UPDATE_MODEL_EVENT } from '@element-plus/constants' +import { usePropsAlias } from './use-props-alias' -import type { ComputedRef } from 'vue' +import type { SetupContext } from 'vue' import type { - DataItem, - Key, TransferCheckedState, + TransferDataItem, + TransferDirection, + TransferEmits, + TransferKey, TransferProps, -} from './transfer' +} from '../transfer' export const useMove = ( props: TransferProps, checkedState: TransferCheckedState, - propsKey: ComputedRef, - emit + emit: SetupContext['emit'] ) => { - const _emit = (value, type: 'left' | 'right', checked: Key[]) => { + const propsAlias = usePropsAlias(props) + + const _emit = ( + value: TransferKey[], + direction: TransferDirection, + movedKeys: TransferKey[] + ) => { emit(UPDATE_MODEL_EVENT, value) - emit(CHANGE_EVENT, value, type, checked) + emit(CHANGE_EVENT, value, direction, movedKeys) } const addToLeft = () => { @@ -36,14 +43,14 @@ export const useMove = ( let currentValue = props.modelValue.slice() const itemsToBeMoved = props.data - .filter((item: DataItem) => { - const itemKey = item[propsKey.value] + .filter((item: TransferDataItem) => { + const itemKey = item[propsAlias.value.key] return ( checkedState.leftChecked.includes(itemKey) && !props.modelValue.includes(itemKey) ) }) - .map((item) => item[propsKey.value]) + .map((item) => item[propsAlias.value.key]) currentValue = props.targetOrder === 'unshift' @@ -52,8 +59,8 @@ export const useMove = ( if (props.targetOrder === 'original') { currentValue = props.data - .filter((item) => currentValue.includes(item[propsKey.value])) - .map((item) => item[propsKey.value]) + .filter((item) => currentValue.includes(item[propsAlias.value.key])) + .map((item) => item[propsAlias.value.key]) } _emit(currentValue, 'right', checkedState.leftChecked) diff --git a/packages/components/transfer/src/composables/use-props-alias.ts b/packages/components/transfer/src/composables/use-props-alias.ts new file mode 100644 index 0000000000..41783b2c70 --- /dev/null +++ b/packages/components/transfer/src/composables/use-props-alias.ts @@ -0,0 +1,16 @@ +import { computed } from 'vue' + +import type { TransferPropsAlias } from '../transfer' + +export const usePropsAlias = (props: { props: TransferPropsAlias }) => { + const initProps: Required = { + label: 'label', + key: 'key', + disabled: 'disabled', + } + + return computed(() => ({ + ...initProps, + ...props.props, + })) +} diff --git a/packages/components/transfer/src/index.vue b/packages/components/transfer/src/index.vue deleted file mode 100644 index d7d4f1aa1a..0000000000 --- a/packages/components/transfer/src/index.vue +++ /dev/null @@ -1,257 +0,0 @@ - - - diff --git a/packages/components/transfer/src/transfer-panel.ts b/packages/components/transfer/src/transfer-panel.ts new file mode 100644 index 0000000000..face80afe9 --- /dev/null +++ b/packages/components/transfer/src/transfer-panel.ts @@ -0,0 +1,40 @@ +import { buildProps, definePropType } from '@element-plus/utils' +import { transferCheckedChangeFn, transferProps } from './transfer' + +import type { ExtractPropTypes, VNode } from 'vue' +import type { TransferDataItem, TransferKey } from './transfer' +import type TransferPanel from './transfer-panel.vue' + +export interface TransferPanelState { + checked: TransferKey[] + allChecked: boolean + query: string + inputHover: boolean + checkChangeByUser: boolean +} + +export const CHECKED_CHANGE_EVENT = 'checked-change' + +export const transferPanelProps = buildProps({ + data: transferProps.data, + optionRender: { + type: definePropType<(option: TransferDataItem) => VNode | VNode[]>( + Function + ), + }, + placeholder: String, + title: String, + filterable: Boolean, + format: transferProps.format, + filterMethod: transferProps.filterMethod, + defaultChecked: transferProps.leftDefaultChecked, + props: transferProps.props, +} as const) +export type TransferPanelProps = ExtractPropTypes + +export const transferPanelEmits = { + [CHECKED_CHANGE_EVENT]: transferCheckedChangeFn, +} +export type TransferPanelEmits = typeof transferPanelEmits + +export type TransferPanelInstance = InstanceType diff --git a/packages/components/transfer/src/transfer-panel.vue b/packages/components/transfer/src/transfer-panel.vue index 5a5f2931b4..a635a324d3 100644 --- a/packages/components/transfer/src/transfer-panel.vue +++ b/packages/components/transfer/src/transfer-panel.vue @@ -18,30 +18,27 @@ :class="ns.be('panel', 'filter')" size="default" :placeholder="placeholder" - :prefix-icon="SearchIcon" + :prefix-icon="Search" clearable @mouseenter="inputHover = true" @mouseleave="inputHover = false" /> - + -

+

{{ hasNoMatch ? t('el.transfer.noMatch') : t('el.transfer.noData') }}

@@ -51,82 +48,59 @@ - diff --git a/packages/components/transfer/src/transfer.ts b/packages/components/transfer/src/transfer.ts index c7a96d9e84..08d37ce8c0 100644 --- a/packages/components/transfer/src/transfer.ts +++ b/packages/components/transfer/src/transfer.ts @@ -1,68 +1,113 @@ -// @ts-nocheck -import { CHANGE_EVENT } from '@element-plus/constants' +import { isNil } from 'lodash-unified' +import { + buildProps, + definePropType, + isArray, + mutable, +} from '@element-plus/utils' +import { CHANGE_EVENT, UPDATE_MODEL_EVENT } from '@element-plus/constants' -import type { VNode } from 'vue' +import type { ExtractPropTypes, h as H, VNode } from 'vue' +import type Transfer from './transfer.vue' -export { CHANGE_EVENT } +export type TransferKey = string | number +export type TransferDirection = 'left' | 'right' -export type Key = string | number +export type TransferDataItem = Record -export type DataItem = { - key: Key - label: string - disabled: boolean +export interface TransferFormat { + noChecked?: string + hasChecked?: string } -export type Format = { - noChecked: string - hasChecked: string -} - -export type Props = { - label: string - key: string - disabled: string -} - -export type TargetOrder = 'original' | 'push' | 'unshift' - -export interface TransferProps { - data: DataItem[] - titles: [string, string] - buttonTexts: [string, string] - filterPlaceholder: string - filterMethod?: (query: string, item: DataItem) => boolean - leftDefaultChecked: Key[] - rightDefaultChecked: Key[] - renderContent?: (h, option) => VNode - modelValue: Key[] - format: Format - filterable: boolean - props: Props - targetOrder: TargetOrder +export interface TransferPropsAlias { + label?: string + key?: string + disabled?: string } export interface TransferCheckedState { - leftChecked: Key[] - rightChecked: Key[] + leftChecked: TransferKey[] + rightChecked: TransferKey[] } -export interface TransferPanelProps { - data: DataItem[] - optionRender: ({ option: VNode }) => VNode - placeholder: string - title: string - filterable: boolean - format: Format - filterMethod: (query: string, item: DataItem) => boolean - defaultChecked: Key[] - props: Props -} +export const LEFT_CHECK_CHANGE_EVENT = 'left-check-change' +export const RIGHT_CHECK_CHANGE_EVENT = 'right-check-change' -export interface TransferPanelState { - checked: Key[] - allChecked: boolean - query: string - inputHover: boolean - checkChangeByUser: boolean +export const transferProps = buildProps({ + data: { + type: definePropType(Array), + default: () => [], + }, + titles: { + type: definePropType<[string, string]>(Array), + default: () => [], + }, + buttonTexts: { + type: definePropType<[string, string]>(Array), + default: () => [], + }, + filterPlaceholder: String, + filterMethod: { + type: definePropType<(query: string, item: TransferDataItem) => boolean>( + Function + ), + }, + leftDefaultChecked: { + type: definePropType(Array), + default: () => [], + }, + rightDefaultChecked: { + type: definePropType(Array), + default: () => [], + }, + renderContent: { + type: definePropType< + (h: typeof H, option: TransferDataItem) => VNode | VNode[] + >(Function), + }, + modelValue: { + type: definePropType(Array), + default: () => [], + }, + format: { + type: definePropType(Object), + default: () => ({}), + }, + filterable: Boolean, + props: { + type: definePropType(Object), + default: () => + mutable({ + label: 'label', + key: 'key', + disabled: 'disabled', + } as const), + }, + targetOrder: { + type: String, + values: ['original', 'push', 'unshift'], + default: 'original', + }, +} as const) +export type TransferProps = ExtractPropTypes + +export const transferCheckedChangeFn = ( + value: TransferKey[], + movedKeys?: TransferKey[] +) => [value, movedKeys].every(isArray) || (isArray(value) && isNil(movedKeys)) + +export const transferEmits = { + [CHANGE_EVENT]: ( + value: TransferKey[], + direction: TransferDirection, + movedKeys: TransferKey[] + ) => + [value, movedKeys].every(isArray) && ['left', 'right'].includes(direction), + [UPDATE_MODEL_EVENT]: (value: TransferKey[]) => isArray(value), + [LEFT_CHECK_CHANGE_EVENT]: transferCheckedChangeFn, + [RIGHT_CHECK_CHANGE_EVENT]: transferCheckedChangeFn, } +export type TransferEmits = typeof transferEmits + +export type TransferInstance = InstanceType diff --git a/packages/components/transfer/src/transfer.vue b/packages/components/transfer/src/transfer.vue new file mode 100644 index 0000000000..c3ecc1b43b --- /dev/null +++ b/packages/components/transfer/src/transfer.vue @@ -0,0 +1,161 @@ + + + diff --git a/packages/components/transfer/src/useCheckedChange.ts b/packages/components/transfer/src/useCheckedChange.ts deleted file mode 100644 index accd686f37..0000000000 --- a/packages/components/transfer/src/useCheckedChange.ts +++ /dev/null @@ -1,23 +0,0 @@ -// @ts-nocheck -import type { Key, TransferCheckedState } from './transfer' - -export const LEFT_CHECK_CHANGE_EVENT = 'left-check-change' -export const RIGHT_CHECK_CHANGE_EVENT = 'right-check-change' -export const useCheckedChange = (checkedState: TransferCheckedState, emit) => { - const onSourceCheckedChange = (val: Key[], movedKeys: Key[]) => { - checkedState.leftChecked = val - if (movedKeys === undefined) return - emit(LEFT_CHECK_CHANGE_EVENT, val, movedKeys) - } - - const onTargetCheckedChange = (val: Key[], movedKeys: Key[]) => { - checkedState.rightChecked = val - if (movedKeys === undefined) return - emit(RIGHT_CHECK_CHANGE_EVENT, val, movedKeys) - } - - return { - onSourceCheckedChange, - onTargetCheckedChange, - } -} diff --git a/packages/components/transfer/src/useComputedData.ts b/packages/components/transfer/src/useComputedData.ts deleted file mode 100644 index fcc2de1778..0000000000 --- a/packages/components/transfer/src/useComputedData.ts +++ /dev/null @@ -1,43 +0,0 @@ -// @ts-nocheck -import { computed } from 'vue' - -import type { TransferProps } from './transfer' - -export const useComputedData = (props: TransferProps) => { - const propsKey = computed(() => props.props.key) - - const dataObj = computed(() => { - return props.data.reduce( - (o, cur) => (o[cur[propsKey.value]] = cur) && o, - {} - ) - }) - - const sourceData = computed(() => { - return props.data.filter( - (item) => !props.modelValue.includes(item[propsKey.value]) - ) - }) - - const targetData = computed(() => { - if (props.targetOrder === 'original') { - return props.data.filter((item) => - props.modelValue.includes(item[propsKey.value]) - ) - } else { - return props.modelValue.reduce((arr, cur) => { - const val = dataObj.value[cur] - if (val) { - arr.push(val) - } - return arr - }, []) - } - }) - - return { - propsKey, - sourceData, - targetData, - } -}