import PropTypes from '../_util/vue-types'; import { alignElement, alignPoint } from 'dom-align'; import addEventListener from '../_util/Dom/addEventListener'; import { isWindow, buffer, isSamePoint } from './util'; import { cloneElement } from '../_util/vnode.js'; import clonedeep from 'lodash/cloneDeep'; 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 { props: { childrenProps: PropTypes.object, align: PropTypes.object.isRequired, target: PropTypes.oneOfType([PropTypes.func, PropTypes.object]).def(() => window), monitorBufferTime: PropTypes.number.def(50), monitorWindowResize: PropTypes.bool.def(false), disabled: PropTypes.bool.def(false), }, data() { this.aligned = false; return {}; }, mounted() { this.$nextTick(() => { this.prevProps = { ...this.$props }; const props = this.$props; // if parent ref not attached .... use document.getElementById !this.aligned && this.forceAlign(); if (!props.disabled && props.monitorWindowResize) { this.startMonitorWindowResize(); } }); }, updated() { this.$nextTick(() => { const prevProps = this.prevProps; const props = this.$props; let reAlign = false; if (!props.disabled) { const source = this.$el; const sourceRect = source ? source.getBoundingClientRect() : null; if (prevProps.disabled) { reAlign = true; } else { const lastElement = getElement(prevProps.target); const currentElement = getElement(props.target); const lastPoint = getPoint(prevProps.target); const currentPoint = getPoint(props.target); if (isWindow(lastElement) && isWindow(currentElement)) { // 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 && (preRect.width !== sourceRect.width || preRect.height !== sourceRect.height) ) { reAlign = true; } } this.sourceRect = sourceRect; } if (reAlign) { this.forceAlign(); } if (props.monitorWindowResize && !props.disabled) { this.startMonitorWindowResize(); } else { this.stopMonitorWindowResize(); } this.prevProps = { ...this.$props, align: clonedeep(this.$props.align) }; }); }, beforeDestroy() { this.stopMonitorWindowResize(); }, methods: { 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; } }, forceAlign() { const { disabled, target, align } = this.$props; if (!disabled && target) { const source = this.$el; let result; const element = getElement(target); const point = getPoint(target); if (element) { result = alignElement(source, element, align); } else if (point) { result = alignPoint(source, point, align); } this.aligned = true; this.$listeners.align && this.$listeners.align(source, result); } }, }, render() { const { childrenProps } = this.$props; const child = this.$slots.default[0]; if (childrenProps) { return cloneElement(child, { props: childrenProps }); } return child; }, };