` | - | - | - |
+| `maxLabel` | 最多显示多少个标签 | `number \| 'responsive'` | - | - | 响应式模式会对性能产生损耗 |
+| `multiple` | 多选模式 | `boolean` | `false` | - | - |
+| `multipleLimit` | 最多选中多少项 | `number` | - | - | - |
+| `searchable` | 是否可搜索 | `boolean \| 'overlay'` | `false` | - | 当为 `true` 时搜索功能集成在选择器上,当为 `overlay` 时,搜索功能集成在悬浮层上 |
+| `searchFn` | 根据搜索的文本进行筛选 | `boolean \| SelectSearchFn` | `true` | - | 为 `true` 时使用默认的搜索规则, 如果使用远程搜索,应该设置为 `false` |
+| `separator` | 设置分割符 | `string` | `/` | - | - |
+| `strategy` | 设置级联策略 | `'all' \| 'parent' \| 'child' \| 'off'` | `'all'` | - | 具体用法参见 [级联策略](#components-cascader-demo-Strategy) |
+| `virtual` | 是否开启虚拟滚动 | `boolean` | `false` | - | 需要设置 `height` |
+| `onSelect` | 选中值触发 | `(option: CascaderData, oldValue: any) => void` | - | - | - |
+| `onExpand` | 点击展开图标时触发 | `(expanded: boolean, isSelected: boolean) => void` | - | - | - |
+| `onExpandedChange` | 展开状态发生变化时触发 | `(expendedKeys: VKey[], expendedData: CascaderData[]) => void` | - | - | - |
+| `onLoaded` | 子节点加载完毕时触发 | `(loadedKeys: VKey[], data: CascaderData) => void` | - | - | - |
+| `onSearch` | 开启搜索功能后,输入后的回调 | `(searchValue: string) => void` | - | - | 通常用于服务端搜索 |
+
+#### CascaderPanelSlots
+
+| 名称 | 说明 | 参数类型 | 备注 |
+| --- | --- | --- | --- |
+| `empty` | 自定义空状态 | - | - |
+| `optionLabel` | 自定义选项的文本 | `data: SelectOption` | - |
diff --git a/packages/components/cascader/index.ts b/packages/components/cascader/index.ts
index 1a10be50f..2f6601b06 100644
--- a/packages/components/cascader/index.ts
+++ b/packages/components/cascader/index.ts
@@ -5,18 +5,23 @@
* found in the LICENSE file at https://github.com/IDuxFE/idux/blob/main/LICENSE
*/
-import type { CascaderComponent } from './src/types'
+import type { CascaderComponent, CascaderPanelComponent } from './src/types'
import Cascader from './src/Cascader'
+import CascaderPanel from './src/panel/Panel'
const IxCascader = Cascader as unknown as CascaderComponent
+const IxCascaderPanel = CascaderPanel as unknown as CascaderPanelComponent
-export { IxCascader }
+export { IxCascader, IxCascaderPanel }
export type {
CascaderInstance,
CascaderComponent,
CascaderPublicProps as CascaderProps,
+ CascaderPanelInstance,
+ CascaderPanelComponent,
+ CascaderPanelPublicProps as CascaderPanelProps,
CascaderData,
CascaderExpandTrigger,
CascaderSearchFn,
diff --git a/packages/components/cascader/src/Cascader.tsx b/packages/components/cascader/src/Cascader.tsx
index 849e3a19a..15073d340 100644
--- a/packages/components/cascader/src/Cascader.tsx
+++ b/packages/components/cascader/src/Cascader.tsx
@@ -5,10 +5,11 @@
* found in the LICENSE file at https://github.com/IDuxFE/idux/blob/main/LICENSE
*/
-import { computed, defineComponent, normalizeClass, provide, ref, watch } from 'vue'
+import { computed, defineComponent, normalizeClass, provide, ref, toRef, watch } from 'vue'
import { useAccessorAndControl } from '@idux/cdk/forms'
-import { type VKey, useState } from '@idux/cdk/utils'
+import { type VKey, callEmit, useState } from '@idux/cdk/utils'
+import { ɵInput } from '@idux/components/_private/input'
import { ɵOverlay } from '@idux/components/_private/overlay'
import { ɵSelector, type ɵSelectorInstance } from '@idux/components/_private/selector'
import { useGlobalConfig } from '@idux/components/config'
@@ -16,13 +17,12 @@ import { useFormItemRegister, useFormSize, useFormStatus } from '@idux/component
import { ɵUseOverlayState } from '@idux/components/select'
import { useGetDisabled, useGetKey } from '@idux/components/utils'
-import { useActiveState } from './composables/useActiveState'
import { useDataSource } from './composables/useDataSource'
-import { useExpandable } from './composables/useExpandable'
-import { useSearchable } from './composables/useSearchable'
+import { usePanelProps } from './composables/usePanelProps'
+import { useSelectedData } from './composables/useSelectedData'
import { useSelectedState } from './composables/useSelectedState'
-import OverlayContent from './contents/OverlayContent'
-import { cascaderToken } from './token'
+import Panel from './panel/Panel'
+import { CASCADER_PANEL_DATA_TOKEN } from './token'
import { cascaderProps } from './types'
const defaultOffset: [number, number] = [0, 4]
@@ -38,7 +38,6 @@ export default defineComponent({
const mergedChildrenKey = computed(() => props.childrenKey ?? config.childrenKey)
const mergedClearIcon = computed(() => props.clearIcon ?? config.clearIcon)
- const mergedExpandIcon = computed(() => props.expandIcon ?? config.expandIcon)
const mergedFullPath = computed(() => props.fullPath ?? config.fullPath)
const mergedGetKey = useGetKey(props, config, 'components/cascader')
const mergedGetDisabled = useGetDisabled(props)
@@ -61,33 +60,19 @@ export default defineComponent({
const mergedSize = useFormSize(props, config)
const mergedStatus = useFormStatus(props, control)
- const { mergedData, mergedDataMap } = useDataSource(
- props,
- mergedGetKey,
- mergedChildrenKey,
- mergedLabelKey,
- mergedFullPath,
- )
- const activeStateContext = useActiveState(props, mergedDataMap)
- const selectedStateContext = useSelectedState(props, accessor, mergedDataMap, mergedFullPath, mergedGetDisabled)
- const { searchedData } = useSearchable(
- props,
- mergedData,
+ const dataSourceContext = useDataSource(props, mergedGetKey, mergedChildrenKey, mergedLabelKey, mergedFullPath)
+ const { mergedDataMap } = dataSourceContext
+ const selectedStateContext = useSelectedState(
mergedDataMap,
- mergedLabelKey,
- inputValue,
- mergedGetDisabled,
- )
- const expandableContext = useExpandable(
- props,
- mergedGetKey,
- mergedGetDisabled,
- mergedChildrenKey,
- mergedLabelKey,
mergedFullPath,
- mergedDataMap,
- selectedStateContext.selectedKeys,
+ mergedGetDisabled,
+ toRef(props, 'multiple'),
+ toRef(props, 'strategy'),
+ toRef(accessor, 'value'),
+ keys => accessor.setValue(keys),
)
+ const { resolvedSelectedKeys, setValue } = selectedStateContext
+ const selectedData = useSelectedData(resolvedSelectedKeys, mergedDataMap)
watch(overlayOpened, opened => {
opened && focus()
@@ -105,31 +90,15 @@ export default defineComponent({
focus()
selectedStateContext.handleSelect(key)
}
+ const handleClear = (evt: MouseEvent) => {
+ evt.stopPropagation()
+ setValue([])
+ callEmit(props.onClear, evt)
+ }
- provide(cascaderToken, {
- props,
- slots,
- config,
- mergedPrefixCls,
- mergedChildrenKey,
- mergedClearIcon,
- mergedExpandIcon,
- mergedFullPath,
- mergedGetKey,
- mergedGetDisabled,
- mergedLabelKey,
- accessor,
- inputValue,
- setInputValue,
- overlayOpened,
- setOverlayOpened,
- updateOverlay,
- mergedData,
- mergedDataMap,
- ...activeStateContext,
+ provide(CASCADER_PANEL_DATA_TOKEN, {
+ ...dataSourceContext,
...selectedStateContext,
- searchedData,
- ...expandableContext,
})
const overlayClasses = computed(() => {
@@ -152,9 +121,9 @@ export default defineComponent({
autofocus={props.autofocus}
borderless={props.borderless}
clearable={props.clearable}
- clearIcon={props.clearIcon}
+ clearIcon={mergedClearIcon.value}
config={config}
- dataSource={selectedStateContext.selectedData.value}
+ dataSource={selectedData.value}
disabled={accessor.disabled}
maxLabel={props.maxLabel}
multiple={props.multiple}
@@ -165,12 +134,11 @@ export default defineComponent({
size={mergedSize.value}
status={mergedStatus.value}
suffix={props.suffix}
- value={selectedStateContext.selectedKeys.value}
+ value={resolvedSelectedKeys.value}
onBlur={handleBlur}
- onClear={selectedStateContext.handleClear}
+ onClear={handleClear}
onInputValueChange={setInputValue}
onItemRemove={handleItemRemove}
- //onKeydown={handleKeyDown}
onOpenedChange={setOverlayOpened}
onResize={updateOverlay}
onSearch={props.onSearch}
@@ -178,7 +146,44 @@ export default defineComponent({
/>
)
- const renderContent = () =>
+ const panelProps = usePanelProps(props, setOverlayOpened)
+ const handleSearchInput = (evt: Event) => {
+ const { value } = evt.target as HTMLInputElement
+ setInputValue(value)
+ props.searchable && callEmit(props.onSearch, value)
+ }
+ const handleSearchClear = () => setInputValue('')
+ const renderContent = () => {
+ const { searchable, overlayRender } = props
+ const searchValue = inputValue.value
+ const prefixCls = mergedPrefixCls.value
+ const panelSlots = { empty: slots.empty, optionLabel: slots.optionLabel }
+
+ const children = [
+ ,
+ ]
+
+ if (searchable === 'overlay') {
+ children.unshift(
+
+ <ɵInput
+ clearable
+ clearIcon={mergedClearIcon.value}
+ clearVisible={!!searchValue}
+ size="sm"
+ suffix="search"
+ value={searchValue}
+ onClear={handleSearchClear}
+ onInput={handleSearchInput}
+ />
+
,
+ )
+ }
+
+ return {overlayRender ? overlayRender(children) : children}
+ }
return () => {
const overlayProps = {
diff --git a/packages/components/cascader/src/composables/useActiveState.ts b/packages/components/cascader/src/composables/useActiveState.ts
index f0fc6048d..4efc6ef2b 100644
--- a/packages/components/cascader/src/composables/useActiveState.ts
+++ b/packages/components/cascader/src/composables/useActiveState.ts
@@ -10,7 +10,6 @@ import { type ComputedRef, type Ref, computed } from 'vue'
import { type VKey, useState } from '@idux/cdk/utils'
import { type MergedData } from './useDataSource'
-import { type CascaderProps } from '../types'
export interface ActiveStateContext {
activeKey: Ref
@@ -18,10 +17,7 @@ export interface ActiveStateContext {
setActiveKey: (key: VKey) => void
}
-export function useActiveState(
- props: CascaderProps,
- mergedDataMap: ComputedRef