summaryrefslogtreecommitdiff
path: root/packages/client/src/components
diff options
context:
space:
mode:
authorsyuilo <Syuilotan@yahoo.co.jp>2022-02-09 14:50:38 +0900
committersyuilo <Syuilotan@yahoo.co.jp>2022-02-09 14:50:38 +0900
commit5f985ee832eed61e31ceb51eaa1c51810ad6de39 (patch)
tree60cd65d92e377ea0a0f0d2bc3b767f8f26196310 /packages/client/src/components
parentMerge branch 'develop' (diff)
parent12.104.0 (diff)
downloadmisskey-5f985ee832eed61e31ceb51eaa1c51810ad6de39.tar.gz
misskey-5f985ee832eed61e31ceb51eaa1c51810ad6de39.tar.bz2
misskey-5f985ee832eed61e31ceb51eaa1c51810ad6de39.zip
Merge branch 'develop'
Diffstat (limited to 'packages/client/src/components')
-rw-r--r--packages/client/src/components/chart-tooltip.vue2
-rw-r--r--packages/client/src/components/chart.vue224
-rw-r--r--packages/client/src/components/emoji-picker.vue4
-rw-r--r--packages/client/src/components/instance-stats.vue6
-rw-r--r--packages/client/src/components/instance-ticker.vue18
-rw-r--r--packages/client/src/components/post-form.vue1
-rw-r--r--packages/client/src/components/ui/modal.vue3
-rw-r--r--packages/client/src/components/ui/tooltip.vue141
8 files changed, 311 insertions, 88 deletions
diff --git a/packages/client/src/components/chart-tooltip.vue b/packages/client/src/components/chart-tooltip.vue
index b080eaf2b4..20e094a5a7 100644
--- a/packages/client/src/components/chart-tooltip.vue
+++ b/packages/client/src/components/chart-tooltip.vue
@@ -1,5 +1,5 @@
<template>
-<MkTooltip ref="tooltip" :showing="showing" :x="x" :y="y" :max-width="340" @closed="emit('closed')">
+<MkTooltip ref="tooltip" :showing="showing" :x="x" :y="y" :max-width="340" :direction="'left'" :inner-margin="16" @closed="emit('closed')">
<div v-if="title" class="qpcyisrl">
<div class="title">{{ title }}</div>
<div v-for="x in series" class="series">
diff --git a/packages/client/src/components/chart.vue b/packages/client/src/components/chart.vue
index 3e46c51b47..ced0d481c4 100644
--- a/packages/client/src/components/chart.vue
+++ b/packages/client/src/components/chart.vue
@@ -29,6 +29,7 @@ import {
import 'chartjs-adapter-date-fns';
import { enUS } from 'date-fns/locale';
import zoomPlugin from 'chartjs-plugin-zoom';
+import gradient from 'chartjs-plugin-gradient';
import * as os from '@/os';
import { defaultStore } from '@/store';
import MkChartTooltip from '@/components/chart-tooltip.vue';
@@ -49,6 +50,7 @@ Chart.register(
SubTitle,
Filler,
zoomPlugin,
+ gradient,
);
const sum = (...arr) => arr.reduce((r, a) => r.map((b, i) => a[i] + b));
@@ -61,9 +63,17 @@ const alpha = (hex, a) => {
return `rgba(${r}, ${g}, ${b}, ${a})`;
};
-const colors = ['#008FFB', '#00E396', '#FEB019', '#FF4560'];
+const colors = {
+ blue: '#008FFB',
+ green: '#00E396',
+ yellow: '#FEB019',
+ red: '#FF4560',
+ purple: '#e300db',
+ orange: '#fe6919',
+};
+const colorSets = [colors.blue, colors.green, colors.yellow, colors.red, colors.purple];
const getColor = (i) => {
- return colors[i % colors.length];
+ return colorSets[i % colorSets.length];
};
export default defineComponent({
@@ -95,6 +105,11 @@ export default defineComponent({
required: false,
default: false
},
+ bar: {
+ type: Boolean,
+ required: false,
+ default: false
+ },
aspectRatio: {
type: Number,
required: false,
@@ -186,22 +201,36 @@ export default defineComponent({
// フォントカラー
Chart.defaults.color = getComputedStyle(document.documentElement).getPropertyValue('--fg');
+ const maxes = data.series.map((x, i) => Math.max(...x.data.map(d => d.y)));
+
chartInstance = new Chart(chartEl.value, {
- type: 'line',
+ type: props.bar ? 'bar' : 'line',
data: {
labels: new Array(props.limit).fill(0).map((_, i) => getDate(i).toLocaleString()).slice().reverse(),
datasets: data.series.map((x, i) => ({
parsing: false,
label: x.name,
data: x.data.slice().reverse(),
+ tension: 0.3,
pointRadius: 0,
- tension: 0,
borderWidth: 2,
borderColor: x.color ? x.color : getColor(i),
borderDash: x.borderDash || [],
borderJoinStyle: 'round',
backgroundColor: alpha(x.color ? x.color : getColor(i), 0.1),
+ gradient: {
+ backgroundColor: {
+ axis: 'y',
+ colors: {
+ 0: alpha(x.color ? x.color : getColor(i), 0),
+ [maxes[i]]: alpha(x.color ? x.color : getColor(i), 0.1),
+ },
+ },
+ },
+ barPercentage: 0.9,
+ categoryPercentage: 0.9,
fill: x.type === 'area',
+ clip: 8,
hidden: !!x.hidden,
})),
},
@@ -210,7 +239,7 @@ export default defineComponent({
layout: {
padding: {
left: 0,
- right: 0,
+ right: 8,
top: 0,
bottom: 0,
},
@@ -218,6 +247,7 @@ export default defineComponent({
scales: {
x: {
type: 'time',
+ stacked: props.stacked,
time: {
stepSize: 1,
unit: props.span === 'day' ? 'month' : 'day',
@@ -228,6 +258,8 @@ export default defineComponent({
},
ticks: {
display: props.detailed,
+ maxRotation: 0,
+ autoSkipPadding: 16,
},
adapters: {
date: {
@@ -245,12 +277,21 @@ export default defineComponent({
},
ticks: {
display: props.detailed,
+ //mirror: true,
},
},
},
interaction: {
intersect: false,
+ mode: 'index',
},
+ elements: {
+ point: {
+ hoverRadius: 5,
+ hoverBorderWidth: 2,
+ },
+ },
+ animation: false,
plugins: {
legend: {
display: props.detailed,
@@ -294,6 +335,7 @@ export default defineComponent({
},
}
},
+ gradient,
},
},
plugins: [{
@@ -324,20 +366,60 @@ export default defineComponent({
// TODO
};
- const fetchFederationInstancesChart = async (total: boolean): Promise<typeof data> => {
+ const fetchFederationChart = async (): Promise<typeof data> => {
const raw = await os.api('charts/federation', { limit: props.limit, span: props.span });
return {
series: [{
- name: 'Instances',
+ name: 'Total',
type: 'area',
- data: format(total
- ? raw.instance.total
- : sum(raw.instance.inc, negate(raw.instance.dec))
- ),
+ data: format(raw.instance.total),
+ color: '#888888',
+ }, {
+ name: 'Inc/Dec',
+ type: 'area',
+ data: format(sum(raw.instance.inc, negate(raw.instance.dec))),
+ color: colors.purple,
+ }, {
+ name: 'Received',
+ type: 'area',
+ data: format(raw.inboxInstances),
+ color: colors.blue,
+ }, {
+ name: 'Delivered',
+ type: 'area',
+ data: format(raw.deliveredInstances),
+ color: colors.green,
+ }, {
+ name: 'Stalled',
+ type: 'area',
+ data: format(raw.stalled),
+ color: colors.red,
}],
};
};
+ const fetchApRequestChart = async (): Promise<typeof data> => {
+ const raw = await os.api('charts/ap-request', { limit: props.limit, span: props.span });
+ return {
+ series: [{
+ name: 'In',
+ type: 'area',
+ color: '#008FFB',
+ data: format(raw.inboxReceived)
+ }, {
+ name: 'Out (succ)',
+ type: 'area',
+ color: '#00E396',
+ data: format(raw.deliverSucceeded)
+ }, {
+ name: 'Out (fail)',
+ type: 'area',
+ color: '#FEB019',
+ data: format(raw.deliverFailed)
+ }]
+ };
+ };
+
const fetchNotesChart = async (type: string): Promise<typeof data> => {
const raw = await os.api('charts/notes', { limit: props.limit, span: props.span });
return {
@@ -349,6 +431,7 @@ export default defineComponent({
? sum(raw.local.inc, negate(raw.local.dec), raw.remote.inc, negate(raw.remote.dec))
: sum(raw[type].inc, negate(raw[type].dec))
),
+ color: '#888888',
}, {
name: 'Renotes',
type: 'area',
@@ -356,6 +439,7 @@ export default defineComponent({
? sum(raw.local.diffs.renote, raw.remote.diffs.renote)
: raw[type].diffs.renote
),
+ color: colors.green,
}, {
name: 'Replies',
type: 'area',
@@ -363,6 +447,7 @@ export default defineComponent({
? sum(raw.local.diffs.reply, raw.remote.diffs.reply)
: raw[type].diffs.reply
),
+ color: colors.yellow,
}, {
name: 'Normal',
type: 'area',
@@ -370,6 +455,15 @@ export default defineComponent({
? sum(raw.local.diffs.normal, raw.remote.diffs.normal)
: raw[type].diffs.normal
),
+ color: colors.blue,
+ }, {
+ name: 'With file',
+ type: 'area',
+ data: format(type == 'combined'
+ ? sum(raw.local.diffs.withFile, raw.remote.diffs.withFile)
+ : raw[type].diffs.withFile
+ ),
+ color: colors.purple,
}],
};
};
@@ -425,17 +519,50 @@ export default defineComponent({
const raw = await os.api('charts/active-users', { limit: props.limit, span: props.span });
return {
series: [{
- name: 'Combined',
- type: 'line',
- data: format(sum(raw.local.users, raw.remote.users)),
+ name: 'Read & Write',
+ type: 'area',
+ data: format(raw.readWrite),
+ color: colors.orange,
}, {
- name: 'Local',
+ name: 'Write',
type: 'area',
- data: format(raw.local.users),
+ data: format(raw.write),
+ color: colors.blue,
}, {
- name: 'Remote',
+ name: 'Read',
+ type: 'area',
+ data: format(raw.read),
+ color: '#888888',
+ }, {
+ name: '< Week',
type: 'area',
- data: format(raw.remote.users),
+ data: format(raw.registeredWithinWeek),
+ color: colors.green,
+ }, {
+ name: '< Month',
+ type: 'area',
+ data: format(raw.registeredWithinMonth),
+ color: colors.yellow,
+ }, {
+ name: '< Year',
+ type: 'area',
+ data: format(raw.registeredWithinYear),
+ color: colors.red,
+ }, {
+ name: '> Week',
+ type: 'area',
+ data: format(raw.registeredOutsideWeek),
+ color: colors.yellow,
+ }, {
+ name: '> Month',
+ type: 'area',
+ data: format(raw.registeredOutsideMonth),
+ color: colors.red,
+ }, {
+ name: '> Year',
+ type: 'area',
+ data: format(raw.registeredOutsideYear),
+ color: colors.purple,
}],
};
};
@@ -476,26 +603,6 @@ export default defineComponent({
};
};
- const fetchDriveTotalChart = async (): Promise<typeof data> => {
- const raw = await os.api('charts/drive', { limit: props.limit, span: props.span });
- return {
- bytes: true,
- series: [{
- name: 'Combined',
- type: 'line',
- data: format(sum(raw.local.totalSize, raw.remote.totalSize)),
- }, {
- name: 'Local',
- type: 'area',
- data: format(raw.local.totalSize),
- }, {
- name: 'Remote',
- type: 'area',
- data: format(raw.remote.totalSize),
- }],
- };
- };
-
const fetchDriveFilesChart = async (): Promise<typeof data> => {
const raw = await os.api('charts/drive', { limit: props.limit, span: props.span });
return {
@@ -531,25 +638,6 @@ export default defineComponent({
};
};
- const fetchDriveFilesTotalChart = async (): Promise<typeof data> => {
- const raw = await os.api('charts/drive', { limit: props.limit, span: props.span });
- return {
- series: [{
- name: 'Combined',
- type: 'line',
- data: format(sum(raw.local.totalCount, raw.remote.totalCount)),
- }, {
- name: 'Local',
- type: 'area',
- data: format(raw.local.totalCount),
- }, {
- name: 'Remote',
- type: 'area',
- data: format(raw.remote.totalCount),
- }],
- };
- };
-
const fetchInstanceRequestsChart = async (): Promise<typeof data> => {
const raw = await os.api('charts/instance', { host: props.args.host, limit: props.limit, span: props.span });
return {
@@ -680,11 +768,26 @@ export default defineComponent({
};
};
+ const fetchPerUserDriveChart = async (): Promise<typeof data> => {
+ const raw = await os.api('charts/user/drive', { userId: props.args.user.id, limit: props.limit, span: props.span });
+ return {
+ series: [{
+ name: 'Inc',
+ type: 'area',
+ data: format(raw.incSize),
+ }, {
+ name: 'Dec',
+ type: 'area',
+ data: format(raw.decSize),
+ }],
+ };
+ };
+
const fetchAndRender = async () => {
const fetchData = () => {
switch (props.src) {
- case 'federation-instances': return fetchFederationInstancesChart(false);
- case 'federation-instances-total': return fetchFederationInstancesChart(true);
+ case 'federation': return fetchFederationChart();
+ case 'ap-request': return fetchApRequestChart();
case 'users': return fetchUsersChart(false);
case 'users-total': return fetchUsersChart(true);
case 'active-users': return fetchActiveUsersChart();
@@ -693,9 +796,7 @@ export default defineComponent({
case 'remote-notes': return fetchNotesChart('remote');
case 'notes-total': return fetchNotesTotalChart();
case 'drive': return fetchDriveChart();
- case 'drive-total': return fetchDriveTotalChart();
case 'drive-files': return fetchDriveFilesChart();
- case 'drive-files-total': return fetchDriveFilesTotalChart();
case 'instance-requests': return fetchInstanceRequestsChart();
case 'instance-users': return fetchInstanceUsersChart(false);
@@ -710,6 +811,7 @@ export default defineComponent({
case 'instance-drive-files-total': return fetchInstanceDriveFilesChart(true);
case 'per-user-notes': return fetchPerUserNotesChart();
+ case 'per-user-drive': return fetchPerUserDriveChart();
}
};
fetching.value = true;
diff --git a/packages/client/src/components/emoji-picker.vue b/packages/client/src/components/emoji-picker.vue
index 6999ad6517..3e1208979f 100644
--- a/packages/client/src/components/emoji-picker.vue
+++ b/packages/client/src/components/emoji-picker.vue
@@ -81,7 +81,7 @@ import { getStaticImageUrl } from '@/scripts/get-static-image-url';
import Ripple from '@/components/ripple.vue';
import * as os from '@/os';
import { isTouchUsing } from '@/scripts/touch';
-import { isMobile } from '@/scripts/is-mobile';
+import { deviceKind } from '@/scripts/device-kind';
import { emojiCategories, instance } from '@/instance';
import XSection from './emoji-picker.section.vue';
import { i18n } from '@/i18n';
@@ -263,7 +263,7 @@ watch(q, () => {
});
function focus() {
- if (!isMobile && !isTouchUsing) {
+ if (!['smartphone', 'tablet'].includes(deviceKind) && !isTouchUsing) {
search.value?.focus({
preventScroll: true
});
diff --git a/packages/client/src/components/instance-stats.vue b/packages/client/src/components/instance-stats.vue
index 409c3a49ca..d2aa5a151a 100644
--- a/packages/client/src/components/instance-stats.vue
+++ b/packages/client/src/components/instance-stats.vue
@@ -3,8 +3,8 @@
<div class="selects" style="display: flex;">
<MkSelect v-model="chartSrc" style="margin: 0; flex: 1;">
<optgroup :label="$ts.federation">
- <option value="federation-instances">{{ $ts._charts.federationInstancesIncDec }}</option>
- <option value="federation-instances-total">{{ $ts._charts.federationInstancesTotal }}</option>
+ <option value="federation">{{ $ts._charts.federation }}</option>
+ <option value="ap-request">{{ $ts._charts.apRequest }}</option>
</optgroup>
<optgroup :label="$ts.users">
<option value="users">{{ $ts._charts.usersIncDec }}</option>
@@ -19,9 +19,7 @@
</optgroup>
<optgroup :label="$ts.drive">
<option value="drive-files">{{ $ts._charts.filesIncDec }}</option>
- <option value="drive-files-total">{{ $ts._charts.filesTotal }}</option>
<option value="drive">{{ $ts._charts.storageUsageIncDec }}</option>
- <option value="drive-total">{{ $ts._charts.storageUsageTotal }}</option>
</optgroup>
</MkSelect>
<MkSelect v-model="chartSpan" style="margin: 0 0 0 10px;">
diff --git a/packages/client/src/components/instance-ticker.vue b/packages/client/src/components/instance-ticker.vue
index 77fd8bb344..9b0a18ec90 100644
--- a/packages/client/src/components/instance-ticker.vue
+++ b/packages/client/src/components/instance-ticker.vue
@@ -7,15 +7,27 @@
<script lang="ts" setup>
import { } from 'vue';
+import { instanceName } from '@/config';
const props = defineProps<{
- instance: any; // TODO
+ instance?: {
+ faviconUrl?: string
+ name: string
+ themeColor?: string
+ }
}>();
-const themeColor = props.instance.themeColor || '#777777';
+// if no instance data is given, this is for the local instance
+const instance = props.instance ?? {
+ faviconUrl: '/favicon.ico',
+ name: instanceName,
+ themeColor: (document.querySelector('meta[name="theme-color-orig"]') as HTMLMetaElement)?.content
+};
+
+const themeColor = instance.themeColor ?? '#777777';
const bg = {
- background: `linear-gradient(90deg, ${themeColor}, ${themeColor + '00'})`
+ background: `linear-gradient(90deg, ${themeColor}, ${themeColor}00)`
};
</script>
diff --git a/packages/client/src/components/post-form.vue b/packages/client/src/components/post-form.vue
index 535218ecf9..64a6478f45 100644
--- a/packages/client/src/components/post-form.vue
+++ b/packages/client/src/components/post-form.vue
@@ -342,6 +342,7 @@ function addTag(tag: string) {
function focus() {
textareaEl.focus();
+ textareaEl.setSelectionRange(textareaEl.value.length, textareaEl.value.length);
}
function chooseFileFrom(ev) {
diff --git a/packages/client/src/components/ui/modal.vue b/packages/client/src/components/ui/modal.vue
index 3c3bb5c226..b42c0e4d42 100644
--- a/packages/client/src/components/ui/modal.vue
+++ b/packages/client/src/components/ui/modal.vue
@@ -14,6 +14,7 @@ import { nextTick, onMounted, computed, ref, watch, provide } from 'vue';
import * as os from '@/os';
import { isTouchUsing } from '@/scripts/touch';
import { defaultStore } from '@/store';
+import { deviceKind } from '@/scripts/device-kind';
function getFixedContainer(el: Element | null): Element | null {
if (el == null || el.tagName === 'BODY') return null;
@@ -62,7 +63,7 @@ const content = ref<HTMLElement>();
const zIndex = os.claimZIndex(props.zPriority);
const type = computed(() => {
if (props.preferType === 'auto') {
- if (!defaultStore.state.disableDrawer && isTouchUsing && window.innerWidth < 500 && window.innerHeight < 1000) {
+ if (!defaultStore.state.disableDrawer && isTouchUsing && deviceKind === 'smartphone') {
return 'drawer';
} else {
return props.src != null ? 'popup' : 'dialog';
diff --git a/packages/client/src/components/ui/tooltip.vue b/packages/client/src/components/ui/tooltip.vue
index 1892877cc1..3ccd1b7316 100644
--- a/packages/client/src/components/ui/tooltip.vue
+++ b/packages/client/src/components/ui/tooltip.vue
@@ -17,8 +17,12 @@ const props = withDefaults(defineProps<{
y?: number;
text?: string;
maxWidth?: number;
+ direction?: 'top' | 'bottom' | 'right' | 'left';
+ innerMargin?: number;
}>(), {
maxWidth: 250,
+ direction: 'top',
+ innerMargin: 0,
});
const emit = defineEmits<{
@@ -34,39 +38,144 @@ const setPosition = () => {
const contentWidth = el.value.offsetWidth;
const contentHeight = el.value.offsetHeight;
- let left: number;
- let top: number;
-
let rect: DOMRect;
if (props.targetElement) {
rect = props.targetElement.getBoundingClientRect();
+ }
+
+ const calcPosWhenTop = () => {
+ let left: number;
+ let top: number;
- left = rect.left + window.pageXOffset + (props.targetElement.offsetWidth / 2);
- top = rect.top + window.pageYOffset - contentHeight;
+ if (props.targetElement) {
+ left = rect.left + window.pageXOffset + (props.targetElement.offsetWidth / 2);
+ top = (rect.top + window.pageYOffset - contentHeight) - props.innerMargin;
+ } else {
+ left = props.x;
+ top = (props.y - contentHeight) - props.innerMargin;
+ }
- el.value.style.transformOrigin = 'center bottom';
- } else {
- left = props.x;
- top = props.y - contentHeight;
+ left -= (el.value.offsetWidth / 2);
+
+ if (left + contentWidth - window.pageXOffset > window.innerWidth) {
+ left = window.innerWidth - contentWidth + window.pageXOffset - 1;
+ }
+
+ return [left, top];
}
- left -= (el.value.offsetWidth / 2);
+ const calcPosWhenBottom = () => {
+ let left: number;
+ let top: number;
+
+ if (props.targetElement) {
+ left = rect.left + window.pageXOffset + (props.targetElement.offsetWidth / 2);
+ top = (rect.top + window.pageYOffset + props.targetElement.offsetHeight) + props.innerMargin;
+ } else {
+ left = props.x;
+ top = (props.y) + props.innerMargin;
+ }
+
+ left -= (el.value.offsetWidth / 2);
- if (left + contentWidth - window.pageXOffset > window.innerWidth) {
- left = window.innerWidth - contentWidth + window.pageXOffset - 1;
+ if (left + contentWidth - window.pageXOffset > window.innerWidth) {
+ left = window.innerWidth - contentWidth + window.pageXOffset - 1;
+ }
+
+ return [left, top];
}
- // ツールチップを上に向かって表示するスペースがなければ下に向かって出す
- if (top - window.pageYOffset < 0) {
+ const calcPosWhenLeft = () => {
+ let left: number;
+ let top: number;
+
if (props.targetElement) {
- top = rect.top + window.pageYOffset + props.targetElement.offsetHeight;
- el.value.style.transformOrigin = 'center top';
+ left = (rect.left + window.pageXOffset - contentWidth) - props.innerMargin;
+ top = rect.top + window.pageYOffset + (props.targetElement.offsetHeight / 2);
} else {
+ left = (props.x - contentWidth) - props.innerMargin;
top = props.y;
}
+
+ top -= (el.value.offsetHeight / 2);
+
+ if (top + contentHeight - window.pageYOffset > window.innerHeight) {
+ top = window.innerHeight - contentHeight + window.pageYOffset - 1;
+ }
+
+ return [left, top];
+ }
+
+ const calcPosWhenRight = () => {
+ let left: number;
+ let top: number;
+
+ if (props.targetElement) {
+ left = (rect.left + window.pageXOffset) + props.innerMargin;
+ top = rect.top + window.pageYOffset + (props.targetElement.offsetHeight / 2);
+ } else {
+ left = props.x + props.innerMargin;
+ top = props.y;
+ }
+
+ top -= (el.value.offsetHeight / 2);
+
+ if (top + contentHeight - window.pageYOffset > window.innerHeight) {
+ top = window.innerHeight - contentHeight + window.pageYOffset - 1;
+ }
+
+ return [left, top];
+ }
+
+ const calc = (): {
+ left: number;
+ top: number;
+ transformOrigin: string;
+ } => {
+ switch (props.direction) {
+ case 'top': {
+ const [left, top] = calcPosWhenTop();
+
+ // ツールチップを上に向かって表示するスペースがなければ下に向かって出す
+ if (top - window.pageYOffset < 0) {
+ const [left, top] = calcPosWhenBottom();
+ return { left, top, transformOrigin: 'center top' };
+ }
+
+ return { left, top, transformOrigin: 'center bottom' };
+ }
+
+ case 'bottom': {
+ const [left, top] = calcPosWhenBottom();
+ // TODO: ツールチップを下に向かって表示するスペースがなければ上に向かって出す
+ return { left, top, transformOrigin: 'center top' };
+ }
+
+ case 'left': {
+ const [left, top] = calcPosWhenLeft();
+
+ // ツールチップを左に向かって表示するスペースがなければ右に向かって出す
+ if (left - window.pageXOffset < 0) {
+ const [left, top] = calcPosWhenRight();
+ return { left, top, transformOrigin: 'left center' };
+ }
+
+ return { left, top, transformOrigin: 'right center' };
+ }
+
+ case 'right': {
+ const [left, top] = calcPosWhenRight();
+ // TODO: ツールチップを右に向かって表示するスペースがなければ左に向かって出す
+ return { left, top, transformOrigin: 'left center' };
+ }
+ }
+
+ return null as never;
}
+ const { left, top, transformOrigin } = calc();
+ el.value.style.transformOrigin = transformOrigin;
el.value.style.left = left + 'px';
el.value.style.top = top + 'px';
};