diff --git a/components/_util/props-util.ts b/components/_util/props-util.ts index 5b719bc79..368ed2a00 100644 --- a/components/_util/props-util.ts +++ b/components/_util/props-util.ts @@ -1,6 +1,6 @@ import isPlainObject from 'lodash/isPlainObject'; import classNames from 'classnames'; -import { isVNode, Fragment, Comment, Text, h } from 'vue'; +import { isVNode, Fragment, Comment, Text, h, VNode, Prop, PropOptions } from 'vue'; import { camelize, hyphenate, isOn, resolvePropValue } from './util'; import isValid from './isValid'; // function getType(fn) { @@ -345,8 +345,8 @@ export function isStringElement(c) { return c && c.type === Text; } -export function filterEmpty(children = []) { - const res = []; +export function filterEmpty(children: VNode[] = []) { + const res: VNode[] = []; children.forEach(child => { if (Array.isArray(child)) { res.push(...child); @@ -358,10 +358,14 @@ export function filterEmpty(children = []) { }); return res.filter(c => !isEmptyElement(c)); } -const initDefaultProps = (propTypes, defaultProps) => { - Object.keys(defaultProps).forEach(k => { - if (propTypes[k]) { - propTypes[k].def && (propTypes[k] = propTypes[k].def(defaultProps[k])); +const initDefaultProps = ( + propTypes: T, + defaultProps: { [K in Extract]?: any }, +): T => { + Object.keys(defaultProps).forEach((k: Extract) => { + let prop = propTypes[k] as PropOptions; + if (prop) { + prop.default = defaultProps[k]; } else { throw new Error(`not have ${k} prop`); } diff --git a/components/_util/responsiveObserve.ts b/components/_util/responsiveObserve.ts index 2d5fccd2e..b4b744664 100644 --- a/components/_util/responsiveObserve.ts +++ b/components/_util/responsiveObserve.ts @@ -1,26 +1,10 @@ -// matchMedia polyfill for -// https://github.com/WickyNilliams/enquire.js/issues/82 -let enquire; +export type Breakpoint = 'xxl' | 'xl' | 'lg' | 'md' | 'sm' | 'xs'; +export type BreakpointMap = Partial>; +export type ScreenMap = Partial>; -// TODO: Will be removed in antd 4.0 because we will no longer support ie9 -if (typeof window !== 'undefined') { - const matchMediaPolyfill = mediaQuery => { - return { - media: mediaQuery, - matches: false, - addListener() {}, - removeListener() {}, - }; - }; - // ref: https://github.com/ant-design/ant-design/issues/18774 - if (!window.matchMedia) window.matchMedia = matchMediaPolyfill; - // eslint-disable-next-line global-require - enquire = require('enquire.js'); -} +export const responsiveArray: Breakpoint[] = ['xxl', 'xl', 'lg', 'md', 'sm', 'xs']; -export const responsiveArray = ['xxl', 'xl', 'lg', 'md', 'sm', 'xs']; - -export const responsiveMap = { +export const responsiveMap: BreakpointMap = { xs: '(max-width: 575px)', sm: '(min-width: 576px)', md: '(min-width: 768px)', @@ -29,65 +13,60 @@ export const responsiveMap = { xxl: '(min-width: 1600px)', }; -let subscribers = []; +type SubscribeFunc = (screens: ScreenMap) => void; +const subscribers = new Map(); let subUid = -1; let screens = {}; const responsiveObserve = { - dispatch(pointMap) { + matchHandlers: {} as { + [prop: string]: { + mql: MediaQueryList; + listener: ((this: MediaQueryList, ev: MediaQueryListEvent) => any) | null; + }; + }, + dispatch(pointMap: ScreenMap) { screens = pointMap; - if (subscribers.length < 1) { - return false; - } - - subscribers.forEach(item => { - item.func(screens); - }); - - return true; + subscribers.forEach(func => func(screens)); + return subscribers.size >= 1; }, - subscribe(func) { - if (subscribers.length === 0) { - this.register(); - } - const token = (++subUid).toString(); - subscribers.push({ - token, - func, - }); + subscribe(func: SubscribeFunc): number { + if (!subscribers.size) this.register(); + subUid += 1; + subscribers.set(subUid, func); func(screens); - return token; + return subUid; }, - unsubscribe(token) { - subscribers = subscribers.filter(item => item.token !== token); - if (subscribers.length === 0) { - this.unregister(); - } + unsubscribe(token: number) { + subscribers.delete(token); + if (!subscribers.size) this.unregister(); }, unregister() { - Object.keys(responsiveMap).map(screen => enquire.unregister(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).map(screen => - enquire.register(responsiveMap[screen], { - match: () => { - const pointMap = { - ...screens, - [screen]: true, - }; - this.dispatch(pointMap); - }, - unmatch: () => { - const pointMap = { - ...screens, - [screen]: false, - }; - this.dispatch(pointMap); - }, - // Keep a empty destroy to avoid triggering unmatch when unregister - destroy() {}, - }), - ); + Object.keys(responsiveMap).forEach((screen: Breakpoint) => { + const matchMediaQuery = responsiveMap[screen]!; + const listener = ({ matches }: { matches: boolean }) => { + this.dispatch({ + ...screens, + [screen]: matches, + }); + }; + const mql = window.matchMedia(matchMediaQuery); + mql.addListener(listener); + this.matchHandlers[matchMediaQuery] = { + mql, + listener, + }; + + listener(mql); + }); }, }; diff --git a/components/_util/type.ts b/components/_util/type.ts index 8d3e0d7cb..8df5e1574 100644 --- a/components/_util/type.ts +++ b/components/_util/type.ts @@ -1,4 +1,4 @@ // https://stackoverflow.com/questions/46176165/ways-to-get-string-literal-type-of-array-values-without-enum-overhead -export const tuple = (...args) => args; +export const tuple = (...args: T) => args; export const tupleNum = (...args) => args; diff --git a/components/_util/vue-types/index.ts b/components/_util/vue-types/index.ts index c92958ef7..e04b3e431 100644 --- a/components/_util/vue-types/index.ts +++ b/components/_util/vue-types/index.ts @@ -1,3 +1,4 @@ +import { PropType } from 'vue'; import isPlainObject from 'lodash/isPlainObject'; import { toType, getType, isFunction, validateType, isInteger, isArray, warn } from './utils'; @@ -9,57 +10,57 @@ const VuePropTypes = { }, get func() { - return toType('function', { + return { type: Function, - }).def(currentDefaults.func); + }; }, get bool() { - return toType('boolean', { + return { type: Boolean, - }).def(currentDefaults.bool); + }; }, get string() { - return toType('string', { + return { type: String, - }).def(currentDefaults.string); + }; }, get number() { - return toType('number', { + return { type: Number, - }).def(currentDefaults.number); + }; }, get array() { - return toType('array', { + return { type: Array, - }).def(currentDefaults.array); + }; }, get object() { - return toType('object', { + return { type: Object, - }).def(currentDefaults.object); + }; }, get integer() { - return toType('integer', { + return { type: Number, - validator(value) { + validator(value: number) { return isInteger(value); }, - }).def(currentDefaults.integer); + }; }, get symbol() { - return toType('symbol', { + return { type: null, - validator(value) { + validator(value: Symbol) { return typeof value === 'symbol'; }, - }); + }; }, custom(validatorFn, warnMsg = 'custom validation failed') { @@ -76,7 +77,13 @@ const VuePropTypes = { }); }, - oneOf(arr) { + tuple() { + return { + type: (String as unknown) as PropType, + }; + }, + + oneOf(arr: unknown[]) { if (!isArray(arr)) { throw new TypeError('[VueTypes error]: You must provide an array as argument'); } @@ -90,7 +97,7 @@ const VuePropTypes = { return toType('oneOf', { type: allowedTypes.length > 0 ? allowedTypes : null, - validator(value) { + validator(value: unknown) { const valid = arr.indexOf(value) !== -1; if (!valid) warn(msg); return valid; @@ -98,10 +105,10 @@ const VuePropTypes = { }); }, - instanceOf(instanceConstructor) { - return toType('instanceOf', { + instanceOf(instanceConstructor: T) { + return { type: instanceConstructor, - }); + }; }, oneOfType(arr) { @@ -158,10 +165,10 @@ const VuePropTypes = { }).def(undefined); }, - arrayOf(type) { + arrayOf(type: T) { return toType('arrayOf', { - type: Array, - validator(values) { + type: Array as PropType, + validator(values: T[]) { const valid = values.every(value => validateType(type, value)); if (!valid) warn(`arrayOf - value must be an array of "${getType(type)}"`); return valid; @@ -171,8 +178,8 @@ const VuePropTypes = { objectOf(type) { return toType('objectOf', { - type: Object, - validator(obj) { + type: Object as PropType, + validator(obj: T): obj is T { const valid = Object.keys(obj).every(key => validateType(type, obj[key])); if (!valid) warn(`objectOf - value must be an object of "${getType(type)}"`); return valid; diff --git a/components/_util/wave.jsx.tsx b/components/_util/wave.tsx similarity index 100% rename from components/_util/wave.jsx.tsx rename to components/_util/wave.tsx diff --git a/components/card/Card.jsx b/components/card/Card.tsx similarity index 100% rename from components/card/Card.jsx rename to components/card/Card.tsx diff --git a/components/card/index.js b/components/card/index.ts similarity index 100% rename from components/card/index.js rename to components/card/index.ts diff --git a/components/col/index.js b/components/col/index.ts similarity index 64% rename from components/col/index.js rename to components/col/index.ts index fd37d7b89..9b6eb4f97 100644 --- a/components/col/index.js +++ b/components/col/index.ts @@ -1,6 +1,7 @@ +import { App } from 'vue'; import { Col } from '../grid'; /* istanbul ignore next */ -Col.install = function(app) { +Col.install = function(app: App) { app.component(Col.name, Col); }; diff --git a/components/col/style/index.js b/components/col/style/index.ts similarity index 100% rename from components/col/style/index.js rename to components/col/style/index.ts diff --git a/components/empty/index.tsx b/components/empty/index.tsx index 9f86a51c2..32ce1299f 100644 --- a/components/empty/index.tsx +++ b/components/empty/index.tsx @@ -1,9 +1,10 @@ -import { defineComponent, CSSProperties, VNodeChild, inject, App } from 'vue'; +import { defineComponent, CSSProperties, VNodeChild, inject, App, PropType, VNode } from 'vue'; import classNames from 'classnames'; import { ConfigConsumerProps } from '../config-provider'; import LocaleReceiver from '../locale-provider/LocaleReceiver'; import DefaultEmptyImg from './empty'; import SimpleEmptyImg from './simple'; +import { filterEmpty } from '../_util/props-util'; const defaultEmptyImg = ; const simpleEmptyImg = ; @@ -12,19 +13,23 @@ export interface TransferLocale { description: string; } -export interface EmptyProps { - prefixCls?: string; - class?: string; - style?: CSSProperties; - imageStyle?: CSSProperties; - image?: VNodeChild; - description?: VNodeChild; - children?: VNodeChild; -} - -const Empty = defineComponent({ +const Empty = defineComponent({ name: 'AEmpty', - setup(props) { + props: { + prefixCls: { + type: String, + }, + imageStyle: { + type: Object as PropType, + }, + image: { + type: Object as PropType, + }, + description: { + type: String, + }, + }, + setup(props, { slots }) { const configProvider = inject('configProvider', ConfigConsumerProps); const { getPrefixCls } = configProvider; const { @@ -32,7 +37,6 @@ const Empty = defineComponent({ prefixCls: customizePrefixCls, image = defaultEmptyImg, description, - children, imageStyle, ...restProps } = props; @@ -44,7 +48,6 @@ const Empty = defineComponent({ const prefixCls = getPrefixCls('empty', customizePrefixCls); const des = typeof description !== 'undefined' ? description : locale.description; const alt = typeof des === 'string' ? des : 'empty'; - let imageNode: any = null; if (typeof image === 'string') { @@ -68,7 +71,9 @@ const Empty = defineComponent({ {imageNode} {des &&

{des}

} - {children &&
{children}
} + {slots.default && ( +
{filterEmpty(slots.default?.())}
+ )} ) as VNodeChild; }} diff --git a/components/grid/Col.jsx b/components/grid/Col.jsx deleted file mode 100644 index 3c8fe8b96..000000000 --- a/components/grid/Col.jsx +++ /dev/null @@ -1,125 +0,0 @@ -import { inject } from 'vue'; -import PropTypes from '../_util/vue-types'; -import { ConfigConsumerProps } from '../config-provider'; -import { getSlot } from '../_util/props-util'; - -const stringOrNumber = PropTypes.oneOfType([PropTypes.string, PropTypes.number]); - -export const ColSize = PropTypes.shape({ - span: stringOrNumber, - order: stringOrNumber, - offset: stringOrNumber, - push: stringOrNumber, - pull: stringOrNumber, -}).loose; - -const objectOrNumber = PropTypes.oneOfType([PropTypes.string, PropTypes.number, ColSize]); - -export const ColProps = { - span: stringOrNumber, - order: stringOrNumber, - offset: stringOrNumber, - push: stringOrNumber, - pull: stringOrNumber, - xs: objectOrNumber, - sm: objectOrNumber, - md: objectOrNumber, - lg: objectOrNumber, - xl: objectOrNumber, - xxl: objectOrNumber, - prefixCls: PropTypes.string, - flex: stringOrNumber, -}; - -export default { - name: 'ACol', - props: ColProps, - setup() { - return { - configProvider: inject('configProvider', ConfigConsumerProps), - rowContext: inject('rowContext', null), - }; - }, - methods: { - parseFlex(flex) { - if (typeof flex === 'number') { - return `${flex} ${flex} auto`; - } - if (/^\d+(\.\d+)?(px|em|rem|%)$/.test(flex)) { - return `0 0 ${flex}`; - } - return flex; - }, - }, - render() { - const { - span, - order, - offset, - push, - pull, - flex, - prefixCls: customizePrefixCls, - rowContext, - } = this; - const getPrefixCls = this.configProvider.getPrefixCls; - const prefixCls = getPrefixCls('col', customizePrefixCls); - - let sizeClassObj = {}; - ['xs', 'sm', 'md', 'lg', 'xl', 'xxl'].forEach(size => { - let sizeProps = {}; - const propSize = this[size]; - if (typeof propSize === 'number') { - sizeProps.span = propSize; - } else if (typeof propSize === 'object') { - sizeProps = propSize || {}; - } - - sizeClassObj = { - ...sizeClassObj, - [`${prefixCls}-${size}-${sizeProps.span}`]: sizeProps.span !== undefined, - [`${prefixCls}-${size}-order-${sizeProps.order}`]: sizeProps.order || sizeProps.order === 0, - [`${prefixCls}-${size}-offset-${sizeProps.offset}`]: - sizeProps.offset || sizeProps.offset === 0, - [`${prefixCls}-${size}-push-${sizeProps.push}`]: sizeProps.push || sizeProps.push === 0, - [`${prefixCls}-${size}-pull-${sizeProps.pull}`]: sizeProps.pull || sizeProps.pull === 0, - }; - }); - const classes = { - [`${prefixCls}`]: true, - [`${prefixCls}-${span}`]: span !== undefined, - [`${prefixCls}-order-${order}`]: order, - [`${prefixCls}-offset-${offset}`]: offset, - [`${prefixCls}-push-${push}`]: push, - [`${prefixCls}-pull-${pull}`]: pull, - ...sizeClassObj, - }; - const divProps = { - class: classes, - style: {}, - }; - if (rowContext) { - const gutter = rowContext.getGutter(); - if (gutter) { - divProps.style = { - ...(gutter[0] > 0 - ? { - paddingLeft: `${gutter[0] / 2}px`, - paddingRight: `${gutter[0] / 2}px`, - } - : {}), - ...(gutter[1] > 0 - ? { - paddingTop: `${gutter[1] / 2}px`, - paddingBottom: `${gutter[1] / 2}px`, - } - : {}), - }; - } - } - if (flex) { - divProps.style.flex = this.parseFlex(flex); - } - return
{getSlot(this)}
; - }, -}; diff --git a/components/grid/Col.tsx b/components/grid/Col.tsx new file mode 100644 index 000000000..03bb5ee58 --- /dev/null +++ b/components/grid/Col.tsx @@ -0,0 +1,131 @@ +import { inject, defineComponent, HTMLAttributes, CSSProperties } from 'vue'; +import classNames from 'classnames'; +import { ConfigConsumerProps } from '../config-provider'; +import { rowContextState } from './Row'; + +type ColSpanType = number | string; + +type FlexType = number | 'none' | 'auto' | string; + +export interface ColSize { + span?: ColSpanType; + order?: ColSpanType; + offset?: ColSpanType; + push?: ColSpanType; + pull?: ColSpanType; +} + +export interface ColProps extends HTMLAttributes { + span?: ColSpanType; + order?: ColSpanType; + offset?: ColSpanType; + push?: ColSpanType; + pull?: ColSpanType; + xs?: ColSpanType | ColSize; + sm?: ColSpanType | ColSize; + md?: ColSpanType | ColSize; + lg?: ColSpanType | ColSize; + xl?: ColSpanType | ColSize; + xxl?: ColSpanType | ColSize; + prefixCls?: string; + flex?: FlexType; +} + +function parseFlex(flex: FlexType): string { + if (typeof flex === 'number') { + return `${flex} ${flex} auto`; + } + + if (/^\d+(\.\d+)?(px|em|rem|%)$/.test(flex)) { + return `0 0 ${flex}`; + } + + return flex; +} + +export default defineComponent({ + name: 'ACol', + setup(props, { slots }) { + const configProvider = inject('configProvider', ConfigConsumerProps); + const rowContext = inject('rowContext', {}); + + return () => { + const { gutter } = rowContext; + const { + prefixCls: customizePrefixCls, + span, + order, + offset, + push, + pull, + class: className, + flex, + style, + ...others + } = props; + const prefixCls = configProvider.getPrefixCls('col', customizePrefixCls); + let sizeClassObj = {}; + ['xs', 'sm', 'md', 'lg', 'xl', 'xxl'].forEach(size => { + let sizeProps: ColSize = {}; + const propSize = (props as any)[size]; + if (typeof propSize === 'number') { + sizeProps.span = propSize; + } else if (typeof propSize === 'object') { + sizeProps = propSize || {}; + } + + delete (others as any)[size]; + + sizeClassObj = { + ...sizeClassObj, + [`${prefixCls}-${size}-${sizeProps.span}`]: sizeProps.span !== undefined, + [`${prefixCls}-${size}-order-${sizeProps.order}`]: + sizeProps.order || sizeProps.order === 0, + [`${prefixCls}-${size}-offset-${sizeProps.offset}`]: + sizeProps.offset || sizeProps.offset === 0, + [`${prefixCls}-${size}-push-${sizeProps.push}`]: sizeProps.push || sizeProps.push === 0, + [`${prefixCls}-${size}-pull-${sizeProps.pull}`]: sizeProps.pull || sizeProps.pull === 0, + }; + }); + const classes = classNames( + prefixCls, + { + [`${prefixCls}-${span}`]: span !== undefined, + [`${prefixCls}-order-${order}`]: order, + [`${prefixCls}-offset-${offset}`]: offset, + [`${prefixCls}-push-${push}`]: push, + [`${prefixCls}-pull-${pull}`]: pull, + }, + className, + sizeClassObj, + ); + let mergedStyle: CSSProperties = {}; + if (gutter) { + mergedStyle = { + ...(gutter[0]! > 0 + ? { + paddingLeft: gutter[0]! / 2, + paddingRight: gutter[0]! / 2, + } + : {}), + ...(gutter[1]! > 0 + ? { + paddingTop: gutter[1]! / 2, + paddingBottom: gutter[1]! / 2, + } + : {}), + ...mergedStyle, + }; + } + if (flex) { + mergedStyle.flex = parseFlex(flex); + } + + return ( +
+ {slots.default?.()} +
+ ); + }; + }, +}); diff --git a/components/grid/Row.jsx b/components/grid/Row.jsx deleted file mode 100644 index 0ba0f7722..000000000 --- a/components/grid/Row.jsx +++ /dev/null @@ -1,114 +0,0 @@ -import { inject, provide, reactive } from 'vue'; -import PropTypes from '../_util/vue-types'; -import BaseMixin from '../_util/BaseMixin'; -import { ConfigConsumerProps } from '../config-provider'; -import ResponsiveObserve from '../_util/responsiveObserve'; -import { getSlot } from '../_util/props-util'; - -const RowProps = { - gutter: PropTypes.oneOfType([PropTypes.object, PropTypes.number, PropTypes.array]), - type: PropTypes.oneOf(['flex']), - align: PropTypes.oneOf(['top', 'middle', 'bottom', 'stretch']), - justify: PropTypes.oneOf(['start', 'end', 'center', 'space-around', 'space-between']), - prefixCls: PropTypes.string, -}; - -const responsiveArray = ['xxl', 'xl', 'lg', 'md', 'sm', 'xs']; - -export default { - name: 'ARow', - mixins: [BaseMixin], - props: { - ...RowProps, - gutter: PropTypes.oneOfType([PropTypes.object, PropTypes.number, PropTypes.array]).def(0), - }, - setup() { - const rowContext = reactive({ - getGutter: undefined, - }); - provide('rowContext', rowContext); - return { - configProvider: inject('configProvider', ConfigConsumerProps), - rowContext, - }; - }, - data() { - return { - screens: {}, - }; - }, - created() { - this.rowContext.getGutter = this.getGutter; - }, - mounted() { - this.$nextTick(() => { - this.token = ResponsiveObserve.subscribe(screens => { - const { gutter } = this; - if ( - typeof gutter === 'object' || - (Array.isArray(gutter) && - (typeof gutter[0] === 'object' || typeof gutter[1] === 'object')) - ) { - this.screens = screens; - } - }); - }); - }, - beforeUnmount() { - ResponsiveObserve.unsubscribe(this.token); - }, - methods: { - getGutter() { - const results = [0, 0]; - const { gutter, screens } = this; - const normalizedGutter = Array.isArray(gutter) ? gutter : [gutter, 0]; - normalizedGutter.forEach((g, index) => { - if (typeof g === 'object') { - for (let i = 0; i < responsiveArray.length; i++) { - const breakpoint = responsiveArray[i]; - if (screens[breakpoint] && g[breakpoint] !== undefined) { - results[index] = g[breakpoint]; - break; - } - } - } else { - results[index] = g || 0; - } - }); - return results; - }, - }, - - render() { - const { type, justify, align, prefixCls: customizePrefixCls } = this; - const getPrefixCls = this.configProvider.getPrefixCls; - const prefixCls = getPrefixCls('row', customizePrefixCls); - - const gutter = this.getGutter(); - const classes = { - [prefixCls]: !type, - [`${prefixCls}-${type}`]: type, - [`${prefixCls}-${type}-${justify}`]: type && justify, - [`${prefixCls}-${type}-${align}`]: type && align, - }; - const rowStyle = { - ...(gutter[0] > 0 - ? { - marginLeft: `${gutter[0] / -2}px`, - marginRight: `${gutter[0] / -2}px`, - } - : {}), - ...(gutter[1] > 0 - ? { - marginTop: `${gutter[1] / -2}px`, - marginBottom: `${gutter[1] / -2}px`, - } - : {}), - }; - return ( -
- {getSlot(this)} -
- ); - }, -}; diff --git a/components/grid/Row.tsx b/components/grid/Row.tsx new file mode 100644 index 000000000..9bd755f50 --- /dev/null +++ b/components/grid/Row.tsx @@ -0,0 +1,149 @@ +import { + inject, + provide, + reactive, + defineComponent, + HTMLAttributes, + ref, + onMounted, + onBeforeUnmount, +} from 'vue'; +import classNames from 'classnames'; +import { tuple } from '../_util/type'; +import PropTypes from '../_util/vue-types'; +import { ConfigConsumerProps } from '../config-provider'; +import ResponsiveObserve, { + Breakpoint, + ScreenMap, + responsiveArray, +} from '../_util/responsiveObserve'; + +const RowProps = { + gutter: PropTypes.oneOfType([PropTypes.object, PropTypes.number, PropTypes.array]), + type: PropTypes.oneOf(['flex']), + align: PropTypes.oneOf(['top', 'middle', 'bottom', 'stretch']), + justify: PropTypes.oneOf(['start', 'end', 'center', 'space-around', 'space-between']), + prefixCls: PropTypes.string, +}; +const RowAligns = tuple('top', 'middle', 'bottom', 'stretch'); +const RowJustify = tuple('start', 'end', 'center', 'space-around', 'space-between'); + +export type Gutter = number | Partial>; + +export interface rowContextState { + gutter?: [number, number]; +} + +export interface RowProps extends HTMLAttributes { + gutter?: Gutter | [Gutter, Gutter]; + align?: typeof RowAligns[number]; + justify?: typeof RowJustify[number]; + prefixCls?: string; +} + +export default defineComponent({ + name: 'ARow', + setup(props, { slots }) { + const rowContext = reactive({ + gutter: undefined, + }); + provide('rowContext', rowContext); + + let token: number; + + onMounted(() => { + token = ResponsiveObserve.subscribe(screen => { + const currentGutter = gutterRef.value || 0; + if ( + (!Array.isArray(currentGutter) && typeof currentGutter === 'object') || + (Array.isArray(currentGutter) && + (typeof currentGutter[0] === 'object' || typeof currentGutter[1] === 'object')) + ) { + screens.value = screen; + } + }); + }); + + onBeforeUnmount(() => { + ResponsiveObserve.unsubscribe(token); + }); + + const screens = ref({ + xs: true, + sm: true, + md: true, + lg: true, + xl: true, + xxl: true, + }); + const gutterRef = ref(); + gutterRef.value = props.gutter; + + const configProvider = inject('configProvider', ConfigConsumerProps); + const { getPrefixCls } = configProvider; + + const getGutter = (): [number, number] => { + const results: [number, number] = [0, 0]; + const { gutter = 0 } = props; + const normalizedGutter = Array.isArray(gutter) ? gutter : [gutter, 0]; + normalizedGutter.forEach((g, index) => { + if (typeof g === 'object') { + for (let i = 0; i < responsiveArray.length; i++) { + const breakpoint: Breakpoint = responsiveArray[i]; + if (screens.value[breakpoint] && g[breakpoint] !== undefined) { + results[index] = g[breakpoint] as number; + break; + } + } + } else { + results[index] = g || 0; + } + }); + return results; + }; + + return () => { + const { + prefixCls: customizePrefixCls, + justify, + align, + class: className, + style, + ...others + } = props; + const prefixCls = getPrefixCls('row', customizePrefixCls); + const gutter = getGutter(); + const classes = classNames( + prefixCls, + { + [`${prefixCls}-${justify}`]: justify, + [`${prefixCls}-${align}`]: align, + }, + className, + ); + const rowStyle = { + ...(gutter[0]! > 0 + ? { + marginLeft: gutter[0]! / -2, + marginRight: gutter[0]! / -2, + } + : {}), + ...(gutter[1]! > 0 + ? { + marginTop: gutter[1]! / -2, + marginBottom: gutter[1]! / 2, + } + : {}), + }; + const otherProps = { ...others, style }; + delete otherProps.gutter; + + rowContext.gutter = gutter; + return ( +
+ {slots.default?.()} +
+ ); + }; + }, +}); diff --git a/components/grid/index.js b/components/grid/index.ts similarity index 100% rename from components/grid/index.js rename to components/grid/index.ts diff --git a/components/grid/style/index.js b/components/grid/style/index.ts similarity index 100% rename from components/grid/style/index.js rename to components/grid/style/index.ts diff --git a/components/locale-provider/LocaleReceiver.tsx b/components/locale-provider/LocaleReceiver.tsx index 41261d20d..8254beeb3 100644 --- a/components/locale-provider/LocaleReceiver.tsx +++ b/components/locale-provider/LocaleReceiver.tsx @@ -1,4 +1,4 @@ -import { inject, defineComponent, VNode, VNodeChild } from 'vue'; +import { inject, defineComponent, VNodeChild, PropType } from 'vue'; import defaultLocaleData from './default'; export interface LocaleReceiverProps { @@ -11,8 +11,21 @@ interface LocaleInterface { [key: string]: any; } -export default defineComponent({ +export default defineComponent({ name: 'LocaleReceiver', + props: { + componentName: { + type: String, + }, + defaultLocale: { + type: [Object, Function], + }, + children: { + type: Function as PropType< + (locale: object, localeCode?: string, fullLocale?: object) => VNodeChild + >, + }, + }, setup() { return { localeData: inject('localeData', {}), diff --git a/components/locale-provider/index.tsx b/components/locale-provider/index.tsx index 48c3a68e7..2904fa144 100644 --- a/components/locale-provider/index.tsx +++ b/components/locale-provider/index.tsx @@ -38,7 +38,9 @@ function setMomentLocale(locale?: Locale) { const LocaleProvider = defineComponent({ name: 'ALocaleProvider', props: { - locale: PropTypes.object.def(() => ({})), + locale: { + type: Object, + }, _ANT_MARK__: PropTypes.string, }, data() { diff --git a/components/modal/index.ts b/components/modal/index.tsx similarity index 100% rename from components/modal/index.ts rename to components/modal/index.tsx diff --git a/components/space/index.tsx b/components/space/index.tsx index 18f05d04c..3105b7cf8 100644 --- a/components/space/index.tsx +++ b/components/space/index.tsx @@ -1,22 +1,15 @@ -import { inject, defineComponent, CSSProperties } from 'vue'; +import { inject, defineComponent, App } from 'vue'; +import { initDefaultProps } from '../_util/props-util'; import PropTypes from '../_util/vue-types'; import { filterEmpty } from '../_util/props-util'; import { ConfigConsumerProps } from '../config-provider'; -export interface SpaceProps { - prefixCls?: string; - class?: any; - style?: CSSProperties | string; - size?: number; - direction?: 'horizontal' | 'vertical'; - // No `stretch` since many components do not support that. - align?: 'start' | 'end' | 'center' | 'baseline'; -} - -export const SpaceSizeType = PropTypes.oneOfType([ - PropTypes.number, - PropTypes.oneOf(['small', 'middle', 'large']), -]); +export const SpaceProps = { + prefixCls: PropTypes.string, + align: PropTypes.tuple<'start' | 'end' | 'center' | 'baseline'>(), + size: PropTypes.tuple<'small' | 'middle' | 'large'>(), + direction: PropTypes.tuple<'horizontal' | 'vertical'>(), +}; const spaceSize = { small: 8, @@ -35,26 +28,25 @@ const Space = (props, { slots }) => { const configProvider = inject('configProvider', ConfigConsumerProps); const { align, size, direction, prefixCls: customizePrefixCls } = props; - const getPrefixCls = configProvider.getPrefixCls; - const prefixCls = getPrefixCls('space', customizePrefixCls); - const items = filterEmpty(slots.default?.()); - const len = items.length; + const { getPrefixCls } = configProvider; + const prefixCls = getPrefixCls('space', customizePrefixCls); + const items = filterEmpty(slots.default?.()); + const len = items.length; - if (len === 0) { - return null; - } + if (len === 0) { + return null; + } - const mergedAlign = align === undefined && direction === 'horizontal' ? 'center' : align; + const mergedAlign = align === undefined && direction === 'horizontal' ? 'center' : align; - const someSpaceClass = { - [prefixCls]: true, - [`${prefixCls}-${direction}`]: true, - // [`${prefixCls}-rtl`]: directionConfig === 'rtl', - [`${prefixCls}-align-${mergedAlign}`]: mergedAlign, - }; + const someSpaceClass = { + [prefixCls]: true, + [`${prefixCls}-${direction}`]: true, + [`${prefixCls}-align-${mergedAlign}`]: mergedAlign, + }; - const itemClassName = `${prefixCls}-item`; - const marginDirection = 'marginRight'; // directionConfig === 'rtl' ? 'marginLeft' : 'marginRight'; + const itemClassName = `${prefixCls}-item`; + const marginDirection = 'marginRight'; // directionConfig === 'rtl' ? 'marginLeft' : 'marginRight'; return (
diff --git a/examples/App.vue b/examples/App.vue index d649b1641..dad93a217 100644 --- a/examples/App.vue +++ b/examples/App.vue @@ -39,8 +39,12 @@ Use Drawer to quickly preview details of an object, such as those in a list. -

User Profile

-

Personal

+

+ User Profile +

+

+ Personal +

@@ -74,7 +78,9 @@ Use Drawer to quickly preview details of an object, such as those in a list. -

Company

+

+ Company +

@@ -104,7 +110,9 @@ Use Drawer to quickly preview details of an object, such as those in a list. -

Contacts

+

+ Contacts +

@@ -117,9 +125,9 @@ Use Drawer to quickly preview details of an object, such as those in a list. diff --git a/examples/index.js b/examples/index.js index ed2e2c975..318767e4e 100644 --- a/examples/index.js +++ b/examples/index.js @@ -1,21 +1,8 @@ import '@babel/polyfill'; -import 'ant-design-vue/style.js'; import { createApp, version } from 'vue'; import App from './App.vue'; -import antd from 'ant-design-vue/index.js'; - // eslint-disable-next-line no-console console.log('Vue version: ', version); -const basic = (_, { slots }) => { - return slots && slots.default && slots.default(); -}; + const app = createApp(App); -app - .component('demo-sort', basic) - .component('md', basic) - .component('api', basic) - .component('CN', basic) - .component('US', basic) - .component('demo-container', basic) - .use(antd) - .mount('#app'); +app.mount('#app'); diff --git a/package.json b/package.json index 1c5cb8197..39524f073 100644 --- a/package.json +++ b/package.json @@ -76,6 +76,7 @@ "@babel/plugin-transform-template-literals": "^7.8.3", "@babel/polyfill": "^7.8.7", "@babel/preset-env": "^7.9.6", + "@babel/preset-typescript": "^7.10.4", "@commitlint/cli": "^8.0.0", "@commitlint/config-conventional": "^8.0.0", "@octokit/rest": "^16.0.0", @@ -159,6 +160,7 @@ "stylelint-config-standard": "^19.0.0", "terser-webpack-plugin": "^3.0.3", "through2": "^3.0.0", + "ts-loader": "^8.0.2", "typescript": "^3.9.7", "umi-mock-middleware": "^1.0.0", "umi-request": "^1.3.5", diff --git a/webpack.config.js b/webpack.config.js index 29abdb915..65df7648b 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -4,6 +4,44 @@ const WebpackBar = require('webpackbar'); const path = require('path'); const MiniCssExtractPlugin = require('mini-css-extract-plugin'); +const babelConfig = { + cacheDirectory: true, + presets: [ + [ + '@babel/preset-env', + { + targets: { + browsers: [ + 'last 2 versions', + 'Firefox ESR', + '> 1%', + 'ie >= 9', + 'iOS >= 8', + 'Android >= 4', + ], + }, + }, + ], + '@babel/preset-typescript', + ], + plugins: [ + [ + 'babel-plugin-import', + { + libraryName: 'ant-design-vue', + libraryDirectory: '', // default: lib + style: true, + }, + ], + ['@ant-design-vue/babel-plugin-jsx', { optimize: true }], + '@babel/plugin-proposal-optional-chaining', + '@babel/plugin-transform-object-assign', + '@babel/plugin-proposal-object-rest-spread', + '@babel/plugin-proposal-export-default-from', + '@babel/plugin-proposal-class-properties', + ], +}; + module.exports = { mode: 'development', entry: { @@ -20,46 +58,27 @@ module.exports = { test: /\.(en-US.md|zh-CN.md)$/, use: [{ loader: 'vue-loader' }, { loader: './loader.js' }], }, + { + test: /\.tsx?$/, + use: [ + { + loader: 'babel-loader', + options: babelConfig, + }, + { + loader: 'ts-loader', + options: { + transpileOnly: true, + }, + }, + ], + exclude: /node_modules/, + }, { test: /\.(js|jsx)$/, loader: 'babel-loader', exclude: /pickr.*js/, - options: { - cacheDirectory: true, - presets: [ - [ - '@babel/preset-env', - { - targets: { - browsers: [ - 'last 2 versions', - 'Firefox ESR', - '> 1%', - 'ie >= 9', - 'iOS >= 8', - 'Android >= 4', - ], - }, - }, - ], - ], - plugins: [ - [ - 'babel-plugin-import', - { - libraryName: 'ant-design-vue', - libraryDirectory: '', // default: lib - style: true, - }, - ], - ['@ant-design-vue/babel-plugin-jsx', { transformOn: true, usePatchFlag: false }], - '@babel/plugin-proposal-optional-chaining', - '@babel/plugin-transform-object-assign', - '@babel/plugin-proposal-object-rest-spread', - '@babel/plugin-proposal-export-default-from', - '@babel/plugin-proposal-class-properties', - ], - }, + options: babelConfig, }, { test: /\.(png|jpg|gif|svg)$/, @@ -115,7 +134,7 @@ module.exports = { vue$: 'vue/dist/vue.esm-bundler.js', }, - extensions: ['.js', '.jsx', '.vue', '.md'], + extensions: ['.js', '.jsx', '.ts', '.tsx', '.vue', '.md'], }, devServer: { historyApiFallback: {