Skip to content

Commit

Permalink
feat(comp: tree-selct): add customAdditional,getKey and overlayContai…
Browse files Browse the repository at this point in the history
…ner (#869)

synchronization with cascader and tree

BREAKING CHANGE: `nodeKey` was deprecated, please use `getKey`
instead. `target` was deprecated, please use `overlayContainer` instead. `dataSource.additional` was
deprecated, please use `customAdditional` instead.
  • Loading branch information
danranVm authored Apr 29, 2022
1 parent d10f5e0 commit 593f86f
Show file tree
Hide file tree
Showing 43 changed files with 621 additions and 356 deletions.
32 changes: 16 additions & 16 deletions packages/components/_private/selector/src/Selector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,21 @@ export default defineComponent({
setup(props, { expose, slots }) {
const common = useGlobalConfig('common')
const mergedPrefixCls = computed(() => `${common.prefixCls}-selector`)
const mergedClearable = computed(() => {
return !props.disabled && !props.readonly && props.clearable && props.value.length > 0
})
const mergedClearIcon = computed(() => props.clearIcon ?? props.config.clearIcon)
const mergedSearchable = computed(() => {
return !props.disabled && !props.readonly && props.searchable === true
})
const mergedSize = useFormSize(props, props.config)
const mergedSuffix = computed(() => {
return props.suffix ?? (mergedSearchable.value && isFocused.value ? 'search' : props.config.suffix)
})
const showPlaceholder = computed(() => {
return props.value.length === 0 && !isComposing.value && !inputValue.value
})

const {
mirrorRef,
inputRef,
Expand All @@ -50,27 +65,12 @@ export default defineComponent({
handleCompositionEnd,
handleInput,
clearInput,
} = useInputState(props)
} = useInputState(props, mergedSearchable)

const getBoundingClientRect = () => elementRef.value?.getBoundingClientRect()

expose({ focus, blur, clearInput, getBoundingClientRect })

const mergedClearable = computed(() => {
return !props.disabled && !props.readonly && props.clearable && props.value.length > 0
})
const mergedClearIcon = computed(() => props.clearIcon ?? props.config.clearIcon)
const mergedSearchable = computed(() => {
return !props.disabled && !props.readonly && props.searchable === true
})
const mergedSize = useFormSize(props, props.config)
const mergedSuffix = computed(() => {
return props.suffix ?? (mergedSearchable.value && isFocused.value ? 'search' : props.config.suffix)
})
const showPlaceholder = computed(() => {
return props.value.length === 0 && !isComposing.value && !inputValue.value
})

const classes = computed(() => {
const config = props.config
const { allowInput, className, borderless = config.borderless, multiple } = props
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

/* eslint-disable @typescript-eslint/no-explicit-any */

import { type Ref, onMounted, ref } from 'vue'
import { type ComputedRef, type Ref, onMounted, ref } from 'vue'

import { callEmit } from '@idux/cdk/utils'
import { useFormFocusMonitor } from '@idux/components/utils'
Expand All @@ -28,7 +28,7 @@ export interface InputStateContext {
clearInput: () => void
}

export function useInputState(props: SelectorProps): InputStateContext {
export function useInputState(props: SelectorProps, mergedSearchable: ComputedRef<boolean>): InputStateContext {
const mirrorRef = ref<HTMLSpanElement>()
const inputValue = ref('')
const isComposing = ref(false)
Expand Down Expand Up @@ -73,17 +73,21 @@ export function useInputState(props: SelectorProps): InputStateContext {

const handleInput = (evt: Event, emitInput = true) => {
emitInput && callEmit(props.onInput, evt)

const inputEnabled = props.allowInput || mergedSearchable.value

if (isComposing.value) {
syncMirrorWidth(evt)
inputEnabled && syncMirrorWidth(evt)
return
}
if (props.allowInput || props.searchable) {

if (inputEnabled) {
const { value } = evt.target as HTMLInputElement
if (value !== inputValue.value) {
inputValue.value = value
callEmit(props.onInputValueChange, value)
}
callEmit(props.onSearch, value)
mergedSearchable.value && callEmit(props.onSearch, value)
syncMirrorWidth()
}
}
Expand Down
2 changes: 1 addition & 1 deletion packages/components/cascader/__tests__/cascader.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ const defaultMultipleValue = [
]
const defaultExpandedKeys = ['components', 'general']

describe.only('Cascader', () => {
describe('Cascader', () => {
describe('single work', () => {
const CascaderMount = (options?: MountingOptions<Partial<CascaderProps>>) => {
const { props, ...rest } = options || {}
Expand Down
2 changes: 1 addition & 1 deletion packages/components/cascader/docs/Index.zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ subtitle: 级联选择
| `childrenKey` | 替代[CascaderData](#CascaderData)中的`children`字段 | `string` | `children` || - |
| `clearable` | 是否显示清除图标 | `boolean` | `false` | - | - |
| `clearIcon` | 设置清除图标 | `string \| #clearIcon` | `'close-circle'` || - |
| `customAdditional` | 自定义下拉选项的额外属性 | `CascaderCustomAdditional` | - | - | 例如 `class`, 或者原生事件 |
| `dataSource` | 树型数据数组,参见[CascaderData](#CascaderData) | `CascaderData[]` | `[]` | - | - |
| `disabled` | 禁用选择器 | `boolean` | - | - | - |
| `empty` | 空数据时的内容 | `string \| EmptyProps \| #empty` | - | - | - |
Expand Down Expand Up @@ -60,7 +61,6 @@ subtitle: 级联选择

| 名称 | 说明 | 类型 | 默认值 | 全局配置 | 备注 |
| --- | --- | --- | --- | --- | --- |
| `additional` | 节点的扩展属性 | `object` | - | - | 可以用于设置节点的 `class`, `style` 或者其他属性 |
| `children` | 子节点数据 | `CascaderData[]` | - | - | - |
| `disabled` | 禁用节点 | `boolean` | - | - | - |
| `isLeaf` | 设置为叶子节点 | `boolean` | - | - | 不为 `true` 且设置了 `loadChildren` 时会强制将其作为父节点 |
Expand Down
19 changes: 11 additions & 8 deletions packages/components/cascader/src/Cascader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -74,14 +74,22 @@ export default defineComponent({
mergedDataMap,
)

watch(overlayOpened, opened => {
opened && focus()
clearInput()
})

const handleOverlayClick = () => {
if (props.searchable !== 'overlay') {
focus()
}
}

const handleBlur = () => accessor.markAsBlurred()
const handleItemRemove = (key: VKey) => {
focus()
selectedStateContext.handleSelect(key)
}
const handleOverlayClick = () => {
overlayOpened.value && focus()
}

provide(cascaderToken, {
props,
Expand All @@ -107,11 +115,6 @@ export default defineComponent({
...expandableContext,
})

watch(overlayOpened, opened => {
opened ? focus() : blur()
clearInput()
})

const overlayClasses = computed(() => {
const { overlayClassName, multiple } = props
const prefixCls = mergedPrefixCls.value
Expand Down
26 changes: 20 additions & 6 deletions packages/components/cascader/src/composables/useSearchable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,7 @@ import { isFunction } from 'lodash-es'

import { NoopArray, type VKey } from '@idux/cdk/utils'

import { type CascaderData, type CascaderProps, CascaderSearchFn } from '../types'
import { getChildrenKeys } from '../utils'
import { type CascaderData, type CascaderProps, type CascaderSearchFn } from '../types'
import { type MergedData } from './useDataSource'

export interface SearchableContext {
Expand Down Expand Up @@ -45,10 +44,7 @@ export function useSearchable(
if (_parentEnabled || data.isLeaf) {
keySet.add(key)
}
const childrenKeys = getChildrenKeys(data, true)
if (childrenKeys.length) {
childrenKeys.forEach(key => keySet.add(key))
}
processChildren(keySet, data, _parentEnabled)
}
})
return [...keySet]
Expand Down Expand Up @@ -78,3 +74,21 @@ function getDefaultSearchFn(labelKey: string): CascaderSearchFn {
return label ? label.toLowerCase().includes(searchValue.toLowerCase()) : false
}
}

function processChildren(keySet: Set<VKey>, data: MergedData, parentEnabled: boolean) {
if (!data || !data.children) {
return
}

data.children.forEach(child => {
if (child.rawData.disabled || keySet.has(child.key)) {
return
}

if (parentEnabled || child.isLeaf) {
keySet.add(child.key)
}

processChildren(keySet, child, parentEnabled)
})
}
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ export default defineComponent({
</div>,
)

return overlayRender ? overlayRender(children) : <div>{children}</div>
return <div>{overlayRender ? overlayRender(children) : children}</div>
}
},
})
10 changes: 9 additions & 1 deletion packages/components/cascader/src/contents/OverlayOption.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { type CascaderData } from '../types'
export default defineComponent({
props: {
children: { type: Array as PropType<MergedData[]>, default: undefined },
index: { type: Number, required: true },
isLeaf: { type: Boolean, required: true },
label: { type: String, required: true },
parentKey: { type: [String, Number, Symbol], default: undefined },
Expand Down Expand Up @@ -91,15 +92,22 @@ export default defineComponent({

const searchValue = inputValue.value
const mergedLabel = searchValue ? label : (rawData[mergedLabelKey.value] as string)
// 优先显示 selectedLimitTitle
const title = (!(disabled || selected) && selectedLimitTitle.value) || mergedLabel
const customAdditional = cascaderProps.customAdditional
? cascaderProps.customAdditional({ data: rawData, index: props.index })
: undefined

return (
<div
class={classes.value}
title={disabled || selected ? undefined : selectedLimitTitle.value}
title={title}
onClick={disabled ? undefined : handleClick}
onMouseenter={disabled ? undefined : handleMouseEnter}
aria-label={label}
aria-selected={selected}
{...rawData.additional}
{...customAdditional}
>
{multiple && (
<IxCheckbox
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,9 @@ export default defineComponent({
let children: VNode
if (dataSource.length > 0) {
const { overlayHeight, overlayItemHeight, virtual } = cascaderProps
const itemRender: VirtualItemRenderFn<MergedData> = ({ item }) => <OverlayOption {...item} />
const itemRender: VirtualItemRenderFn<MergedData> = ({ item, index }) => (
<OverlayOption index={index} {...item} />
)
children = (
<CdkVirtualScroll
dataSource={dataSource}
Expand Down
9 changes: 9 additions & 0 deletions packages/components/cascader/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export const cascaderProps = {
childrenKey: { type: String, default: undefined },
clearable: { type: Boolean, default: false },
clearIcon: { type: String, default: undefined },
customAdditional: { type: Object as PropType<CascaderCustomAdditional>, default: undefined },
dataSource: { type: Array as PropType<CascaderData[]>, default: () => [] },
disabled: { type: Boolean, default: false },
empty: { type: [String, Object] as PropType<string | EmptyProps>, default: undefined },
Expand Down Expand Up @@ -87,7 +88,15 @@ export type CascaderInstance = InstanceType<DefineComponent<CascaderProps, Casca

export type CascaderStrategy = 'all' | 'parent' | 'child' | 'off'

export type CascaderCustomAdditional = (options: {
data: CascaderData
index: number
}) => Record<string, any> | undefined

export interface CascaderData {
/**
* @deprecated please use `customAdditional` instead'
*/
additional?: {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
class?: any
Expand Down
6 changes: 4 additions & 2 deletions packages/components/config/src/defaultConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -332,16 +332,18 @@ export const defaultConfig: GlobalConfig = {
},
tree: {
blocked: false,
childrenKey: 'children',
expandIcon: 'right',
nodeKey: 'key',
getKey: 'key',
labelKey: 'label',
showLine: false,
},
treeSelect: {
borderless: false,
childrenKey: 'children',
clearIcon: 'close-circle',
labelKey: 'label',
nodeKey: 'key',
getKey: 'key',
overlayMatchWidth: true,
size: 'md',
suffix: 'down',
Expand Down
21 changes: 18 additions & 3 deletions packages/components/config/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@
* found in the LICENSE file at https://github.com/IDuxFE/idux/blob/main/LICENSE
*/

import type { VKey } from '@idux/cdk'
import type { BreakpointKey } from '@idux/cdk/breakpoint'
import type { PopperPlacement, PopperTrigger } from '@idux/cdk/popper'
import type { PortalTargetType } from '@idux/cdk/portal'
import type { VKey } from '@idux/cdk/utils'
import type { AlertType } from '@idux/components/alert'
import type { AvatarShape, AvatarSize } from '@idux/components/avatar'
import type { ButtonSize } from '@idux/components/button'
Expand All @@ -33,6 +33,7 @@ import type { StepperLabelPlacement, StepperSize } from '@idux/components/steppe
import type { TableColumnAlign, TableColumnSortOrder, TablePaginationPosition, TableSize } from '@idux/components/table'
import type { TagShape } from '@idux/components/tag'
import type { TextareaAutoRows, TextareaResize } from '@idux/components/textarea'
import type { TreeNode } from '@idux/components/tree'
import type { UploadFilesType, UploadIconType, UploadRequestMethod, UploadRequestOption } from '@idux/components/upload'
import type { VNode } from 'vue'

Expand Down Expand Up @@ -501,20 +502,34 @@ export interface TooltipConfig {

export interface TreeConfig {
blocked: boolean
childrenKey: string
expandIcon: string
nodeKey: string
getKey: string | ((data: TreeNode) => VKey)
labelKey: string
/**
* @deprecated please use `labelKey` instead'
*/
nodeKey?: string
showLine: boolean
}

export interface TreeSelectConfig {
borderless: boolean
childrenKey: string
clearIcon: string
getKey: string | ((data: TreeNode) => VKey)
labelKey: string
nodeKey: string
/**
* @deprecated please use `labelKey` instead'
*/
nodeKey?: string
overlayMatchWidth: boolean
overlayContainer?: PortalTargetType
size: FormSize
suffix: string
/**
* @deprecated please use `overlayContainer` instead'
*/
target?: PortalTargetType
}

Expand Down
2 changes: 1 addition & 1 deletion packages/components/input/style/mixin.less
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@

.input-size(@font-size; @padding-vertical; @padding-horizontal;) {
font-size: @font-size;
padding: @padding-vertical @padding-horizontal;
padding: @padding-vertical (@padding-horizontal - 4) @padding-vertical @padding-horizontal;
}

.input-hover(@color: @input-hover-color) {
Expand Down
2 changes: 1 addition & 1 deletion packages/components/select/demo/CustomLabel.vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<template>
<IxSelect v-model:value="value" :dataSource="dataSource" multiple :maxLabel="3" :multipleLimit="5" @change="onChange">
<template #selectedLabel="option">
<IxIcon :name="option.value" style="margin-left: 8px" />{{ option.label }}
<IxIcon :name="option.key" style="margin-right: 4px" />{{ option.label }}
</template>
<template #overflowedLabel="moreOptions">and {{ moreOptions.length }} more selected</template>
</IxSelect>
Expand Down
4 changes: 2 additions & 2 deletions packages/components/select/demo/Key.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ order: 6

## zh

使用 `dataSource` 时,可以通过设置 `labelKey`, `valueKey``childrenKey` 来进行数据转换。
使用 `dataSource` 时,可以通过设置 `labelKey`, `getKey``childrenKey` 来进行数据转换。

## en

When using `dataSource`, you can conversion data by setting `labelKey`, `valueKey` and `childrenKey`.
When using `dataSource`, you can conversion data by setting `labelKey`, `getKey` and `childrenKey`.
Loading

0 comments on commit 593f86f

Please sign in to comment.