refactor(frontend): src -> anchorElement

This commit is contained in:
syuilo 2025-05-22 12:24:52 +09:00
parent aa55663ef7
commit 74c857e23d
13 changed files with 61 additions and 61 deletions

View File

@ -12,7 +12,7 @@ SPDX-License-Identifier: AGPL-3.0-only
:hasInteractionWithOtherFocusTrappedEls="true" :hasInteractionWithOtherFocusTrappedEls="true"
:transparentBg="true" :transparentBg="true"
:manualShowing="manualShowing" :manualShowing="manualShowing"
:src="src" :anchorElement="anchorElement"
@click="modal?.close()" @click="modal?.close()"
@esc="modal?.close()" @esc="modal?.close()"
@opening="opening" @opening="opening"
@ -44,7 +44,7 @@ import { prefer } from '@/preferences.js';
const props = withDefaults(defineProps<{ const props = withDefaults(defineProps<{
manualShowing?: boolean | null; manualShowing?: boolean | null;
src?: HTMLElement; anchorElement?: HTMLElement;
showPinned?: boolean; showPinned?: boolean;
pinnedEmojis?: string[], pinnedEmojis?: string[],
asReactionPicker?: boolean; asReactionPicker?: boolean;

View File

@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
--> -->
<template> <template>
<MkModal ref="modal" v-slot="{ type, maxHeight }" :preferType="preferedModalType" :anchor="anchor" :transparentBg="true" :src="src" @click="modal?.close()" @closed="emit('closed')" @esc="modal?.close()"> <MkModal ref="modal" v-slot="{ type, maxHeight }" :preferType="preferedModalType" :anchor="anchor" :transparentBg="true" :anchorElement="anchorElement" @click="modal?.close()" @closed="emit('closed')" @esc="modal?.close()">
<div class="szkkfdyq _popup _shadow" :class="{ asDrawer: type === 'drawer' }" :style="{ maxHeight: maxHeight ? maxHeight + 'px' : '' }"> <div class="szkkfdyq _popup _shadow" :class="{ asDrawer: type === 'drawer' }" :style="{ maxHeight: maxHeight ? maxHeight + 'px' : '' }">
<div class="main"> <div class="main">
<template v-for="item in items" :key="item.text"> <template v-for="item in items" :key="item.text">
@ -34,7 +34,7 @@ import { deviceKind } from '@/utility/device-kind.js';
import { prefer } from '@/preferences.js'; import { prefer } from '@/preferences.js';
const props = withDefaults(defineProps<{ const props = withDefaults(defineProps<{
src?: HTMLElement; anchorElement?: HTMLElement;
anchor?: { x: string; y: string; }; anchor?: { x: string; y: string; };
}>(), { }>(), {
anchor: () => ({ x: 'right', y: 'center' }), anchor: () => ({ x: 'right', y: 'center' }),
@ -44,7 +44,7 @@ const emit = defineEmits<{
(ev: 'closed'): void; (ev: 'closed'): void;
}>(); }>();
const preferedModalType = (deviceKind === 'desktop' && props.src != null) ? 'popup' : const preferedModalType = (deviceKind === 'desktop' && props.anchorElement != null) ? 'popup' :
deviceKind === 'smartphone' ? 'drawer' : deviceKind === 'smartphone' ? 'drawer' :
'dialog'; 'dialog';

View File

@ -67,7 +67,7 @@ type ModalTypes = 'popup' | 'dialog' | 'drawer';
const props = withDefaults(defineProps<{ const props = withDefaults(defineProps<{
manualShowing?: boolean | null; manualShowing?: boolean | null;
anchor?: { x: string; y: string; }; anchor?: { x: string; y: string; };
src?: HTMLElement | null; anchorElement?: HTMLElement | null;
preferType?: ModalTypes | 'auto'; preferType?: ModalTypes | 'auto';
zPriority?: 'low' | 'middle' | 'high'; zPriority?: 'low' | 'middle' | 'high';
noOverlap?: boolean; noOverlap?: boolean;
@ -76,7 +76,7 @@ const props = withDefaults(defineProps<{
returnFocusTo?: HTMLElement | null; returnFocusTo?: HTMLElement | null;
}>(), { }>(), {
manualShowing: null, manualShowing: null,
src: null, anchorElement: null,
anchor: () => ({ x: 'center', y: 'bottom' }), anchor: () => ({ x: 'center', y: 'bottom' }),
preferType: 'auto', preferType: 'auto',
zPriority: 'low', zPriority: 'low',
@ -110,7 +110,7 @@ const type = computed<ModalTypes>(() => {
if ((prefer.s.menuStyle === 'drawer') || (prefer.s.menuStyle === 'auto' && isTouchUsing && deviceKind === 'smartphone')) { if ((prefer.s.menuStyle === 'drawer') || (prefer.s.menuStyle === 'auto' && isTouchUsing && deviceKind === 'smartphone')) {
return 'drawer'; return 'drawer';
} else { } else {
return props.src != null ? 'popup' : 'dialog'; return props.anchorElement != null ? 'popup' : 'dialog';
} }
} else { } else {
return props.preferType!; return props.preferType!;
@ -149,7 +149,7 @@ function close(opts: { useSendAnimation?: boolean } = {}) {
} }
// eslint-disable-next-line vue/no-mutating-props // eslint-disable-next-line vue/no-mutating-props
if (props.src) props.src.style.pointerEvents = 'auto'; if (props.anchorElement) props.anchorElement.style.pointerEvents = 'auto';
showing.value = false; showing.value = false;
emit('close'); emit('close');
} }
@ -174,13 +174,13 @@ const MARGIN = 16;
const SCROLLBAR_THICKNESS = 16; const SCROLLBAR_THICKNESS = 16;
const align = () => { const align = () => {
if (props.src == null) return; if (props.anchorElement == null) return;
if (type.value === 'drawer') return; if (type.value === 'drawer') return;
if (type.value === 'dialog') return; if (type.value === 'dialog') return;
if (content.value == null) return; if (content.value == null) return;
const srcRect = props.src.getBoundingClientRect(); const anchorRect = props.anchorElement.getBoundingClientRect();
const width = content.value!.offsetWidth; const width = content.value!.offsetWidth;
const height = content.value!.offsetHeight; const height = content.value!.offsetHeight;
@ -188,15 +188,15 @@ const align = () => {
let left; let left;
let top; let top;
const x = srcRect.left + (fixed.value ? 0 : window.scrollX); const x = anchorRect.left + (fixed.value ? 0 : window.scrollX);
const y = srcRect.top + (fixed.value ? 0 : window.scrollY); const y = anchorRect.top + (fixed.value ? 0 : window.scrollY);
if (props.anchor.x === 'center') { if (props.anchor.x === 'center') {
left = x + (props.src.offsetWidth / 2) - (width / 2); left = x + (props.anchorElement.offsetWidth / 2) - (width / 2);
} else if (props.anchor.x === 'left') { } else if (props.anchor.x === 'left') {
// TODO // TODO
} else if (props.anchor.x === 'right') { } else if (props.anchor.x === 'right') {
left = x + props.src.offsetWidth; left = x + props.anchorElement.offsetWidth;
} }
if (props.anchor.y === 'center') { if (props.anchor.y === 'center') {
@ -204,7 +204,7 @@ const align = () => {
} else if (props.anchor.y === 'top') { } else if (props.anchor.y === 'top') {
// TODO // TODO
} else if (props.anchor.y === 'bottom') { } else if (props.anchor.y === 'bottom') {
top = y + props.src.offsetHeight; top = y + props.anchorElement.offsetHeight;
} }
if (fixed.value) { if (fixed.value) {
@ -214,7 +214,7 @@ const align = () => {
} }
const underSpace = ((window.innerHeight - SCROLLBAR_THICKNESS) - MARGIN) - top; const underSpace = ((window.innerHeight - SCROLLBAR_THICKNESS) - MARGIN) - top;
const upperSpace = (srcRect.top - MARGIN); const upperSpace = (anchorRect.top - MARGIN);
// //
if (top + height > ((window.innerHeight - SCROLLBAR_THICKNESS) - MARGIN)) { if (top + height > ((window.innerHeight - SCROLLBAR_THICKNESS) - MARGIN)) {
@ -238,7 +238,7 @@ const align = () => {
} }
const underSpace = ((window.innerHeight - SCROLLBAR_THICKNESS) - MARGIN) - (top - window.scrollY); const underSpace = ((window.innerHeight - SCROLLBAR_THICKNESS) - MARGIN) - (top - window.scrollY);
const upperSpace = (srcRect.top - MARGIN); const upperSpace = (anchorRect.top - MARGIN);
// //
if (top + height - window.scrollY > ((window.innerHeight - SCROLLBAR_THICKNESS) - MARGIN)) { if (top + height - window.scrollY > ((window.innerHeight - SCROLLBAR_THICKNESS) - MARGIN)) {
@ -268,15 +268,15 @@ const align = () => {
let transformOriginX = 'center'; let transformOriginX = 'center';
let transformOriginY = 'center'; let transformOriginY = 'center';
if (top >= srcRect.top + props.src.offsetHeight + (fixed.value ? 0 : window.scrollY)) { if (top >= anchorRect.top + props.anchorElement.offsetHeight + (fixed.value ? 0 : window.scrollY)) {
transformOriginY = 'top'; transformOriginY = 'top';
} else if ((top + height) <= srcRect.top + (fixed.value ? 0 : window.scrollY)) { } else if ((top + height) <= anchorRect.top + (fixed.value ? 0 : window.scrollY)) {
transformOriginY = 'bottom'; transformOriginY = 'bottom';
} }
if (left >= srcRect.left + props.src.offsetWidth + (fixed.value ? 0 : window.scrollX)) { if (left >= anchorRect.left + props.anchorElement.offsetWidth + (fixed.value ? 0 : window.scrollX)) {
transformOriginX = 'left'; transformOriginX = 'left';
} else if ((left + width) <= srcRect.left + (fixed.value ? 0 : window.scrollX)) { } else if ((left + width) <= anchorRect.left + (fixed.value ? 0 : window.scrollX)) {
transformOriginX = 'right'; transformOriginX = 'right';
} }
@ -317,12 +317,12 @@ const alignObserver = new ResizeObserver((entries, observer) => {
}); });
onMounted(() => { onMounted(() => {
watch(() => props.src, async () => { watch(() => props.anchorElement, async () => {
if (props.src) { if (props.anchorElement) {
// eslint-disable-next-line vue/no-mutating-props // eslint-disable-next-line vue/no-mutating-props
props.src.style.pointerEvents = 'none'; props.anchorElement.style.pointerEvents = 'none';
} }
fixed.value = (type.value === 'drawer') || (getFixedContainer(props.src) != null); fixed.value = (type.value === 'drawer') || (getFixedContainer(props.anchorElement) != null);
await nextTick(); await nextTick();
@ -339,7 +339,7 @@ onMounted(() => {
} }
} else { } else {
releaseFocusTrap?.(); releaseFocusTrap?.();
focusParent(props.returnFocusTo ?? props.src, true, false); focusParent(props.returnFocusTo ?? props.anchorElement, true, false);
} }
}, { immediate: true }); }, { immediate: true });

View File

@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
--> -->
<template> <template>
<MkModal ref="modal" v-slot="{ type, maxHeight }" :manualShowing="manualShowing" :zPriority="'high'" :src="src" :transparentBg="true" :returnFocusTo="returnFocusTo" @click="click" @close="onModalClose" @closed="onModalClosed"> <MkModal ref="modal" v-slot="{ type, maxHeight }" :manualShowing="manualShowing" :zPriority="'high'" :anchorElement="anchorElement" :transparentBg="true" :returnFocusTo="returnFocusTo" @click="click" @close="onModalClose" @closed="onModalClosed">
<MkMenu :items="items" :align="align" :width="width" :max-height="maxHeight" :asDrawer="type === 'drawer'" :returnFocusTo="returnFocusTo" :class="{ [$style.drawer]: type === 'drawer' }" @close="onMenuClose" @hide="hide"/> <MkMenu :items="items" :align="align" :width="width" :max-height="maxHeight" :asDrawer="type === 'drawer'" :returnFocusTo="returnFocusTo" :class="{ [$style.drawer]: type === 'drawer' }" @close="onMenuClose" @hide="hide"/>
</MkModal> </MkModal>
</template> </template>
@ -19,7 +19,7 @@ defineProps<{
items: MenuItem[]; items: MenuItem[];
align?: 'center' | string; align?: 'center' | string;
width?: number; width?: number;
src?: HTMLElement | null; anchorElement?: HTMLElement | null;
returnFocusTo?: HTMLElement | null; returnFocusTo?: HTMLElement | null;
}>(); }>();

View File

@ -470,7 +470,7 @@ function setVisibility() {
currentVisibility: visibility.value, currentVisibility: visibility.value,
isSilenced: $i.isSilenced, isSilenced: $i.isSilenced,
localOnly: localOnly.value, localOnly: localOnly.value,
src: visibilityButton.value, anchorElement: visibilityButton.value,
...(props.reply ? { isReplyVisibilitySpecified: props.reply.visibility === 'specified' } : {}), ...(props.reply ? { isReplyVisibilitySpecified: props.reply.visibility === 'specified' } : {}),
}, { }, {
changeVisibility: v => { changeVisibility: v => {

View File

@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
--> -->
<template> <template>
<MkModal ref="modal" v-slot="{ type }" :zPriority="'high'" :src="src" @click="modal?.close()" @closed="emit('closed')" @esc="modal?.close()"> <MkModal ref="modal" v-slot="{ type }" :zPriority="'high'" :anchorElement="anchorElement" @click="modal?.close()" @closed="emit('closed')" @esc="modal?.close()">
<div class="_popup" :class="{ [$style.root]: true, [$style.asDrawer]: type === 'drawer' }"> <div class="_popup" :class="{ [$style.root]: true, [$style.asDrawer]: type === 'drawer' }">
<div :class="[$style.label, $style.item]"> <div :class="[$style.label, $style.item]">
{{ i18n.ts.visibility }} {{ i18n.ts.visibility }}
@ -53,7 +53,7 @@ const props = withDefaults(defineProps<{
currentVisibility: typeof Misskey.noteVisibilities[number]; currentVisibility: typeof Misskey.noteVisibilities[number];
isSilenced: boolean; isSilenced: boolean;
localOnly: boolean; localOnly: boolean;
src?: HTMLElement; anchorElement?: HTMLElement;
isReplyVisibilitySpecified?: boolean; isReplyVisibilitySpecified?: boolean;
}>(), { }>(), {
}); });

View File

@ -609,10 +609,10 @@ export async function selectRole(params: ComponentProps<typeof MkRoleSelectDialo
}); });
} }
export async function pickEmoji(src: HTMLElement, opts: ComponentProps<typeof MkEmojiPickerDialog_TypeReferenceOnly>): Promise<string> { export async function pickEmoji(anchorElement: HTMLElement, opts: ComponentProps<typeof MkEmojiPickerDialog_TypeReferenceOnly>): Promise<string> {
return new Promise(resolve => { return new Promise(resolve => {
const { dispose } = popup(defineAsyncComponent(() => import('@/components/MkEmojiPickerDialog.vue')), { const { dispose } = popup(defineAsyncComponent(() => import('@/components/MkEmojiPickerDialog.vue')), {
src, anchorElement,
...opts, ...opts,
}, { }, {
done: emoji => { done: emoji => {
@ -639,20 +639,20 @@ export async function cropImageFile(imageFile: File | Blob, options: {
}); });
} }
export function popupMenu(items: MenuItem[], src?: HTMLElement | EventTarget | null, options?: { export function popupMenu(items: MenuItem[], anchorElement?: HTMLElement | EventTarget | null, options?: {
align?: string; align?: string;
width?: number; width?: number;
onClosing?: () => void; onClosing?: () => void;
}): Promise<void> { }): Promise<void> {
if (!(src instanceof HTMLElement)) { if (!(anchorElement instanceof HTMLElement)) {
src = null; anchorElement = null;
} }
let returnFocusTo = getHTMLElementOrNull(src) ?? getHTMLElementOrNull(window.document.activeElement); let returnFocusTo = getHTMLElementOrNull(anchorElement) ?? getHTMLElementOrNull(window.document.activeElement);
return new Promise(resolve => nextTick(() => { return new Promise(resolve => nextTick(() => {
const { dispose } = popup(MkPopupMenu, { const { dispose } = popup(MkPopupMenu, {
items, items,
src, anchorElement,
width: options?.width, width: options?.width,
align: options?.align, align: options?.align,
returnFocusTo, returnFocusTo,

View File

@ -73,7 +73,7 @@ const otherNavItemIndicated = computed<boolean>(() => {
function more(ev: MouseEvent) { function more(ev: MouseEvent) {
const { dispose } = os.popup(defineAsyncComponent(() => import('@/components/MkLaunchPad.vue')), { const { dispose } = os.popup(defineAsyncComponent(() => import('@/components/MkLaunchPad.vue')), {
src: ev.currentTarget ?? ev.target, anchorElement: ev.currentTarget ?? ev.target,
anchor: { x: 'center', y: 'bottom' }, anchor: { x: 'center', y: 'bottom' },
}, { }, {
closed: () => dispose(), closed: () => dispose(),

View File

@ -180,7 +180,7 @@ function more(ev: MouseEvent) {
const target = getHTMLElementOrNull(ev.currentTarget ?? ev.target); const target = getHTMLElementOrNull(ev.currentTarget ?? ev.target);
if (!target) return; if (!target) return;
const { dispose } = os.popup(defineAsyncComponent(() => import('@/components/MkLaunchPad.vue')), { const { dispose } = os.popup(defineAsyncComponent(() => import('@/components/MkLaunchPad.vue')), {
src: target, anchorElement: target,
}, { }, {
closed: () => dispose(), closed: () => dispose(),
}); });

View File

@ -197,7 +197,7 @@ export function chooseFileFromUrl(): Promise<Misskey.entities.DriveFile> {
}); });
} }
function select(src: HTMLElement | EventTarget | null, label: string | null, multiple: boolean): Promise<Misskey.entities.DriveFile[]> { function select(anchorElement: HTMLElement | EventTarget | null, label: string | null, multiple: boolean): Promise<Misskey.entities.DriveFile[]> {
return new Promise((res, rej) => { return new Promise((res, rej) => {
os.popupMenu([label ? { os.popupMenu([label ? {
text: label, text: label,
@ -214,16 +214,16 @@ function select(src: HTMLElement | EventTarget | null, label: string | null, mul
text: i18n.ts.fromUrl, text: i18n.ts.fromUrl,
icon: 'ti ti-link', icon: 'ti ti-link',
action: () => chooseFileFromUrl().then(file => res([file])), action: () => chooseFileFromUrl().then(file => res([file])),
}], src); }], anchorElement);
}); });
} }
export function selectFile(src: HTMLElement | EventTarget | null, label: string | null = null): Promise<Misskey.entities.DriveFile> { export function selectFile(anchorElement: HTMLElement | EventTarget | null, label: string | null = null): Promise<Misskey.entities.DriveFile> {
return select(src, label, false).then(files => files[0]); return select(anchorElement, label, false).then(files => files[0]);
} }
export function selectFiles(src: HTMLElement | EventTarget | null, label: string | null = null): Promise<Misskey.entities.DriveFile[]> { export function selectFiles(anchorElement: HTMLElement | EventTarget | null, label: string | null = null): Promise<Misskey.entities.DriveFile[]> {
return select(src, label, true); return select(anchorElement, label, true);
} }
export async function createCroppedImageDriveFileFromImageDriveFile(imageDriveFile: Misskey.entities.DriveFile, options: { export async function createCroppedImageDriveFileFromImageDriveFile(imageDriveFile: Misskey.entities.DriveFile, options: {

View File

@ -15,7 +15,7 @@ import { prefer } from '@/preferences.js';
* 使 * 使
*/ */
class EmojiPicker { class EmojiPicker {
private src: Ref<HTMLElement | null> = ref(null); private anchorElement: Ref<HTMLElement | null> = ref(null);
private manualShowing = ref(false); private manualShowing = ref(false);
private onChosen?: (emoji: string) => void; private onChosen?: (emoji: string) => void;
private onClosed?: () => void; private onClosed?: () => void;
@ -34,7 +34,7 @@ class EmojiPicker {
}); });
await popup(defineAsyncComponent(() => import('@/components/MkEmojiPickerDialog.vue')), { await popup(defineAsyncComponent(() => import('@/components/MkEmojiPickerDialog.vue')), {
src: this.src, anchorElement: this.anchorElement,
pinnedEmojis: emojisRef, pinnedEmojis: emojisRef,
asReactionPicker: false, asReactionPicker: false,
manualShowing: this.manualShowing, manualShowing: this.manualShowing,
@ -47,18 +47,18 @@ class EmojiPicker {
this.manualShowing.value = false; this.manualShowing.value = false;
}, },
closed: () => { closed: () => {
this.src.value = null; this.anchorElement.value = null;
if (this.onClosed) this.onClosed(); if (this.onClosed) this.onClosed();
}, },
}); });
} }
public show( public show(
src: HTMLElement, anchorElement: HTMLElement,
onChosen?: EmojiPicker['onChosen'], onChosen?: EmojiPicker['onChosen'],
onClosed?: EmojiPicker['onClosed'], onClosed?: EmojiPicker['onClosed'],
) { ) {
this.src.value = src; this.anchorElement.value = anchorElement;
this.manualShowing.value = true; this.manualShowing.value = true;
this.onChosen = onChosen; this.onChosen = onChosen;
this.onClosed = onClosed; this.onClosed = onClosed;

View File

@ -4,20 +4,20 @@
*/ */
import { nextTick } from 'vue'; import { nextTick } from 'vue';
import { MFM_TAGS } from '@@/js/const.js';
import type { Ref } from 'vue'; import type { Ref } from 'vue';
import type { MenuItem } from '@/types/menu.js';
import * as os from '@/os.js'; import * as os from '@/os.js';
import { i18n } from '@/i18n.js'; import { i18n } from '@/i18n.js';
import { MFM_TAGS } from '@@/js/const.js';
import type { MenuItem } from '@/types/menu.js';
/** /**
* MFMの装飾のリストを表示する * MFMの装飾のリストを表示する
*/ */
export function mfmFunctionPicker(src: HTMLElement | EventTarget | null, textArea: HTMLInputElement | HTMLTextAreaElement, textRef: Ref<string>) { export function mfmFunctionPicker(anchorElement: HTMLElement | EventTarget | null, textArea: HTMLInputElement | HTMLTextAreaElement, textRef: Ref<string>) {
os.popupMenu([{ os.popupMenu([{
text: i18n.ts.addMfmFunction, text: i18n.ts.addMfmFunction,
type: 'label', type: 'label',
}, ...getFunctionList(textArea, textRef)], src); }, ...getFunctionList(textArea, textRef)], anchorElement);
} }
function getFunctionList(textArea: HTMLInputElement | HTMLTextAreaElement, textRef: Ref<string>): MenuItem[] { function getFunctionList(textArea: HTMLInputElement | HTMLTextAreaElement, textRef: Ref<string>): MenuItem[] {

View File

@ -10,7 +10,7 @@ import { popup } from '@/os.js';
import { prefer } from '@/preferences.js'; import { prefer } from '@/preferences.js';
class ReactionPicker { class ReactionPicker {
private src: Ref<HTMLElement | null> = ref(null); private anchorElement: Ref<HTMLElement | null> = ref(null);
private manualShowing = ref(false); private manualShowing = ref(false);
private targetNote: Ref<Misskey.entities.Note | null> = ref(null); private targetNote: Ref<Misskey.entities.Note | null> = ref(null);
private onChosen?: (reaction: string) => void; private onChosen?: (reaction: string) => void;
@ -30,7 +30,7 @@ class ReactionPicker {
}); });
await popup(defineAsyncComponent(() => import('@/components/MkEmojiPickerDialog.vue')), { await popup(defineAsyncComponent(() => import('@/components/MkEmojiPickerDialog.vue')), {
src: this.src, anchorElement: this.anchorElement,
pinnedEmojis: reactionsRef, pinnedEmojis: reactionsRef,
asReactionPicker: true, asReactionPicker: true,
targetNote: this.targetNote, targetNote: this.targetNote,
@ -43,14 +43,14 @@ class ReactionPicker {
this.manualShowing.value = false; this.manualShowing.value = false;
}, },
closed: () => { closed: () => {
this.src.value = null; this.anchorElement.value = null;
if (this.onClosed) this.onClosed(); if (this.onClosed) this.onClosed();
}, },
}); });
} }
public show(src: HTMLElement | null, targetNote: Misskey.entities.Note | null, onChosen?: ReactionPicker['onChosen'], onClosed?: ReactionPicker['onClosed']) { public show(anchorElement: HTMLElement | null, targetNote: Misskey.entities.Note | null, onChosen?: ReactionPicker['onChosen'], onClosed?: ReactionPicker['onClosed']) {
this.src.value = src; this.anchorElement.value = anchorElement;
this.targetNote.value = targetNote; this.targetNote.value = targetNote;
this.manualShowing.value = true; this.manualShowing.value = true;
this.onChosen = onChosen; this.onChosen = onChosen;