diff --git a/packages/ui/src/components/chat/ChatInput.tsx b/packages/ui/src/components/chat/ChatInput.tsx index a0f4683d..e18adf87 100644 --- a/packages/ui/src/components/chat/ChatInput.tsx +++ b/packages/ui/src/components/chat/ChatInput.tsx @@ -792,7 +792,6 @@ const ChatInputComponent: React.FC = ({ onOpenSettings, scrollTo const agents = getVisibleAgents(); const primaryAgents = React.useMemo(() => agents.filter((agent) => agent.mode === 'primary'), [agents]); const isMobile = useUIStore((state) => state.isMobile); - const isKeyboardOpen = useUIStore((state) => state.isKeyboardOpen); const inputBarOffset = useUIStore((state) => state.inputBarOffset); const persistChatDraft = useUIStore((state) => state.persistChatDraft); const inputSpellcheckEnabled = useUIStore((state) => state.inputSpellcheckEnabled); @@ -3303,10 +3302,9 @@ const ChatInputComponent: React.FC = ({ onOpenSettings, scrollTo className={cn( "relative pt-0 pb-4", isDesktopExpanded && 'flex h-full min-h-0 flex-col pt-4', - isMobile && (isKeyboardOpen ? 'ios-keyboard-safe-area' : 'bottom-safe-area') + isMobile && 'bottom-safe-area' )} - data-keyboard-avoid="none" - style={isMobile && inputBarOffset > 0 && !isKeyboardOpen ? { marginBottom: `${inputBarOffset}px` } : undefined} + style={isMobile && inputBarOffset > 0 ? { marginBottom: `${inputBarOffset}px` } : undefined} >
diff --git a/packages/ui/src/components/layout/MainLayout.tsx b/packages/ui/src/components/layout/MainLayout.tsx index 750f4bbb..8e08e4c0 100644 --- a/packages/ui/src/components/layout/MainLayout.tsx +++ b/packages/ui/src/components/layout/MainLayout.tsx @@ -20,7 +20,6 @@ import { useUIStore } from '@/stores/useUIStore'; import { useUpdateStore } from '@/stores/useUpdateStore'; import { useDeviceInfo } from '@/lib/device'; import { useEffectiveDirectory } from '@/hooks/useEffectiveDirectory'; -import { useMobileKeyboardManager } from '@/hooks/useMobileKeyboardManager'; import { useI18n } from '@/lib/i18n'; import { cn } from '@/lib/utils'; import { isDesktopShell } from '@/lib/desktop'; @@ -354,8 +353,6 @@ export const MainLayout: React.FC = () => { }; }, []); - useMobileKeyboardManager(isMobile); - const secondaryView = React.useMemo(() => { switch (activeMainTab) { case 'plan': @@ -387,8 +384,7 @@ export const MainLayout: React.FC = () => {
{ - if (typeof window === 'undefined' || typeof document === 'undefined') { - return; - } - - const root = document.documentElement; - - let stickyKeyboardInset = 0; - let ignoreOpenUntilZero = false; - let previousHeight = 0; - let maxObservedLayoutHeight = 0; - let previousOrientation = ''; - let keyboardAvoidTarget: HTMLElement | null = null; - - const setKeyboardOpen = useUIStore.getState().setKeyboardOpen; - const userAgent = typeof navigator === 'undefined' ? '' : navigator.userAgent; - const isAndroid = /Android/i.test(userAgent); - const isIOS = /iPad|iPhone|iPod/.test(userAgent); - - const clearKeyboardAvoidTarget = () => { - if (!keyboardAvoidTarget) { - return; - } - keyboardAvoidTarget.style.setProperty('--oc-keyboard-avoid-offset', '0px'); - keyboardAvoidTarget.removeAttribute('data-keyboard-avoid-active'); - keyboardAvoidTarget = null; - }; - - const resolveKeyboardAvoidTarget = (active: HTMLElement | null) => { - if (!active) { - return null; - } - const explicitTargetId = active.getAttribute('data-keyboard-avoid-target-id'); - if (explicitTargetId) { - const explicitTarget = document.getElementById(explicitTargetId); - if (explicitTarget instanceof HTMLElement) { - return explicitTarget; - } - } - const markedTarget = active.closest('[data-keyboard-avoid]') as HTMLElement | null; - if (markedTarget) { - if (markedTarget.getAttribute('data-keyboard-avoid') === 'none') { - return null; - } - return markedTarget; - } - if (active.classList.contains('overlay-scrollbar-container')) { - const parent = active.parentElement; - if (parent instanceof HTMLElement) { - return parent; - } - } - return active; - }; - - const forceKeyboardClosed = () => { - stickyKeyboardInset = 0; - ignoreOpenUntilZero = true; - root.style.setProperty('--oc-keyboard-inset', '0px'); - setKeyboardOpen(false); - }; - - const isTextInputTarget = (element: HTMLElement | null) => { - if (!element) { - return false; - } - const tagName = element.tagName; - const isInput = tagName === 'INPUT' || tagName === 'TEXTAREA' || tagName === 'SELECT'; - return isInput || element.isContentEditable; - }; - - let rafId = 0; - - const updateVisualViewport = () => { - const viewport = window.visualViewport; - - const height = viewport ? Math.round(viewport.height) : window.innerHeight; - const offsetTop = viewport ? Math.max(0, Math.round(viewport.offsetTop)) : 0; - const orientation = window.innerWidth >= window.innerHeight ? 'landscape' : 'portrait'; - - root.style.setProperty('--oc-visual-viewport-offset-top', `${offsetTop}px`); - root.style.setProperty('--oc-visual-viewport-height', `${height}px`); - - const active = document.activeElement as HTMLElement | null; - const isTextTarget = isTextInputTarget(active); - - const layoutHeight = Math.round(root.clientHeight || window.innerHeight); - if (previousOrientation !== orientation) { - previousOrientation = orientation; - maxObservedLayoutHeight = layoutHeight; - } else if (layoutHeight > maxObservedLayoutHeight || maxObservedLayoutHeight === 0) { - maxObservedLayoutHeight = layoutHeight; - } - const viewportSum = height + offsetTop; - const rawInset = Math.max(0, layoutHeight - viewportSum); - const rawAndroidResizeInset = isAndroid - ? Math.max(0, maxObservedLayoutHeight - layoutHeight) - : 0; - - const openThreshold = isTextTarget ? 120 : 180; - const measuredInset = rawInset >= openThreshold ? rawInset : 0; - const androidResizeInset = isTextTarget && rawAndroidResizeInset >= openThreshold - ? rawAndroidResizeInset - : 0; - const effectiveMeasuredInset = Math.max(measuredInset, androidResizeInset); - - if (ignoreOpenUntilZero) { - if (effectiveMeasuredInset === 0) { - ignoreOpenUntilZero = false; - } - stickyKeyboardInset = 0; - } else if (stickyKeyboardInset === 0) { - if (effectiveMeasuredInset > 0 && isTextTarget) { - stickyKeyboardInset = effectiveMeasuredInset; - setKeyboardOpen(true); - } - } else { - const closingByHeight = !isTextTarget && height > previousHeight + 6; - - if (effectiveMeasuredInset === 0) { - stickyKeyboardInset = 0; - setKeyboardOpen(false); - } else if (closingByHeight) { - forceKeyboardClosed(); - } else if (effectiveMeasuredInset > 0 && isTextTarget) { - stickyKeyboardInset = effectiveMeasuredInset; - setKeyboardOpen(true); - } else if (effectiveMeasuredInset > stickyKeyboardInset) { - stickyKeyboardInset = effectiveMeasuredInset; - setKeyboardOpen(true); - } - } - - root.style.setProperty('--oc-keyboard-inset', `${stickyKeyboardInset}px`); - previousHeight = height; - - const keyboardHomeIndicator = isIOS && stickyKeyboardInset > 0 ? 34 : 0; - root.style.setProperty('--oc-keyboard-home-indicator', `${keyboardHomeIndicator}px`); - - const avoidTarget = isTextTarget ? resolveKeyboardAvoidTarget(active) : null; - - if (!isMobile || !avoidTarget || !active) { - clearKeyboardAvoidTarget(); - } else { - if (avoidTarget !== keyboardAvoidTarget) { - clearKeyboardAvoidTarget(); - keyboardAvoidTarget = avoidTarget; - } - const viewportBottom = offsetTop + height; - const rect = active.getBoundingClientRect(); - const overlap = rect.bottom - viewportBottom; - const clearance = 8; - const keyboardInset = Math.max(stickyKeyboardInset, effectiveMeasuredInset); - const avoidOffset = overlap > clearance && keyboardInset > 0 - ? Math.min(overlap, keyboardInset) - : 0; - const target = keyboardAvoidTarget; - if (target) { - target.style.setProperty('--oc-keyboard-avoid-offset', `${avoidOffset}px`); - target.setAttribute('data-keyboard-avoid-active', 'true'); - } - } - - if (isMobile && isTextTarget) { - const scroller = document.scrollingElement; - if (scroller && scroller.scrollTop !== 0) { - scroller.scrollTop = 0; - } - if (window.scrollY !== 0) { - window.scrollTo(0, 0); - } - } - }; - - const scheduleVisualViewportUpdate = () => { - if (rafId) return; - rafId = requestAnimationFrame(() => { - rafId = 0; - updateVisualViewport(); - }); - }; - - updateVisualViewport(); - - const viewport = window.visualViewport; - viewport?.addEventListener('resize', scheduleVisualViewportUpdate); - viewport?.addEventListener('scroll', scheduleVisualViewportUpdate); - window.addEventListener('resize', scheduleVisualViewportUpdate); - window.addEventListener('orientationchange', scheduleVisualViewportUpdate); - - const handleFocusIn = (event: FocusEvent) => { - const target = event.target as HTMLElement | null; - if (isTextInputTarget(target)) { - ignoreOpenUntilZero = false; - } - scheduleVisualViewportUpdate(); - }; - document.addEventListener('focusin', handleFocusIn, true); - - const handleFocusOut = (event: FocusEvent) => { - const target = event.target as HTMLElement | null; - if (!isTextInputTarget(target)) { - return; - } - - const related = event.relatedTarget as HTMLElement | null; - if (isTextInputTarget(related)) { - return; - } - - window.requestAnimationFrame(() => { - if (isTextInputTarget(document.activeElement as HTMLElement | null)) { - return; - } - - const currentViewport = window.visualViewport; - const height = currentViewport ? Math.round(currentViewport.height) : window.innerHeight; - const offsetTop = currentViewport ? Math.max(0, Math.round(currentViewport.offsetTop)) : 0; - const layoutHeight = Math.round(root.clientHeight || window.innerHeight); - const viewportSum = height + offsetTop; - const rawInset = Math.max(0, layoutHeight - viewportSum); - - if (rawInset > 0) { - updateVisualViewport(); - return; - } - - forceKeyboardClosed(); - updateVisualViewport(); - }); - }; - - document.addEventListener('focusout', handleFocusOut, true); - - return () => { - if (rafId) cancelAnimationFrame(rafId); - viewport?.removeEventListener('resize', scheduleVisualViewportUpdate); - viewport?.removeEventListener('scroll', scheduleVisualViewportUpdate); - window.removeEventListener('resize', scheduleVisualViewportUpdate); - window.removeEventListener('orientationchange', scheduleVisualViewportUpdate); - document.removeEventListener('focusin', handleFocusIn, true); - document.removeEventListener('focusout', handleFocusOut, true); - clearKeyboardAvoidTarget(); - }; - }, [isMobile]); -} diff --git a/packages/ui/src/index.css b/packages/ui/src/index.css index e1155348..bece505b 100644 --- a/packages/ui/src/index.css +++ b/packages/ui/src/index.css @@ -223,10 +223,6 @@ textarea[data-chat-input="true"]:focus-visible { -webkit-appearance: none; } -[data-keyboard-avoid-active="true"] { - transform: translateY(calc(-1 * var(--oc-keyboard-avoid-offset, 0px))); -} - /* Scroll shadow (HeroUI) fallback styling to ensure visible gradients without the Tailwind plugin */ [data-scroll-shadow="true"] { --scroll-shadow-size: var(--scroll-shadow-size, 48px); diff --git a/packages/ui/src/lib/device.ts b/packages/ui/src/lib/device.ts index 53d854ee..62c9322e 100644 --- a/packages/ui/src/lib/device.ts +++ b/packages/ui/src/lib/device.ts @@ -32,7 +32,6 @@ const setRootDeviceAttributes = ( isTauriShellRuntime: boolean, deviceType: DeviceType, hasTouchInput: boolean, - isAndroid: boolean, ) => { if (typeof window === 'undefined') { return; @@ -42,7 +41,7 @@ const setRootDeviceAttributes = ( const isMobile = deviceType === 'mobile'; const isTablet = deviceType === 'tablet'; - root.classList.remove('device-mobile', 'device-tablet', 'device-desktop', 'device-android'); + root.classList.remove('device-mobile', 'device-tablet', 'device-desktop'); root.classList.add( deviceType === 'mobile' ? 'device-mobile' @@ -61,9 +60,6 @@ const setRootDeviceAttributes = ( root.classList.remove('mobile-pointer'); } else { root.classList.remove('desktop-runtime'); - if (isAndroid) { - root.classList.add('device-android'); - } root.style.setProperty('--is-mobile', isMobile ? '1' : '0'); root.style.setProperty('--device-type', deviceType); root.style.setProperty('--font-scale', isMobile ? '0.9' : isTablet ? '0.95' : '1'); @@ -85,8 +81,6 @@ export function getDeviceInfo(): DeviceInfo { const prefersCoarsePointer = pointerQuery?.matches ?? false; const noHover = hoverQuery?.matches ?? false; const maxTouchPoints = typeof navigator !== 'undefined' ? navigator.maxTouchPoints ?? 0 : 0; - const isAndroid = typeof navigator !== 'undefined' && /Android/i.test(navigator.userAgent); - const isDesktopShellRuntime = isDesktopShell(); const hasTouchInput = prefersCoarsePointer || noHover || maxTouchPoints > 0; @@ -113,7 +107,7 @@ export function getDeviceInfo(): DeviceInfo { deviceType = 'desktop'; } - setRootDeviceAttributes(isDesktopShellRuntime, deviceType, hasTouchInput, !isDesktopShellRuntime && isAndroid); + setRootDeviceAttributes(isDesktopShellRuntime, deviceType, hasTouchInput); let breakpoint: keyof typeof BREAKPOINTS = 'xs'; for (const [key, value] of Object.entries(BREAKPOINTS)) { @@ -225,14 +219,8 @@ export function useDeviceInfo(): DeviceInfo { const prefersCoarsePointer = pointerQuery?.matches ?? false; const noHover = hoverQuery?.matches ?? false; const maxTouchPoints = typeof navigator !== 'undefined' ? navigator.maxTouchPoints ?? 0 : 0; - const isAndroid = typeof navigator !== 'undefined' && /Android/i.test(navigator.userAgent); const hasTouchInput = prefersCoarsePointer || noHover || maxTouchPoints > 0; - setRootDeviceAttributes( - isDesktopShellRuntime, - deviceInfo.deviceType, - hasTouchInput, - !isDesktopShellRuntime && isAndroid, - ); + setRootDeviceAttributes(isDesktopShellRuntime, deviceInfo.deviceType, hasTouchInput); }, [deviceInfo.deviceType, deviceInfo.hasTouchInput]); return deviceInfo; diff --git a/packages/ui/src/stores/useUIStore.ts b/packages/ui/src/stores/useUIStore.ts index 0cceaff4..829cae20 100644 --- a/packages/ui/src/stores/useUIStore.ts +++ b/packages/ui/src/stores/useUIStore.ts @@ -482,7 +482,6 @@ interface UIStore { pendingFileNavigation: PendingFileNavigation | null; pendingFileFocusPath: string | null; isMobile: boolean; - isKeyboardOpen: boolean; isQuickOpenOpen: boolean; isCommandPaletteOpen: boolean; isHelpDialogOpen: boolean; @@ -637,7 +636,6 @@ interface UIStore { setPadding: (size: number) => void; setCornerRadius: (radius: number) => void; setInputBarOffset: (offset: number) => void; - setKeyboardOpen: (open: boolean) => void; applyTypography: () => void; applyPadding: () => void; updateProportionalSidebarWidths: () => void; @@ -724,7 +722,6 @@ export const useUIStore = create()( pendingFileNavigation: null, pendingFileFocusPath: null, isMobile: false, - isKeyboardOpen: false, isQuickOpenOpen: false, isCommandPaletteOpen: false, isHelpDialogOpen: false, @@ -1470,10 +1467,6 @@ export const useUIStore = create()( set({ inputBarOffset: offset }); }, - setKeyboardOpen: (open) => { - set((state) => state.isKeyboardOpen === open ? state : { isKeyboardOpen: open }); - }, - toggleFavoriteModel: (providerID, modelID) => { set((state) => { const exists = state.favoriteModels.some( diff --git a/packages/ui/src/styles/design-system.css b/packages/ui/src/styles/design-system.css index 0f1d5c72..b2b76a8d 100644 --- a/packages/ui/src/styles/design-system.css +++ b/packages/ui/src/styles/design-system.css @@ -9,9 +9,6 @@ --oc-header-height: 56px; --oc-visual-viewport-offset-top: 0px; --oc-visual-viewport-height: 100dvh; - --oc-keyboard-inset: 0px; - --oc-keyboard-avoid-offset: 0px; - --oc-keyboard-home-indicator: 0px; --oc-wco-left-inset: 0px; --oc-wco-right-inset: 0px; --oc-wco-titlebar-height: 0px; diff --git a/packages/ui/src/styles/mobile.css b/packages/ui/src/styles/mobile.css index ea7c2af0..381c0484 100644 --- a/packages/ui/src/styles/mobile.css +++ b/packages/ui/src/styles/mobile.css @@ -164,28 +164,6 @@ overflow: hidden; } - :root.device-mobile:not(.desktop-runtime) body, - :root.device-mobile:not(.desktop-runtime) #root { - position: fixed; - top: 0; - left: 0; - right: 0; - height: var(--oc-visual-viewport-height, 100dvh); - width: 100%; - overflow: hidden; - overscroll-behavior-y: none; - } - - :root.device-android:not(.desktop-runtime) body, - :root.device-android:not(.desktop-runtime) #root { - position: relative; - top: auto; - left: auto; - right: auto; - height: 100%; - min-height: 0; - } - /* Fix main layout container */ :root.mobile-pointer:not(.desktop-runtime) .flex.flex-col.h-screen { height: 100vh; @@ -288,10 +266,6 @@ padding-bottom: var(--oc-safe-area-bottom-visual); } - :root.device-mobile:not(.desktop-runtime) .ios-keyboard-safe-area { - padding-bottom: calc(var(--oc-keyboard-home-indicator, 34px) + var(--oc-safe-area-bottom-visual, 0px)) !important; - } - /* Fix iOS viewport issues */ :root.device-mobile:not(.desktop-runtime) .flex.flex-col.h-screen { min-height: 100vh; diff --git a/packages/web/index.html b/packages/web/index.html index 92dcedef..662d1203 100644 --- a/packages/web/index.html +++ b/packages/web/index.html @@ -2,9 +2,7 @@ - - +