diff --git a/packages/renderless/src/autocomplete/index.ts b/packages/renderless/src/autocomplete/index.ts index b40e09a7d..07fd42484 100644 --- a/packages/renderless/src/autocomplete/index.ts +++ b/packages/renderless/src/autocomplete/index.ts @@ -10,8 +10,26 @@ * */ +import type { + IAutoCompleteProps, + IAutoCompleteState, + IAutoCompleteApi, + IAutoCompleteConstants, + IAutoCompleteRenderlessParamUtils +} from '@/types' + export const getData = - ({ props, state, updatePopper, nextTick }) => + ({ + props, + state, + updatePopper, + nextTick + }: { + props: IAutoCompleteProps + state: IAutoCompleteState + updatePopper: (popperElm?: HTMLElement | undefined) => void + nextTick: IAutoCompleteRenderlessParamUtils['nextTick'] + }) => (queryString) => { if (state.suggestionDisabled) { return @@ -19,7 +37,7 @@ export const getData = state.loading = true - props.fetchSuggestions(queryString, (suggestions) => { + props?.fetchSuggestions?.(queryString, (suggestions) => { state.loading = false if (state.suggestionDisabled) { @@ -38,7 +56,17 @@ export const getData = } export const handleChange = - ({ api, emit, state, props }) => + ({ + api, + emit, + state, + props + }: { + api: IAutoCompleteApi + emit: IAutoCompleteRenderlessParamUtils['emit'] + state: IAutoCompleteState + props: IAutoCompleteProps + }) => (value) => { state.activated = true emit('update:modelValue', value) @@ -55,7 +83,17 @@ export const handleChange = } export const handleFocus = - ({ api, emit, props, state }) => + ({ + api, + emit, + props, + state + }: { + api: IAutoCompleteApi + emit: IAutoCompleteRenderlessParamUtils['emit'] + state: IAutoCompleteState + props: IAutoCompleteProps + }) => (event) => { state.activated = true emit('focus', event) @@ -67,25 +105,37 @@ export const handleFocus = } export const handleBlur = - ({ emit, state }) => + ({ emit, state }: { emit: IAutoCompleteRenderlessParamUtils['emit']; state: IAutoCompleteState }) => (event) => { state.suggestionDisabled = true emit('blur', event) } export const handleClear = - ({ emit, state }) => + ({ emit, state }: { emit: IAutoCompleteRenderlessParamUtils['emit']; state: IAutoCompleteState }) => () => { state.activated = false emit('clear') } -export const close = (state) => () => { +export const close = (state: IAutoCompleteState) => () => { state.activated = false } export const handleKeyEnter = - ({ api, emit, nextTick, props, state }) => + ({ + api, + emit, + nextTick, + props, + state + }: { + api: IAutoCompleteApi + emit: IAutoCompleteRenderlessParamUtils['emit'] + nextTick: IAutoCompleteRenderlessParamUtils['nextTick'] + props: IAutoCompleteProps + state: IAutoCompleteState + }) => (event) => { if (state.suggestionVisible && state.highlightedIndex >= 0 && state.highlightedIndex < state.suggestions.length) { event.preventDefault() @@ -101,7 +151,17 @@ export const handleKeyEnter = } export const select = - ({ emit, nextTick, props, state }) => + ({ + emit, + nextTick, + props, + state + }: { + emit: IAutoCompleteRenderlessParamUtils['emit'] + nextTick: IAutoCompleteRenderlessParamUtils['nextTick'] + props: IAutoCompleteProps + state: IAutoCompleteState + }) => (item) => { emit('update:modelValue', item[props.valueKey]) emit('select', item) @@ -114,7 +174,15 @@ export const select = } export const highlight = - ({ constants, refs, state }) => + ({ + constants, + refs, + state + }: { + constants: IAutoCompleteConstants + refs: IAutoCompleteRenderlessParamUtils['refs'] + state: IAutoCompleteState + }) => (index) => { if (!state.suggestionVisible || state.loading) { return @@ -150,7 +218,7 @@ export const highlight = $input.setAttribute('aria-activedescendant', `${state.id}-item-${state.highlightedIndex}`) } -export const computedVisible = (state) => { +export const computedVisible = (state: IAutoCompleteState) => { const suggestions = state.suggestions let isValidData = Array.isArray(suggestions) && suggestions.length > 0 @@ -158,7 +226,13 @@ export const computedVisible = (state) => { } export const watchVisible = - ({ suggestionState, refs }) => + ({ + suggestionState, + refs + }: { + suggestionState: IAutoCompleteApi['suggestionState'] + refs: IAutoCompleteRenderlessParamUtils['refs'] + }) => (val) => { let $input = refs.input.getInput() @@ -169,7 +243,15 @@ export const watchVisible = } export const mounted = - ({ refs, state, suggestionState }) => + ({ + refs, + state, + suggestionState + }: { + refs: IAutoCompleteRenderlessParamUtils['refs'] + state: IAutoCompleteState + suggestionState: IAutoCompleteApi['suggestionState'] + }) => () => { const input = refs.input const $input = input.getInput() diff --git a/packages/renderless/src/autocomplete/vue.ts b/packages/renderless/src/autocomplete/vue.ts index 99586882a..084e81c17 100644 --- a/packages/renderless/src/autocomplete/vue.ts +++ b/packages/renderless/src/autocomplete/vue.ts @@ -12,6 +12,7 @@ import debounce from '../common/deps/debounce' import userPopper from '../common/deps/vue-popper' +import type { Ref } from 'vue' import { guid } from '../common/string' import { computedVisible, @@ -27,6 +28,13 @@ import { select, highlight } from './index' +import type { + IAutoCompleteProps, + IAutoCompleteState, + IAutoCompleteApi, + ISharedRenderlessFunctionParams, + IAutoCompleteRenderlessParamUtils +} from '@/types' export const api = [ 'state', @@ -44,8 +52,16 @@ export const api = [ 'doDestroy' ] -const initState = ({ reactive, $prefix, computed }) => { - const state = reactive({ +const initState = ({ + reactive, + $prefix, + computed +}: { + reactive: ISharedRenderlessFunctionParams['reactive'] + $prefix: string + computed: ISharedRenderlessFunctionParams['computed'] +}) => { + const state = reactive({ activated: false, suggestions: [], loading: false, @@ -55,10 +71,22 @@ const initState = ({ reactive, $prefix, computed }) => { suggestionVisible: computed(() => computedVisible(state)) }) - return state + return state as IAutoCompleteState } -const initSuggestionState = ({ reactive, parent, showPopper, popperElm, referenceElm }) => +export const initSuggestionState = ({ + reactive, + parent, + showPopper, + popperElm, + referenceElm +}: { + reactive: ISharedRenderlessFunctionParams['reactive'] + parent: IAutoCompleteRenderlessParamUtils['parent'] + showPopper: Ref + popperElm: Ref + referenceElm: Ref +}) => reactive({ parent, dropdownWidth: '', @@ -89,11 +117,19 @@ const initApi = ({ api, state, doDestroy, suggestionState, emit, refs, props, up } export const renderless = ( - props, - { computed, onBeforeUnmount, onMounted, reactive, watch, toRefs, onDeactivated }, - { $prefix, refs, parent, emit, constants, nextTick, slots } + props: IAutoCompleteProps, + { + computed, + onBeforeUnmount, + onMounted, + reactive, + watch, + toRefs, + onDeactivated + }: ISharedRenderlessFunctionParams, + { $prefix, refs, parent, emit, constants, nextTick, slots }: IAutoCompleteRenderlessParamUtils ) => { - const api = {} + const api: Partial = {} const state = initState({ reactive, $prefix, computed }) const { showPopper, popperElm, referenceElm, doDestroy, updatePopper } = userPopper({ @@ -107,15 +143,15 @@ export const renderless = ( onBeforeUnmount, toRefs, onDeactivated - }) + } as any) const suggestionState = initSuggestionState({ reactive, parent, showPopper, popperElm, referenceElm }) initApi({ api, state, doDestroy, suggestionState, emit, refs, props, updatePopper, nextTick, constants }) - watch(() => state.suggestionVisible, api.watchVisible) + watch(() => state.suggestionVisible, (api as IAutoCompleteApi).watchVisible) - onMounted(api.mounted) + onMounted((api as IAutoCompleteApi).mounted) return api } diff --git a/packages/renderless/types/autocomplete.type.ts b/packages/renderless/types/autocomplete.type.ts index e69de29bb..460857ad2 100644 --- a/packages/renderless/types/autocomplete.type.ts +++ b/packages/renderless/types/autocomplete.type.ts @@ -0,0 +1,47 @@ +import type { ExtractPropTypes, ComputedRef } from 'vue' +import type { autoCompleteProps } from '@/autocomplete/src' +import type { initSuggestionState } from '../src/autocomplete/vue' +import type { + watchVisible, + mounted, + handleChange, + handleFocus, + handleBlur, + handleClear, + close, + handleKeyEnter, + select, + highlight +} from '../src/autocomplete' +import type { ISharedRenderlessParamUtils } from './shared.type' + +export interface IAutoCompleteState { + activated: boolean + suggestions: unknown[] + loading: boolean + highlightedIndex: number + suggestionDisabled: boolean + id: string + suggestionVisible: ComputedRef +} +export type IAutoCompleteProps = ExtractPropTypes +export interface IAutoCompleteApi { + state: IAutoCompleteState + doDestroy: (forceDestroy?: boolean | undefined) => void + suggestionState: ReturnType + close: ReturnType + handleBlur: ReturnType + mounted: ReturnType + highlight: ReturnType + handleClear: ReturnType + select: ReturnType + watchVisible: ReturnType + handleChange: ReturnType + handleFocus: ReturnType + handleKeyEnter: ReturnType + debouncedGetData: Function +} + +export type IAutoCompleteConstants = ReturnType + +export type IAutoCompleteRenderlessParamUtils = ISharedRenderlessParamUtils diff --git a/packages/vue/src/autocomplete/src/index.ts b/packages/vue/src/autocomplete/src/index.ts index 97c5307fc..4fbfe381b 100644 --- a/packages/vue/src/autocomplete/src/index.ts +++ b/packages/vue/src/autocomplete/src/index.ts @@ -16,68 +16,69 @@ const $constants = { WARP_CLS: '.tiny-autocomplete-suggestion__wrap', ITEM_CLS: '.tiny-autocomplete-suggestion__list li' } +export const autoCompleteProps = { + ...$props, + _constants: { + type: Object, + default: () => $constants + }, + autofocus: Boolean, + clearable: { + type: Boolean, + default: () => false + }, + customItem: String, + debounce: { + type: Number, + default: () => 300 + }, + disabled: Boolean, + fetchSuggestions: Function, + hideLoading: Boolean, + highlightFirstItem: { + type: Boolean, + default: () => false + }, + label: String, + maxlength: Number, + minlength: Number, + modelValue: String, + name: String, + placeholder: String, + placement: { + type: String, + default: () => 'bottom-start' + }, + popperAppendToBody: { + type: Boolean, + default: () => true + }, + popperClass: String, + popperOptions: Object, + prefixIcon: [String, Object], + selectWhenUnmatched: { + type: Boolean, + default: () => false + }, + size: String, + suffixIcon: [String, Object], + triggerOnFocus: { + type: Boolean, + default: () => true + }, + valueKey: { + type: String, + default: () => 'value' + }, + displayOnly: { + type: Boolean, + default: false + } +} export default defineComponent({ name: $prefix + 'Autocomplete', - props: { - ...$props, - _constants: { - type: Object, - default: () => $constants - }, - autofocus: Boolean, - clearable: { - type: Boolean, - default: () => false - }, - customItem: String, - debounce: { - type: Number, - default: () => 300 - }, - disabled: Boolean, - fetchSuggestions: Function, - hideLoading: Boolean, - highlightFirstItem: { - type: Boolean, - default: () => false - }, - label: String, - maxlength: Number, - minlength: Number, - modelValue: String, - name: String, - placeholder: String, - placement: { - type: String, - default: () => 'bottom-start' - }, - popperAppendToBody: { - type: Boolean, - default: () => true - }, - popperClass: String, - popperOptions: Object, - prefixIcon: [String, Object], - selectWhenUnmatched: { - type: Boolean, - default: () => false - }, - size: String, - suffixIcon: [String, Object], - triggerOnFocus: { - type: Boolean, - default: () => true - }, - valueKey: { - type: String, - default: () => 'value' - }, - displayOnly: { - type: Boolean, - default: false - } - }, + props: autoCompleteProps, setup(props, context) { return $setup({ props, context, template }) }