fix: simplify mobile composer keyboard handling

Use browser keyboard resizing by default on mobile
Remove mobile-only autocomplete button and tabs
Keep autocomplete behavior consistent across mobile and desktop
This commit is contained in:
Bohdan Triapitsyn
2026-05-25 21:27:10 +03:00
parent 6e16504dac
commit b2f7373b79
6 changed files with 24 additions and 290 deletions
+2 -126
View File
@@ -470,14 +470,12 @@ const RevertedMessageDock: React.FC<RevertedMessageDockProps> = React.memo(({ se
RevertedMessageDock.displayName = 'RevertedMessageDock';
type ComposerAttachmentControlsProps = {
isMobile: boolean;
isVSCode: boolean;
footerIconButtonClass: string;
iconSizeClass: string;
fileInputRef: React.RefObject<HTMLInputElement | null>;
handleLocalFileSelect: (event: React.ChangeEvent<HTMLInputElement>) => void | Promise<void>;
handlePickLocalFiles: () => void;
handleOpenCommandMenu: () => void;
openIssuePicker: () => void;
openPrPicker: () => void;
onOpenSettings?: () => void;
@@ -486,14 +484,12 @@ type ComposerAttachmentControlsProps = {
const ComposerAttachmentControls = React.memo(function ComposerAttachmentControls(props: ComposerAttachmentControlsProps) {
const { t } = useI18n();
const {
isMobile,
isVSCode,
footerIconButtonClass,
iconSizeClass,
fileInputRef,
handleLocalFileSelect,
handlePickLocalFiles,
handleOpenCommandMenu,
openIssuePicker,
openPrPicker,
onOpenSettings,
@@ -501,27 +497,6 @@ const ComposerAttachmentControls = React.memo(function ComposerAttachmentControl
return (
<div className="flex items-center gap-x-1.5">
{isMobile ? (
<button
type="button"
className={cn(
footerIconButtonClass,
'rounded-md',
'hover:bg-interactive-hover/40'
)}
onPointerDownCapture={(event) => {
if (event.pointerType === 'touch') {
event.preventDefault();
event.stopPropagation();
}
}}
onClick={handleOpenCommandMenu}
title={t('chat.chatInput.actions.commands')}
aria-label={t('chat.chatInput.actions.commands')}
>
<Icon name="command" className={cn(iconSizeClass)} />
</button>
) : null}
<input
ref={fileInputRef}
type="file"
@@ -598,8 +573,7 @@ const ComposerAttachmentControls = React.memo(function ComposerAttachmentControl
</div>
);
}, (prev, next) => (
prev.isMobile === next.isMobile
&& prev.isVSCode === next.isVSCode
prev.isVSCode === next.isVSCode
&& prev.footerIconButtonClass === next.footerIconButtonClass
&& prev.iconSizeClass === next.iconSizeClass
&& prev.onOpenSettings === next.onOpenSettings
@@ -960,7 +934,6 @@ const ChatInputComponent: React.FC<ChatInputProps> = ({ onOpenSettings, scrollTo
const [mentionQuery, setMentionQuery] = React.useState('');
const [showCommandAutocomplete, setShowCommandAutocomplete] = React.useState(false);
const [commandQuery, setCommandQuery] = React.useState('');
const [autocompleteTab, setAutocompleteTab] = React.useState<'commands' | 'agents' | 'files'>('commands');
const [showSkillAutocomplete, setShowSkillAutocomplete] = React.useState(false);
const [skillQuery, setSkillQuery] = React.useState('');
const [showSnippetAutocomplete, setShowSnippetAutocomplete] = React.useState(false);
@@ -2525,7 +2498,6 @@ const ChatInputComponent: React.FC<ChatInputProps> = ({ onOpenSettings, scrollTo
if (cursorPosition <= commandEnd && firstSpace === -1) {
const commandText = value.substring(1, commandEnd);
setCommandQuery(commandText);
setAutocompleteTab('commands');
setShowCommandAutocomplete(true);
setShowFileMention(false);
setShowSkillAutocomplete(false);
@@ -2578,7 +2550,6 @@ const ChatInputComponent: React.FC<ChatInputProps> = ({ onOpenSettings, scrollTo
const isWordBoundary = !charBefore || /\s/.test(charBefore);
if (isWordBoundary && !textAfterAt.includes(' ') && !textAfterAt.includes('\n')) {
setMentionQuery(textAfterAt);
setAutocompleteTab((current) => current === 'files' ? 'files' : 'agents');
setShowFileMention(true);
} else {
setShowFileMention(false);
@@ -2586,92 +2557,7 @@ const ChatInputComponent: React.FC<ChatInputProps> = ({ onOpenSettings, scrollTo
} else {
setShowFileMention(false);
}
}, [inputMode, setAutocompleteTab, setCommandQuery, setMentionQuery, setShowCommandAutocomplete, setShowFileMention, setShowSkillAutocomplete, setSkillQuery]);
const applyAutocompletePrefix = React.useCallback((prefix: '/' | '@') => {
const nextMessage = message.length === 0
? prefix
: (message[0] === '/' || message[0] === '@')
? `${prefix}${message.slice(1)}`
: `${prefix}${message}`;
setMessage(nextMessage);
requestAnimationFrame(() => {
if (textareaRef.current) {
const nextCursor = Math.min(nextMessage.length, textareaRef.current.value.length);
textareaRef.current.selectionStart = nextCursor;
textareaRef.current.selectionEnd = nextCursor;
}
adjustTextareaHeight();
updateAutocompleteState(nextMessage, nextMessage.length);
});
}, [adjustTextareaHeight, message, setMessage, updateAutocompleteState]);
const handleAutocompleteTabSelect = React.useCallback((tab: 'commands' | 'agents' | 'files') => {
const textarea = textareaRef.current;
if (isMobile && textarea) {
try {
textarea.focus({ preventScroll: true });
} catch {
textarea.focus();
}
const len = textarea.value.length;
try {
textarea.setSelectionRange(len, len);
} catch {
// ignored
}
}
const cursorPosition = textarea?.selectionStart ?? message.length;
const textBeforeCursor = message.substring(0, cursorPosition);
const lastAtSymbol = textBeforeCursor.lastIndexOf('@');
const nextMentionQuery = lastAtSymbol !== -1
? textBeforeCursor.substring(lastAtSymbol + 1).replace(/[\s\n].*$/, '')
: '';
setAutocompleteTab(tab);
setCommandQuery('');
if (tab === 'commands') {
setMentionQuery('');
applyAutocompletePrefix('/');
}
if (tab === 'agents') {
setMentionQuery(nextMentionQuery);
applyAutocompletePrefix('@');
}
if (tab === 'files') {
setMentionQuery(nextMentionQuery);
applyAutocompletePrefix('@');
}
setShowSkillAutocomplete(false);
setShowCommandAutocomplete(tab === 'commands');
setShowFileMention(tab === 'agents' || tab === 'files');
}, [applyAutocompletePrefix, isMobile, message, setAutocompleteTab, setCommandQuery, setMentionQuery, setShowCommandAutocomplete, setShowFileMention, setShowSkillAutocomplete]);
const handleOpenCommandMenu = React.useCallback(() => {
if (!isMobile) {
return;
}
const textarea = textareaRef.current;
if (textarea) {
try {
textarea.focus({ preventScroll: true });
} catch {
textarea.focus();
}
const len = textarea.value.length;
try {
textarea.setSelectionRange(len, len);
} catch {
// ignored
}
}
applyAutocompletePrefix('/');
setCommandQuery('');
setAutocompleteTab('commands');
setShowCommandAutocomplete(true);
setShowFileMention(false);
setShowSkillAutocomplete(false);
}, [applyAutocompletePrefix, isMobile, setAutocompleteTab, setCommandQuery, setShowCommandAutocomplete, setShowFileMention, setShowSkillAutocomplete]);
}, [inputMode, setCommandQuery, setMentionQuery, setShowCommandAutocomplete, setShowFileMention, setShowSkillAutocomplete, setSkillQuery]);
const insertTextAtSelection = React.useCallback((text: string) => {
if (!text) {
@@ -4183,9 +4069,6 @@ const ChatInputComponent: React.FC<ChatInputProps> = ({ onOpenSettings, scrollTo
ref={commandRef}
searchQuery={commandQuery}
onCommandSelect={handleCommandSelect}
showTabs={isMobile}
activeTab={autocompleteTab}
onTabSelect={handleAutocompleteTabSelect}
onClose={() => setShowCommandAutocomplete(false)}
style={isDesktopExpanded && autocompleteOverlayPosition
? {
@@ -4245,9 +4128,6 @@ const ChatInputComponent: React.FC<ChatInputProps> = ({ onOpenSettings, scrollTo
searchQuery={mentionQuery}
onFileSelect={handleFileSelect}
onAgentSelect={handleAgentSelect}
showTabs={isMobile}
activeTab={autocompleteTab}
onTabSelect={handleAutocompleteTabSelect}
onClose={() => setShowFileMention(false)}
style={isDesktopExpanded && autocompleteOverlayPosition
? {
@@ -4368,14 +4248,12 @@ const ChatInputComponent: React.FC<ChatInputProps> = ({ onOpenSettings, scrollTo
<div className="flex w-full items-center justify-between gap-x-1.5">
<div className="flex items-center gap-x-1.5">
<ComposerAttachmentControls
isMobile={isMobile}
isVSCode={isVSCode}
footerIconButtonClass={footerIconButtonClass}
iconSizeClass={iconSizeClass}
fileInputRef={fileInputRef}
handleLocalFileSelect={handleLocalFileSelect}
handlePickLocalFiles={handlePickLocalFiles}
handleOpenCommandMenu={handleOpenCommandMenu}
openIssuePicker={openIssuePicker}
openPrPicker={openPrPicker}
onOpenSettings={onOpenSettings}
@@ -4426,14 +4304,12 @@ const ChatInputComponent: React.FC<ChatInputProps> = ({ onOpenSettings, scrollTo
<>
<div className={cn("flex items-center flex-shrink-0", footerGapClass)}>
<ComposerAttachmentControls
isMobile={isMobile}
isVSCode={isVSCode}
footerIconButtonClass={footerIconButtonClass}
iconSizeClass={iconSizeClass}
fileInputRef={fileInputRef}
handleLocalFileSelect={handleLocalFileSelect}
handlePickLocalFiles={handlePickLocalFiles}
handleOpenCommandMenu={handleOpenCommandMenu}
openIssuePicker={openIssuePicker}
openPrPicker={openPrPicker}
onOpenSettings={onOpenSettings}
@@ -27,8 +27,6 @@ export interface CommandAutocompleteHandle {
handleKeyDown: (key: string) => void;
}
type AutocompleteTab = 'commands' | 'agents' | 'files';
const BASE_BADGE_CLASS = "text-[10px] leading-none uppercase font-bold tracking-tight px-1.5 py-1 rounded border flex-shrink-0";
const TYPE_BADGE_CLASS = cn(
BASE_BADGE_CLASS,
@@ -51,9 +49,6 @@ interface CommandAutocompleteProps {
searchQuery: string;
onCommandSelect: (command: CommandInfo, options?: { dismissKeyboard?: boolean }) => void;
onClose: () => void;
showTabs?: boolean;
activeTab?: AutocompleteTab;
onTabSelect?: (tab: AutocompleteTab) => void;
style?: React.CSSProperties;
}
@@ -61,9 +56,6 @@ export const CommandAutocomplete = React.forwardRef<CommandAutocompleteHandle, C
searchQuery,
onCommandSelect,
onClose,
showTabs,
activeTab = 'commands',
onTabSelect,
style,
}, ref) => {
const { t } = useI18n();
@@ -88,7 +80,6 @@ export const CommandAutocomplete = React.forwardRef<CommandAutocompleteHandle, C
const ignoreClickRef = React.useRef(false);
const pointerStartRef = React.useRef<{ x: number; y: number } | null>(null);
const pointerMovedRef = React.useRef(false);
const ignoreTabClickRef = React.useRef(false);
React.useEffect(() => {
const handlePointerDown = (event: MouseEvent | TouchEvent) => {
@@ -305,46 +296,6 @@ export const CommandAutocomplete = React.forwardRef<CommandAutocompleteHandle, C
className="absolute z-[100] min-w-0 w-full max-w-[450px] max-h-64 bg-background border-2 border-border/60 rounded-xl shadow-none bottom-full mb-2 left-0 flex flex-col"
style={style}
>
{showTabs ? (
<div className="px-2 pt-2 pb-1 border-b border-border/60">
<div className="flex items-center gap-1 rounded-lg bg-[var(--surface-elevated)] p-1">
{([
{ id: 'commands' as const, label: t('chat.autocomplete.tabs.commands') },
{ id: 'agents' as const, label: t('chat.autocomplete.tabs.agents') },
{ id: 'files' as const, label: t('chat.autocomplete.tabs.files') },
]).map((tab) => (
<button
key={tab.id}
type="button"
className={cn(
'flex-1 px-2.5 py-1 rounded-md typography-meta font-semibold transition-none',
activeTab === tab.id
? 'bg-interactive-selection text-interactive-selection-foreground shadow-none'
: 'text-muted-foreground hover:bg-interactive-hover/50'
)}
onPointerDown={(event) => {
if (event.pointerType !== 'touch') {
return;
}
event.preventDefault();
event.stopPropagation();
ignoreTabClickRef.current = true;
onTabSelect?.(tab.id);
}}
onClick={() => {
if (ignoreTabClickRef.current) {
ignoreTabClickRef.current = false;
return;
}
onTabSelect?.(tab.id);
}}
>
{tab.label}
</button>
))}
</div>
</div>
) : null}
<ScrollableOverlay outerClassName="flex-1 min-h-0" className="px-0 pb-2">
{loading ? (
<div className="flex items-center justify-center py-4">
@@ -19,23 +19,16 @@ type AgentInfo = {
description?: string;
mode?: string | null;
};
const EMPTY_FILES: FileInfo[] = [];
const EMPTY_AGENTS: AgentInfo[] = [];
export interface FileMentionHandle {
handleKeyDown: (key: string) => void;
}
type AutocompleteTab = 'commands' | 'agents' | 'files';
interface FileMentionAutocompleteProps {
searchQuery: string;
onFileSelect: (file: FileInfo) => void;
onAgentSelect?: (agentName: string) => void;
onClose: () => void;
showTabs?: boolean;
activeTab?: AutocompleteTab;
onTabSelect?: (tab: AutocompleteTab) => void;
style?: React.CSSProperties;
}
@@ -44,9 +37,6 @@ export const FileMentionAutocomplete = React.forwardRef<FileMentionHandle, FileM
onFileSelect,
onAgentSelect,
onClose,
showTabs,
activeTab = 'files',
onTabSelect,
style,
}, ref) => {
const { t } = useI18n();
@@ -87,9 +77,7 @@ export const FileMentionAutocomplete = React.forwardRef<FileMentionHandle, FileM
const labelRefs = React.useRef<(HTMLSpanElement | null)[]>([]);
const measureRefs = React.useRef<(HTMLSpanElement | null)[]>([]);
const containerRef = React.useRef<HTMLDivElement | null>(null);
const ignoreTabClickRef = React.useRef(false);
const normalizedSearchQuery = (searchQuery ?? '').trim();
const scopeResultsToActiveTab = showTabs === true;
const recentFiles = React.useMemo(() => {
if (!projectRoot || !projectTabs) {
return [] as FileInfo[];
@@ -128,14 +116,12 @@ export const FileMentionAutocomplete = React.forwardRef<FileMentionHandle, FileM
return mapped;
}, [normalizedSearchQuery, projectRoot, projectTabs]);
const visibleAgents = React.useMemo(
() => !scopeResultsToActiveTab || activeTab === 'agents'
? (normalizedSearchQuery.length > 0 ? agents : agents.slice(0, 2))
: EMPTY_AGENTS,
[activeTab, agents, normalizedSearchQuery.length, scopeResultsToActiveTab],
() => normalizedSearchQuery.length > 0 ? agents : agents.slice(0, 2),
[agents, normalizedSearchQuery.length],
);
const visibleDirectories = !scopeResultsToActiveTab || activeTab === 'files' ? directories : EMPTY_FILES;
const visibleRecentFiles = !scopeResultsToActiveTab || activeTab === 'files' ? recentFiles : EMPTY_FILES;
const visibleFiles = !scopeResultsToActiveTab || activeTab === 'files' ? files : EMPTY_FILES;
const visibleDirectories = directories;
const visibleRecentFiles = recentFiles;
const visibleFiles = files;
React.useEffect(() => {
const handlePointerDown = (event: MouseEvent | TouchEvent) => {
@@ -452,56 +438,14 @@ export const FileMentionAutocomplete = React.forwardRef<FileMentionHandle, FileM
}
};
const tabs = React.useMemo(() => ([
{ id: 'commands' as const, label: t('chat.autocomplete.tabs.commands') },
{ id: 'agents' as const, label: t('chat.autocomplete.tabs.agents') },
{ id: 'files' as const, label: t('chat.autocomplete.tabs.files') },
]), [t]);
return (
<div
ref={containerRef}
className="absolute z-[100] min-w-0 w-full max-w-[640px] max-h-64 bg-background border-2 border-border/60 rounded-xl shadow-none bottom-full mb-2 left-0 flex flex-col"
style={style}
>
{showTabs ? (
<div className="px-2 pt-2 pb-1 border-b border-border/60">
<div className="flex items-center gap-1 rounded-lg bg-[var(--surface-elevated)] p-1">
{tabs.map((tab) => (
<button
key={tab.id}
type="button"
className={cn(
'flex-1 px-2.5 py-1 rounded-md typography-meta font-semibold transition-none',
activeTab === tab.id
? 'bg-interactive-selection text-interactive-selection-foreground shadow-none'
: 'text-muted-foreground hover:bg-interactive-hover/50'
)}
onPointerDown={(event) => {
if (event.pointerType !== 'touch') {
return;
}
event.preventDefault();
event.stopPropagation();
ignoreTabClickRef.current = true;
onTabSelect?.(tab.id);
}}
onClick={() => {
if (ignoreTabClickRef.current) {
ignoreTabClickRef.current = false;
return;
}
onTabSelect?.(tab.id);
}}
>
{tab.label}
</button>
))}
</div>
</div>
) : null}
<ScrollableOverlay outerClassName="flex-1 min-h-0" className="px-0">
{(!scopeResultsToActiveTab || activeTab === 'files') && loading ? (
{loading ? (
<div className="flex items-center justify-center py-4">
<Icon name="refresh" className="h-4 w-4 animate-spin text-muted-foreground" />
</div>
@@ -20,7 +20,6 @@ import { useUIStore } from '@/stores/useUIStore';
import { useSessionUIStore } from '@/sync/session-ui-store';
import { useUpdateStore } from '@/stores/useUpdateStore';
import { useDeviceInfo } from '@/lib/device';
import { useVisualViewport } from '@/hooks/useVisualViewport';
import { cn } from '@/lib/utils';
import { lazyWithChunkRecovery } from '@/lib/chunkLoadRecovery';
@@ -60,7 +59,6 @@ export const MainLayout: React.FC = () => {
const setMultiRunLauncherOpen = useUIStore((state) => state.setMultiRunLauncherOpen);
const multiRunLauncherPrefillPrompt = useUIStore((state) => state.multiRunLauncherPrefillPrompt);
const { isMobile, isTablet } = useDeviceInfo();
const visualViewport = useVisualViewport();
const sidebarWidth = useUIStore((state) => state.sidebarWidth);
const rightSidebarWidth = useUIStore((state) => state.rightSidebarWidth);
const rightSidebarAutoClosedRef = React.useRef(false);
@@ -431,10 +429,9 @@ export const MainLayout: React.FC = () => {
data-page-scroll-lock="true"
className={cn(
'main-content-safe-area',
isMobile ? 'flex flex-col' : 'flex h-[100dvh]',
isMobile ? 'flex h-[100dvh] flex-col' : 'flex h-[100dvh]',
'bg-background'
)}
style={isMobile && visualViewport.height > 0 ? { height: visualViewport.height } : undefined}
>
<CommandPalette />
<HelpDialog />
+8 -25
View File
@@ -5,24 +5,7 @@ export const VIEWPORT_META_SELECTOR = 'meta[name="viewport"]';
export const VIEWPORT_CONTENT_BASE = 'width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover';
export const supportsMobileKeyboardResizeContent = (): boolean => {
if (typeof navigator === 'undefined') {
return true;
}
const userAgent = navigator.userAgent || '';
const platform = navigator.platform || '';
const maxTouchPoints = navigator.maxTouchPoints ?? 0;
const isIOS = /iPhone|iPad|iPod/i.test(userAgent)
|| ((/Macintosh|MacIntel/i.test(userAgent) || /MacIntel/i.test(platform)) && maxTouchPoints > 1);
return !isIOS;
};
const getSupportedMobileKeyboardMode = (mode: MobileKeyboardMode): MobileKeyboardMode => {
if (mode === 'resize-content' && !supportsMobileKeyboardResizeContent()) {
return 'native';
}
return mode;
return true;
};
export function normalizeMobileKeyboardMode(value: unknown): MobileKeyboardMode;
@@ -30,7 +13,7 @@ export function normalizeMobileKeyboardMode(value: unknown, fallback: MobileKeyb
export function normalizeMobileKeyboardMode(value: unknown, fallback: undefined): MobileKeyboardMode | undefined;
export function normalizeMobileKeyboardMode(
value: unknown,
fallback: MobileKeyboardMode | undefined = 'native',
fallback: MobileKeyboardMode | undefined = 'resize-content',
): MobileKeyboardMode | undefined {
if (value === 'native' || value === 'resize-content') {
return value;
@@ -39,7 +22,7 @@ export function normalizeMobileKeyboardMode(
}
export const getViewportContentForMobileKeyboardMode = (value: unknown): string => {
const mode = getSupportedMobileKeyboardMode(normalizeMobileKeyboardMode(value));
const mode = normalizeMobileKeyboardMode(value);
return mode === 'resize-content'
? `${VIEWPORT_CONTENT_BASE}, interactive-widget=resizes-content`
: VIEWPORT_CONTENT_BASE;
@@ -47,22 +30,22 @@ export const getViewportContentForMobileKeyboardMode = (value: unknown): string
export const getStoredMobileKeyboardMode = (): MobileKeyboardMode => {
if (typeof window === 'undefined') {
return 'native';
return 'resize-content';
}
try {
return getSupportedMobileKeyboardMode(normalizeMobileKeyboardMode(localStorage.getItem(MOBILE_KEYBOARD_MODE_STORAGE_KEY)));
return normalizeMobileKeyboardMode(localStorage.getItem(MOBILE_KEYBOARD_MODE_STORAGE_KEY));
} catch {
return 'native';
return 'resize-content';
}
};
export const setStoredMobileKeyboardMode = (value: unknown): MobileKeyboardMode => {
const mode = getSupportedMobileKeyboardMode(normalizeMobileKeyboardMode(value));
const mode = normalizeMobileKeyboardMode(value);
if (typeof window !== 'undefined') {
try {
if (mode === 'native') {
if (mode === 'resize-content') {
localStorage.removeItem(MOBILE_KEYBOARD_MODE_STORAGE_KEY);
} else {
localStorage.setItem(MOBILE_KEYBOARD_MODE_STORAGE_KEY, mode);
+7 -24
View File
@@ -2,7 +2,7 @@
<html lang="en" class="h-full">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover, interactive-widget=resizes-content" />
<!-- Favicon -->
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
@@ -57,46 +57,29 @@
return fallback;
};
const normalizeMobileKeyboardMode = (value, fallback = 'native') => {
const normalizeMobileKeyboardMode = (value, fallback = 'resize-content') => {
if (value === 'native' || value === 'resize-content') {
return value;
}
return fallback;
};
const supportsMobileKeyboardResizeContent = () => {
const userAgent = navigator.userAgent || '';
const platform = navigator.platform || '';
const maxTouchPoints = navigator.maxTouchPoints || 0;
const isIOS = /iPhone|iPad|iPod/i.test(userAgent)
|| ((/Macintosh|MacIntel/i.test(userAgent) || /MacIntel/i.test(platform)) && maxTouchPoints > 1);
return !isIOS;
};
const getSupportedMobileKeyboardMode = (mode) => {
if (mode === 'resize-content' && !supportsMobileKeyboardResizeContent()) {
return 'native';
}
return mode;
};
const getViewportContentForMobileKeyboardMode = (value) => {
const mode = getSupportedMobileKeyboardMode(normalizeMobileKeyboardMode(value, 'native'));
const mode = normalizeMobileKeyboardMode(value, 'resize-content');
return mode === 'resize-content'
? `${viewportBaseContent}, interactive-widget=resizes-content`
: viewportBaseContent;
};
const applyStoredMobileKeyboardMode = () => {
let mode = 'native';
let mode = 'resize-content';
try {
mode = getSupportedMobileKeyboardMode(normalizeMobileKeyboardMode(localStorage.getItem(mobileKeyboardModeStorageKey), 'native'));
if (mode === 'native') {
mode = normalizeMobileKeyboardMode(localStorage.getItem(mobileKeyboardModeStorageKey), 'resize-content');
if (mode === 'resize-content') {
localStorage.removeItem(mobileKeyboardModeStorageKey);
}
} catch {
mode = 'native';
mode = 'resize-content';
}
document.documentElement.setAttribute('data-oc-mobile-keyboard-mode', mode);