diff --git a/packages/frontend/src/directives/adaptive-bg.ts b/packages/frontend/src/directives/adaptive-bg.ts index f88996019f9d..87f77db20dbb 100644 --- a/packages/frontend/src/directives/adaptive-bg.ts +++ b/packages/frontend/src/directives/adaptive-bg.ts @@ -3,11 +3,11 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { Directive } from 'vue'; +import type { ObjectDirective } from 'vue'; import { getBgColor } from '@/scripts/get-bg-color.js'; -export default { - mounted(src, binding, vn) { +export const vAdaptiveBg: ObjectDirective = { + mounted(src) { const parentBg = getBgColor(src.parentElement) ?? 'transparent'; const myBg = window.getComputedStyle(src).backgroundColor; @@ -18,4 +18,4 @@ export default { src.style.backgroundColor = myBg; } }, -} as Directive; +}; diff --git a/packages/frontend/src/directives/adaptive-border.ts b/packages/frontend/src/directives/adaptive-border.ts index 1305f312bdeb..da43de3a28e5 100644 --- a/packages/frontend/src/directives/adaptive-border.ts +++ b/packages/frontend/src/directives/adaptive-border.ts @@ -3,11 +3,11 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { Directive } from 'vue'; +import type { ObjectDirective } from 'vue'; import { getBgColor } from '@/scripts/get-bg-color.js'; -export default { - mounted(src, binding, vn) { +export const vAdaptiveBorder: ObjectDirective = { + mounted(src) { const parentBg = getBgColor(src.parentElement) ?? 'transparent'; const myBg = window.getComputedStyle(src).backgroundColor; @@ -18,4 +18,4 @@ export default { src.style.borderColor = myBg; } }, -} as Directive; +}; diff --git a/packages/frontend/src/directives/anim.ts b/packages/frontend/src/directives/anim.ts index d5b6ae428799..089bd919d55f 100644 --- a/packages/frontend/src/directives/anim.ts +++ b/packages/frontend/src/directives/anim.ts @@ -3,21 +3,23 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { Directive } from 'vue'; +import type { ObjectDirective } from 'vue'; -export default { - beforeMount(src, binding, vn) { +export const vAnim: ObjectDirective = { + beforeMount(src) { src.style.opacity = '0'; src.style.transform = 'scale(0.9)'; // ページネーションと相性が悪いので - //if (typeof binding.value === 'number') src.style.transitionDelay = `${binding.value * 30}ms`; + // if (typeof binding.value === 'number') { + // src.style.transitionDelay = `${binding.value * 30}ms`; + // } src.classList.add('_zoom'); }, - mounted(src, binding, vn) { + mounted(src) { window.setTimeout(() => { src.style.opacity = '1'; src.style.transform = 'none'; }, 1); }, -} as Directive; +}; diff --git a/packages/frontend/src/directives/appear.ts b/packages/frontend/src/directives/appear.ts index 706d4a9ee4b6..790ab16ab586 100644 --- a/packages/frontend/src/directives/appear.ts +++ b/packages/frontend/src/directives/appear.ts @@ -3,15 +3,15 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { Directive } from 'vue'; +import type { ObjectDirective } from 'vue'; -export default { - mounted(src, binding, vn) { +export const vAppear: ObjectDirective unknown) | null | undefined> = { + mounted(src, binding) { const fn = binding.value; if (fn == null) return; - const observer = new IntersectionObserver(entries => { - if (entries.some(entry => entry.isIntersecting)) { + const observer = new IntersectionObserver((entries) => { + if (entries.some((entry) => entry.isIntersecting)) { fn(); } }); @@ -21,7 +21,7 @@ export default { src._observer_ = observer; }, - unmounted(src, binding, vn) { - if (src._observer_) src._observer_.disconnect(); + unmounted(src) { + src._observer_?.disconnect(); }, -} as Directive; +}; diff --git a/packages/frontend/src/directives/click-anime.ts b/packages/frontend/src/directives/click-anime.ts index 5bb48bbcdd48..ad539d972c73 100644 --- a/packages/frontend/src/directives/click-anime.ts +++ b/packages/frontend/src/directives/click-anime.ts @@ -3,20 +3,20 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { Directive } from 'vue'; +import type { ObjectDirective } from 'vue'; import { defaultStore } from '@/store.js'; -export default { - mounted(el: HTMLElement, binding, vn) { +export const vClickAnime: ObjectDirective = { + mounted(src) { if (!defaultStore.state.animation) return; - const target = el.children[0]; + const target = src.children[0]; if (target == null) return; target.classList.add('_anime_bounce_standBy'); - el.addEventListener('mousedown', () => { + src.addEventListener('mousedown', () => { target.classList.remove('_anime_bounce'); target.classList.add('_anime_bounce_standBy'); @@ -27,14 +27,14 @@ export default { }); }); - el.addEventListener('click', () => { + src.addEventListener('click', () => { target.classList.add('_anime_bounce'); target.classList.remove('_anime_bounce_ready'); }); - el.addEventListener('animationend', () => { + src.addEventListener('animationend', () => { target.classList.remove('_anime_bounce'); target.classList.add('_anime_bounce_standBy'); }); }, -} as Directive; +}; diff --git a/packages/frontend/src/directives/get-size.ts b/packages/frontend/src/directives/get-size.ts index 2655c76c482e..ce465d077ae5 100644 --- a/packages/frontend/src/directives/get-size.ts +++ b/packages/frontend/src/directives/get-size.ts @@ -3,15 +3,36 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { Directive } from 'vue'; +import type { ObjectDirective } from 'vue'; -const mountings = new Map void; }>(); -function calc(src: Element) { +export const vGetSize: ObjectDirective unknown) | null | undefined> = { + mounted(src, binding) { + const resize = new ResizeObserver(() => { + calc(src); + }); + resize.observe(src); + + mountings.set(src, { resize, fn: binding.value }); + calc(src); + }, + + unmounted(src, binding) { + if (binding.value != null) binding.value(0, 0); + const info = mountings.get(src); + if (!info) return; + info.resize.disconnect(); + if (info.intersection) info.intersection.disconnect(); + mountings.delete(src); + }, +}; + +function calc(src: HTMLElement) { const info = mountings.get(src); const height = src.clientHeight; const width = src.clientWidth; @@ -22,8 +43,8 @@ function calc(src: Element) { if (!height) { // IntersectionObserverで表示検出する if (!info.intersection) { - info.intersection = new IntersectionObserver(entries => { - if (entries.some(entry => entry.isIntersecting)) calc(src); + info.intersection = new IntersectionObserver((entries) => { + if (entries.some((entry) => entry.isIntersecting)) calc(src); }); } info.intersection.observe(src); @@ -36,24 +57,3 @@ function calc(src: Element) { info.fn(width, height); } - -export default { - mounted(src, binding, vn) { - const resize = new ResizeObserver((entries, observer) => { - calc(src); - }); - resize.observe(src); - - mountings.set(src, { resize, fn: binding.value }); - calc(src); - }, - - unmounted(src, binding, vn) { - binding.value(0, 0); - const info = mountings.get(src); - if (!info) return; - info.resize.disconnect(); - if (info.intersection) info.intersection.disconnect(); - mountings.delete(src); - }, -} as Directive void>; diff --git a/packages/frontend/src/directives/hotkey.ts b/packages/frontend/src/directives/hotkey.ts index 0e5c7ede2404..af02ed89be2e 100644 --- a/packages/frontend/src/directives/hotkey.ts +++ b/packages/frontend/src/directives/hotkey.ts @@ -3,27 +3,28 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { Directive } from 'vue'; +import type { ObjectDirective } from 'vue'; +import type { Keymap } from '@/scripts/hotkey.js'; import { makeHotkey } from '@/scripts/hotkey.js'; -export default { - mounted(el, binding) { - el._hotkey_global = binding.modifiers.global === true; +export const vHotkey: ObjectDirective = { + mounted(src, binding) { + src._hotkey_global = binding.modifiers.global === true; - el._keyHandler = makeHotkey(binding.value); + src._keyHandler = makeHotkey(binding.value); - if (el._hotkey_global) { - document.addEventListener('keydown', el._keyHandler, { passive: false }); + if (src._hotkey_global) { + document.addEventListener('keydown', src._keyHandler, { passive: false }); } else { - el.addEventListener('keydown', el._keyHandler, { passive: false }); + src.addEventListener('keydown', src._keyHandler, { passive: false }); } }, - unmounted(el) { - if (el._hotkey_global) { - document.removeEventListener('keydown', el._keyHandler); + unmounted(src) { + if (src._hotkey_global) { + document.removeEventListener('keydown', src._keyHandler); } else { - el.removeEventListener('keydown', el._keyHandler); + src.removeEventListener('keydown', src._keyHandler); } }, -} as Directive; +}; diff --git a/packages/frontend/src/directives/index.ts b/packages/frontend/src/directives/index.ts index bda7738ccdaa..5a106cb63777 100644 --- a/packages/frontend/src/directives/index.ts +++ b/packages/frontend/src/directives/index.ts @@ -3,19 +3,19 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { App } from 'vue'; +import type { App } from 'vue'; -import userPreview from './user-preview.js'; -import getSize from './get-size.js'; -import ripple from './ripple.js'; -import tooltip from './tooltip.js'; -import hotkey from './hotkey.js'; -import appear from './appear.js'; -import anim from './anim.js'; -import clickAnime from './click-anime.js'; -import panel from './panel.js'; -import adaptiveBorder from './adaptive-border.js'; -import adaptiveBg from './adaptive-bg.js'; +import { vAdaptiveBg } from '@/directives/adaptive-bg.js'; +import { vAdaptiveBorder } from '@/directives/adaptive-border.js'; +import { vAnim } from '@/directives/anim.js'; +import { vAppear } from '@/directives/appear.js'; +import { vClickAnime } from '@/directives/click-anime.js'; +import { vGetSize } from '@/directives/get-size.js'; +import { vHotkey } from '@/directives/hotkey.js'; +import { vPanel } from '@/directives/panel.js'; +import { vRipple } from '@/directives/ripple.js'; +import { vTooltip } from '@/directives/tooltip.js'; +import { vUserPreview } from '@/directives/user-preview.js'; export default function(app: App) { for (const [key, value] of Object.entries(directives)) { @@ -24,16 +24,31 @@ export default function(app: App) { } export const directives = { - 'userPreview': userPreview, - 'user-preview': userPreview, - 'get-size': getSize, - 'ripple': ripple, - 'tooltip': tooltip, - 'hotkey': hotkey, - 'appear': appear, - 'anim': anim, - 'click-anime': clickAnime, - 'panel': panel, - 'adaptive-border': adaptiveBorder, - 'adaptive-bg': adaptiveBg, -}; + 'adaptive-bg': vAdaptiveBg, + 'adaptive-border': vAdaptiveBorder, + 'anim': vAnim, + 'appear': vAppear, + 'click-anime': vClickAnime, + 'get-size': vGetSize, + 'hotkey': vHotkey, + 'panel': vPanel, + 'ripple': vRipple, + 'tooltip': vTooltip, + 'user-preview': vUserPreview, +} as const; + +declare module '@vue/runtime-core' { + export interface GlobalDirectives { + vAdaptiveBg: typeof vAdaptiveBg; + vAdaptiveBorder: typeof vAdaptiveBorder; + vAnim: typeof vAnim; + vAppear: typeof vAppear; + vClickAnime: typeof vClickAnime; + vGetSize: typeof vGetSize; + vHotkey: typeof vHotkey; + vPanel: typeof vPanel; + vRipple: typeof vRipple; + vTooltip: typeof vTooltip; + vUserPreview: typeof vUserPreview; + } +} diff --git a/packages/frontend/src/directives/panel.ts b/packages/frontend/src/directives/panel.ts index aa26b94d0bc0..d5efe1b4b8e8 100644 --- a/packages/frontend/src/directives/panel.ts +++ b/packages/frontend/src/directives/panel.ts @@ -3,11 +3,11 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { Directive } from 'vue'; +import type { ObjectDirective } from 'vue'; import { getBgColor } from '@/scripts/get-bg-color.js'; -export default { - mounted(src, binding, vn) { +export const vPanel: ObjectDirective = { + mounted(src) { const parentBg = getBgColor(src.parentElement) ?? 'transparent'; const myBg = getComputedStyle(document.documentElement).getPropertyValue('--MI_THEME-panel'); @@ -18,4 +18,4 @@ export default { src.style.backgroundColor = 'var(--MI_THEME-panel)'; } }, -} as Directive; +}; diff --git a/packages/frontend/src/directives/ripple.ts b/packages/frontend/src/directives/ripple.ts index a043ff212dec..df61837b57fd 100644 --- a/packages/frontend/src/directives/ripple.ts +++ b/packages/frontend/src/directives/ripple.ts @@ -3,19 +3,20 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import MkRippleEffect from '@/components/MkRippleEffect.vue'; +import type { ObjectDirective } from 'vue'; import { popup } from '@/os.js'; +import MkRippleEffect from '@/components/MkRippleEffect.vue'; -export default { - mounted(el, binding, vn) { +export const vRipple: ObjectDirective = { + mounted(src, binding) { // 明示的に false であればバインドしない if (binding.value === false) return; - el.addEventListener('click', () => { - const rect = el.getBoundingClientRect(); + src.addEventListener('click', () => { + const rect = src.getBoundingClientRect(); - const x = rect.left + (el.offsetWidth / 2); - const y = rect.top + (el.offsetHeight / 2); + const x = rect.left + (src.offsetWidth / 2); + const y = rect.top + (src.offsetHeight / 2); const { dispose } = popup(MkRippleEffect, { x, y }, { end: () => dispose(), diff --git a/packages/frontend/src/directives/tooltip.ts b/packages/frontend/src/directives/tooltip.ts index 251ce5675f42..2a9814722b5d 100644 --- a/packages/frontend/src/directives/tooltip.ts +++ b/packages/frontend/src/directives/tooltip.ts @@ -6,18 +6,19 @@ // TODO: useTooltip関数使うようにしたい // ただディレクティブ内でonUnmountedなどのcomposition api使えるのか不明 -import { defineAsyncComponent, Directive, ref } from 'vue'; +import { defineAsyncComponent, ref } from 'vue'; +import type { ObjectDirective } from 'vue'; import { isTouchUsing } from '@/scripts/touch.js'; import { popup, alert } from '@/os.js'; const start = isTouchUsing ? 'touchstart' : 'mouseenter'; const end = isTouchUsing ? 'touchend' : 'mouseleave'; -export default { - mounted(el: HTMLElement, binding, vn) { +export const vTooltip: ObjectDirective = { + mounted(src, binding) { const delay = binding.modifiers.noDelay ? 0 : 100; - const self = (el as any)._tooltipDirective_ = {} as any; + const self = (src as any)._tooltipDirective_ = {} as any; self.text = binding.value as string; self._close = null; @@ -34,7 +35,7 @@ export default { }; if (binding.arg === 'dialog') { - el.addEventListener('click', (ev) => { + src.addEventListener('click', (ev) => { ev.preventDefault(); ev.stopPropagation(); alert({ @@ -46,7 +47,7 @@ export default { } self.show = () => { - if (!document.body.contains(el)) return; + if (!document.body.contains(src)) return; if (self._close) return; if (self.text == null) return; @@ -56,7 +57,7 @@ export default { text: self.text, asMfm: binding.modifiers.mfm, direction: binding.modifiers.left ? 'left' : binding.modifiers.right ? 'right' : binding.modifiers.top ? 'top' : binding.modifiers.bottom ? 'bottom' : 'top', - targetElement: el, + targetElement: src, }, { closed: () => dispose(), }); @@ -66,11 +67,11 @@ export default { }; }; - el.addEventListener('selectstart', ev => { + src.addEventListener('selectstart', (ev) => { ev.preventDefault(); }); - el.addEventListener(start, (ev) => { + src.addEventListener(start, () => { window.clearTimeout(self.showTimer); window.clearTimeout(self.hideTimer); if (delay === 0) { @@ -80,7 +81,7 @@ export default { } }, { passive: true }); - el.addEventListener(end, () => { + src.addEventListener(end, () => { window.clearTimeout(self.showTimer); window.clearTimeout(self.hideTimer); if (delay === 0) { @@ -90,19 +91,19 @@ export default { } }, { passive: true }); - el.addEventListener('click', () => { + src.addEventListener('click', () => { window.clearTimeout(self.showTimer); self.close(); }); }, - updated(el, binding) { - const self = el._tooltipDirective_; + updated(src, binding) { + const self = (src as any)._tooltipDirective_; self.text = binding.value as string; }, - unmounted(el, binding, vn) { - const self = el._tooltipDirective_; + unmounted(src) { + const self = (src as any)._tooltipDirective_; window.clearInterval(self.checkTimer); }, -} as Directive; +}; diff --git a/packages/frontend/src/directives/user-preview.ts b/packages/frontend/src/directives/user-preview.ts index 278d842d09fe..340f63d29104 100644 --- a/packages/frontend/src/directives/user-preview.ts +++ b/packages/frontend/src/directives/user-preview.ts @@ -3,10 +3,30 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { defineAsyncComponent, Directive, ref } from 'vue'; +import { defineAsyncComponent, ref } from 'vue'; +import type { ObjectDirective } from 'vue'; import { popup } from '@/os.js'; -export class UserPreview { +export const vUserPreview: ObjectDirective = { + mounted(src, binding) { + if (binding.value == null) return; + + // TODO: 新たにプロパティを作るのをやめMapを使う + // ただメモリ的には↓の方が省メモリかもしれないので検討中 + const self = (src as any)._userPreviewDirective_ = {} as any; + + self.preview = new UserPreview(src, binding.value); + }, + + unmounted(src, binding) { + if (binding.value == null) return; + + const self = src._userPreviewDirective_; + self.preview.detach(); + }, +}; + +class UserPreview { private el; private user; private showTimer; @@ -102,22 +122,3 @@ export class UserPreview { this.el.removeEventListener('click', this.onClick); } } - -export default { - mounted(el: HTMLElement, binding, vn) { - if (binding.value == null) return; - - // TODO: 新たにプロパティを作るのをやめMapを使う - // ただメモリ的には↓の方が省メモリかもしれないので検討中 - const self = (el as any)._userPreviewDirective_ = {} as any; - - self.preview = new UserPreview(el, binding.value); - }, - - unmounted(el, binding, vn) { - if (binding.value == null) return; - - const self = el._userPreviewDirective_; - self.preview.detach(); - }, -} as Directive;