109 lines
		
	
	
		
			2.5 KiB
		
	
	
	
		
			Vue
		
	
	
	
	
	
			
		
		
	
	
			109 lines
		
	
	
		
			2.5 KiB
		
	
	
	
		
			Vue
		
	
	
	
	
	
| <template>
 | |
| <Transition
 | |
| 	:enter-active-class="defaultStore.state.animation ? $style.transition_tooltip_enterActive : ''"
 | |
| 	:leave-active-class="defaultStore.state.animation ? $style.transition_tooltip_leaveActive : ''"
 | |
| 	:enter-from-class="defaultStore.state.animation ? $style.transition_tooltip_enterFrom : ''"
 | |
| 	:leave-to-class="defaultStore.state.animation ? $style.transition_tooltip_leaveTo : ''"
 | |
| 	appear @after-leave="emit('closed')"
 | |
| >
 | |
| 	<div v-show="showing" ref="el" :class="$style.root" class="_acrylic _shadow" :style="{ zIndex, maxWidth: maxWidth + 'px' }">
 | |
| 		<slot>
 | |
| 			<Mfm v-if="asMfm" :text="text"/>
 | |
| 			<span v-else>{{ text }}</span>
 | |
| 		</slot>
 | |
| 	</div>
 | |
| </Transition>
 | |
| </template>
 | |
| 
 | |
| <script lang="ts" setup>
 | |
| import { nextTick, onMounted, onUnmounted, shallowRef } from 'vue';
 | |
| import * as os from '@/os';
 | |
| import { calcPopupPosition } from '@/scripts/popup-position';
 | |
| import { defaultStore } from '@/store';
 | |
| 
 | |
| const props = withDefaults(defineProps<{
 | |
| 	showing: boolean;
 | |
| 	targetElement?: HTMLElement;
 | |
| 	x?: number;
 | |
| 	y?: number;
 | |
| 	text?: string;
 | |
| 	asMfm?: boolean;
 | |
| 	maxWidth?: number;
 | |
| 	direction?: 'top' | 'bottom' | 'right' | 'left';
 | |
| 	innerMargin?: number;
 | |
| }>(), {
 | |
| 	maxWidth: 250,
 | |
| 	direction: 'top',
 | |
| 	innerMargin: 0,
 | |
| });
 | |
| 
 | |
| const emit = defineEmits<{
 | |
| 	(ev: 'closed'): void;
 | |
| }>();
 | |
| 
 | |
| const el = shallowRef<HTMLElement>();
 | |
| const zIndex = os.claimZIndex('high');
 | |
| 
 | |
| function setPosition() {
 | |
| 	const data = calcPopupPosition(el.value, {
 | |
| 		anchorElement: props.targetElement,
 | |
| 		direction: props.direction,
 | |
| 		align: 'center',
 | |
| 		innerMargin: props.innerMargin,
 | |
| 		x: props.x,
 | |
| 		y: props.y,
 | |
| 	});
 | |
| 
 | |
| 	el.value.style.transformOrigin = data.transformOrigin;
 | |
| 	el.value.style.left = data.left + 'px';
 | |
| 	el.value.style.top = data.top + 'px';
 | |
| }
 | |
| 
 | |
| let loopHandler;
 | |
| 
 | |
| onMounted(() => {
 | |
| 	nextTick(() => {
 | |
| 		setPosition();
 | |
| 
 | |
| 		const loop = () => {
 | |
| 			loopHandler = window.requestAnimationFrame(() => {
 | |
| 				setPosition();
 | |
| 				loop();
 | |
| 			});
 | |
| 		};
 | |
| 
 | |
| 		loop();
 | |
| 	});
 | |
| });
 | |
| 
 | |
| onUnmounted(() => {
 | |
| 	window.cancelAnimationFrame(loopHandler);
 | |
| });
 | |
| </script>
 | |
| 
 | |
| <style lang="scss" module>
 | |
| .transition_tooltip_enterActive,
 | |
| .transition_tooltip_leaveActive {
 | |
| 	opacity: 1;
 | |
| 	transform: scale(1);
 | |
| 	transition: transform 200ms cubic-bezier(0.23, 1, 0.32, 1), opacity 200ms cubic-bezier(0.23, 1, 0.32, 1);
 | |
| }
 | |
| .transition_tooltip_enterFrom,
 | |
| .transition_tooltip_leaveTo {
 | |
| 	opacity: 0;
 | |
| 	transform: scale(0.75);
 | |
| }
 | |
| 
 | |
| .root {
 | |
| 	position: absolute;
 | |
| 	font-size: 0.8em;
 | |
| 	padding: 8px 12px;
 | |
| 	box-sizing: border-box;
 | |
| 	text-align: center;
 | |
| 	border-radius: 4px;
 | |
| 	border: solid 0.5px var(--divider);
 | |
| 	pointer-events: none;
 | |
| 	transform-origin: center center;
 | |
| }
 | |
| </style>
 | 
