diff --git a/components/_util/component-classes.ts b/components/_util/component-classes.ts new file mode 100644 index 0000000000..cfbb9cf669 --- /dev/null +++ b/components/_util/component-classes.ts @@ -0,0 +1,168 @@ +/** + * source by `component-classes` + * https://github.com/component/classes.git + */ + +import { indexOf } from 'lodash-es'; + +/** + * Whitespace regexp. + */ +const re = /\s+/; + +export class ClassList { + el: Element; + list: DOMTokenList; + + constructor(el: Element) { + if (!el || !el.nodeType) { + throw new Error('A DOM element reference is required'); + } + this.el = el; + this.list = el.classList; + } + + array() { + const className = this.el.getAttribute('class') || ''; + const str = className.replace(/^\s+|\s+$/g, ''); + const arr = str.split(re); + if ('' === arr[0]) arr.shift(); + return arr; + } + + /** + * Add class `name` if not already present. + * + * @param {String} name + * @return {ClassList} + * @api public + */ + add(name: string): ClassList { + // classList + if (this.list) { + this.list.add(name); + return this; + } + + // fallback + const arr = this.array(); + const i = indexOf(arr, name); + if (!~i) arr.push(name); + this.el.className = arr.join(' '); + return this; + } + /** + * Remove class `name` when present, or + * pass a regular expression to remove + * any which match. + * + * @param {String|RegExp} name + * @return {ClassList} + * @api public + */ + remove(name: string | RegExp): ClassList { + if ('[object RegExp]' === toString.call(name)) { + return this._removeMatching(name as RegExp); + } + + // classList + if (this.list) { + this.list.remove(name as string); + return this; + } + + // fallback + const arr = this.array(); + const i = indexOf(arr, name); + if (~i) arr.splice(i, 1); + this.el.className = arr.join(' '); + return this; + } + /** + * Remove all classes matching `re`. + * + * @param {RegExp} re + * @return {ClassList} + * @api private + */ + _removeMatching(re: RegExp): ClassList { + const arr = this.array(); + for (let i = 0; i < arr.length; i++) { + if (re.test(arr[i])) { + this.remove(arr[i]); + } + } + return this; + } + + /** + * Toggle class `name`, can force state via `force`. + * + * For browsers that support classList, but do not support `force` yet, + * the mistake will be detected and corrected. + * + * @param {String} name + * @param {Boolean} force + * @return {ClassList} + * @api public + */ + toggle(name: string, force: boolean): ClassList { + // classList + if (this.list) { + if ('undefined' !== typeof force) { + if (force !== this.list.toggle(name, force)) { + this.list.toggle(name); // toggle again to correct + } + } else { + this.list.toggle(name); + } + return this; + } + + // fallback + if ('undefined' !== typeof force) { + if (!force) { + this.remove(name); + } else { + this.add(name); + } + } else { + if (this.has(name)) { + this.remove(name); + } else { + this.add(name); + } + } + + return this; + } + /** + * Check if class `name` is present. + * + * @param {String} name + * @api public + */ + has(name: string) { + return this.list ? this.list.contains(name) : !!~indexOf(this.array(), name); + } + /** + * Check if class `name` is present. + * + * @param {String} name + * @api public + */ + contains(name: string) { + return this.has(name); + } +} + +/** + * Wrap `el` in a `ClassList`. + * + * @param {Element} el + * @return {ClassList} + * @api public + */ +export default function(el: Element): ClassList { + return new ClassList(el); +} diff --git a/components/_util/css-animation/index.js b/components/_util/css-animation/index.js index e7d2f45db7..b9b400ec9d 100644 --- a/components/_util/css-animation/index.js +++ b/components/_util/css-animation/index.js @@ -1,7 +1,7 @@ // https://github.com/yiminghe/css-animation 1.5.0 import Event from './Event'; -import classes from 'component-classes'; +import classes from '../component-classes'; import { requestAnimationTimeout, cancelAnimationTimeout } from '../requestAnimationTimeout'; const isCssAnimationSupported = Event.endEvents.length !== 0; diff --git a/components/_util/dom-closest.js b/components/_util/dom-closest.js new file mode 100644 index 0000000000..13f73f7d34 --- /dev/null +++ b/components/_util/dom-closest.js @@ -0,0 +1,24 @@ +/** + * source by `dom-closest` + * https://github.com/necolas/dom-closest.git + */ + +import matches from './dom-matches'; + +/** + * @param element {Element} + * @param selector {String} + * @param context {Element=} + * @return {Element} + */ +export default function(element, selector, context) { + context = context || document; + // guard against orphans + element = { parentNode: element }; + + while ((element = element.parentNode) && element !== context) { + if (matches(element, selector)) { + return element; + } + } +} diff --git a/components/_util/dom-matches.js b/components/_util/dom-matches.js new file mode 100644 index 0000000000..681982d0eb --- /dev/null +++ b/components/_util/dom-matches.js @@ -0,0 +1,47 @@ +/** + * source by `dom-matches` + * https://github.com/necolas/dom-matches.git + */ + +/** + * Determine if a DOM element matches a CSS selector + * + * @param {Element} elem + * @param {String} selector + * @return {Boolean} + * @api public + */ + +export default function matches(elem, selector) { + // Vendor-specific implementations of `Element.prototype.matches()`. + const proto = window.Element.prototype; + const nativeMatches = + proto.matches || + proto.mozMatchesSelector || + proto.msMatchesSelector || + proto.oMatchesSelector || + proto.webkitMatchesSelector; + + if (!elem || elem.nodeType !== 1) { + return false; + } + + const parentElem = elem.parentNode; + + // use native 'matches' + if (nativeMatches) { + return nativeMatches.call(elem, selector); + } + + // native support for `matches` is missing and a fallback is required + const nodes = parentElem.querySelectorAll(selector); + const len = nodes.length; + + for (let i = 0; i < len; i++) { + if (nodes[i] === elem) { + return true; + } + } + + return false; +} diff --git a/components/_util/json2mq.js b/components/_util/json2mq.js new file mode 100644 index 0000000000..e3095c35cc --- /dev/null +++ b/components/_util/json2mq.js @@ -0,0 +1,60 @@ +/** + * source by `json2mq` + * https://github.com/akiran/json2mq.git + */ + +const camel2hyphen = function(str) { + return str + .replace(/[A-Z]/g, function(match) { + return '-' + match.toLowerCase(); + }) + .toLowerCase(); +}; + +const isDimension = function(feature) { + const re = /[height|width]$/; + return re.test(feature); +}; + +const obj2mq = function(obj) { + let mq = ''; + const features = Object.keys(obj); + features.forEach(function(feature, index) { + let value = obj[feature]; + feature = camel2hyphen(feature); + // Add px to dimension features + if (isDimension(feature) && typeof value === 'number') { + value = value + 'px'; + } + if (value === true) { + mq += feature; + } else if (value === false) { + mq += 'not ' + feature; + } else { + mq += '(' + feature + ': ' + value + ')'; + } + if (index < features.length - 1) { + mq += ' and '; + } + }); + return mq; +}; + +export default function(query) { + let mq = ''; + if (typeof query === 'string') { + return query; + } + // Handling array of media queries + if (query instanceof Array) { + query.forEach(function(q, index) { + mq += obj2mq(q); + if (index < query.length - 1) { + mq += ', '; + } + }); + return mq; + } + // Handling single media query + return obj2mq(query); +} diff --git a/components/_util/openAnimation.js b/components/_util/openAnimation.js index 0cd76cdc42..bd92642330 100644 --- a/components/_util/openAnimation.js +++ b/components/_util/openAnimation.js @@ -1,5 +1,4 @@ import cssAnimation from './css-animation'; -import raf from 'raf'; import { nextTick } from 'vue'; function animate(node, show, done) { @@ -9,7 +8,7 @@ function animate(node, show, done) { return cssAnimation(node, 'ant-motion-collapse-legacy', { start() { if (appearRequestAnimationFrameId) { - raf.cancel(appearRequestAnimationFrameId); + cancelAnimationFrame(appearRequestAnimationFrameId); } if (!show) { node.style.height = `${node.offsetHeight}px`; @@ -19,7 +18,7 @@ function animate(node, show, done) { // not get offsetHeight when appear // set it into raf get correct offsetHeight if (height === 0) { - appearRequestAnimationFrameId = raf(() => { + appearRequestAnimationFrameId = requestAnimationFrame(() => { height = node.offsetHeight; node.style.height = '0px'; node.style.opacity = '0'; @@ -32,19 +31,19 @@ function animate(node, show, done) { }, active() { if (requestAnimationFrameId) { - raf.cancel(requestAnimationFrameId); + cancelAnimationFrame(requestAnimationFrameId); } - requestAnimationFrameId = raf(() => { + requestAnimationFrameId = requestAnimationFrame(() => { node.style.height = `${show ? height : 0}px`; node.style.opacity = show ? '1' : '0'; }); }, end() { if (appearRequestAnimationFrameId) { - raf.cancel(appearRequestAnimationFrameId); + cancelAnimationFrame(appearRequestAnimationFrameId); } if (requestAnimationFrameId) { - raf.cancel(requestAnimationFrameId); + cancelAnimationFrame(requestAnimationFrameId); } node.style.height = ''; node.style.opacity = ''; diff --git a/components/_util/raf.ts b/components/_util/raf.ts index 6659e3d7d1..f927f1c04a 100644 --- a/components/_util/raf.ts +++ b/components/_util/raf.ts @@ -1,5 +1,3 @@ -import raf from 'raf'; - interface RafMap { [id: number]: number; } @@ -19,11 +17,11 @@ export default function wrapperRaf(callback: () => void, delayFrames = 1): numbe callback(); delete ids[myId]; } else { - ids[myId] = raf(internalCallback); + ids[myId] = requestAnimationFrame(internalCallback); } } - ids[myId] = raf(internalCallback); + ids[myId] = requestAnimationFrame(internalCallback); return myId; } @@ -31,7 +29,7 @@ export default function wrapperRaf(callback: () => void, delayFrames = 1): numbe wrapperRaf.cancel = function cancel(pid?: number) { if (pid === undefined) return; - raf.cancel(ids[pid]); + cancelAnimationFrame(ids[pid]); delete ids[pid]; }; diff --git a/components/_util/scrollTo.ts b/components/_util/scrollTo.ts index 8f4115cba5..31237d2487 100644 --- a/components/_util/scrollTo.ts +++ b/components/_util/scrollTo.ts @@ -1,4 +1,3 @@ -import raf from 'raf'; import getScroll from './getScroll'; import { easeInOutCubic } from './easings'; @@ -28,10 +27,10 @@ export default function scrollTo(y: number, options: ScrollToOptions = {}) { (container as HTMLElement).scrollTop = nextScrollTop; } if (time < duration) { - raf(frameFunc); + requestAnimationFrame(frameFunc); } else if (typeof callback === 'function') { callback(); } }; - raf(frameFunc); + requestAnimationFrame(frameFunc); } diff --git a/components/_util/shallowequal.js b/components/_util/shallowequal.js index aa269ee752..6e1dd790a1 100644 --- a/components/_util/shallowequal.js +++ b/components/_util/shallowequal.js @@ -1,6 +1,50 @@ -import shallowequal from 'shallowequal'; import { toRaw } from 'vue'; +function shallowEqual(objA, objB, compare, compareContext) { + let ret = compare ? compare.call(compareContext, objA, objB) : void 0; + + if (ret !== void 0) { + return !!ret; + } + + if (objA === objB) { + return true; + } + + if (typeof objA !== 'object' || !objA || typeof objB !== 'object' || !objB) { + return false; + } + + const keysA = Object.keys(objA); + const keysB = Object.keys(objB); + + if (keysA.length !== keysB.length) { + return false; + } + + const bHasOwnProperty = Object.prototype.hasOwnProperty.bind(objB); + + // Test for A's keys different from B. + for (let idx = 0; idx < keysA.length; idx++) { + const key = keysA[idx]; + + if (!bHasOwnProperty(key)) { + return false; + } + + const valueA = objA[key]; + const valueB = objB[key]; + + ret = compare ? compare.call(compareContext, valueA, valueB, key) : void 0; + + if (ret === false || (ret === void 0 && valueA !== valueB)) { + return false; + } + } + + return true; +} + export default function(value, other, customizer, thisArg) { - return shallowequal(toRaw(value), toRaw(other), customizer, thisArg); + return shallowEqual(toRaw(value), toRaw(other), customizer, thisArg); } diff --git a/components/_util/throttleByAnimationFrame.ts b/components/_util/throttleByAnimationFrame.ts index 78e9ee0367..9f13d0b72a 100644 --- a/components/_util/throttleByAnimationFrame.ts +++ b/components/_util/throttleByAnimationFrame.ts @@ -1,5 +1,3 @@ -import raf from 'raf'; - export default function throttleByAnimationFrame(fn: (...args: any[]) => void) { let requestId: number | null; @@ -10,11 +8,11 @@ export default function throttleByAnimationFrame(fn: (...args: any[]) => void) { const throttled = (...args: any[]) => { if (requestId == null) { - requestId = raf(later(args)); + requestId = requestAnimationFrame(later(args)); } }; - (throttled as any).cancel = () => raf.cancel(requestId!); + (throttled as any).cancel = () => cancelAnimationFrame(requestId!); return throttled; } diff --git a/components/table/filterDropdown.tsx b/components/table/filterDropdown.tsx index e0cb5a0a13..4db529b325 100755 --- a/components/table/filterDropdown.tsx +++ b/components/table/filterDropdown.tsx @@ -1,7 +1,7 @@ import { reactive, defineComponent, nextTick, computed, watch } from 'vue'; import FilterFilled from '@ant-design/icons-vue/FilterFilled'; import Menu, { SubMenu, Item as MenuItem } from '../vc-menu'; -import closest from 'dom-closest'; +import closest from '../_util/dom-closest'; import classNames from '../_util/classNames'; import shallowequal from '../_util/shallowequal'; import Dropdown from '../dropdown'; diff --git a/components/vc-slick/src/slider.js b/components/vc-slick/src/slider.js index 5fa27e9de1..6029fc9d77 100644 --- a/components/vc-slick/src/slider.js +++ b/components/vc-slick/src/slider.js @@ -1,4 +1,4 @@ -import json2mq from 'json2mq'; +import json2mq from '../../_util/json2mq'; import BaseMixin from '../../_util/BaseMixin'; import { cloneElement } from '../../_util/vnode'; import InnerSlider from './inner-slider'; diff --git a/components/vc-table/src/Table.jsx b/components/vc-table/src/Table.jsx index 659565dddc..032cde2f59 100644 --- a/components/vc-table/src/Table.jsx +++ b/components/vc-table/src/Table.jsx @@ -2,7 +2,7 @@ import { provide, markRaw, defineComponent } from 'vue'; import shallowequal from '../../_util/shallowequal'; import merge from 'lodash-es/merge'; -import classes from 'component-classes'; +import classes from '../../_util/component-classes'; import classNames from '../../_util/classNames'; import PropTypes from '../../_util/vue-types'; import { debounce, getDataAndAriaProps } from './utils'; diff --git a/components/vc-tabs/src/Tabs.jsx b/components/vc-tabs/src/Tabs.jsx index f6be1d2150..8c66e3a057 100644 --- a/components/vc-tabs/src/Tabs.jsx +++ b/components/vc-tabs/src/Tabs.jsx @@ -1,7 +1,6 @@ import { defineComponent, provide, reactive, watchEffect } from 'vue'; import BaseMixin from '../../_util/BaseMixin'; import PropTypes from '../../_util/vue-types'; -import raf from 'raf'; import KeyCode from './KeyCode'; import { cloneElement } from '../../_util/vnode'; import Sentinel from './Sentinel'; @@ -80,7 +79,7 @@ export default defineComponent({ }, beforeUnmount() { this.destroy = true; - raf.cancel(this.sentinelId); + cancelAnimationFrame(this.sentinelId); }, methods: { onTabClick(activeKey, e) { @@ -171,8 +170,8 @@ export default defineComponent({ updateSentinelContext() { if (this.destroy) return; - raf.cancel(this.sentinelId); - this.sentinelId = raf(() => { + cancelAnimationFrame(this.sentinelId); + this.sentinelId = requestAnimationFrame(() => { if (this.destroy) return; this.$forceUpdate(); }); diff --git a/components/vc-time-picker/Select.jsx b/components/vc-time-picker/Select.jsx index a9a7fcc483..a151999a16 100644 --- a/components/vc-time-picker/Select.jsx +++ b/components/vc-time-picker/Select.jsx @@ -1,14 +1,13 @@ import PropTypes from '../_util/vue-types'; import BaseMixin from '../_util/BaseMixin'; import classnames from '../_util/classNames'; -import raf from 'raf'; import { findDOMNode } from '../_util/props-util'; function noop() {} const scrollTo = (element, to, duration) => { // jump to target if duration zero if (duration <= 0) { - raf(() => { + requestAnimationFrame(() => { element.scrollTop = to; }); return; @@ -16,7 +15,7 @@ const scrollTo = (element, to, duration) => { const difference = to - element.scrollTop; const perTick = (difference / duration) * 10; - raf(() => { + requestAnimationFrame(() => { element.scrollTop += perTick; if (element.scrollTop === to) return; scrollTo(element, to, duration - 10); diff --git a/components/vc-tree-select/src/Select.jsx b/components/vc-tree-select/src/Select.jsx index 5ad33fd0f8..33edb0edba 100644 --- a/components/vc-tree-select/src/Select.jsx +++ b/components/vc-tree-select/src/Select.jsx @@ -20,7 +20,6 @@ */ import { defineComponent, provide } from 'vue'; import shallowEqual from '../../_util/shallowequal'; -import raf from 'raf'; import scrollIntoView from 'dom-scroll-into-view'; import warning from 'warning'; import PropTypes, { withUndefined } from '../../_util/vue-types'; @@ -227,7 +226,7 @@ const Select = defineComponent({ if (treeNode) { const domNode = findDOMNode(treeNode); - raf(() => { + requestAnimationFrame(() => { const popupNode = findDOMNode(this.popup); const triggerContainer = findPopupContainer(popupNode, `${prefixCls}-dropdown`); @@ -886,7 +885,7 @@ const Select = defineComponent({ }, onChoiceAnimationLeave() { - raf(() => { + requestAnimationFrame(() => { this.forcePopupAlign(); }); }, @@ -963,8 +962,8 @@ const Select = defineComponent({ delayForcePopupAlign() { // Wait 2 frame to avoid dom update & dom algin in the same time // https://github.com/ant-design/ant-design/issues/12031 - raf(() => { - raf(this.forcePopupAlign); + requestAnimationFrame(() => { + requestAnimationFrame(this.forcePopupAlign); }); }, diff --git a/package.json b/package.json index bcdaae75f4..b7eccba3dd 100644 --- a/package.json +++ b/package.json @@ -201,35 +201,25 @@ "webpack-dev-server": "^3.1.14", "webpack-merge": "^4.1.1", "webpackbar": "^4.0.0", - "xhr-mock": "^2.5.1" + "xhr-mock": "^2.5.1", + "node-emoji": "^1.10.0" }, "dependencies": { "@ant-design-vue/use": "^0.0.1-0", "@ant-design/icons-vue": "^5.1.7", "@babel/runtime": "^7.10.5", "@simonwep/pickr": "~1.7.0", - "add-dom-event-listener": "^1.0.2", "array-tree-filter": "^2.1.0", "async-validator": "^3.3.0", - "babel-helper-vue-jsx-merge-props": "^2.0.3", - "component-classes": "^1.2.6", "dom-align": "^1.10.4", - "dom-closest": "^0.2.0", "dom-scroll-into-view": "^2.0.0", - "intersperse": "^1.0.0", "is-mobile": "^2.2.1", - "is-negative-zero": "^2.0.0", - "ismobilejs": "^1.0.0", - "json2mq": "^0.2.0", "lodash-es": "^4.17.15", "moment": "^2.27.0", - "node-emoji": "^1.10.0", "omit.js": "^2.0.0", - "raf": "^3.4.0", "resize-observer-polyfill": "^1.5.1", "scroll-into-view-if-needed": "^2.2.25", "shallow-equal": "^1.0.0", - "shallowequal": "^1.0.2", "vue-types": "^3.0.0", "warning": "^4.0.0" }, diff --git a/typings/custom-typings.d.ts b/typings/custom-typings.d.ts index a38f077c00..81a4b36715 100644 --- a/typings/custom-typings.d.ts +++ b/typings/custom-typings.d.ts @@ -1,4 +1,3 @@ -declare module 'component-classes'; declare module 'omit.js'; declare module '*.json' {