refactor: wave

pull/5202/head
tangjinzhou 2022-01-23 14:48:27 +08:00
parent 7657157586
commit d442db08eb
3 changed files with 95 additions and 92 deletions

View File

@ -1,7 +1,7 @@
import type { RequiredMark } from '../../form/Form'; import type { RequiredMark } from '../../form/Form';
import type { ComputedRef, UnwrapRef } from 'vue'; import type { ComputedRef, UnwrapRef } from 'vue';
import { computed, inject } from 'vue'; import { computed, inject } from 'vue';
import type { ConfigProviderProps, Direction, SizeType } from '../../config-provider'; import type { ConfigProviderProps, CSPConfig, Direction, SizeType } from '../../config-provider';
import { defaultConfigProvider } from '../../config-provider'; import { defaultConfigProvider } from '../../config-provider';
import type { VueNode } from '../type'; import type { VueNode } from '../type';
@ -27,6 +27,7 @@ export default (
getPopupContainer: ComputedRef<ConfigProviderProps['getPopupContainer']>; getPopupContainer: ComputedRef<ConfigProviderProps['getPopupContainer']>;
getPrefixCls: ConfigProviderProps['getPrefixCls']; getPrefixCls: ConfigProviderProps['getPrefixCls'];
autocomplete: ComputedRef<string>; autocomplete: ComputedRef<string>;
csp: ComputedRef<CSPConfig>;
} => { } => {
const configProvider = inject<UnwrapRef<ConfigProviderProps>>( const configProvider = inject<UnwrapRef<ConfigProviderProps>>(
'configProvider', 'configProvider',
@ -52,6 +53,7 @@ export default (
); );
const size = computed(() => props.size || configProvider.componentSize); const size = computed(() => props.size || configProvider.componentSize);
const autocomplete = computed(() => props.autocomplete || configProvider.input?.autocomplete); const autocomplete = computed(() => props.autocomplete || configProvider.input?.autocomplete);
const csp = computed(() => configProvider.csp);
return { return {
configProvider, configProvider,
prefixCls, prefixCls,
@ -69,5 +71,6 @@ export default (
rootPrefixCls, rootPrefixCls,
getPrefixCls: configProvider.getPrefixCls, getPrefixCls: configProvider.getPrefixCls,
autocomplete, autocomplete,
csp,
}; };
}; };

View File

@ -1,18 +1,18 @@
import { nextTick, inject, defineComponent } from 'vue'; import { nextTick, defineComponent, getCurrentInstance, onMounted, onBeforeUnmount } from 'vue';
import TransitionEvents from './css-animation/Event'; import TransitionEvents from './css-animation/Event';
import raf from './raf'; import raf from './raf';
import { defaultConfigProvider } from '../config-provider';
import { findDOMNode } from './props-util'; import { findDOMNode } from './props-util';
let styleForPesudo; import useConfigInject from './hooks/useConfigInject';
let styleForPesudo: HTMLStyleElement;
// Where el is the DOM element you'd like to test for visibility // Where el is the DOM element you'd like to test for visibility
function isHidden(element) { function isHidden(element: HTMLElement) {
if (process.env.NODE_ENV === 'test') { if (process.env.NODE_ENV === 'test') {
return false; return false;
} }
return !element || element.offsetParent === null; return !element || element.offsetParent === null;
} }
function isNotGrey(color) { function isNotGrey(color: string) {
// eslint-disable-next-line no-useless-escape // eslint-disable-next-line no-useless-escape
const match = (color || '').match(/rgba?\((\d*), (\d*), (\d*)(, [\.\d]*)?\)/); const match = (color || '').match(/rgba?\((\d*), (\d*), (\d*)(, [\.\d]*)?\)/);
if (match && match[1] && match[2] && match[3]) { if (match && match[1] && match[2] && match[3]) {
@ -22,40 +22,51 @@ function isNotGrey(color) {
} }
export default defineComponent({ export default defineComponent({
name: 'Wave', name: 'Wave',
props: ['insertExtraNode'], props: {
setup() { insertExtraNode: Boolean,
const configProvider = inject('configProvider', defaultConfigProvider);
return {
configProvider,
};
}, },
mounted() { setup(props, { slots, expose }) {
nextTick(() => { const instance = getCurrentInstance();
const node = findDOMNode(this); const { csp } = useConfigInject('', props);
if (node.nodeType !== 1) { expose({
csp,
});
let eventIns = null;
let clickWaveTimeoutId = null;
let animationStartId = null;
let animationStart = false;
let extraNode = null;
let isUnmounted = false;
const onTransitionStart = e => {
if (isUnmounted) return;
const node = findDOMNode(instance);
if (!e || e.target !== node) {
return; return;
} }
this.instance = this.bindAnimationEvent(node);
}); if (!animationStart) {
}, resetEffect(node);
beforeUnmount() {
if (this.instance) {
this.instance.cancel();
} }
if (this.clickWaveTimeoutId) { };
clearTimeout(this.clickWaveTimeoutId); const onTransitionEnd = (e: any) => {
if (!e || e.animationName !== 'fadeEffect') {
return;
} }
}, resetEffect(e.target);
methods: { };
onClick(node, waveColor) { const getAttributeName = () => {
const { insertExtraNode } = props;
return insertExtraNode ? 'ant-click-animating' : 'ant-click-animating-without-extra-node';
};
const onClick = (node: HTMLElement, waveColor: string) => {
if (!node || isHidden(node) || node.className.indexOf('-leave') >= 0) { if (!node || isHidden(node) || node.className.indexOf('-leave') >= 0) {
return; return;
} }
const { insertExtraNode } = this.$props; const { insertExtraNode } = props;
this.extraNode = document.createElement('div'); extraNode = document.createElement('div');
const extraNode = this.extraNode;
extraNode.className = 'ant-click-animating-node'; extraNode.className = 'ant-click-animating-node';
const attributeName = this.getAttributeName(); const attributeName = getAttributeName();
node.removeAttribute(attributeName); node.removeAttribute(attributeName);
node.setAttribute(attributeName, 'true'); node.setAttribute(attributeName, 'true');
// Not white or transparent or grey // Not white or transparent or grey
@ -69,8 +80,8 @@ export default defineComponent({
waveColor !== 'transparent' waveColor !== 'transparent'
) { ) {
// Add nonce if CSP exist // Add nonce if CSP exist
if (this.csp && this.csp.nonce) { if (csp.value?.nonce) {
styleForPesudo.nonce = this.csp.nonce; styleForPesudo.nonce = csp.value.nonce;
} }
extraNode.style.borderColor = waveColor; extraNode.style.borderColor = waveColor;
styleForPesudo.innerHTML = ` styleForPesudo.innerHTML = `
@ -84,32 +95,26 @@ export default defineComponent({
if (insertExtraNode) { if (insertExtraNode) {
node.appendChild(extraNode); node.appendChild(extraNode);
} }
TransitionEvents.addStartEventListener(node, this.onTransitionStart); TransitionEvents.addStartEventListener(node, onTransitionStart);
TransitionEvents.addEndEventListener(node, this.onTransitionEnd); TransitionEvents.addEndEventListener(node, onTransitionEnd);
}, };
onTransitionStart(e) { const resetEffect = (node: HTMLElement) => {
if (this._.isUnmounted) return; if (!node || node === extraNode || !(node instanceof Element)) {
const node = findDOMNode(this);
if (!e || e.target !== node) {
return; return;
} }
const { insertExtraNode } = props;
if (!this.animationStart) { const attributeName = getAttributeName();
this.resetEffect(node); node.setAttribute(attributeName, 'false'); // edge has bug on `removeAttribute` #14466
if (styleForPesudo) {
styleForPesudo.innerHTML = '';
} }
}, if (insertExtraNode && extraNode && node.contains(extraNode)) {
onTransitionEnd(e) { node.removeChild(extraNode);
if (!e || e.animationName !== 'fadeEffect') {
return;
} }
this.resetEffect(e.target); TransitionEvents.removeStartEventListener(node, onTransitionStart);
}, TransitionEvents.removeEndEventListener(node, onTransitionEnd);
getAttributeName() { };
const { insertExtraNode } = this.$props; const bindAnimationEvent = (node: HTMLElement) => {
return insertExtraNode ? 'ant-click-animating' : 'ant-click-animating-without-extra-node';
},
bindAnimationEvent(node) {
if ( if (
!node || !node ||
!node.getAttribute || !node.getAttribute ||
@ -118,57 +123,51 @@ export default defineComponent({
) { ) {
return; return;
} }
const onClick = e => { const newClick = (e: MouseEvent) => {
// Fix radio button click twice // Fix radio button click twice
if (e.target.tagName === 'INPUT' || isHidden(e.target)) { if ((e.target as any).tagName === 'INPUT' || isHidden(e.target as HTMLElement)) {
return; return;
} }
this.resetEffect(node); resetEffect(node);
// Get wave color from target // Get wave color from target
const waveColor = const waveColor =
getComputedStyle(node).getPropertyValue('border-top-color') || // Firefox Compatible getComputedStyle(node).getPropertyValue('border-top-color') || // Firefox Compatible
getComputedStyle(node).getPropertyValue('border-color') || getComputedStyle(node).getPropertyValue('border-color') ||
getComputedStyle(node).getPropertyValue('background-color'); getComputedStyle(node).getPropertyValue('background-color');
this.clickWaveTimeoutId = setTimeout(() => this.onClick(node, waveColor), 0); clickWaveTimeoutId = setTimeout(() => onClick(node, waveColor), 0);
raf.cancel(this.animationStartId); raf.cancel(animationStartId);
this.animationStart = true; animationStart = true;
// Render to trigger transition event cost 3 frames. Let's delay 10 frames to reset this. // Render to trigger transition event cost 3 frames. Let's delay 10 frames to reset this.
this.animationStartId = raf(() => { animationStartId = raf(() => {
this.animationStart = false; animationStart = false;
}, 10); }, 10);
}; };
node.addEventListener('click', onClick, true); node.addEventListener('click', newClick, true);
return { return {
cancel: () => { cancel: () => {
node.removeEventListener('click', onClick, true); node.removeEventListener('click', newClick, true);
}, },
}; };
}, };
onMounted(() => {
resetEffect(node) { nextTick(() => {
if (!node || node === this.extraNode || !(node instanceof Element)) { const node = findDOMNode(instance);
if (node.nodeType !== 1) {
return; return;
} }
const { insertExtraNode } = this.$props; eventIns = bindAnimationEvent(node);
const attributeName = this.getAttributeName(); });
node.setAttribute(attributeName, 'false'); // edge has bug on `removeAttribute` #14466 });
if (styleForPesudo) { onBeforeUnmount(() => {
styleForPesudo.innerHTML = ''; if (eventIns) {
eventIns.cancel();
} }
if (insertExtraNode && this.extraNode && node.contains(this.extraNode)) { clearTimeout(clickWaveTimeoutId);
node.removeChild(this.extraNode); isUnmounted = true;
} });
TransitionEvents.removeStartEventListener(node, this.onTransitionStart); return () => {
TransitionEvents.removeEndEventListener(node, this.onTransitionEnd); return slots.default?.()[0];
}, };
},
render() {
const csp = this.configProvider.csp;
if (csp) {
this.csp = csp;
}
return this.$slots.default?.()[0];
}, },
}); });

View File

@ -161,6 +161,7 @@ export const configProviderProps = {
}, },
csp: { csp: {
type: Object as PropType<CSPConfig>, type: Object as PropType<CSPConfig>,
default: undefined as CSPConfig,
}, },
input: { input: {
type: Object as PropType<{ autocomplete: string }>, type: Object as PropType<{ autocomplete: string }>,