diff --git a/components/vc-align/Align.jsx b/components/vc-align/Align.jsx index 74255d67d..f2dabd69f 100644 --- a/components/vc-align/Align.jsx +++ b/components/vc-align/Align.jsx @@ -2,10 +2,11 @@ import { nextTick } from 'vue'; import PropTypes from '../_util/vue-types'; import { alignElement, alignPoint } from 'dom-align'; import addEventListener from '../vc-util/Dom/addEventListener'; -import { isWindow, buffer, isSamePoint, isSimilarValue, restoreFocus } from './util'; +import { isWindow, buffer, isSamePoint, restoreFocus, monitorResize } from './util'; import { cloneElement } from '../_util/vnode'; import clonedeep from 'lodash-es/cloneDeep'; import { getSlot, findDOMNode } from '../_util/props-util'; +import useBuffer from './hooks/useBuffer'; function getElement(func) { if (typeof func !== 'function' || !func) return null; @@ -28,8 +29,14 @@ export default { }, data() { this.aligned = false; + this.sourceResizeMonitor = { cancel: () => {} }; + this.resizeMonitor = { cancel: () => {} }; this.prevProps = { ...this.$props }; - return {}; + const [forceAlign, cancelForceAlign] = useBuffer(this.goAlign, 0); + return { + forceAlign, + cancelForceAlign, + }; }, mounted() { nextTick(() => { @@ -39,6 +46,7 @@ export default { if (!props.disabled && props.monitorWindowResize) { this.startMonitorWindowResize(); } + this.startMonitorElementResize(); }); }, updated() { @@ -47,9 +55,6 @@ export default { const props = this.$props; let reAlign = false; if (!props.disabled) { - const source = findDOMNode(this); - const sourceRect = source ? source.getBoundingClientRect() : null; - if (prevProps.disabled) { reAlign = true; } else { @@ -61,30 +66,18 @@ export default { // Skip if is window reAlign = false; } else if ( - lastElement !== currentElement || // Element change (lastElement && !currentElement && currentPoint) || // Change from element to point - (lastPoint && currentPoint && currentElement) || // Change from point to element - (currentPoint && !isSamePoint(lastPoint, currentPoint)) - ) { - reAlign = true; - } - - // If source element size changed - const preRect = this.sourceRect || {}; - if ( - !reAlign && - source && - (!isSimilarValue(preRect.width, sourceRect.width) || - !isSimilarValue(preRect.height, sourceRect.height)) + (lastPoint && currentPoint && currentElement) // Change from point to element ) { reAlign = true; } } - this.sourceRect = { width: sourceRect.width, height: sourceRect.height }; } if (reAlign) { this.forceAlign(); + } else { + this.startMonitorElementResize(); } if (props.monitorWindowResize && !props.disabled) { @@ -97,12 +90,34 @@ export default { }, beforeUnmount() { this.stopMonitorWindowResize(); + this.resizeMonitor?.cancel(); + this.sourceResizeMonitor?.cancel(); + this.cancelForceAlign(); }, methods: { - // TODO startMonitorElementResize() { - const currentElement = getElement(this.$props.target); - const element = findDOMNode(this); + const prevProps = this.prevProps; + const props = this.$props; + const lastElement = getElement(prevProps.target); + const currentElement = getElement(props.target); + const lastPoint = getPoint(prevProps.target); + const currentPoint = getPoint(props.target); + const source = findDOMNode(this); + const { sourceResizeMonitor, resizeMonitor } = this; + if (source !== sourceResizeMonitor.element) { + sourceResizeMonitor?.cancel(); + sourceResizeMonitor.element = source; + sourceResizeMonitor.cancel = monitorResize(source, this.forceAlign); + } + if (lastElement !== currentElement || !isSamePoint(lastPoint, currentPoint)) { + //this.forceAlign(); + // Add resize observer + if (resizeMonitor.element !== currentElement) { + resizeMonitor?.cancel(); + resizeMonitor.element = currentElement; + resizeMonitor.cancel = monitorResize(currentElement, this.forceAlign); + } + } }, startMonitorWindowResize() { if (!this.resizeHandler) { @@ -118,8 +133,7 @@ export default { this.resizeHandler = null; } }, - - forceAlign() { + goAlign() { const { disabled, target, align } = this.$props; if (!disabled && target) { const source = findDOMNode(this); @@ -138,8 +152,10 @@ export default { } restoreFocus(activeElement, source); this.aligned = true; - this.$attrs.onAlign && this.$attrs.onAlign(source, result); + this.$attrs.onAlign && result && this.$attrs.onAlign(source, result); + return true; } + return false; }, }, diff --git a/components/vc-align/hooks/useBuffer.tsx b/components/vc-align/hooks/useBuffer.tsx new file mode 100644 index 000000000..9c4f66581 --- /dev/null +++ b/components/vc-align/hooks/useBuffer.tsx @@ -0,0 +1,37 @@ +export default (callback: () => boolean, buffer: number) => { + let called = false; + let timeout = null; + + function cancelTrigger() { + window.clearTimeout(timeout); + } + + function trigger(force?: boolean) { + if (!called || force === true) { + if (callback() === false) { + // Not delay since callback cancelled self + return; + } + + called = true; + cancelTrigger(); + timeout = window.setTimeout(() => { + called = false; + }, buffer); + } else { + cancelTrigger(); + timeout = window.setTimeout(() => { + called = false; + trigger(); + }, buffer); + } + } + + return [ + trigger, + () => { + called = false; + cancelTrigger(); + }, + ]; +}; diff --git a/components/vc-select2/SelectTrigger.tsx b/components/vc-select2/SelectTrigger.tsx index 29acbb37c..52ab56b19 100644 --- a/components/vc-select2/SelectTrigger.tsx +++ b/components/vc-select2/SelectTrigger.tsx @@ -90,7 +90,7 @@ const SelectTrigger = defineComponent({ dropdownRender, animation, transitionName, - } = props; + } = props as SelectTriggerProps; const dropdownPrefixCls = `${prefixCls}-dropdown`; let popupNode = popupElement; diff --git a/components/vc-select2/Selector/index.tsx b/components/vc-select2/Selector/index.tsx index 40ef31fcb..19b09d6a2 100644 --- a/components/vc-select2/Selector/index.tsx +++ b/components/vc-select2/Selector/index.tsx @@ -65,7 +65,7 @@ export interface SelectorProps { maxTagCount?: number; maxTagTextLength?: number; maxTagPlaceholder?: VNodeChild; - tagRender?: (props: CustomTagProps) => VNode; + tagRender?: (props: CustomTagProps) => VNodeChild; /** Check if `tokenSeparators` contains `\n` or `\r\n` */ tokenWithEnter?: boolean; @@ -225,7 +225,7 @@ const Selector = defineComponent({ onInputChange, onInputCompositionStart, onInputCompositionEnd, - } = this; + } = this as any; const sharedProps = { inputRef, onInputKeyDown: onInternalInputKeyDown, diff --git a/components/vc-select2/generate.tsx b/components/vc-select2/generate.tsx index 5f4d0622f..1ec304552 100644 --- a/components/vc-select2/generate.tsx +++ b/components/vc-select2/generate.tsx @@ -219,7 +219,7 @@ export default function generateSelector< key?: Key; disabled?: boolean; }[] ->(config: GenerateConfig): DefineComponent { +>(config: GenerateConfig): DefineComponent> { const { prefixCls: defaultPrefixCls, components: { optionList: OptionList }, @@ -600,11 +600,7 @@ export default function generateSelector< } }; - useSelectTriggerControl( - [containerRef.value, triggerRef.value && triggerRef.value.getPopupElement()], - triggerOpen, - onToggleOpen, - ); + useSelectTriggerControl([containerRef, triggerRef], triggerOpen, onToggleOpen); // ============================= Search ============================= const triggerSearch = (searchText: string, fromTyping: boolean, isCompositing: boolean) => { @@ -923,6 +919,7 @@ export default function generateSelector< onSearchSubmit, containerRef, listRef, + triggerRef, }; }, methods: { @@ -964,7 +961,7 @@ export default function generateSelector< displayValues, activeValue, onSearchSubmit, - } = this; + } = this as any; const { prefixCls = defaultPrefixCls, class: className, diff --git a/components/vc-select2/hooks/useSelectTriggerControl.ts b/components/vc-select2/hooks/useSelectTriggerControl.ts index f3e42c5de..2f2efe26b 100644 --- a/components/vc-select2/hooks/useSelectTriggerControl.ts +++ b/components/vc-select2/hooks/useSelectTriggerControl.ts @@ -1,12 +1,13 @@ import { onBeforeUnmount, onMounted, Ref } from 'vue'; export default function useSelectTriggerControl( - elements: (HTMLElement | undefined)[], + refs: Ref[], open: Ref, triggerOpen: (open: boolean) => void, ) { function onGlobalMouseDown(event: MouseEvent) { const target = event.target as HTMLElement; + const elements = [refs[0]?.value, refs[1]?.value?.getPopupElement()]; if ( open.value && elements.every(element => element && !element.contains(target) && element !== target)