summaryrefslogtreecommitdiff
path: root/packages/frontend/src/scripts/focus.ts
diff options
context:
space:
mode:
authordakkar <dakkar@thenautilus.net>2024-08-02 12:25:58 +0100
committerdakkar <dakkar@thenautilus.net>2024-08-02 12:25:58 +0100
commitcfa9b852df9e0293865b3acbd67d59265962e552 (patch)
treea408ad670956a45c4e162e4ecc97a3624e2b0f20 /packages/frontend/src/scripts/focus.ts
parentmerge: rate limit all password checks - fixes #540 (!568) (diff)
parentMerge pull request #14233 from misskey-dev/develop (diff)
downloadsharkey-cfa9b852df9e0293865b3acbd67d59265962e552.tar.gz
sharkey-cfa9b852df9e0293865b3acbd67d59265962e552.tar.bz2
sharkey-cfa9b852df9e0293865b3acbd67d59265962e552.zip
Merge remote-tracking branch 'misskey/master' into feature/misskey-2024.07
Diffstat (limited to 'packages/frontend/src/scripts/focus.ts')
-rw-r--r--packages/frontend/src/scripts/focus.ts94
1 files changed, 71 insertions, 23 deletions
diff --git a/packages/frontend/src/scripts/focus.ts b/packages/frontend/src/scripts/focus.ts
index ea6ee61c88..eb2da5ad86 100644
--- a/packages/frontend/src/scripts/focus.ts
+++ b/packages/frontend/src/scripts/focus.ts
@@ -3,30 +3,78 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-export function focusPrev(el: Element | null, self = false, scroll = true) {
- if (el == null) return;
- if (!self) el = el.previousElementSibling;
- if (el) {
- if (el.hasAttribute('tabindex')) {
- (el as HTMLElement).focus({
- preventScroll: !scroll,
- });
- } else {
- focusPrev(el.previousElementSibling, true);
- }
+import { getScrollPosition, getScrollContainer, getStickyBottom, getStickyTop } from '@/scripts/scroll.js';
+import { getElementOrNull, getNodeOrNull } from '@/scripts/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 function focusNext(el: Element | null, self = false, scroll = true) {
- if (el == null) return;
- if (!self) el = el.nextElementSibling;
- if (el) {
- if (el.hasAttribute('tabindex')) {
- (el as HTMLElement).focus({
- preventScroll: !scroll,
- });
- } else {
- focusPrev(el.nextElementSibling, true);
+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 });
}
-}
+};