From b19ca0aaafb1f76ecb62fced60aa793fe3b5723c Mon Sep 17 00:00:00 2001 From: Amour1688 Date: Thu, 1 Oct 2020 17:19:53 +0800 Subject: [PATCH 1/4] fix: tag --- components/tag/index.tsx | 40 ++++++++++++++++++---------------------- 1 file changed, 18 insertions(+), 22 deletions(-) diff --git a/components/tag/index.tsx b/components/tag/index.tsx index 8a0d564d4..e321ecc03 100644 --- a/components/tag/index.tsx +++ b/components/tag/index.tsx @@ -5,7 +5,6 @@ import { defineComponent, SetupContext, App, - watchEffect, VNodeTypes, CSSProperties, } from 'vue'; @@ -44,29 +43,8 @@ const Tag = defineComponent({ const { getPrefixCls } = inject('configProvider', defaultConfigProvider); const visible = ref(true); - const props = attrs as TagProps; - watchEffect(() => { - if ('visible' in props) { - visible.value = props.visible!; - } - }); - - const handleCloseClick = (e: MouseEvent) => { - e.stopPropagation(); - if (props.onClose) { - props.onClose(e); - } - - if (e.defaultPrevented) { - return; - } - if (!('visible' in props)) { - visible.value = false; - } - }; - return () => { const { prefixCls: customizePrefixCls, @@ -79,6 +57,24 @@ const Tag = defineComponent({ ...restProps } = props; + if ('visible' in props) { + visible.value = props.visible!; + } + + const handleCloseClick = (e: MouseEvent) => { + e.stopPropagation(); + if (props.onClose) { + props.onClose(e); + } + + if (e.defaultPrevented) { + return; + } + if (!('visible' in props)) { + visible.value = false; + } + }; + const isPresetColor = (): boolean => { if (!color) { return false; From 150ebf15a565ee642d11a883fa828598937cf296 Mon Sep 17 00:00:00 2001 From: Amour1688 Date: Thu, 1 Oct 2020 17:20:10 +0800 Subject: [PATCH 2/4] chore: refactor virtual list --- components/_util/BaseMixin.ts | 2 +- components/_util/createRef.js | 8 - components/_util/createRef.ts | 18 +++ components/_util/type.ts | 4 +- components/_util/vue-types/index.ts | 2 +- .../{index.jsx => index.tsx} | 17 ++- .../{Filler.jsx => Filler.tsx} | 26 ++-- components/vc-virtual-list/Item.jsx | 17 --- components/vc-virtual-list/Item.tsx | 17 +++ .../vc-virtual-list/{List.jsx => List.tsx} | 143 +++++++++++------- .../{ScrollBar.jsx => ScrollBar.tsx} | 55 +++---- .../{useFrameWheel.js => useFrameWheel.ts} | 23 ++- .../hooks/{useHeights.jsx => useHeights.tsx} | 25 +-- ...bileTouchMove.js => useMobileTouchMove.ts} | 41 ++--- ...{useOriginScroll.js => useOriginScroll.ts} | 8 +- .../{useScrollTo.jsx => useScrollTo.tsx} | 40 ++--- .../vc-virtual-list/{index.js => index.ts} | 0 components/vc-virtual-list/interface.ts | 14 ++ .../utils/{isFirefox.js => isFirefox.ts} | 0 19 files changed, 274 insertions(+), 186 deletions(-) delete mode 100644 components/_util/createRef.js create mode 100644 components/_util/createRef.ts rename components/vc-resize-observer/{index.jsx => index.tsx} (85%) rename components/vc-virtual-list/{Filler.jsx => Filler.tsx} (77%) delete mode 100644 components/vc-virtual-list/Item.jsx create mode 100644 components/vc-virtual-list/Item.tsx rename components/vc-virtual-list/{List.jsx => List.tsx} (71%) rename components/vc-virtual-list/{ScrollBar.jsx => ScrollBar.tsx} (87%) rename components/vc-virtual-list/hooks/{useFrameWheel.js => useFrameWheel.ts} (64%) rename components/vc-virtual-list/hooks/{useHeights.jsx => useHeights.tsx} (58%) rename components/vc-virtual-list/hooks/{useMobileTouchMove.js => useMobileTouchMove.ts} (59%) rename components/vc-virtual-list/hooks/{useOriginScroll.js => useOriginScroll.ts} (78%) rename components/vc-virtual-list/hooks/{useScrollTo.jsx => useScrollTo.tsx} (70%) rename components/vc-virtual-list/{index.js => index.ts} (100%) create mode 100644 components/vc-virtual-list/interface.ts rename components/vc-virtual-list/utils/{isFirefox.js => isFirefox.ts} (100%) diff --git a/components/_util/BaseMixin.ts b/components/_util/BaseMixin.ts index bb3a786fe..18756b0fc 100644 --- a/components/_util/BaseMixin.ts +++ b/components/_util/BaseMixin.ts @@ -2,7 +2,7 @@ import { getOptionProps } from './props-util'; export default { methods: { - setState(state = {}, callback: () => any) { + setState(state = {}, callback: () => void) { let newState = typeof state === 'function' ? state(this.$data, this.$props) : state; if (this.getDerivedStateFromProps) { const s = this.getDerivedStateFromProps(getOptionProps(this), { 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..441b57042 --- /dev/null +++ b/components/_util/createRef.ts @@ -0,0 +1,18 @@ +interface RefObject { + readonly current: T | null; +} +function createRef() { + // const refObject = { + // current: null + // } + + // Object.seal(refObject); + + // return refObject; + function setRef(node: T) { + Object.assign(setRef, { current: node }); + } + return setRef; +} + +export default createRef; diff --git a/components/_util/type.ts b/components/_util/type.ts index 4d981b16b..1e8b9167b 100644 --- a/components/_util/type.ts +++ b/components/_util/type.ts @@ -1,4 +1,4 @@ -import { PropType } from 'vue'; +import { PropType, VNodeProps } from 'vue'; export type Omit = Pick>; // https://stackoverflow.com/questions/46176165/ways-to-get-string-literal-type-of-array-values-without-enum-overhead @@ -25,6 +25,8 @@ export type EventHandlers = { export type Data = Record; +export type Key = string | number; + export declare type DefaultFactory = (props: Data) => T | null | undefined; export declare interface PropOptions { type?: PropType | true | null; diff --git a/components/_util/vue-types/index.ts b/components/_util/vue-types/index.ts index e4121ebc8..04533b9cd 100644 --- a/components/_util/vue-types/index.ts +++ b/components/_util/vue-types/index.ts @@ -16,7 +16,7 @@ const PropTypes = { get func() { return { type: Function, - } as BaseTypes; + }; }, get bool() { diff --git a/components/vc-resize-observer/index.jsx b/components/vc-resize-observer/index.tsx similarity index 85% rename from components/vc-resize-observer/index.jsx rename to components/vc-resize-observer/index.tsx index 3f7a8d822..8c5403e14 100644 --- a/components/vc-resize-observer/index.jsx +++ b/components/vc-resize-observer/index.tsx @@ -1,19 +1,24 @@ // based on rc-resize-observer 0.1.3 +import { defineComponent, PropType } from 'vue'; import ResizeObserver from 'resize-observer-polyfill'; 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: { disabled: Boolean, - onResize: Function, + onResize: Function as PropType< + (size: { width: number; height: number; offsetWidth: number; offsetHeight: number }) => void + >, }, - data() { + beforeCreate() { this.currentElement = null; this.resizeObserver = null; + }, + data() { return { width: 0, height: 0, @@ -54,7 +59,7 @@ const VueResizeObserver = { } }, - handleResize(entries) { + handleResize(entries: ResizeObserverEntry[]) { const { target } = entries[0]; const { width, height } = target.getBoundingClientRect(); /** @@ -82,8 +87,8 @@ const VueResizeObserver = { }, render() { - return this.$slots.default && this.$slots.default()[0]; + return this.$slots.default?.()[0]; }, -}; +}); export default VueResizeObserver; diff --git a/components/vc-virtual-list/Filler.jsx b/components/vc-virtual-list/Filler.tsx similarity index 77% rename from components/vc-virtual-list/Filler.jsx rename to components/vc-virtual-list/Filler.tsx index ad1ba70d3..3add1a86b 100644 --- a/components/vc-virtual-list/Filler.jsx +++ b/components/vc-virtual-list/Filler.tsx @@ -1,10 +1,23 @@ import classNames from '../_util/classNames'; import ResizeObserver from '../vc-resize-observer'; +import { CSSProperties, FunctionalComponent } from 'vue'; -const Filter = ({ height, offset, prefixCls, onInnerResize }, { slots }) => { +interface FillerProps { + prefixCls?: string; + /** Virtual filler height. Should be `count * itemMinHeight` */ + height: number; + /** Set offset of visible items. Should be the top of start item position */ + offset?: number; + onInnerResize?: () => void; +} + +const Filter: FunctionalComponent = ( + { height, offset, prefixCls, onInnerResize }, + { slots }, +) => { let outerStyle = {}; - let innerStyle = { + let innerStyle: CSSProperties = { display: 'flex', flexDirection: 'column', }; @@ -43,15 +56,8 @@ const Filter = ({ height, offset, prefixCls, onInnerResize }, { slots }) => { ); }; + Filter.displayName = 'Filter'; Filter.inheritAttrs = false; -Filter.props = { - prefixCls: String, - /** Virtual filler height. Should be `count * itemMinHeight` */ - height: Number, - /** Set offset of visible items. Should be the top of start item position */ - offset: Number, - onInnerResize: Function, -}; export default Filter; diff --git a/components/vc-virtual-list/Item.jsx b/components/vc-virtual-list/Item.jsx deleted file mode 100644 index c36c23b0a..000000000 --- a/components/vc-virtual-list/Item.jsx +++ /dev/null @@ -1,17 +0,0 @@ -import { cloneVNode } from 'vue'; - -function Item({ setRef }, { slots }) { - const children = slots?.default(); - return children && children.length - ? cloneVNode(children[0], { - ref: setRef, - }) - : children; -} -Item.props = { - setRef: { - type: Function, - default: () => {}, - }, -}; -export default Item; diff --git a/components/vc-virtual-list/Item.tsx b/components/vc-virtual-list/Item.tsx new file mode 100644 index 000000000..7350b37fa --- /dev/null +++ b/components/vc-virtual-list/Item.tsx @@ -0,0 +1,17 @@ +import { cloneVNode, FunctionalComponent } from 'vue'; + +export interface ItemProps { + setRef: (element: HTMLElement) => void; +} + +const Item: FunctionalComponent = ({ setRef }, { slots }) => { + const children = slots.default?.(); + + return children && children.length + ? cloneVNode(children[0], { + ref: setRef, + }) + : children; +}; + +export default Item; diff --git a/components/vc-virtual-list/List.jsx b/components/vc-virtual-list/List.tsx similarity index 71% rename from components/vc-virtual-list/List.jsx rename to components/vc-virtual-list/List.tsx index 95a666db1..eceb50063 100644 --- a/components/vc-virtual-list/List.jsx +++ b/components/vc-virtual-list/List.tsx @@ -1,3 +1,16 @@ +import { + ref, + defineComponent, + PropType, + watchEffect, + Component, + computed, + nextTick, + onBeforeUnmount, + reactive, + CSSProperties, +} from 'vue'; +import { Key } from '../_util/type'; import Filler from './Filler'; import Item from './Item'; import ScrollBar from './ScrollBar'; @@ -7,18 +20,24 @@ import useFrameWheel from './hooks/useFrameWheel'; import useMobileTouchMove from './hooks/useMobileTouchMove'; import useOriginScroll from './hooks/useOriginScroll'; import PropTypes from '../_util/vue-types'; -import { computed, nextTick, onBeforeUnmount, reactive, watchEffect } from 'vue'; import classNames from '../_util/classNames'; -import createRef from '../_util/createRef'; +import { RenderFunc, SharedConfig } from './interface'; const EMPTY_DATA = []; -const ScrollStyle = { +const ScrollStyle: CSSProperties = { overflowY: 'auto', overflowAnchor: 'none', }; -function renderChildren(list, startIndex, endIndex, setNodeRef, renderFunc, { getKey }) { +function renderChildren( + list: T[], + startIndex: number, + endIndex: number, + setNodeRef: (item: T, element: HTMLElement) => void, + renderFunc: RenderFunc, + { getKey }: SharedConfig, +) { return list.slice(startIndex, endIndex + 1).map((item, index) => { const eleIndex = startIndex + index; const node = renderFunc(item, eleIndex, { @@ -33,43 +52,58 @@ function renderChildren(list, startIndex, endIndex, setNodeRef, renderFunc, { ge }); } -const ListProps = { - prefixCls: PropTypes.string, - data: PropTypes.array, - height: PropTypes.number, - itemHeight: PropTypes.number, - /** If not match virtual scroll condition, Set List still use height of container. */ - fullHeight: PropTypes.bool.def(true), - itemKey: PropTypes.any, - component: PropTypes.any, - /** Set `false` will always use real scroll instead of virtual one */ - virtual: PropTypes.bool, - children: PropTypes.func, - onScroll: PropTypes.func, -}; +export interface ListState { + scrollTop: number; + scrollMoving: boolean; + mergedData: T[]; +} -const List = { - props: ListProps, +const List = defineComponent({ inheritAttrs: false, name: 'List', + props: { + prefixCls: PropTypes.string, + data: PropTypes.array, + height: PropTypes.number, + itemHeight: PropTypes.number, + /** If not match virtual scroll condition, Set List still use height of container. */ + fullHeight: PropTypes.bool, + itemKey: { + type: [String, Number, Function] as PropType Key)>, + required: true, + }, + component: { + type: [String, Object] as PropType, + }, + /** Set `false` will always use real scroll instead of virtual one */ + virtual: PropTypes.bool, + children: PropTypes.func, + onScroll: PropTypes.func, + }, setup(props) { // ================================= MISC ================================= const inVirtual = computed(() => { const { height, itemHeight, data, virtual } = props; - return virtual !== false && height && itemHeight && data && itemHeight * data.length > height; + return !!( + virtual !== false && + height && + itemHeight && + data && + itemHeight * data.length > height + ); }); - const state = reactive({ + const state = reactive({ scrollTop: 0, scrollMoving: false, - mergedData: computed(() => props.data || EMPTY_DATA), + mergedData: computed(() => props.data || EMPTY_DATA) as any, }); - const componentRef = createRef(); + const componentRef = ref(); // =============================== Item Key =============================== - const getKey = item => { + const getKey = (item: Record) => { if (typeof props.itemKey === 'function') { return props.itemKey(item); } @@ -81,8 +115,8 @@ const List = { }; // ================================ Scroll ================================ - function syncScrollTop(newTop) { - let value; + function syncScrollTop(newTop: number | ((prev: number) => number)) { + let value: number; if (typeof newTop === 'function') { value = newTop(state.scrollTop); } else { @@ -91,8 +125,8 @@ const List = { const alignedTop = keepInRange(value); - if (componentRef.current) { - componentRef.current.scrollTop = alignedTop; + if (componentRef.value) { + componentRef.value.scrollTop = alignedTop; } state.scrollTop = alignedTop; @@ -112,9 +146,9 @@ const List = { }; } let itemTop = 0; - let startIndex; - let startOffset; - let endIndex; + let startIndex: number | undefined; + let startOffset: number | undefined; + let endIndex: number | undefined; const dataLen = state.mergedData.length; for (let i = 0; i < dataLen; i += 1) { const item = state.mergedData[i]; @@ -122,7 +156,7 @@ const List = { const cacheHeight = heights[key]; const currentItemBottom = - itemTop + (cacheHeight === undefined ? props.itemHeight : cacheHeight); + itemTop + (cacheHeight === undefined ? props.itemHeight! : cacheHeight); if (currentItemBottom >= state.scrollTop && startIndex === undefined) { startIndex = i; @@ -130,7 +164,7 @@ const List = { } // Check item bottom in the range. We will render additional one item for motion usage - if (currentItemBottom > state.scrollTop + props.height && endIndex === undefined) { + if (currentItemBottom > state.scrollTop + props.height! && endIndex === undefined) { endIndex = i; } @@ -157,9 +191,9 @@ const List = { }; }); // =============================== In Range =============================== - const maxScrollHeight = computed(() => calRes.value.scrollHeight - props.height); + const maxScrollHeight = computed(() => calRes.value.scrollHeight! - props.height!); - function keepInRange(newScrollTop) { + function keepInRange(newScrollTop: number) { let newTop = Math.max(newScrollTop, 0); if (!Number.isNaN(maxScrollHeight.value)) { newTop = Math.min(newTop, maxScrollHeight.value); @@ -173,15 +207,15 @@ const List = { const originScroll = useOriginScroll(isScrollAtTop, isScrollAtBottom); // ================================ Scroll ================================ - function onScrollBar(newScrollTop) { + function onScrollBar(newScrollTop: number) { const newTop = newScrollTop; syncScrollTop(newTop); } // This code may only trigger in test case. // But we still need a sync if some special escape - function onFallbackScroll(e) { - const { scrollTop: newScrollTop } = e.currentTarget; + function onFallbackScroll(e: UIEvent) { + const { scrollTop: newScrollTop } = e.currentTarget as Element; if (newScrollTop !== state.scrollTop) { syncScrollTop(newScrollTop); } @@ -209,29 +243,29 @@ const List = { return false; } - onRawWheel({ preventDefault() {}, deltaY }); + onRawWheel({ preventDefault() {}, deltaY } as WheelEvent); return true; }); // Firefox only - function onMozMousePixelScroll(e) { + function onMozMousePixelScroll(e: MouseEvent) { if (inVirtual.value) { e.preventDefault(); } } const removeEventListener = () => { - if (componentRef.current) { - componentRef.current.removeEventListener('wheel', onRawWheel); - componentRef.current.removeEventListener('DOMMouseScroll', onFireFoxScroll); - componentRef.current.removeEventListener('MozMousePixelScroll', onMozMousePixelScroll); + if (componentRef.value) { + componentRef.value.removeEventListener('wheel', onRawWheel); + componentRef.value.removeEventListener('DOMMouseScroll', onFireFoxScroll); + componentRef.value.removeEventListener('MozMousePixelScroll', onMozMousePixelScroll); } }; watchEffect(() => { nextTick(() => { - if (componentRef.current) { + if (componentRef.value) { removeEventListener(); - componentRef.current.addEventListener('wheel', onRawWheel); - componentRef.current.addEventListener('DOMMouseScroll', onFireFoxScroll); - componentRef.current.addEventListener('MozMousePixelScroll', onMozMousePixelScroll); + componentRef.value.addEventListener('wheel', onRawWheel); + componentRef.value.addEventListener('DOMMouseScroll', onFireFoxScroll); + componentRef.value.addEventListener('MozMousePixelScroll', onMozMousePixelScroll); } }); }); @@ -252,15 +286,15 @@ const List = { ); const componentStyle = computed(() => { - let cs = null; + let cs: CSSProperties | null = null; if (props.height) { cs = { [props.fullHeight ? 'height' : 'maxHeight']: props.height + 'px', ...ScrollStyle }; if (inVirtual.value) { - cs.overflowY = 'hidden'; + cs!.overflowY = 'hidden'; if (state.scrollMoving) { - cs.pointerEvents = 'none'; + cs!.pointerEvents = 'none'; } } } @@ -305,7 +339,6 @@ const List = { componentStyle, onFallbackScroll, onScrollBar, - componentRef, inVirtual, collectHeight, sharedConfig, @@ -332,7 +365,7 @@ const List = { ); }, -}; +}); export default List; diff --git a/components/vc-virtual-list/ScrollBar.jsx b/components/vc-virtual-list/ScrollBar.tsx similarity index 87% rename from components/vc-virtual-list/ScrollBar.jsx rename to components/vc-virtual-list/ScrollBar.tsx index 6c2cb9c88..40ecf99df 100644 --- a/components/vc-virtual-list/ScrollBar.jsx +++ b/components/vc-virtual-list/ScrollBar.tsx @@ -1,4 +1,4 @@ -import { reactive } from 'vue'; +import { defineComponent, PropType, reactive } from 'vue'; import classNames from '../_util/classNames'; import createRef from '../_util/createRef'; import raf from '../_util/raf'; @@ -6,29 +6,18 @@ import PropTypes from '../_util/vue-types'; const MIN_SIZE = 20; -// export interface ScrollBarProps { -// prefixCls: string; -// scrollTop: number; -// scrollHeight: number; -// height: number; -// count: number; -// onScroll: (scrollTop: number) => void; -// onStartMove: () => void; -// onStopMove: () => void; -// } +interface ScrollBarState { + dragging: boolean; + pageY: number | null; + startTop: number | null; + visible: boolean; +} -// interface ScrollBarState { -// dragging: boolean; -// pageY: number; -// startTop: number; -// visible: boolean; -// } - -function getPageY(e) { +function getPageY(e: MouseEvent | TouchEvent) { return 'touches' in e ? e.touches[0].pageY : e.pageY; } -export default { +export default defineComponent({ name: 'ScrollBar', inheritAttrs: false, props: { @@ -37,9 +26,15 @@ export default { scrollHeight: PropTypes.number, height: PropTypes.number, count: PropTypes.number, - onScroll: PropTypes.func, - onStartMove: PropTypes.func, - onStopMove: PropTypes.func, + onScroll: { + type: Function as PropType<(scrollTop: number) => void>, + }, + onStartMove: { + type: Function as PropType<() => void>, + }, + onStopMove: { + type: Function as PropType<() => void>, + }, }, setup() { return { @@ -47,7 +42,7 @@ export default { scrollbarRef: createRef(), thumbRef: createRef(), visibleTimeout: null, - state: reactive({ + state: reactive({ dragging: false, pageY: null, startTop: null, @@ -83,11 +78,11 @@ export default { }, 2000); }, - onScrollbarTouchStart(e) { + onScrollbarTouchStart(e: TouchEvent) { e.preventDefault(); }, - onContainerMouseDown(e) { + onContainerMouseDown(e: MouseEvent) { e.stopPropagation(); e.preventDefault(); }, @@ -114,7 +109,7 @@ export default { }, // ======================= Thumb ======================= - onMouseDown(e) { + onMouseDown(e: MouseEvent | TouchEvent) { const { onStartMove } = this.$props; Object.assign(this.state, { @@ -129,7 +124,7 @@ export default { e.preventDefault(); }, - onMouseMove(e) { + onMouseMove(e: MouseEvent | TouchEvent) { const { dragging, pageY, startTop } = this.state; const { onScroll } = this.$props; @@ -203,7 +198,7 @@ export default { bottom: 0, right: 0, position: 'absolute', - display: visible ? null : 'none', + display: visible ? undefined : 'none', }} onMousedown={this.onContainerMouseDown} onMousemove={this.delayHidden} @@ -229,4 +224,4 @@ export default { ); }, -}; +}); diff --git a/components/vc-virtual-list/hooks/useFrameWheel.js b/components/vc-virtual-list/hooks/useFrameWheel.ts similarity index 64% rename from components/vc-virtual-list/hooks/useFrameWheel.js rename to components/vc-virtual-list/hooks/useFrameWheel.ts index dbdf8802d..df7b3286f 100644 --- a/components/vc-virtual-list/hooks/useFrameWheel.js +++ b/components/vc-virtual-list/hooks/useFrameWheel.ts @@ -1,22 +1,33 @@ +import { Ref } from 'vue'; import raf from '../../_util/raf'; import isFF from '../utils/isFirefox'; import useOriginScroll from './useOriginScroll'; -export default function useFrameWheel(inVirtual, isScrollAtTop, isScrollAtBottom, onWheelDelta) { +interface FireFoxDOMMouseScrollEvent { + detail: number; + preventDefault: Function; +} + +export default function useFrameWheel( + inVirtual: Ref, + isScrollAtTop: Ref, + isScrollAtBottom: Ref, + onWheelDelta: (offset: number) => void, +): [(e: WheelEvent) => void, (e: FireFoxDOMMouseScrollEvent) => void] { let offsetRef = 0; - let nextFrame = null; + let nextFrame: number | null | undefined = null; // Firefox patch - let wheelValue = null; + let wheelValue: null = null; let isMouseScroll = false; // Scroll status sync const originScroll = useOriginScroll(isScrollAtTop, isScrollAtBottom); - function onWheel(event) { + function onWheel(event: { preventDefault?: any; deltaY?: any }) { if (!inVirtual.value) return; - raf.cancel(nextFrame); + raf.cancel(nextFrame!); const { deltaY } = event; offsetRef += deltaY; @@ -40,7 +51,7 @@ export default function useFrameWheel(inVirtual, isScrollAtTop, isScrollAtBottom } // A patch for firefox - function onFireFoxScroll(event) { + function onFireFoxScroll(event: { detail: any }) { if (!inVirtual.value) return; isMouseScroll = event.detail === wheelValue; diff --git a/components/vc-virtual-list/hooks/useHeights.jsx b/components/vc-virtual-list/hooks/useHeights.tsx similarity index 58% rename from components/vc-virtual-list/hooks/useHeights.jsx rename to components/vc-virtual-list/hooks/useHeights.tsx index 1ea2c42ee..efe7daba7 100644 --- a/components/vc-virtual-list/hooks/useHeights.jsx +++ b/components/vc-virtual-list/hooks/useHeights.tsx @@ -1,9 +1,15 @@ -import { reactive, ref } from 'vue'; -import { findDOMNode } from '../../_util/props-util'; +import { reactive, Ref, ref, VNodeProps } from 'vue'; +import { GetKey } from '../interface'; -export default function useHeights(getKey, onItemAdd, onItemRemove) { - const instance = new Map(); - const heights = reactive({}); +type CacheMap = Record; + +export default function useHeights( + getKey: GetKey, + onItemAdd?: ((item: T) => void) | null, + onItemRemove?: ((item: T) => void) | null, +): [(item: T, instance: HTMLElement) => void, () => void, CacheMap, Ref] { + const instance = new Map(); + const heights = reactive({}); let updatedMark = ref(0); let heightUpdateId = 0; function collectHeight() { @@ -15,11 +21,10 @@ export default function useHeights(getKey, onItemAdd, onItemRemove) { let changed = false; instance.forEach((element, key) => { if (element && element.offsetParent) { - const htmlElement = findDOMNode(element); - const { offsetHeight } = htmlElement; - if (heights[key] !== offsetHeight) { + const { offsetHeight } = element; + if (heights[key!] !== offsetHeight) { changed = true; - heights[key] = htmlElement.offsetHeight; + heights[key!] = element.offsetHeight; } } }); @@ -29,7 +34,7 @@ export default function useHeights(getKey, onItemAdd, onItemRemove) { }); } - function setInstance(item, ins) { + function setInstance(item: T, ins: HTMLElement) { const key = getKey(item); const origin = instance.get(key); diff --git a/components/vc-virtual-list/hooks/useMobileTouchMove.js b/components/vc-virtual-list/hooks/useMobileTouchMove.ts similarity index 59% rename from components/vc-virtual-list/hooks/useMobileTouchMove.js rename to components/vc-virtual-list/hooks/useMobileTouchMove.ts index 6618d662b..7bbc76ce0 100644 --- a/components/vc-virtual-list/hooks/useMobileTouchMove.js +++ b/components/vc-virtual-list/hooks/useMobileTouchMove.ts @@ -1,19 +1,28 @@ -import { watch } from 'vue'; +import { watch, Ref } from 'vue'; const SMOOTH_PTG = 14 / 15; -export default function useMobileTouchMove(inVirtual, listRef, callback) { +export default function useMobileTouchMove( + inVirtual: Ref, + listRef: Ref, + callback: (offsetY: number, smoothOffset?: boolean) => boolean, +) { let touched = false; let touchY = 0; - let element = null; + let element: HTMLElement | null = null; // Smooth scroll - let interval = null; + let interval: any = null; - let cleanUpEvents; + const cleanUpEvents = () => { + if (element) { + element.removeEventListener('touchmove', onTouchMove); + element.removeEventListener('touchend', onTouchEnd); + } + }; - const onTouchMove = e => { + const onTouchMove = (e: TouchEvent) => { if (touched) { const currentY = Math.ceil(e.touches[0].pageY); let offsetY = touchY - currentY; @@ -41,31 +50,25 @@ export default function useMobileTouchMove(inVirtual, listRef, callback) { cleanUpEvents(); }; - const onTouchStart = e => { + const onTouchStart = (e: TouchEvent) => { cleanUpEvents(); if (e.touches.length === 1 && !touched) { touched = true; touchY = Math.ceil(e.touches[0].pageY); - element = e.target; - element.addEventListener('touchmove', onTouchMove); - element.addEventListener('touchend', onTouchEnd); + element = e.target as HTMLElement; + element!.addEventListener('touchmove', onTouchMove); + element!.addEventListener('touchend', onTouchEnd); } }; - cleanUpEvents = () => { - if (element) { - element.removeEventListener('touchmove', onTouchMove); - element.removeEventListener('touchend', onTouchEnd); - } - }; watch(inVirtual, val => { - listRef.current.removeEventListener('touchstart', onTouchStart); + listRef.value.removeEventListener('touchstart', onTouchStart); cleanUpEvents(); clearInterval(interval); - if (val.value) { - listRef.current.addEventListener('touchstart', onTouchStart); + if (val) { + listRef.value.addEventListener('touchstart', onTouchStart); } }); } diff --git a/components/vc-virtual-list/hooks/useOriginScroll.js b/components/vc-virtual-list/hooks/useOriginScroll.ts similarity index 78% rename from components/vc-virtual-list/hooks/useOriginScroll.js rename to components/vc-virtual-list/hooks/useOriginScroll.ts index a359dbc11..84f96966b 100644 --- a/components/vc-virtual-list/hooks/useOriginScroll.js +++ b/components/vc-virtual-list/hooks/useOriginScroll.ts @@ -1,7 +1,9 @@ -export default (isScrollAtTop, isScrollAtBottom) => { +import { Ref } from 'vue'; + +export default (isScrollAtTop: Ref, isScrollAtBottom: Ref) => { // Do lock for a wheel when scrolling let lock = false; - let lockTimeout = null; + let lockTimeout: any = null; function lockScroll() { clearTimeout(lockTimeout); @@ -11,7 +13,7 @@ export default (isScrollAtTop, isScrollAtBottom) => { lock = false; }, 50); } - return (deltaY, smoothOffset = false) => { + return (deltaY: number, smoothOffset = false) => { const originScroll = // Pass origin wheel when on the top (deltaY < 0 && isScrollAtTop.value) || diff --git a/components/vc-virtual-list/hooks/useScrollTo.jsx b/components/vc-virtual-list/hooks/useScrollTo.tsx similarity index 70% rename from components/vc-virtual-list/hooks/useScrollTo.jsx rename to components/vc-virtual-list/hooks/useScrollTo.tsx index 35390dab5..36126a595 100644 --- a/components/vc-virtual-list/hooks/useScrollTo.jsx +++ b/components/vc-virtual-list/hooks/useScrollTo.tsx @@ -1,41 +1,43 @@ -/* eslint-disable no-param-reassign */ - +import { Data } from '../../_util/type'; +import { Ref } from 'vue'; import raf from '../../_util/raf'; +import { GetKey } from '../interface'; +import { ListState } from '../List'; export default function useScrollTo( - containerRef, - state, - heights, + containerRef: Ref, + state: ListState, + heights: Data, props, - getKey, - collectHeight, - syncScrollTop, + getKey: GetKey, + collectHeight: () => void, + syncScrollTop: (newTop: number) => void, ) { - let scroll = null; + let scroll: number | null = null; return arg => { - raf.cancel(scroll); + raf.cancel(scroll!); const data = state.mergedData; const itemHeight = props.itemHeight; if (typeof arg === 'number') { syncScrollTop(arg); } else if (arg && typeof arg === 'object') { - let index; + let index: number; const { align } = arg; if ('index' in arg) { ({ index } = arg); } else { - index = data.findIndex(item => getKey(item) === arg.key); + index = data.findIndex((item: object) => getKey(item) === arg.key); } const { offset = 0 } = arg; // We will retry 3 times in case dynamic height shaking - const syncScroll = (times, targetAlign) => { - if (times < 0 || !containerRef.current) return; + const syncScroll = (times: number, targetAlign?: 'top' | 'bottom') => { + if (times < 0 || !containerRef.value) return; - const height = containerRef.current.clientHeight; + const height = containerRef.value.clientHeight; let needCollectHeight = false; let newTargetAlign = targetAlign; @@ -51,7 +53,7 @@ export default function useScrollTo( for (let i = 0; i <= index; i += 1) { const key = getKey(data[i]); itemTop = stackTop; - const cacheHeight = heights[key]; + const cacheHeight = heights[key!]; itemBottom = itemTop + (cacheHeight === undefined ? itemHeight : cacheHeight); stackTop = itemBottom; @@ -62,7 +64,7 @@ export default function useScrollTo( } // Scroll to - let targetTop = null; + let targetTop: number | null = null; switch (mergedAlign) { case 'top': @@ -73,7 +75,7 @@ export default function useScrollTo( break; default: { - const { scrollTop } = containerRef.current; + const { scrollTop } = containerRef.value; const scrollBottom = scrollTop + height; if (itemTop < scrollTop) { newTargetAlign = 'top'; @@ -83,7 +85,7 @@ export default function useScrollTo( } } - if (targetTop !== null && targetTop !== containerRef.current.scrollTop) { + if (targetTop !== null && targetTop !== containerRef.value.scrollTop) { syncScrollTop(targetTop); } } diff --git a/components/vc-virtual-list/index.js b/components/vc-virtual-list/index.ts similarity index 100% rename from components/vc-virtual-list/index.js rename to components/vc-virtual-list/index.ts diff --git a/components/vc-virtual-list/interface.ts b/components/vc-virtual-list/interface.ts new file mode 100644 index 000000000..863b5d153 --- /dev/null +++ b/components/vc-virtual-list/interface.ts @@ -0,0 +1,14 @@ +import { CSSProperties, VNodeTypes } from 'vue'; +import { Key } from '../_util/type'; + +export type RenderFunc = ( + item: T, + index: number, + props: { style?: CSSProperties }, +) => VNodeTypes; + +export interface SharedConfig { + getKey: (item: T) => Key; +} + +export type GetKey = (item: T) => Key; diff --git a/components/vc-virtual-list/utils/isFirefox.js b/components/vc-virtual-list/utils/isFirefox.ts similarity index 100% rename from components/vc-virtual-list/utils/isFirefox.js rename to components/vc-virtual-list/utils/isFirefox.ts From 81e15834c2016a625c8d48b31aab3173cb2a949d Mon Sep 17 00:00:00 2001 From: Amour1688 Date: Thu, 1 Oct 2020 20:56:26 +0800 Subject: [PATCH 3/4] chore: ts-jest --- .jest.js | 8 +++++++- components/{index.ts => index.js} | 0 package.json | 3 ++- 3 files changed, 9 insertions(+), 2 deletions(-) rename components/{index.ts => index.js} (100%) diff --git a/.jest.js b/.jest.js index d35db6229..24c57d146 100644 --- a/.jest.js +++ b/.jest.js @@ -13,12 +13,13 @@ if (process.env.WORKFLOW === 'true') { module.exports = { testURL: 'http://localhost/', setupFiles: ['./tests/setup.js'], - moduleFileExtensions: ['js', 'jsx', 'json', 'vue', 'md', 'jpg'], + moduleFileExtensions: ['js', 'jsx', 'ts', 'tsx', 'json', 'vue', 'md', 'jpg'], modulePathIgnorePatterns: ['/_site/'], testPathIgnorePatterns: testPathIgnorePatterns, transform: { '^.+\\.(vue|md)$': '/node_modules/vue-jest', '^.+\\.(js|jsx)$': '/node_modules/babel-jest', + '^.+\\.(ts|tsx)$': '/node_modules/ts-jest', '^.+\\.svg$': '/node_modules/jest-transform-stub', }, testRegex: libDir === 'dist' ? 'demo\\.test\\.js$' : '.*\\.test\\.js$', @@ -45,4 +46,9 @@ module.exports = { ], testEnvironment: 'jest-environment-jsdom-fifteen', transformIgnorePatterns, + globals: { + 'ts-jest': { + babelConfig: true, + }, + }, }; diff --git a/components/index.ts b/components/index.js similarity index 100% rename from components/index.ts rename to components/index.js diff --git a/package.json b/package.json index 4ab392e1b..cfe164656 100644 --- a/package.json +++ b/package.json @@ -130,7 +130,7 @@ "html-webpack-plugin": "^3.2.0", "husky": "^4.0.0", "istanbul-instrumenter-loader": "^3.0.0", - "jest": "^25.4.0", + "jest": "^26.0.0", "jest-environment-jsdom-fifteen": "^1.0.2", "jest-serializer-vue": "^2.0.0", "jest-transform-stub": "^2.0.0", @@ -168,6 +168,7 @@ "stylelint-config-standard": "^19.0.0", "terser-webpack-plugin": "^3.0.3", "through2": "^3.0.0", + "ts-jest": "^26.4.1", "ts-loader": "^8.0.2", "typescript": "^4.0.2", "umi-mock-middleware": "^1.0.0", From 2fd04595bfff128c79169e16add17f499b6b4dfa Mon Sep 17 00:00:00 2001 From: Amour1688 Date: Thu, 1 Oct 2020 20:57:03 +0800 Subject: [PATCH 4/4] fix: ts error --- components/_util/classNames.ts | 2 +- components/_util/vue-types/index.ts | 12 +++++++----- .../modal/{ActionButton.tsx => ActionButton.jsx} | 0 .../modal/{ConfirmDialog.tsx => ConfirmDialog.jsx} | 0 components/modal/{Modal.tsx => Modal.jsx} | 0 components/modal/{confirm.tsx => confirm.jsx} | 0 components/modal/{index.tsx => index.jsx} | 0 examples/App.tsx | 9 +++++++++ tsconfig.json | 2 +- 9 files changed, 18 insertions(+), 7 deletions(-) rename components/modal/{ActionButton.tsx => ActionButton.jsx} (100%) rename components/modal/{ConfirmDialog.tsx => ConfirmDialog.jsx} (100%) rename components/modal/{Modal.tsx => Modal.jsx} (100%) rename components/modal/{confirm.tsx => confirm.jsx} (100%) rename components/modal/{index.tsx => index.jsx} (100%) create mode 100644 examples/App.tsx diff --git a/components/_util/classNames.ts b/components/_util/classNames.ts index 310700c9d..d20fa9cc2 100644 --- a/components/_util/classNames.ts +++ b/components/_util/classNames.ts @@ -16,7 +16,7 @@ export type ClassValue = | boolean; function classNames(...args: ClassValue[]): string { - const classes = []; + const classes: string[] = []; for (let i = 0; i < args.length; i++) { const value = args[i]; if (!value) { diff --git a/components/_util/vue-types/index.ts b/components/_util/vue-types/index.ts index 04533b9cd..5248734a2 100644 --- a/components/_util/vue-types/index.ts +++ b/components/_util/vue-types/index.ts @@ -1,11 +1,13 @@ import { PropType } from 'vue'; import isPlainObject from 'lodash-es/isPlainObject'; import { toType, getType, isFunction, validateType, isInteger, isArray, warn } from './utils'; -interface BaseTypes { - type: any; - def: Function; - validator: Function; -} + +// interface BaseTypes { +// type: any; +// def: Function; +// validator: Function; +// } + const PropTypes = { get any() { return toType('any', { diff --git a/components/modal/ActionButton.tsx b/components/modal/ActionButton.jsx similarity index 100% rename from components/modal/ActionButton.tsx rename to components/modal/ActionButton.jsx diff --git a/components/modal/ConfirmDialog.tsx b/components/modal/ConfirmDialog.jsx similarity index 100% rename from components/modal/ConfirmDialog.tsx rename to components/modal/ConfirmDialog.jsx diff --git a/components/modal/Modal.tsx b/components/modal/Modal.jsx similarity index 100% rename from components/modal/Modal.tsx rename to components/modal/Modal.jsx diff --git a/components/modal/confirm.tsx b/components/modal/confirm.jsx similarity index 100% rename from components/modal/confirm.tsx rename to components/modal/confirm.jsx diff --git a/components/modal/index.tsx b/components/modal/index.jsx similarity index 100% rename from components/modal/index.tsx rename to components/modal/index.jsx diff --git a/examples/App.tsx b/examples/App.tsx new file mode 100644 index 000000000..a9f17de90 --- /dev/null +++ b/examples/App.tsx @@ -0,0 +1,9 @@ +import { defaultTo } from 'lodash-es'; +import Empty from '../components/empty'; +import '../components/empty/style'; + +export default { + render() { + return ; + }, +}; diff --git a/tsconfig.json b/tsconfig.json index a3eed429e..bc32c5bde 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -12,7 +12,7 @@ "jsx": "preserve", "noUnusedParameters": true, "noUnusedLocals": true, - "noImplicitAny": true, + "noImplicitAny": false, "target": "es6", "lib": ["dom", "es2017"], "skipLibCheck": true