diff options
| author | syuilo <4439005+syuilo@users.noreply.github.com> | 2025-03-09 14:28:01 +0900 |
|---|---|---|
| committer | syuilo <4439005+syuilo@users.noreply.github.com> | 2025-03-09 14:28:01 +0900 |
| commit | be7e3b9a0cb81b78a744993fef2fa2fd2833fa9c (patch) | |
| tree | c82e18ce93ec0a24c57d7e36eb54a09266b3a25b /packages/frontend/src/utility/focus.ts | |
| parent | enhnace(frontend): 文字列比較のためのローマナイズを強化(... (diff) | |
| download | misskey-be7e3b9a0cb81b78a744993fef2fa2fd2833fa9c.tar.gz misskey-be7e3b9a0cb81b78a744993fef2fa2fd2833fa9c.tar.bz2 misskey-be7e3b9a0cb81b78a744993fef2fa2fd2833fa9c.zip | |
refactor(frontend): scripts -> utility
Diffstat (limited to 'packages/frontend/src/utility/focus.ts')
| -rw-r--r-- | packages/frontend/src/utility/focus.ts | 80 |
1 files changed, 80 insertions, 0 deletions
diff --git a/packages/frontend/src/utility/focus.ts b/packages/frontend/src/utility/focus.ts new file mode 100644 index 0000000000..e3fd928d1d --- /dev/null +++ b/packages/frontend/src/utility/focus.ts @@ -0,0 +1,80 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { getScrollPosition, getScrollContainer, getStickyBottom, getStickyTop } from '@@/js/scroll.js'; +import { getElementOrNull, getNodeOrNull } from '@/utility/get-dom-node-or-null.js'; + +type MaybeHTMLElement = EventTarget | Node | Element | HTMLElement; + +export const isFocusable = (input: MaybeHTMLElement | null | undefined): input is HTMLElement => { + if (input == null || !(input instanceof HTMLElement)) return false; + + if (input.tabIndex < 0) return false; + if ('disabled' in input && input.disabled === true) return false; + if ('readonly' in input && input.readonly === true) return false; + + if (!input.ownerDocument.contains(input)) return false; + + const style = window.getComputedStyle(input); + if (style.display === 'none') return false; + if (style.visibility === 'hidden') return false; + if (style.opacity === '0') return false; + if (style.pointerEvents === 'none') return false; + + return true; +}; + +export const focusPrev = (input: MaybeHTMLElement | null | undefined, self = false, scroll = true) => { + const element = self ? input : getElementOrNull(input)?.previousElementSibling; + if (element == null) return; + if (isFocusable(element)) { + focusOrScroll(element, scroll); + } else { + focusPrev(element, false, scroll); + } +}; + +export const focusNext = (input: MaybeHTMLElement | null | undefined, self = false, scroll = true) => { + const element = self ? input : getElementOrNull(input)?.nextElementSibling; + if (element == null) return; + if (isFocusable(element)) { + focusOrScroll(element, scroll); + } else { + focusNext(element, false, scroll); + } +}; + +export const focusParent = (input: MaybeHTMLElement | null | undefined, self = false, scroll = true) => { + const element = self ? input : getNodeOrNull(input)?.parentElement; + if (element == null) return; + if (isFocusable(element)) { + focusOrScroll(element, scroll); + } else { + focusParent(element, false, scroll); + } +}; + +const focusOrScroll = (element: HTMLElement, scroll: boolean) => { + if (scroll) { + const scrollContainer = getScrollContainer(element) ?? document.documentElement; + const scrollContainerTop = getScrollPosition(scrollContainer); + const stickyTop = getStickyTop(element, scrollContainer); + const stickyBottom = getStickyBottom(element, scrollContainer); + const top = element.getBoundingClientRect().top; + const bottom = element.getBoundingClientRect().bottom; + + let scrollTo = scrollContainerTop; + if (top < stickyTop) { + scrollTo += top - stickyTop; + } else if (bottom > window.innerHeight - stickyBottom) { + scrollTo += bottom - window.innerHeight + stickyBottom; + } + scrollContainer.scrollTo({ top: scrollTo, behavior: 'instant' }); + } + + if (document.activeElement !== element) { + element.focus({ preventScroll: true }); + } +}; |