diff --git a/components/_util/hooks/useConfigInject.ts b/components/_util/hooks/useConfigInject.ts index d4ebde713..f2151da19 100644 --- a/components/_util/hooks/useConfigInject.ts +++ b/components/_util/hooks/useConfigInject.ts @@ -1,7 +1,7 @@ import type { RequiredMark } from '../../form/Form'; import type { ComputedRef, UnwrapRef } 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 type { VueNode } from '../type'; @@ -27,6 +27,7 @@ export default ( getPopupContainer: ComputedRef; getPrefixCls: ConfigProviderProps['getPrefixCls']; autocomplete: ComputedRef; + csp: ComputedRef; } => { const configProvider = inject>( 'configProvider', @@ -52,6 +53,7 @@ export default ( ); const size = computed(() => props.size || configProvider.componentSize); const autocomplete = computed(() => props.autocomplete || configProvider.input?.autocomplete); + const csp = computed(() => configProvider.csp); return { configProvider, prefixCls, @@ -69,5 +71,6 @@ export default ( rootPrefixCls, getPrefixCls: configProvider.getPrefixCls, autocomplete, + csp, }; }; diff --git a/components/_util/wave.jsx b/components/_util/wave.tsx similarity index 50% rename from components/_util/wave.jsx rename to components/_util/wave.tsx index aa8179881..4fd70e193 100644 --- a/components/_util/wave.jsx +++ b/components/_util/wave.tsx @@ -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 raf from './raf'; -import { defaultConfigProvider } from '../config-provider'; 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 -function isHidden(element) { +function isHidden(element: HTMLElement) { if (process.env.NODE_ENV === 'test') { return false; } return !element || element.offsetParent === null; } -function isNotGrey(color) { +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]) { @@ -22,40 +22,51 @@ function isNotGrey(color) { } export default defineComponent({ name: 'Wave', - props: ['insertExtraNode'], - setup() { - const configProvider = inject('configProvider', defaultConfigProvider); - return { - configProvider, - }; + props: { + insertExtraNode: Boolean, }, - mounted() { - nextTick(() => { - const node = findDOMNode(this); - if (node.nodeType !== 1) { + setup(props, { slots, expose }) { + const instance = getCurrentInstance(); + const { csp } = 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; } - this.instance = this.bindAnimationEvent(node); - }); - }, - beforeUnmount() { - if (this.instance) { - this.instance.cancel(); - } - if (this.clickWaveTimeoutId) { - clearTimeout(this.clickWaveTimeoutId); - } - }, - methods: { - onClick(node, waveColor) { + + if (!animationStart) { + resetEffect(node); + } + }; + const onTransitionEnd = (e: any) => { + if (!e || e.animationName !== 'fadeEffect') { + return; + } + resetEffect(e.target); + }; + 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) { return; } - const { insertExtraNode } = this.$props; - this.extraNode = document.createElement('div'); - const extraNode = this.extraNode; + const { insertExtraNode } = props; + extraNode = document.createElement('div'); extraNode.className = 'ant-click-animating-node'; - const attributeName = this.getAttributeName(); + const attributeName = getAttributeName(); node.removeAttribute(attributeName); node.setAttribute(attributeName, 'true'); // Not white or transparent or grey @@ -69,8 +80,8 @@ export default defineComponent({ waveColor !== 'transparent' ) { // Add nonce if CSP exist - if (this.csp && this.csp.nonce) { - styleForPesudo.nonce = this.csp.nonce; + if (csp.value?.nonce) { + styleForPesudo.nonce = csp.value.nonce; } extraNode.style.borderColor = waveColor; styleForPesudo.innerHTML = ` @@ -84,32 +95,26 @@ export default defineComponent({ if (insertExtraNode) { node.appendChild(extraNode); } - TransitionEvents.addStartEventListener(node, this.onTransitionStart); - TransitionEvents.addEndEventListener(node, this.onTransitionEnd); - }, - onTransitionStart(e) { - if (this._.isUnmounted) return; - - const node = findDOMNode(this); - if (!e || e.target !== node) { + TransitionEvents.addStartEventListener(node, onTransitionStart); + TransitionEvents.addEndEventListener(node, onTransitionEnd); + }; + const resetEffect = (node: HTMLElement) => { + if (!node || node === extraNode || !(node instanceof Element)) { return; } - - if (!this.animationStart) { - this.resetEffect(node); + const { insertExtraNode } = props; + const attributeName = getAttributeName(); + node.setAttribute(attributeName, 'false'); // edge has bug on `removeAttribute` #14466 + if (styleForPesudo) { + styleForPesudo.innerHTML = ''; } - }, - onTransitionEnd(e) { - if (!e || e.animationName !== 'fadeEffect') { - return; + if (insertExtraNode && extraNode && node.contains(extraNode)) { + node.removeChild(extraNode); } - this.resetEffect(e.target); - }, - getAttributeName() { - const { insertExtraNode } = this.$props; - return insertExtraNode ? 'ant-click-animating' : 'ant-click-animating-without-extra-node'; - }, - bindAnimationEvent(node) { + TransitionEvents.removeStartEventListener(node, onTransitionStart); + TransitionEvents.removeEndEventListener(node, onTransitionEnd); + }; + const bindAnimationEvent = (node: HTMLElement) => { if ( !node || !node.getAttribute || @@ -118,57 +123,51 @@ export default defineComponent({ ) { return; } - const onClick = e => { + const newClick = (e: MouseEvent) => { // 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; } - this.resetEffect(node); + 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'); - this.clickWaveTimeoutId = setTimeout(() => this.onClick(node, waveColor), 0); - raf.cancel(this.animationStartId); - this.animationStart = true; + 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. - this.animationStartId = raf(() => { - this.animationStart = false; + animationStartId = raf(() => { + animationStart = false; }, 10); }; - node.addEventListener('click', onClick, true); + node.addEventListener('click', newClick, true); return { cancel: () => { - node.removeEventListener('click', onClick, true); + node.removeEventListener('click', newClick, true); }, }; - }, - - resetEffect(node) { - if (!node || node === this.extraNode || !(node instanceof Element)) { - return; + }; + onMounted(() => { + nextTick(() => { + const node = findDOMNode(instance); + if (node.nodeType !== 1) { + return; + } + eventIns = bindAnimationEvent(node); + }); + }); + onBeforeUnmount(() => { + if (eventIns) { + eventIns.cancel(); } - const { insertExtraNode } = this.$props; - const attributeName = this.getAttributeName(); - node.setAttribute(attributeName, 'false'); // edge has bug on `removeAttribute` #14466 - if (styleForPesudo) { - styleForPesudo.innerHTML = ''; - } - if (insertExtraNode && this.extraNode && node.contains(this.extraNode)) { - node.removeChild(this.extraNode); - } - TransitionEvents.removeStartEventListener(node, this.onTransitionStart); - TransitionEvents.removeEndEventListener(node, this.onTransitionEnd); - }, - }, - - render() { - const csp = this.configProvider.csp; - if (csp) { - this.csp = csp; - } - return this.$slots.default?.()[0]; + clearTimeout(clickWaveTimeoutId); + isUnmounted = true; + }); + return () => { + return slots.default?.()[0]; + }; }, }); diff --git a/components/config-provider/index.tsx b/components/config-provider/index.tsx index 6dd282e64..f9730ac13 100644 --- a/components/config-provider/index.tsx +++ b/components/config-provider/index.tsx @@ -161,6 +161,7 @@ export const configProviderProps = { }, csp: { type: Object as PropType, + default: undefined as CSPConfig, }, input: { type: Object as PropType<{ autocomplete: string }>,