Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(comp: tree-selct): add customAdditional,getKey and overlayContainer #869

Merged
merged 1 commit into from
Apr 29, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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