to keep things manageable i merged a lot of one off values into just a handful of common sizes, so some parts of the ui will look different than upstream even with the "Misskey" rounding mode
115 lines
2.8 KiB
Vue
115 lines
2.8 KiB
Vue
<!--
|
|
SPDX-FileCopyrightText: syuilo and other misskey contributors
|
|
SPDX-License-Identifier: AGPL-3.0-only
|
|
-->
|
|
|
|
<template>
|
|
<Transition
|
|
:enterActiveClass="defaultStore.state.animation ? $style.transition_tooltip_enterActive : ''"
|
|
:leaveActiveClass="defaultStore.state.animation ? $style.transition_tooltip_leaveActive : ''"
|
|
:enterFromClass="defaultStore.state.animation ? $style.transition_tooltip_enterFrom : ''"
|
|
:leaveToClass="defaultStore.state.animation ? $style.transition_tooltip_leaveTo : ''"
|
|
appear @afterLeave="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.js';
|
|
import { calcPopupPosition } from '@/scripts/popup-position.js';
|
|
import { defaultStore } from '@/store.js';
|
|
|
|
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;
|
|
}>();
|
|
|
|
// タイミングによっては最初から showing = false な場合があり、その場合に closed 扱いにしないと永久にDOMに残ることになる
|
|
if (!props.showing) emit('closed');
|
|
|
|
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 = () => {
|
|
setPosition();
|
|
loopHandler = window.requestAnimationFrame(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: var(--radius-xs);
|
|
border: solid 0.5px var(--divider);
|
|
pointer-events: none;
|
|
transform-origin: center center;
|
|
}
|
|
</style>
|