ant-design-vue/components/vc-resize-observer/index.tsx
2021-06-26 09:35:40 +08:00

140 lines
3.3 KiB
Vue

// based on rc-resize-observer 1.0.0
import type { PropType } from 'vue';
import {
defineComponent,
getCurrentInstance,
onMounted,
onUnmounted,
onUpdated,
reactive,
watch,
} from 'vue';
import { findDOMNode } from '../_util/props-util';
interface ResizeObserverState {
height: number;
width: number;
offsetHeight: number;
offsetWidth: number;
}
const ResizeObserver = defineComponent({
name: 'ResizeObserver',
props: {
disabled: Boolean,
onResize: Function as PropType<
(
size: {
width: number;
height: number;
offsetWidth: number;
offsetHeight: number;
},
element: HTMLElement,
) => void
>,
},
emits: ['resize'],
setup(props, { slots }) {
const state = reactive<ResizeObserverState>({
width: 0,
height: 0,
offsetHeight: 0,
offsetWidth: 0,
});
let currentElement: Element | null = null;
let resizeObserver: ResizeObserver | null = null;
const destroyObserver = () => {
if (resizeObserver) {
resizeObserver.disconnect();
resizeObserver = null;
}
};
const onResize: ResizeObserverCallback = (entries: ResizeObserverEntry[]) => {
const { onResize } = props;
const target = entries[0].target as HTMLElement;
const { width, height } = target.getBoundingClientRect();
const { offsetWidth, offsetHeight } = target;
/**
* Resize observer trigger when content size changed.
* In most case we just care about element size,
* let's use `boundary` instead of `contentRect` here to avoid shaking.
*/
const fixedWidth = Math.floor(width);
const fixedHeight = Math.floor(height);
if (
state.width !== fixedWidth ||
state.height !== fixedHeight ||
state.offsetWidth !== offsetWidth ||
state.offsetHeight !== offsetHeight
) {
const size = { width: fixedWidth, height: fixedHeight, offsetWidth, offsetHeight };
Object.assign(state, size);
if (onResize) {
// defer the callback but not defer to next frame
Promise.resolve().then(() => {
onResize(
{
...size,
offsetWidth,
offsetHeight,
},
target,
);
});
}
}
};
const instance = getCurrentInstance();
const registerObserver = () => {
const { disabled } = props;
// Unregister if disabled
if (disabled) {
destroyObserver();
return;
}
// Unregister if element changed
const element = findDOMNode(instance) as Element;
const elementChanged = element !== currentElement;
if (elementChanged) {
destroyObserver();
currentElement = element;
}
if (!resizeObserver && element) {
resizeObserver = new window.ResizeObserver(onResize);
resizeObserver.observe(element);
}
};
onMounted(() => {
registerObserver();
});
onUpdated(() => {
registerObserver();
});
onUnmounted(() => {
destroyObserver();
});
watch(
() => props.disabled,
() => {
registerObserver();
},
{ flush: 'post' },
);
return () => {
return slots.default?.()[0];
};
},
});
export default ResizeObserver;