ant-design-vue/components/vc-align/Align.jsx

182 lines
5.7 KiB
Vue

import { nextTick, defineComponent } 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, restoreFocus, monitorResize } from './util';
import { cloneElement } from '../_util/vnode';
import { getSlot, findDOMNode } from '../_util/props-util';
import useBuffer from './hooks/useBuffer';
import isVisible from '../vc-util/Dom/isVisible';
function getElement(func) {
if (typeof func !== 'function' || !func) return null;
return func();
}
function getPoint(point) {
if (typeof point !== 'object' || !point) return null;
return point;
}
export default defineComponent({
props: {
childrenProps: PropTypes.object,
align: PropTypes.object.isRequired,
target: PropTypes.oneOfType([PropTypes.func, PropTypes.object]).def(() => window),
monitorBufferTime: PropTypes.number.def(50),
monitorWindowResize: PropTypes.looseBool.def(false),
disabled: PropTypes.looseBool.def(false),
},
setup() {
return {
aligned: false,
sourceResizeMonitor: { cancel: () => {} },
resizeMonitor: { cancel: () => {} },
cacheInfo: {},
};
},
data() {
this.prevProps = { ...this.$props };
const [forceAlign, cancelForceAlign] = useBuffer(this.goAlign, 0);
return {
forceAlign,
cancelForceAlign,
};
},
mounted() {
nextTick(() => {
const props = this.$props;
// if parent ref not attached .... use document.getElementById
!this.aligned && this.forceAlign();
if (!props.disabled && props.monitorWindowResize) {
this.startMonitorWindowResize();
}
this.startMonitorElementResize();
this.updateCache();
});
},
updated() {
nextTick(() => {
const prevProps = this.prevProps;
const props = this.$props;
let reAlign = false;
if (!props.disabled) {
if (prevProps.disabled) {
reAlign = true;
} else {
const { element: lastElement, point: lastPoint } = this.cacheInfo;
const currentElement = getElement(props.target);
const currentPoint = getPoint(props.target);
if (isWindow(lastElement) && isWindow(currentElement)) {
// Skip if is window
reAlign = false;
} else if (
(lastElement && !currentElement && currentPoint) || // Change from element to point
(lastPoint && currentPoint && currentElement) // Change from point to element
) {
reAlign = true;
}
}
}
if (reAlign) {
this.forceAlign();
} else {
this.startMonitorElementResize();
}
if (props.monitorWindowResize && !props.disabled) {
this.startMonitorWindowResize();
} else {
this.stopMonitorWindowResize();
}
this.prevProps = { ...this.$props };
this.updateCache();
});
},
beforeUnmount() {
this.stopMonitorWindowResize();
this.resizeMonitor?.cancel();
this.sourceResizeMonitor?.cancel();
this.cancelForceAlign();
},
methods: {
updateCache() {
const element = getElement(this.$props.target);
const point = getPoint(this.$props.target);
this.cacheInfo = {
element,
point,
};
},
startMonitorElementResize() {
const props = this.$props;
const { element: lastElement, point: lastPoint } = this.cacheInfo;
const currentElement = getElement(props.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) {
this.bufferMonitor = buffer(this.forceAlign, this.$props.monitorBufferTime);
this.resizeHandler = addEventListener(window, 'resize', this.bufferMonitor);
}
},
stopMonitorWindowResize() {
if (this.resizeHandler) {
this.bufferMonitor.clear();
this.resizeHandler.remove();
this.resizeHandler = null;
}
},
goAlign() {
const { disabled, target, align } = this.$props;
if (!disabled && target) {
const source = findDOMNode(this);
let result;
const element = getElement(target);
const point = getPoint(target);
// IE lose focus after element realign
// We should record activeElement and restore later
const activeElement = document.activeElement;
if (element && isVisible(element)) {
result = alignElement(source, element, align);
} else if (point) {
result = alignPoint(source, point, align);
}
restoreFocus(activeElement, source);
this.aligned = true;
this.$attrs.onAlign && result && this.$attrs.onAlign(source, result);
return true;
}
return false;
},
},
render() {
const { childrenProps } = this.$props;
const child = getSlot(this);
if (child && childrenProps) {
return cloneElement(child[0], childrenProps);
}
return child && child[0];
},
});