From 4b4fc9266ca302d5637a44c7e6159339b4f786bb Mon Sep 17 00:00:00 2001 From: tangjinzhou <415800467@qq.com> Date: Sat, 3 Oct 2020 15:54:52 +0800 Subject: [PATCH] style: vc-select2 to ts --- .eslintrc | 3 +- components/_util/createRef.js | 8 --- components/_util/createRef.ts | 12 ++++ ...ponsiveObserve.js => responsiveObserve.ts} | 34 +++++---- components/vc-resize-observer/index.jsx | 5 +- .../vc-select2/{OptGroup.jsx => OptGroup.tsx} | 0 .../vc-select2/{Option.jsx => Option.tsx} | 0 .../{OptionList.jsx => OptionList.tsx} | 8 +-- .../{SelectTrigger.jsx => SelectTrigger.tsx} | 71 +++++++++++-------- components/vc-select2/TransBtn.jsx | 41 ----------- components/vc-select2/TransBtn.tsx | 59 +++++++++++++++ components/vc-select2/interface/generator.ts | 64 +++++++++++++++++ components/vc-select2/interface/index.ts | 54 ++++++++++++++ components/vc-trigger/Trigger.jsx | 6 +- 14 files changed, 266 insertions(+), 99 deletions(-) delete mode 100644 components/_util/createRef.js create mode 100644 components/_util/createRef.ts rename components/_util/{responsiveObserve.js => responsiveObserve.ts} (52%) rename components/vc-select2/{OptGroup.jsx => OptGroup.tsx} (100%) rename components/vc-select2/{Option.jsx => Option.tsx} (100%) rename components/vc-select2/{OptionList.jsx => OptionList.tsx} (98%) rename components/vc-select2/{SelectTrigger.jsx => SelectTrigger.tsx} (67%) delete mode 100644 components/vc-select2/TransBtn.jsx create mode 100644 components/vc-select2/TransBtn.tsx create mode 100644 components/vc-select2/interface/generator.ts create mode 100644 components/vc-select2/interface/index.ts diff --git a/.eslintrc b/.eslintrc index 1139ac647..ca4887c21 100644 --- a/.eslintrc +++ b/.eslintrc @@ -31,7 +31,8 @@ "@typescript-eslint/no-explicit-any": 0, "@typescript-eslint/ban-types": 0, "@typescript-eslint/explicit-module-boundary-types": 0, - "@typescript-eslint/no-empty-function": 0 + "@typescript-eslint/no-empty-function": 0, + "@typescript-eslint/no-non-null-assertion": 0 } } ], diff --git a/components/_util/createRef.js b/components/_util/createRef.js deleted file mode 100644 index 9e98baf1a..000000000 --- a/components/_util/createRef.js +++ /dev/null @@ -1,8 +0,0 @@ -function createRef() { - const func = function setRef(node) { - func.current = node; - }; - return func; -} - -export default createRef; diff --git a/components/_util/createRef.ts b/components/_util/createRef.ts new file mode 100644 index 000000000..f8a7a43de --- /dev/null +++ b/components/_util/createRef.ts @@ -0,0 +1,12 @@ +interface RefObject extends Function { + current?: any; +} + +function createRef(): RefObject { + const func: RefObject = (node: any) => { + func.current = node; + }; + return func; +} + +export default createRef; diff --git a/components/_util/responsiveObserve.js b/components/_util/responsiveObserve.ts similarity index 52% rename from components/_util/responsiveObserve.js rename to components/_util/responsiveObserve.ts index 9b6fa3c76..b4b744664 100644 --- a/components/_util/responsiveObserve.js +++ b/components/_util/responsiveObserve.ts @@ -1,6 +1,10 @@ -export const responsiveArray = ['xxl', 'xl', 'lg', 'md', 'sm', 'xs']; +export type Breakpoint = 'xxl' | 'xl' | 'lg' | 'md' | 'sm' | 'xs'; +export type BreakpointMap = Partial>; +export type ScreenMap = Partial>; -export const responsiveMap = { +export const responsiveArray: Breakpoint[] = ['xxl', 'xl', 'lg', 'md', 'sm', 'xs']; + +export const responsiveMap: BreakpointMap = { xs: '(max-width: 575px)', sm: '(min-width: 576px)', md: '(min-width: 768px)', @@ -9,40 +13,46 @@ export const responsiveMap = { xxl: '(min-width: 1600px)', }; -const subscribers = new Map(); +type SubscribeFunc = (screens: ScreenMap) => void; +const subscribers = new Map(); let subUid = -1; let screens = {}; const responsiveObserve = { - matchHandlers: {}, - dispatch(pointMap) { + matchHandlers: {} as { + [prop: string]: { + mql: MediaQueryList; + listener: ((this: MediaQueryList, ev: MediaQueryListEvent) => any) | null; + }; + }, + dispatch(pointMap: ScreenMap) { screens = pointMap; subscribers.forEach(func => func(screens)); return subscribers.size >= 1; }, - subscribe(func) { + subscribe(func: SubscribeFunc): number { if (!subscribers.size) this.register(); subUid += 1; subscribers.set(subUid, func); func(screens); return subUid; }, - unsubscribe(token) { + unsubscribe(token: number) { subscribers.delete(token); if (!subscribers.size) this.unregister(); }, unregister() { - Object.keys(responsiveMap).forEach(screen => { - const matchMediaQuery = responsiveMap[screen]; + Object.keys(responsiveMap).forEach((screen: Breakpoint) => { + const matchMediaQuery = responsiveMap[screen]!; const handler = this.matchHandlers[matchMediaQuery]; handler?.mql.removeListener(handler?.listener); }); subscribers.clear(); }, register() { - Object.keys(responsiveMap).forEach(screen => { - const matchMediaQuery = responsiveMap[screen]; - const listener = ({ matches }) => { + Object.keys(responsiveMap).forEach((screen: Breakpoint) => { + const matchMediaQuery = responsiveMap[screen]!; + const listener = ({ matches }: { matches: boolean }) => { this.dispatch({ ...screens, [screen]: matches, diff --git a/components/vc-resize-observer/index.jsx b/components/vc-resize-observer/index.jsx index 3f7a8d822..640fa69bd 100644 --- a/components/vc-resize-observer/index.jsx +++ b/components/vc-resize-observer/index.jsx @@ -1,10 +1,11 @@ // based on rc-resize-observer 0.1.3 import ResizeObserver from 'resize-observer-polyfill'; +import { defineComponent } from 'vue'; import BaseMixin from '../_util/BaseMixin'; import { findDOMNode } from '../_util/props-util'; // Still need to be compatible with React 15, we use class component here -const VueResizeObserver = { +const VueResizeObserver = defineComponent({ name: 'ResizeObserver', mixins: [BaseMixin], props: { @@ -84,6 +85,6 @@ const VueResizeObserver = { render() { return this.$slots.default && this.$slots.default()[0]; }, -}; +}); export default VueResizeObserver; diff --git a/components/vc-select2/OptGroup.jsx b/components/vc-select2/OptGroup.tsx similarity index 100% rename from components/vc-select2/OptGroup.jsx rename to components/vc-select2/OptGroup.tsx diff --git a/components/vc-select2/Option.jsx b/components/vc-select2/Option.tsx similarity index 100% rename from components/vc-select2/Option.jsx rename to components/vc-select2/Option.tsx diff --git a/components/vc-select2/OptionList.jsx b/components/vc-select2/OptionList.tsx similarity index 98% rename from components/vc-select2/OptionList.jsx rename to components/vc-select2/OptionList.tsx index d526affc3..975f50c78 100644 --- a/components/vc-select2/OptionList.jsx +++ b/components/vc-select2/OptionList.tsx @@ -5,7 +5,7 @@ import classNames from '../_util/classNames'; import pickAttrs from '../_util/pickAttrs'; import { isValidElement } from '../_util/props-util'; import createRef from '../_util/createRef'; -import { computed, reactive, watch } from 'vue'; +import { computed, defineComponent, reactive, watch } from 'vue'; import List from '../vc-virtual-list/List'; const OptionListProps = { @@ -39,7 +39,7 @@ const OptionListProps = { * Using virtual list of option display. * Will fallback to dom if use customize render. */ -const OptionList = { +const OptionList = defineComponent({ props: OptionListProps, name: 'OptionList', inheritAttrs: false, @@ -138,7 +138,7 @@ const OptionList = { const mergedLabel = props.childrenAsData ? children : label; return item ? (
); }, -}; +}); export default OptionList; diff --git a/components/vc-select2/SelectTrigger.jsx b/components/vc-select2/SelectTrigger.tsx similarity index 67% rename from components/vc-select2/SelectTrigger.jsx rename to components/vc-select2/SelectTrigger.tsx index 863d3abde..88fe04cca 100644 --- a/components/vc-select2/SelectTrigger.jsx +++ b/components/vc-select2/SelectTrigger.tsx @@ -3,8 +3,10 @@ import PropTypes from '../_util/vue-types'; import { getSlot } from '../_util/props-util'; import classNames from '../_util/classNames'; import createRef from '../_util/createRef'; +import { CSSProperties, defineComponent, VNodeChild } from 'vue'; +import { RenderDOMFunc } from './interface'; -const getBuiltInPlacements = dropdownMatchSelectWidth => { +const getBuiltInPlacements = (dropdownMatchSelectWidth: number | boolean) => { // Enable horizontal overflow auto-adjustment when a custom dropdown width is provided const adjustX = typeof dropdownMatchSelectWidth !== 'number' ? 0 : 1; @@ -43,33 +45,27 @@ const getBuiltInPlacements = dropdownMatchSelectWidth => { }, }; }; - -export default { +export interface SelectTriggerProps { + prefixCls: string; + disabled: boolean; + visible: boolean; + popupElement: VNodeChild; + animation?: string; + transitionName?: string; + containerWidth: number; + dropdownStyle: CSSProperties; + dropdownClassName: string; + direction: string; + dropdownMatchSelectWidth?: boolean | number; + dropdownRender?: (menu: VNodeChild) => VNodeChild; + getPopupContainer?: RenderDOMFunc; + dropdownAlign: object; + empty: boolean; + getTriggerDOMNode: () => HTMLElement; +} +const SelectTrigger = defineComponent({ name: 'SelectTrigger', inheritAttrs: false, - props: { - // onPopupFocus: PropTypes.func, - // onPopupScroll: PropTypes.func, - dropdownAlign: PropTypes.object, - visible: PropTypes.bool, - disabled: PropTypes.bool, - dropdownClassName: PropTypes.string, - dropdownStyle: PropTypes.object, - empty: PropTypes.bool, - prefixCls: PropTypes.string, - popupClassName: PropTypes.string, - // children: PropTypes.any, - animation: PropTypes.string, - transitionName: PropTypes.string, - getPopupContainer: PropTypes.func, - dropdownRender: PropTypes.func, - containerWidth: PropTypes.number, - dropdownMatchSelectWidth: PropTypes.oneOfType([Number, Boolean]).def(true), - popupElement: PropTypes.any, - direction: PropTypes.string, - getTriggerDOMNode: PropTypes.func, - }, - created() { this.popupRef = createRef(); }, @@ -89,7 +85,7 @@ export default { }, render() { - const { empty, ...props } = { ...this.$props, ...this.$attrs }; + const { empty = false, ...props } = { ...this.$props, ...this.$attrs }; const { visible, dropdownAlign, @@ -122,7 +118,6 @@ export default { showAction={[]} hideAction={[]} popupPlacement={this.direction === 'rtl' ? 'bottomRight' : 'bottomLeft'} - popupPlacement="bottomLeft" builtinPlacements={builtInPlacements} prefixCls={dropdownPrefixCls} popupTransitionName={this.getDropdownTransitionName()} @@ -141,4 +136,24 @@ export default { ); }, +}); +SelectTrigger.props = { + dropdownAlign: PropTypes.object, + visible: PropTypes.bool, + disabled: PropTypes.bool, + dropdownClassName: PropTypes.string, + dropdownStyle: PropTypes.object, + empty: PropTypes.bool, + prefixCls: PropTypes.string, + popupClassName: PropTypes.string, + animation: PropTypes.string, + transitionName: PropTypes.string, + getPopupContainer: PropTypes.func, + dropdownRender: PropTypes.func, + containerWidth: PropTypes.number, + dropdownMatchSelectWidth: PropTypes.oneOfType([Number, Boolean]).def(true), + popupElement: PropTypes.any, + direction: PropTypes.string, + getTriggerDOMNode: PropTypes.func, }; +export default SelectTrigger; diff --git a/components/vc-select2/TransBtn.jsx b/components/vc-select2/TransBtn.jsx deleted file mode 100644 index 47bfd4740..000000000 --- a/components/vc-select2/TransBtn.jsx +++ /dev/null @@ -1,41 +0,0 @@ -const TransBtn = ( - _, - { attrs: { class: className, customizeIcon, customizeIconProps, onMousedown, onClick }, slots }, -) => { - let icon; - - if (typeof customizeIcon === 'function') { - icon = customizeIcon(customizeIconProps); - } else { - icon = customizeIcon; - } - - return ( - { - event.preventDefault(); - if (onMousedown) { - onMousedown(event); - } - }} - style={{ - userSelect: 'none', - WebkitUserSelect: 'none', - }} - unselectable="on" - onClick={onClick} - aria-hidden - > - {icon !== undefined ? ( - icon - ) : ( - `${cls}-icon`)}>{slots?.default()} - )} - - ); -}; - -TransBtn.inheritAttrs = false; - -export default TransBtn; diff --git a/components/vc-select2/TransBtn.tsx b/components/vc-select2/TransBtn.tsx new file mode 100644 index 000000000..2d37983b7 --- /dev/null +++ b/components/vc-select2/TransBtn.tsx @@ -0,0 +1,59 @@ +import { SetupContext, VNodeChild } from 'vue'; +import PropTypes from '../_util/vue-types'; + +export interface TransBtnProps { + class: string; + customizeIcon: VNodeChild | ((props?: any) => VNodeChild); + customizeIconProps?: any; + onMousedown?: (payload: MouseEvent) => void; + onClick?: (payload: MouseEvent) => void; +} + +const TransBtn = (props: TransBtnProps, { slots }: SetupContext) => { + const { class: className, customizeIcon, customizeIconProps, onMousedown, onClick } = props; + let icon: VNodeChild; + + if (typeof customizeIcon === 'function') { + icon = customizeIcon(customizeIconProps); + } else { + icon = customizeIcon; + } + + return ( + { + event.preventDefault(); + if (onMousedown) { + onMousedown(event); + } + }} + style={{ + userSelect: 'none', + WebkitUserSelect: 'none', + }} + unselectable="on" + onClick={onClick} + aria-hidden + > + {icon !== undefined ? ( + icon + ) : ( + `${cls}-icon`)}> + {slots.default && slots.default()} + + )} + + ); +}; + +TransBtn.inheritAttrs = false; +TransBtn.props = { + class: PropTypes.string, + customizeIcon: PropTypes.any, + customizeIconProps: PropTypes.any, + onMousedown: PropTypes.func, + onClick: PropTypes.func, +}; + +export default TransBtn; diff --git a/components/vc-select2/interface/generator.ts b/components/vc-select2/interface/generator.ts new file mode 100644 index 000000000..12a4b9a83 --- /dev/null +++ b/components/vc-select2/interface/generator.ts @@ -0,0 +1,64 @@ +import * as Vue from 'vue'; +import { SelectProps, RefSelectProps } from '../generate'; + +export type SelectSource = 'option' | 'selection' | 'input'; + +export const INTERNAL_PROPS_MARK = 'RC_SELECT_INTERNAL_PROPS_MARK'; + +// =================================== Shared Type =================================== +export type Key = string | number; + +export type RawValueType = string | number; + +export interface LabelValueType { + key?: Key; + value?: RawValueType; + label?: Vue.VNodeChild; +} +export type DefaultValueType = RawValueType | RawValueType[] | LabelValueType | LabelValueType[]; + +export interface DisplayLabelValueType extends LabelValueType { + disabled?: boolean; +} + +export type SingleType = MixType extends (infer Single)[] ? Single : MixType; + +export type OnClear = () => void; + +export type CustomTagProps = { + label: DefaultValueType; + value: DefaultValueType; + disabled: boolean; + onClose: (event?: MouseEvent) => void; + closable: boolean; +}; + +// ==================================== Generator ==================================== +export type GetLabeledValue = ( + value: RawValueType, + config: { + options: FOT; + prevValue: DefaultValueType; + labelInValue: boolean; + optionLabelProp: string; + }, +) => LabelValueType; + +export type FilterOptions = ( + searchValue: string, + options: OptionsType, + /** Component props, since Select & TreeSelect use different prop name, use any here */ + config: { + optionFilterProp: string; + filterOption: boolean | FilterFunc; + }, +) => OptionsType; + +export type FilterFunc = (inputValue: string, option?: OptionType) => boolean; + +export type FlattenOptionsType = { + key: Key; + data: OptionsType[number]; + /** Used for customize data */ + [name: string]: any; // eslint-disable-line @typescript-eslint/no-explicit-any +}[]; diff --git a/components/vc-select2/interface/index.ts b/components/vc-select2/interface/index.ts new file mode 100644 index 000000000..85d475212 --- /dev/null +++ b/components/vc-select2/interface/index.ts @@ -0,0 +1,54 @@ +import * as Vue from 'vue'; +import { Key, RawValueType } from './generator'; + +export type RenderDOMFunc = (props: any) => HTMLElement; + +export type RenderNode = Vue.VNodeChild | ((props: any) => Vue.VNodeChild); + +export type Mode = 'multiple' | 'tags' | 'combobox'; + +// ======================== Option ======================== +export type OnActiveValue = ( + active: RawValueType, + index: number, + info?: { source?: 'keyboard' | 'mouse' }, +) => void; + +export interface OptionCoreData { + key?: Key; + disabled?: boolean; + value: Key; + title?: string; + className?: string; + class?: string; + style?: Vue.CSSProperties; + label?: Vue.VNodeChild; + /** @deprecated Only works when use `children` as option data */ + children?: Vue.VNodeChild; +} + +export interface OptionData extends OptionCoreData { + /** Save for customize data */ + [prop: string]: any; // eslint-disable-line @typescript-eslint/no-explicit-any +} + +export interface OptionGroupData { + key?: Key; + label?: Vue.VNodeChild; + options: OptionData[]; + className?: string; + class?: string; + style?: Vue.CSSProperties; + + /** Save for customize data */ + [prop: string]: any; // eslint-disable-line @typescript-eslint/no-explicit-any +} + +export type OptionsType = (OptionData | OptionGroupData)[]; + +export interface FlattenOptionData { + group?: boolean; + groupOption?: boolean; + key: string | number; + data: OptionData | OptionGroupData; +} diff --git a/components/vc-trigger/Trigger.jsx b/components/vc-trigger/Trigger.jsx index 1dc384973..ee42820e2 100644 --- a/components/vc-trigger/Trigger.jsx +++ b/components/vc-trigger/Trigger.jsx @@ -1,4 +1,4 @@ -import { inject, provide } from 'vue'; +import { defineComponent, inject, provide } from 'vue'; import PropTypes from '../_util/vue-types'; import contains from '../vc-util/Dom/contains'; import { @@ -37,7 +37,7 @@ const ALL_HANDLERS = [ 'onContextmenu', ]; -export default { +export default defineComponent({ name: 'Trigger', mixins: [BaseMixin], inheritAttrs: false, @@ -653,4 +653,4 @@ export default { } return [portal, trigger]; }, -}; +});