From afb5efd56d10a47ace28c4072fada1502d19ee20 Mon Sep 17 00:00:00 2001 From: "ilia.brauer" Date: Thu, 4 Jun 2026 13:23:35 +0200 Subject: [PATCH 1/5] [notice-bubble] fixed timer for keyboard interactions + removed unnecessary focusLock --- semcore/notice-bubble/src/NoticeBubble.tsx | 37 ++++++++++++------- .../notice-bubble/src/NoticeBubble.type.ts | 2 + 2 files changed, 25 insertions(+), 14 deletions(-) diff --git a/semcore/notice-bubble/src/NoticeBubble.tsx b/semcore/notice-bubble/src/NoticeBubble.tsx index e7da1b61f7..f1658174ba 100644 --- a/semcore/notice-bubble/src/NoticeBubble.tsx +++ b/semcore/notice-bubble/src/NoticeBubble.tsx @@ -7,9 +7,8 @@ import i18nEnhance from '@semcore/core/lib/utils/enhances/i18nEnhance'; import fire from '@semcore/core/lib/utils/fire'; import { getFocusableIn } from '@semcore/core/lib/utils/focus-lock/getFocusableIn'; import isNode from '@semcore/core/lib/utils/isNode'; -import { useForkRef } from '@semcore/core/lib/utils/ref'; import { contextThemeEnhance } from '@semcore/core/lib/utils/ThemeProvider'; -import { useFocusLock, setFocus } from '@semcore/core/lib/utils/use/useFocusLock'; +import { setFocus, isFocusInside } from '@semcore/core/lib/utils/use/useFocusLock'; import { cssVariableEnhance } from '@semcore/core/lib/utils/useCssVariable'; import { ZIndexStackingContextProvider, @@ -159,15 +158,6 @@ class NoticeBubbleContainerRoot extends Component< } } -const FocusLock = React.forwardRef((props: any, outerRef: React.ForwardedRef) => { - const { focusLock, ...other } = props; - const innerRef = React.useRef(null); - useFocusLock(innerRef, false, 'auto', !focusLock, true); - const ref = useForkRef(outerRef, innerRef); - - return ; -}); - const PortalForNoticeItem = (props: NoticeBubbleViewItemProps & { containerNode: HTMLElement; tag: typeof ViewInfo }) => { const [showContent, setShowContent] = React.useState(false); @@ -206,6 +196,7 @@ class ViewInfo extends Component { timer: Timer | null = null; ref = React.createRef(); closeButtonRef = React.createRef(); + triggerButtonComponent: Element | null = null; componentDidMount() { const { duration } = this.props; @@ -214,6 +205,8 @@ class ViewInfo extends Component { document.body.addEventListener('mousemove', this.handleBodyMouseMove); } + this.triggerButtonComponent = document.activeElement; + const noticeElement = this.ref.current; if (noticeElement) { @@ -228,6 +221,11 @@ class ViewInfo extends Component { } componentWillUnmount() { + const triggerElement = this.triggerButtonComponent; + if (this.ref.current && isFocusInside(this.ref.current) && triggerElement instanceof HTMLElement) { + setTimeout(() => setFocus(triggerElement), 0); + } + this.clearTimer(); document.body.removeEventListener('mousemove', this.handleBodyMouseMove); } @@ -251,6 +249,16 @@ class ViewInfo extends Component { } }; + handleFocus = () => { + if (!this.timer) return; + this.timer.pause(); + }; + + handleBlur = () => { + if (!this.timer) return; + this.timer.resume(); + }; + handleMouseEnter = () => { if (!this.timer) return; this.timer.pause(); @@ -275,7 +283,7 @@ class ViewInfo extends Component { }; render() { - const SBubble = FocusLock; + const SBubble = Root; const SDismiss = Button; const SContent = Flex; const SMessage = 'div'; @@ -291,7 +299,6 @@ class ViewInfo extends Component { icon, children, action, - focusLock, } = this.props; return sstyled(styles)( @@ -303,13 +310,15 @@ class ViewInfo extends Component { keyframes={[styles['@enter'], styles['@exit']]} > Date: Thu, 11 Jun 2026 14:37:57 +0200 Subject: [PATCH 2/5] [notice-bubble] rolled back focus lock --- semcore/notice-bubble/src/NoticeBubble.tsx | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/semcore/notice-bubble/src/NoticeBubble.tsx b/semcore/notice-bubble/src/NoticeBubble.tsx index 0f03b39be6..ca19646540 100644 --- a/semcore/notice-bubble/src/NoticeBubble.tsx +++ b/semcore/notice-bubble/src/NoticeBubble.tsx @@ -1,14 +1,15 @@ import { Animation, Box, Flex, Portal } from '@semcore/base-components'; import Button from '@semcore/button'; -import { createComponent, Component, sstyled, Root } from '@semcore/core'; +import { createComponent, Component, sstyled, Root, lastInteraction } from '@semcore/core'; import type { Intergalactic } from '@semcore/core'; import type { WithI18nEnhanceProps } from '@semcore/core/lib/utils/enhances/i18nEnhance'; import i18nEnhance from '@semcore/core/lib/utils/enhances/i18nEnhance'; import fire from '@semcore/core/lib/utils/fire'; import { getFocusableIn } from '@semcore/core/lib/utils/focus-lock/getFocusableIn'; import isNode from '@semcore/core/lib/utils/isNode'; +import { useForkRef } from '@semcore/core/lib/utils/ref'; import { contextThemeEnhance } from '@semcore/core/lib/utils/ThemeProvider'; -import { setFocus, isFocusInside } from '@semcore/core/lib/utils/use/useFocusLock'; +import { setFocus, isFocusInside, useFocusLock } from '@semcore/core/lib/utils/use/useFocusLock'; import { cssVariableEnhance } from '@semcore/core/lib/utils/useCssVariable'; import { ZIndexStackingContextProvider, @@ -219,9 +220,13 @@ class ViewInfo extends Component { const noticeElement = this.ref.current; if (noticeElement) { - const focusableNodes = getFocusableIn(noticeElement).filter( - (node) => node !== this.closeButtonRef.current, - ); + let focusableNodes = getFocusableIn(noticeElement); + + if (lastInteraction.isMouse()) { + focusableNodes = focusableNodes.filter( + (node) => node !== this.closeButtonRef.current, + ); + } if (focusableNodes.length > 0) { setTimeout(() => setFocus(noticeElement), 0); @@ -308,6 +313,7 @@ class ViewInfo extends Component { icon, children, action, + focusLock, } = this.props; return sstyled(styles)( From 06bc573c6a29396c46213b064d0c02ac10131863 Mon Sep 17 00:00:00 2001 From: "ilia.brauer" Date: Fri, 12 Jun 2026 11:16:04 +0200 Subject: [PATCH 3/5] [notice-bubble] rolled back focus to notice logic --- semcore/notice-bubble/src/NoticeBubble.tsx | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/semcore/notice-bubble/src/NoticeBubble.tsx b/semcore/notice-bubble/src/NoticeBubble.tsx index ca19646540..21f767855c 100644 --- a/semcore/notice-bubble/src/NoticeBubble.tsx +++ b/semcore/notice-bubble/src/NoticeBubble.tsx @@ -220,13 +220,9 @@ class ViewInfo extends Component { const noticeElement = this.ref.current; if (noticeElement) { - let focusableNodes = getFocusableIn(noticeElement); - - if (lastInteraction.isMouse()) { - focusableNodes = focusableNodes.filter( - (node) => node !== this.closeButtonRef.current, - ); - } + const focusableNodes = getFocusableIn(noticeElement).filter( + (node) => node !== this.closeButtonRef.current, + ); if (focusableNodes.length > 0) { setTimeout(() => setFocus(noticeElement), 0); From 803dc0a0f3a1b08fef84a6162851f5da5fe135f6 Mon Sep 17 00:00:00 2001 From: "ilia.brauer" Date: Fri, 12 Jun 2026 15:17:05 +0200 Subject: [PATCH 4/5] [notice-bubble] set focus only if nb doesn't close by itself of have some focusable element except close button --- semcore/notice-bubble/src/NoticeBubble.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/semcore/notice-bubble/src/NoticeBubble.tsx b/semcore/notice-bubble/src/NoticeBubble.tsx index 21f767855c..d8dd56db42 100644 --- a/semcore/notice-bubble/src/NoticeBubble.tsx +++ b/semcore/notice-bubble/src/NoticeBubble.tsx @@ -220,11 +220,12 @@ class ViewInfo extends Component { const noticeElement = this.ref.current; if (noticeElement) { - const focusableNodes = getFocusableIn(noticeElement).filter( + const allFocusableNodes = getFocusableIn(noticeElement); + const focusableNodes = allFocusableNodes.filter( (node) => node !== this.closeButtonRef.current, ); - if (focusableNodes.length > 0) { + if (focusableNodes.length > 0 || (!duration && allFocusableNodes.length > 0)) { setTimeout(() => setFocus(noticeElement), 0); } } From 08c4b5d80a16efb29ce6c1e4a5c9ff1752df5587 Mon Sep 17 00:00:00 2001 From: "ilia.brauer" Date: Mon, 15 Jun 2026 15:19:43 +0200 Subject: [PATCH 5/5] [notice-bubble] fixed corner cases with focus --- semcore/notice-bubble/src/NoticeBubble.tsx | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/semcore/notice-bubble/src/NoticeBubble.tsx b/semcore/notice-bubble/src/NoticeBubble.tsx index d8dd56db42..cf2b57db4b 100644 --- a/semcore/notice-bubble/src/NoticeBubble.tsx +++ b/semcore/notice-bubble/src/NoticeBubble.tsx @@ -241,6 +241,12 @@ class ViewInfo extends Component { document.body.removeEventListener('mousemove', this.handleBodyMouseMove); } + isFocusInBubble() { + const noticeElement = this.ref.current; + + return noticeElement ? isFocusInside(noticeElement) : false; + } + clearTimer() { if (this.timer) { this.timer.clear(); @@ -276,12 +282,12 @@ class ViewInfo extends Component { }; handleMouseLeave = () => { - if (!this.timer) return; + if (!this.timer || this.isFocusInBubble()) return; this.timer.resume(); }; handleBodyMouseMove = (event: MouseEvent) => { - if (!this.timer?.paused) return; + if (!this.timer?.paused || this.isFocusInBubble()) return; const rect = this.ref.current?.getBoundingClientRect(); if (!rect) return; const mouseInRect =