forked from vueComponent/ant-design-vue
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathwave.tsx
175 lines (170 loc) · 5.8 KB
/
wave.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
import { nextTick, defineComponent, getCurrentInstance, onMounted, onBeforeUnmount } from 'vue';
import TransitionEvents from './css-animation/Event';
import raf from './raf';
import { findDOMNode } from './props-util';
import useConfigInject from './hooks/useConfigInject';
let styleForPesudo: HTMLStyleElement;
// Where el is the DOM element you'd like to test for visibility
function isHidden(element: HTMLElement) {
if (process.env.NODE_ENV === 'test') {
return false;
}
return !element || element.offsetParent === null;
}
function isNotGrey(color: string) {
// eslint-disable-next-line no-useless-escape
const match = (color || '').match(/rgba?\((\d*), (\d*), (\d*)(, [\.\d]*)?\)/);
if (match && match[1] && match[2] && match[3]) {
return !(match[1] === match[2] && match[2] === match[3]);
}
return true;
}
export default defineComponent({
name: 'Wave',
props: {
insertExtraNode: Boolean,
disabled: Boolean,
},
setup(props, { slots, expose }) {
const instance = getCurrentInstance();
const { csp, prefixCls } = useConfigInject('', props);
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;
}
if (!animationStart) {
resetEffect(node);
}
};
const onTransitionEnd = (e: any) => {
if (!e || e.animationName !== 'fadeEffect') {
return;
}
resetEffect(e.target);
};
const getAttributeName = () => {
const { insertExtraNode } = props;
return insertExtraNode ? `${prefixCls.value}-click-animating` : `${prefixCls.value}-click-animating-without-extra-node`;
};
const onClick = (node: HTMLElement, waveColor: string) => {
const { insertExtraNode, disabled } = props;
if (disabled || !node || isHidden(node) || node.className.indexOf('-leave') >= 0) {
return;
}
extraNode = document.createElement('div');
extraNode.className = `${prefixCls.value}-click-animating-node`;
const attributeName = getAttributeName();
node.removeAttribute(attributeName);
node.setAttribute(attributeName, 'true');
// Not white or transparent or grey
styleForPesudo = styleForPesudo || document.createElement('style');
if (
waveColor &&
waveColor !== '#ffffff' &&
waveColor !== 'rgb(255, 255, 255)' &&
isNotGrey(waveColor) &&
!/rgba\(\d*, \d*, \d*, 0\)/.test(waveColor) && // any transparent rgba color
waveColor !== 'transparent'
) {
// Add nonce if CSP exist
if (csp.value?.nonce) {
styleForPesudo.nonce = csp.value.nonce;
}
extraNode.style.borderColor = waveColor;
styleForPesudo.innerHTML = `
[${prefixCls.value}-click-animating-without-extra-node='true']::after, .${prefixCls.value}-click-animating-node {
--antd-wave-shadow-color: ${waveColor};
}`;
if (!document.body.contains(styleForPesudo)) {
document.body.appendChild(styleForPesudo);
}
}
if (insertExtraNode) {
node.appendChild(extraNode);
}
TransitionEvents.addStartEventListener(node, onTransitionStart);
TransitionEvents.addEndEventListener(node, onTransitionEnd);
};
const resetEffect = (node: HTMLElement) => {
if (!node || node === extraNode || !(node instanceof Element)) {
return;
}
const { insertExtraNode } = props;
const attributeName = getAttributeName();
node.setAttribute(attributeName, 'false'); // edge has bug on `removeAttribute` #14466
if (styleForPesudo) {
styleForPesudo.innerHTML = '';
}
if (insertExtraNode && extraNode && node.contains(extraNode)) {
node.removeChild(extraNode);
}
TransitionEvents.removeStartEventListener(node, onTransitionStart);
TransitionEvents.removeEndEventListener(node, onTransitionEnd);
};
const bindAnimationEvent = (node: HTMLElement) => {
if (
!node ||
!node.getAttribute ||
node.getAttribute('disabled') ||
node.className.indexOf('disabled') >= 0
) {
return;
}
const newClick = (e: MouseEvent) => {
// Fix radio button click twice
if ((e.target as any).tagName === 'INPUT' || isHidden(e.target as HTMLElement)) {
return;
}
resetEffect(node);
// Get wave color from target
const waveColor =
getComputedStyle(node).getPropertyValue('border-top-color') || // Firefox Compatible
getComputedStyle(node).getPropertyValue('border-color') ||
getComputedStyle(node).getPropertyValue('background-color');
clickWaveTimeoutId = setTimeout(() => onClick(node, waveColor), 0);
raf.cancel(animationStartId);
animationStart = true;
// Render to trigger transition event cost 3 frames. Let's delay 10 frames to reset this.
animationStartId = raf(() => {
animationStart = false;
}, 10);
};
node.addEventListener('click', newClick, true);
return {
cancel: () => {
node.removeEventListener('click', newClick, true);
},
};
};
onMounted(() => {
nextTick(() => {
const node = findDOMNode(instance);
if (node.nodeType !== 1) {
return;
}
eventIns = bindAnimationEvent(node);
});
});
onBeforeUnmount(() => {
if (eventIns) {
eventIns.cancel();
}
clearTimeout(clickWaveTimeoutId);
isUnmounted = true;
});
return () => {
return slots.default?.()[0];
};
},
});