From e44673f5320a76d9daca61f887a44e377d206815 Mon Sep 17 00:00:00 2001 From: saller Date: Thu, 15 Dec 2022 20:48:45 +0800 Subject: [PATCH] feat(pro:search): search btn triggers search change now (#1321) --- packages/pro/search/src/ProSearch.tsx | 31 ++- .../src/composables/useSearchTrigger.ts | 46 ++++ .../src/composables/useSegmentStates.ts | 14 + .../pro/search/src/searchItem/SearchItem.tsx | 110 ++------ .../search/src/searchItem/SearchItemTag.tsx | 88 ++++++ .../pro/search/src/searchItem/Segment.tsx | 4 - packages/pro/search/src/token.ts | 3 +- packages/pro/search/src/types.ts | 252 ------------------ packages/pro/search/src/types/index.ts | 13 + packages/pro/search/src/types/panels.ts | 51 ++++ packages/pro/search/src/types/proSearch.ts | 62 +++++ packages/pro/search/src/types/searchFields.ts | 90 +++++++ packages/pro/search/src/types/searchItem.ts | 46 ++++ packages/pro/search/src/types/searchValue.ts | 15 ++ packages/pro/search/src/types/segment.ts | 47 ++++ packages/pro/search/style/index.less | 44 ++- 16 files changed, 536 insertions(+), 380 deletions(-) create mode 100644 packages/pro/search/src/composables/useSearchTrigger.ts create mode 100644 packages/pro/search/src/searchItem/SearchItemTag.tsx delete mode 100644 packages/pro/search/src/types.ts create mode 100644 packages/pro/search/src/types/index.ts create mode 100644 packages/pro/search/src/types/panels.ts create mode 100644 packages/pro/search/src/types/proSearch.ts create mode 100644 packages/pro/search/src/types/searchFields.ts create mode 100644 packages/pro/search/src/types/searchItem.ts create mode 100644 packages/pro/search/src/types/searchValue.ts create mode 100644 packages/pro/search/src/types/segment.ts diff --git a/packages/pro/search/src/ProSearch.tsx b/packages/pro/search/src/ProSearch.tsx index d5048c3c8..2bcb2d3f9 100644 --- a/packages/pro/search/src/ProSearch.tsx +++ b/packages/pro/search/src/ProSearch.tsx @@ -18,9 +18,11 @@ import { useCommonOverlayProps } from './composables/useCommonOverlayProps' import { useFocusedState } from './composables/useFocusedState' import { useSearchItems } from './composables/useSearchItem' import { useSearchItemErrors } from './composables/useSearchItemErrors' -import { useSearchStates } from './composables/useSearchStates' +import { tempSearchStateKey, useSearchStates } from './composables/useSearchStates' +import { useSearchTrigger } from './composables/useSearchTrigger' import { useSearchValues } from './composables/useSearchValues' import SearchItemComp from './searchItem/SearchItem' +import SearchItemTagComp from './searchItem/SearchItemTag' import { proSearchContext } from './token' import { type SearchItem, proSearchProps } from './types' import { renderIcon } from './utils/RenderIcon' @@ -47,6 +49,7 @@ export default defineComponent({ errors, dateConfig, ) + const searchTriggerContext = useSearchTrigger() const elementRef = ref() const activeSegmentContext = useActiveSegment( @@ -66,7 +69,7 @@ export default defineComponent({ const currentZIndex = useZIndex(toRef(props, 'zIndex'), toRef(componentCommon, 'overlayZIndex'), focused) - const { initSearchStates, clearSearchState } = searchStateContext + const { initSearchStates, clearSearchState, getSearchStateByKey } = searchStateContext const { activeSegment } = activeSegmentContext watch( @@ -98,8 +101,13 @@ export default defineComponent({ expose({ focus, blur }) - const handleSearchBtnClick = () => { + const { onSearchTrigger, triggerSearch } = searchTriggerContext + onSearchTrigger(() => { callEmit(props.onSearch, searchValues.value) + }, 'post') + + const handleSearchBtnClick = () => { + triggerSearch() } const handleClearBtnClick = () => { clearSearchState() @@ -120,13 +128,26 @@ export default defineComponent({ ...searchStateContext, ...activeSegmentContext, + ...searchTriggerContext, }) return () => { const prefixCls = mergedPrefixCls.value const overflowSlots = { - item: (item: SearchItem) => , + item: (item: SearchItem) => { + const searchState = getSearchStateByKey(item.key)! + + const tagSegments = item.segments.map(segment => { + const segmentValue = searchState.segmentValues.find(sv => sv.name === segment.name)! + return { + name: segment.name, + input: segment.format(segmentValue?.value), + } + }) + + return + }, rest: (rest: SearchItem[]) => ( {slots.overflowedLabel?.(rest) ?? `+ ${rest.length}`} @@ -142,7 +163,7 @@ export default defineComponent({ v-show={!focused.value} v-slots={overflowSlots} prefixCls={prefixCls} - dataSource={searchItems.value} + dataSource={searchItems.value.filter(item => item.key !== tempSearchStateKey)} getKey={item => item.key} maxLabel={props.maxLabel} /> diff --git a/packages/pro/search/src/composables/useSearchTrigger.ts b/packages/pro/search/src/composables/useSearchTrigger.ts new file mode 100644 index 000000000..1c1ba987c --- /dev/null +++ b/packages/pro/search/src/composables/useSearchTrigger.ts @@ -0,0 +1,46 @@ +/** + * @license + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://github.com/IDuxFE/idux/blob/main/LICENSE + */ + +export interface SearchTriggerContext { + triggerSearch: () => Promise + onSearchTrigger: (cb: Handler, enforce?: HandlerEnforce) => void +} + +type HandlerEnforce = 'pre' | 'post' +type Handler = () => void | Promise + +export function useSearchTrigger(): SearchTriggerContext { + const preHandlers: Handler[] = [] + const handlers: Handler[] = [] + const postHandlers: Handler[] = [] + + const onSearchTrigger = (cb: Handler, enforce?: HandlerEnforce) => { + if (enforce === 'pre') { + preHandlers.unshift(cb) + } else if (enforce === 'post') { + postHandlers.push(cb) + } else { + handlers.push(cb) + } + } + + const _invokeHandlers = async (handlers: Handler[]) => { + for (const handler of handlers) { + await handler() + } + } + const triggerSearch = async () => { + await _invokeHandlers(preHandlers) + await _invokeHandlers(handlers) + await _invokeHandlers(postHandlers) + } + + return { + triggerSearch, + onSearchTrigger, + } +} diff --git a/packages/pro/search/src/composables/useSegmentStates.ts b/packages/pro/search/src/composables/useSegmentStates.ts index 92d6a5fc7..8f071599f 100644 --- a/packages/pro/search/src/composables/useSegmentStates.ts +++ b/packages/pro/search/src/composables/useSegmentStates.ts @@ -37,8 +37,10 @@ export function useSegmentStates( removeSearchState, convertStateToValue, initTempSearchState, + activeSegment, changeActive, setInactive, + onSearchTrigger, } = proSearchContext const segmentStates = ref({}) @@ -150,6 +152,18 @@ export function useSegmentStates( } } + onSearchTrigger(() => { + if ( + !props.searchItem?.key || + !activeSegment.value?.itemKey || + activeSegment.value.itemKey !== props.searchItem.key + ) { + return + } + + handleSegmentConfirm(activeSegment.value.name, true) + }, 'pre') + return { segmentStates, initSegmentStates, diff --git a/packages/pro/search/src/searchItem/SearchItem.tsx b/packages/pro/search/src/searchItem/SearchItem.tsx index 223e4a812..0293e2e2b 100644 --- a/packages/pro/search/src/searchItem/SearchItem.tsx +++ b/packages/pro/search/src/searchItem/SearchItem.tsx @@ -7,31 +7,21 @@ import { computed, defineComponent, inject, normalizeClass, provide, watch } from 'vue' -import { IxTooltip } from '@idux/components/tooltip' - import { tempSearchStateKey } from '../composables/useSearchStates' import { useSegmentOverlayUpdate } from '../composables/useSegmentOverlayUpdate' import { useSegmentStates } from '../composables/useSegmentStates' import { proSearchContext, searchItemContext } from '../token' import { searchItemProps } from '../types' -import { renderIcon } from '../utils/RenderIcon' +import SearchItemTag from './SearchItemTag' import Segment from './Segment' export default defineComponent({ props: searchItemProps, setup(props) { const context = inject(proSearchContext)! - const { - props: proSearchProps, - mergedPrefixCls, - activeSegment, - changeActive, - setActiveSegment, - removeSearchState, - } = context + const { props: proSearchProps, mergedPrefixCls, activeSegment } = context const prefixCls = computed(() => `${mergedPrefixCls.value}-search-item`) - const segmentStateContext = useSegmentStates(props, proSearchProps, context) const segmentOverlayUpdateContext = useSegmentOverlayUpdate() const { segmentStates, initSegmentStates } = segmentStateContext @@ -69,58 +59,21 @@ export default defineComponent({ }) }) - const setSegmentActive = (name: string) => { - setActiveSegment({ - itemKey: props.searchItem!.key, - name, - overlayOpened: true, - }) - } - const handleCloseIconClick = (evt: Event) => { - evt.stopPropagation() - removeSearchState(props.searchItem!.key) - } - - const handleTagSegmentMouseDown = (evt: Event, name: string) => { - if (proSearchProps.disabled) { - return - } - - setSegmentActive(name) - - if (name === 'name') { - changeActive(1) - } - } - - const renderTag = () => { - const content = segmentRenderDatas.value.map(data => data.input).join(' ') - - return [ - - {segmentRenderDatas.value.map(data => ( - handleTagSegmentMouseDown(evt, data.name)} - > - {data.input} - - ))} - , - - {content} - , - ] - } - return () => { - const children = [] + if (!isActive.value || proSearchProps.disabled) { + return props.searchItem?.key !== tempSearchStateKey ? ( + + ) : undefined + } - if (!props.tagOnly) { - children.push( - ...segmentRenderDatas.value.map(segment => ( + return ( + evt.preventDefault()}> + {segmentRenderDatas.value.map(segment => ( - )), - ) - } - - if (!isActive.value) { - children.push(...renderTag()) - - if (!proSearchProps.disabled) { - children.push( - - {renderIcon('close')} - , - ) - } - } - - const itemNode = ( - evt.preventDefault()} - > - {children} + ))} ) - - const message = props.searchItem?.error?.message - if (isActive.value || !message) { - return itemNode - } - - return ( - - {itemNode} - - ) } }, }) diff --git a/packages/pro/search/src/searchItem/SearchItemTag.tsx b/packages/pro/search/src/searchItem/SearchItemTag.tsx new file mode 100644 index 000000000..b68ef7a4c --- /dev/null +++ b/packages/pro/search/src/searchItem/SearchItemTag.tsx @@ -0,0 +1,88 @@ +/** + * @license + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://github.com/IDuxFE/idux/blob/main/LICENSE + */ + +import { computed, defineComponent, inject, normalizeClass } from 'vue' + +import { IxTooltip } from '@idux/components/tooltip' + +import { proSearchContext } from '../token' +import { searchItemTagProps } from '../types' +import { renderIcon } from '../utils/RenderIcon' +export default defineComponent({ + props: searchItemTagProps, + setup(props) { + const context = inject(proSearchContext)! + const { props: proSearchProps, mergedPrefixCls, changeActive, setActiveSegment, removeSearchState } = context + + const prefixCls = computed(() => `${mergedPrefixCls.value}-search-item-tag`) + const classes = computed(() => { + return normalizeClass({ + [prefixCls.value]: true, + [`${prefixCls.value}-invalid`]: !!props?.error, + }) + }) + + const setSegmentActive = (name: string) => { + setActiveSegment({ + itemKey: props.itemKey!, + name, + overlayOpened: true, + }) + } + const handleCloseIconClick = (evt: Event) => { + evt.stopPropagation() + removeSearchState(props.itemKey!) + } + + const handleTagSegmentMouseDown = (name: string) => { + if (proSearchProps.disabled) { + return + } + + setSegmentActive(name) + + if (name === 'name') { + changeActive(1) + } + } + + const renderTag = () => { + const content = props.segments!.map(data => data.input).join(' ') + + return [ + + {props.segments!.map(segmeng => ( + handleTagSegmentMouseDown(segmeng.name)}> + {segmeng.input} + + ))} + , + + {content} + , + ] + } + + return () => ( + + evt.preventDefault()}> + {renderTag()} + {!proSearchProps.disabled && ( + + {renderIcon('close')} + + )} + + + ) + }, +}) diff --git a/packages/pro/search/src/searchItem/Segment.tsx b/packages/pro/search/src/searchItem/Segment.tsx index 4b3a595f9..f00f4a6f9 100644 --- a/packages/pro/search/src/searchItem/Segment.tsx +++ b/packages/pro/search/src/searchItem/Segment.tsx @@ -184,10 +184,6 @@ export default defineComponent({ setOnKeyDown: setPanelOnKeyDown, }) - if (!renderedContent && isActive.value) { - setCurrentAsActive(false) - } - return renderedContent } diff --git a/packages/pro/search/src/token.ts b/packages/pro/search/src/token.ts index 86f7f9a5a..00c0f8bb0 100644 --- a/packages/pro/search/src/token.ts +++ b/packages/pro/search/src/token.ts @@ -7,6 +7,7 @@ import type { ActiveSegmentContext } from './composables/useActiveSegment' import type { SearchStateContext } from './composables/useSearchStates' +import type { SearchTriggerContext } from './composables/useSearchTrigger' import type { SegmentOverlayUpdateContext } from './composables/useSegmentOverlayUpdate' import type { SegmentStatesContext } from './composables/useSegmentStates' import type { ProSearchProps } from './types' @@ -14,7 +15,7 @@ import type { ɵOverlayProps } from '@idux/components/_private/overlay' import type { ProSearchLocale } from '@idux/pro/locales' import type { ComputedRef, InjectionKey, Slots } from 'vue' -export interface ProSearchContext extends SearchStateContext, ActiveSegmentContext { +export interface ProSearchContext extends SearchStateContext, ActiveSegmentContext, SearchTriggerContext { props: ProSearchProps slots: Slots locale: ProSearchLocale diff --git a/packages/pro/search/src/types.ts b/packages/pro/search/src/types.ts deleted file mode 100644 index 90386c2e5..000000000 --- a/packages/pro/search/src/types.ts +++ /dev/null @@ -1,252 +0,0 @@ -/** - * @license - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://github.com/IDuxFE/idux/blob/main/LICENSE - */ - -import type { ExtractInnerPropTypes, ExtractPublicPropTypes, MaybeArray, VKey } from '@idux/cdk/utils' -import type { DatePanelProps, DateRangePanelProps } from '@idux/components/date-picker' -import type { SelectData } from '@idux/components/select' -import type { OverlayContainerType } from '@idux/components/utils' -import type { DefineComponent, HTMLAttributes, PropType, VNode, VNodeChild } from 'vue' - -export interface SearchValue { - key: VKey - name?: string - value: V - operator?: string -} - -export interface SearchItemError { - index: number - message?: string -} - -export interface SearchItemConfirmContext extends Partial> { - nameInput?: string - operatorInput?: string - valueInput?: string - removed: boolean -} - -export interface PanelRenderContext { - input: string - value: V - ok: () => void - cancel: () => void - setValue: (value: V) => void - setOnKeyDown: (onKeyDown: ((evt: KeyboardEvent) => boolean) | undefined) => void -} - -export interface Segment { - name: string - inputClassName: string | (string | undefined)[] - defaultValue?: V - format: InputFormater - parse: InputParser - panelRenderer?: (context: PanelRenderContext) => VNodeChild - onVisibleChange?: (visible: boolean) => void -} - -export type InputFormater = (value: V) => string -export type InputParser = (input: string) => V | null - -export interface SearchItem { - key: VKey - optionKey?: VKey - error?: SearchItemError - segments: Segment[] -} - -interface SearchFieldBase { - key: VKey - label: string - multiple?: boolean - operators?: string[] - defaultOperator?: string - defaultValue?: V - inputClassName?: string - validator?: (value: SearchValue) => Omit | undefined - onPanelVisibleChange?: (visible: boolean) => void -} - -export type SelectPanelData = Required> & SelectData - -export const searchDataTypes = ['select', 'input', 'datePicker', 'dateRangePicker', 'custom'] as const -export type SearchDataTypes = typeof searchDataTypes[number] - -export interface SelectSearchField extends SearchFieldBase { - type: 'select' - fieldConfig: { - dataSource: SelectPanelData[] - multiple?: boolean - searchable?: boolean - separator?: string - showSelectAll?: boolean - virtual?: boolean - searchFn?: (data: SelectPanelData, searchText: string) => boolean - overlayItemWidth?: number - } -} - -export interface InputSearchField extends SearchFieldBase { - type: 'input' - fieldConfig: { - trim?: boolean - } -} - -export interface DatePickerSearchField extends SearchFieldBase { - type: 'datePicker' - fieldConfig: { - format?: string - type?: DatePanelProps['type'] - cellTooltip?: DatePanelProps['cellTooltip'] - disabledDate?: DatePanelProps['disabledDate'] - timePanelOptions?: DatePanelProps['timePanelOptions'] - } -} - -export interface DateRangePickerSearchField extends SearchFieldBase { - type: 'dateRangePicker' - fieldConfig: { - format?: string - separator?: string - type?: DateRangePanelProps['type'] - cellTooltip?: DateRangePanelProps['cellTooltip'] - disabledDate?: DateRangePanelProps['disabledDate'] - timePanelOptions?: DateRangePanelProps['timePanelOptions'] - } -} - -export interface CustomSearchField extends SearchFieldBase { - type: 'custom' - fieldConfig: { - customPanel?: string | ((context: PanelRenderContext) => VNodeChild) - format: InputFormater - parse: InputParser - } -} - -export type SearchField = - | SelectSearchField - | InputSearchField - | DatePickerSearchField - | DateRangePickerSearchField - | CustomSearchField - -export const proSearchProps = { - value: Array as PropType, - clearable: { - type: Boolean, - default: undefined, - }, - clearIcon: [String, Object] as PropType, - maxLabel: { type: [Number, String] as PropType, default: 'responsive' }, - searchIcon: [String, Object] as PropType, - disabled: { - type: Boolean, - default: false, - }, - errors: Array as PropType, - overlayContainer: { - type: [String, HTMLElement, Function] as PropType, - default: undefined, - }, - placeholder: String, - searchFields: Array as PropType, - zIndex: Number, - - //events - 'onUpdate:value': [Function, Array] as PropType void>>, - 'onUpdate:errors': [Function, Array] as PropType void>>, - onChange: [Function, Array] as PropType< - MaybeArray<(value: SearchValue[] | undefined, oldValue: SearchValue[] | undefined) => void> - >, - onClear: [Function, Array] as PropType void>>, - onFocus: [Function, Array] as PropType void>>, - onBlur: [Function, Array] as PropType void>>, - onItemRemove: [Array, Function] as PropType void>>, - onSearch: [Array, Function] as PropType void>>, - onItemConfirm: [Array, Function] as PropType void>>, -} as const - -export interface ProSearchBindings { - focus: (options?: FocusOptions) => void - blur: () => void -} - -export type ProSearchProps = ExtractInnerPropTypes -export type ProSearchPublicProps = ExtractPublicPropTypes -export type ProSearchComponent = DefineComponent< - Omit & ProSearchPublicProps, - ProSearchBindings -> -export type ProSearchInstance = InstanceType> - -export const searchItemProps = { - searchItem: { - type: Object as PropType, - required: true, - }, - error: Object as PropType, - tagOnly: { - type: Boolean, - default: false, - }, -} -export type SearchItemProps = ExtractInnerPropTypes - -export const segmentProps = { - itemKey: { - type: [String, Number, Symbol] as PropType, - required: true, - }, - input: String, - value: null, - disabled: Boolean, - segment: { - type: Object as PropType, - required: true, - }, -} as const -export type SegmentProps = ExtractInnerPropTypes - -export const proSearchSelectPanelProps = { - value: { type: Array as PropType, default: undefined }, - dataSource: { type: Array as PropType, default: undefined }, - multiple: { type: Boolean, default: false }, - showSelectAll: { type: Boolean, default: true }, - allSelected: Boolean, - virtual: { type: Boolean, default: false }, - setOnKeyDown: Function as PropType<(onKeyDown: ((evt: KeyboardEvent) => boolean) | undefined) => void>, - - onChange: Function as PropType<(value: VKey[]) => void>, - onSelectAllClick: Function as PropType<() => void>, - onConfirm: Function as PropType<() => void>, - onCancel: Function as PropType<() => void>, -} as const -export type ProSearchSelectPanelProps = ExtractInnerPropTypes - -export const proSearchDatePanelProps = { - panelType: { - type: String as PropType<'datePicker' | 'dateRangePicker'>, - required: true, - }, - value: { type: [Date, Array] as PropType, default: undefined }, - cellTooltip: Function as PropType<(cell: { value: Date; disabled: boolean }) => string | void>, - disabledDate: Function as PropType<(date: Date) => boolean>, - defaultOpenValue: [Date, Array] as PropType, - type: { - type: String as PropType, - default: 'date', - }, - timePanelOptions: [Object, Array] as PropType< - DatePanelProps['timePanelOptions'] | DateRangePanelProps['timePanelOptions'] - >, - onChange: Function as PropType<(value: Date | Date[] | undefined) => void>, - onConfirm: Function as PropType<() => void>, - onCancel: Function as PropType<() => void>, -} as const -export type ProSearchDatePanelProps = ExtractInnerPropTypes diff --git a/packages/pro/search/src/types/index.ts b/packages/pro/search/src/types/index.ts new file mode 100644 index 000000000..3550b988d --- /dev/null +++ b/packages/pro/search/src/types/index.ts @@ -0,0 +1,13 @@ +/** + * @license + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://github.com/IDuxFE/idux/blob/main/LICENSE + */ + +export * from './segment' +export * from './searchItem' +export * from './searchValue' +export * from './searchFields' +export * from './panels' +export * from './proSearch' diff --git a/packages/pro/search/src/types/panels.ts b/packages/pro/search/src/types/panels.ts new file mode 100644 index 000000000..1b21c8d1d --- /dev/null +++ b/packages/pro/search/src/types/panels.ts @@ -0,0 +1,51 @@ +/** + * @license + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://github.com/IDuxFE/idux/blob/main/LICENSE + */ + +import type { ExtractInnerPropTypes, VKey } from '@idux/cdk/utils' +import type { DatePanelProps, DateRangePanelProps } from '@idux/components/date-picker' +import type { SelectData } from '@idux/components/select' +import type { PropType } from 'vue' + +export type SelectPanelData = Required> & SelectData + +export const proSearchSelectPanelProps = { + value: { type: Array as PropType, default: undefined }, + dataSource: { type: Array as PropType, default: undefined }, + multiple: { type: Boolean, default: false }, + showSelectAll: { type: Boolean, default: true }, + allSelected: Boolean, + virtual: { type: Boolean, default: false }, + setOnKeyDown: Function as PropType<(onKeyDown: ((evt: KeyboardEvent) => boolean) | undefined) => void>, + + onChange: Function as PropType<(value: VKey[]) => void>, + onSelectAllClick: Function as PropType<() => void>, + onConfirm: Function as PropType<() => void>, + onCancel: Function as PropType<() => void>, +} as const +export type ProSearchSelectPanelProps = ExtractInnerPropTypes + +export const proSearchDatePanelProps = { + panelType: { + type: String as PropType<'datePicker' | 'dateRangePicker'>, + required: true, + }, + value: { type: [Date, Array] as PropType, default: undefined }, + cellTooltip: Function as PropType<(cell: { value: Date; disabled: boolean }) => string | void>, + disabledDate: Function as PropType<(date: Date) => boolean>, + defaultOpenValue: [Date, Array] as PropType, + type: { + type: String as PropType, + default: 'date', + }, + timePanelOptions: [Object, Array] as PropType< + DatePanelProps['timePanelOptions'] | DateRangePanelProps['timePanelOptions'] + >, + onChange: Function as PropType<(value: Date | Date[] | undefined) => void>, + onConfirm: Function as PropType<() => void>, + onCancel: Function as PropType<() => void>, +} as const +export type ProSearchDatePanelProps = ExtractInnerPropTypes diff --git a/packages/pro/search/src/types/proSearch.ts b/packages/pro/search/src/types/proSearch.ts new file mode 100644 index 000000000..9d10c2443 --- /dev/null +++ b/packages/pro/search/src/types/proSearch.ts @@ -0,0 +1,62 @@ +/** + * @license + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://github.com/IDuxFE/idux/blob/main/LICENSE + */ + +import type { SearchField } from './searchFields' +import type { SearchItemConfirmContext, SearchItemError } from './searchItem' +import type { SearchValue } from './searchValue' +import type { ExtractInnerPropTypes, ExtractPublicPropTypes, MaybeArray } from '@idux/cdk/utils' +import type { OverlayContainerType } from '@idux/components/utils' +import type { DefineComponent, HTMLAttributes, PropType, VNode } from 'vue' + +export const proSearchProps = { + value: Array as PropType, + clearable: { + type: Boolean, + default: undefined, + }, + clearIcon: [String, Object] as PropType, + maxLabel: { type: [Number, String] as PropType, default: 'responsive' }, + searchIcon: [String, Object] as PropType, + disabled: { + type: Boolean, + default: false, + }, + errors: Array as PropType, + overlayContainer: { + type: [String, HTMLElement, Function] as PropType, + default: undefined, + }, + placeholder: String, + searchFields: Array as PropType, + zIndex: Number, + + //events + 'onUpdate:value': [Function, Array] as PropType void>>, + 'onUpdate:errors': [Function, Array] as PropType void>>, + onChange: [Function, Array] as PropType< + MaybeArray<(value: SearchValue[] | undefined, oldValue: SearchValue[] | undefined) => void> + >, + onClear: [Function, Array] as PropType void>>, + onFocus: [Function, Array] as PropType void>>, + onBlur: [Function, Array] as PropType void>>, + onItemRemove: [Array, Function] as PropType void>>, + onSearch: [Array, Function] as PropType void>>, + onItemConfirm: [Array, Function] as PropType void>>, +} as const + +export interface ProSearchBindings { + focus: (options?: FocusOptions) => void + blur: () => void +} + +export type ProSearchProps = ExtractInnerPropTypes +export type ProSearchPublicProps = ExtractPublicPropTypes +export type ProSearchComponent = DefineComponent< + Omit & ProSearchPublicProps, + ProSearchBindings +> +export type ProSearchInstance = InstanceType> diff --git a/packages/pro/search/src/types/searchFields.ts b/packages/pro/search/src/types/searchFields.ts new file mode 100644 index 000000000..bc9cf59bc --- /dev/null +++ b/packages/pro/search/src/types/searchFields.ts @@ -0,0 +1,90 @@ +/** + * @license + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://github.com/IDuxFE/idux/blob/main/LICENSE + */ + +import type { SelectPanelData } from './panels' +import type { SearchItemError } from './searchItem' +import type { SearchValue } from './searchValue' +import type { InputFormater, InputParser, PanelRenderContext } from './segment' +import type { VKey } from '@idux/cdk/utils' +import type { DatePanelProps, DateRangePanelProps } from '@idux/components/date-picker' +import type { VNodeChild } from 'vue' + +interface SearchFieldBase { + key: VKey + label: string + multiple?: boolean + operators?: string[] + defaultOperator?: string + defaultValue?: V + inputClassName?: string + placeholder?: string + validator?: (value: SearchValue) => Omit | undefined + onPanelVisibleChange?: (visible: boolean) => void +} + +export const searchDataTypes = ['select', 'input', 'datePicker', 'dateRangePicker', 'custom'] as const +export type SearchDataTypes = typeof searchDataTypes[number] + +export interface SelectSearchField extends SearchFieldBase { + type: 'select' + fieldConfig: { + dataSource: SelectPanelData[] + multiple?: boolean + searchable?: boolean + separator?: string + showSelectAll?: boolean + virtual?: boolean + searchFn?: (data: SelectPanelData, searchText: string) => boolean + overlayItemWidth?: number + } +} + +export interface InputSearchField extends SearchFieldBase { + type: 'input' + fieldConfig: { + trim?: boolean + } +} + +export interface DatePickerSearchField extends SearchFieldBase { + type: 'datePicker' + fieldConfig: { + format?: string + type?: DatePanelProps['type'] + cellTooltip?: DatePanelProps['cellTooltip'] + disabledDate?: DatePanelProps['disabledDate'] + timePanelOptions?: DatePanelProps['timePanelOptions'] + } +} + +export interface DateRangePickerSearchField extends SearchFieldBase { + type: 'dateRangePicker' + fieldConfig: { + format?: string + separator?: string + type?: DateRangePanelProps['type'] + cellTooltip?: DateRangePanelProps['cellTooltip'] + disabledDate?: DateRangePanelProps['disabledDate'] + timePanelOptions?: DateRangePanelProps['timePanelOptions'] + } +} + +export interface CustomSearchField extends SearchFieldBase { + type: 'custom' + fieldConfig: { + customPanel?: string | ((context: PanelRenderContext) => VNodeChild) + format: InputFormater + parse: InputParser + } +} + +export type SearchField = + | SelectSearchField + | InputSearchField + | DatePickerSearchField + | DateRangePickerSearchField + | CustomSearchField diff --git a/packages/pro/search/src/types/searchItem.ts b/packages/pro/search/src/types/searchItem.ts new file mode 100644 index 000000000..9738d8008 --- /dev/null +++ b/packages/pro/search/src/types/searchItem.ts @@ -0,0 +1,46 @@ +/** + * @license + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://github.com/IDuxFE/idux/blob/main/LICENSE + */ + +import type { SearchValue } from './searchValue' +import type { Segment } from './segment' +import type { ExtractInnerPropTypes, VKey } from '@idux/cdk/utils' +import type { PropType } from 'vue' + +export interface SearchItemError { + index: number + message?: string +} + +export interface SearchItemConfirmContext extends Partial> { + nameInput?: string + operatorInput?: string + valueInput?: string + removed: boolean +} + +export interface SearchItem { + key: VKey + optionKey?: VKey + error?: SearchItemError + segments: Segment[] +} + +export const searchItemProps = { + searchItem: { + type: Object as PropType, + required: true, + }, + error: Object as PropType, +} +export type SearchItemProps = ExtractInnerPropTypes + +export const searchItemTagProps = { + itemKey: { type: [String, Number, Symbol] as PropType, required: true }, + segments: { type: Array as PropType<{ input: string; name: string }[]>, required: true }, + error: Object as PropType, +} +export type SearchItemTagProps = ExtractInnerPropTypes diff --git a/packages/pro/search/src/types/searchValue.ts b/packages/pro/search/src/types/searchValue.ts new file mode 100644 index 000000000..8a90e82bf --- /dev/null +++ b/packages/pro/search/src/types/searchValue.ts @@ -0,0 +1,15 @@ +/** + * @license + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://github.com/IDuxFE/idux/blob/main/LICENSE + */ + +import type { VKey } from '@idux/cdk/utils' + +export interface SearchValue { + key: VKey + name?: string + value: V + operator?: string +} diff --git a/packages/pro/search/src/types/segment.ts b/packages/pro/search/src/types/segment.ts new file mode 100644 index 000000000..eb8809e0b --- /dev/null +++ b/packages/pro/search/src/types/segment.ts @@ -0,0 +1,47 @@ +/** + * @license + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://github.com/IDuxFE/idux/blob/main/LICENSE + */ + +import type { ExtractInnerPropTypes, VKey } from '@idux/cdk/utils' +import type { PropType, VNodeChild } from 'vue' + +export type InputFormater = (value: V) => string +export type InputParser = (input: string) => V | null + +export interface PanelRenderContext { + input: string + value: V + ok: () => void + cancel: () => void + setValue: (value: V) => void + setOnKeyDown: (onKeyDown: ((evt: KeyboardEvent) => boolean) | undefined) => void +} + +export interface Segment { + name: string + inputClassName: string | (string | undefined)[] + placeholder?: string + defaultValue?: V + format: InputFormater + parse: InputParser + panelRenderer?: (context: PanelRenderContext) => VNodeChild + onVisibleChange?: (visible: boolean) => void +} + +export const segmentProps = { + itemKey: { + type: [String, Number, Symbol] as PropType, + required: true, + }, + input: String, + value: null, + disabled: Boolean, + segment: { + type: Object as PropType, + required: true, + }, +} as const +export type SegmentProps = ExtractInnerPropTypes diff --git a/packages/pro/search/style/index.less b/packages/pro/search/style/index.less index c6ae1ae85..96849660e 100644 --- a/packages/pro/search/style/index.less +++ b/packages/pro/search/style/index.less @@ -107,18 +107,16 @@ display: inline-block; max-width: 100%; color: @pro-search-color; - - &:not(&-tag) { - margin-left: @pro-search-item-margin-left; - padding-bottom: @pro-search-item-tag-padding-vertical; - &:first-child { - margin-left: @pro-search-item-margin-left + @pro-search-item-tag-margin-left; - } + margin-left: @pro-search-item-margin-left; + padding-bottom: @pro-search-item-tag-padding-vertical; + &:first-child { + margin-left: @pro-search-item-margin-left + @pro-search-item-tag-margin-left; } &-tag { position: relative; display: inline-flex; + max-width: 100%; align-items: center; height: @pro-search-item-height; padding: @pro-search-item-tag-padding-vertical @pro-search-item-tag-padding-horizontal; @@ -148,25 +146,25 @@ .ellipsis(); } - &.@{pro-search-prefix}-search-item-invalid { - border: 1px solid @pro-search-item-tag-invalid-border-color; + &-close-icon { + display: flex; + align-items: center; + margin-left: @pro-search-close-icon-margin-left; + cursor: pointer; + z-index: 1; } - } - &-close-icon { - display: flex; - align-items: center; - margin-left: @pro-search-close-icon-margin-left; - cursor: pointer; - z-index: 1; - } - - &-invalid-tooltip { - background-color: @pro-search-item-tag-invalid-tooltip-background-color; - color: @pro-search-item-tag-invalid-tooltip-color; + &&-invalid { + border: 1px solid @pro-search-item-tag-invalid-border-color; + } - .@{overlay-prefix}-arrow { - color: @pro-search-item-tag-invalid-tooltip-background-color; + &-invalid-tooltip { + background-color: @pro-search-item-tag-invalid-tooltip-background-color; + color: @pro-search-item-tag-invalid-tooltip-color; + + .@{overlay-prefix}-arrow { + color: @pro-search-item-tag-invalid-tooltip-background-color; + } } } }