ui: Refactor isMobile as reactive value in viewport store (#23330)

* refactor: `isMobile` as reactive value in `viewport` store

* refactor: Use Svelte media query for the viewport store
This commit is contained in:
Aleksander Grygier
2026-05-20 10:52:00 +02:00
committed by GitHub
parent 585080d310
commit 5028447384
7 changed files with 18 additions and 24 deletions
@@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import { IsMobile } from '$lib/hooks/is-mobile.svelte'; import { isMobile } from '$lib/stores/viewport.svelte';
import ChatFormActionAddDropdown from './ChatFormActionAddDropdown.svelte'; import ChatFormActionAddDropdown from './ChatFormActionAddDropdown.svelte';
import ChatFormActionAddSheet from './ChatFormActionAddSheet.svelte'; import ChatFormActionAddSheet from './ChatFormActionAddSheet.svelte';
import ChatFormActionAddButton from './ChatFormActionAddButton.svelte'; import ChatFormActionAddButton from './ChatFormActionAddButton.svelte';
@@ -31,8 +31,6 @@
onMcpSettingsClick, onMcpSettingsClick,
onSystemPromptClick onSystemPromptClick
}: Props = $props(); }: Props = $props();
const isMobile = new IsMobile();
</script> </script>
{#if isMobile.current} {#if isMobile.current}
@@ -3,7 +3,7 @@
import { modelsStore, modelOptions, selectedModelId } from '$lib/stores/models.svelte'; import { modelsStore, modelOptions, selectedModelId } from '$lib/stores/models.svelte';
import { isRouterMode, serverError } from '$lib/stores/server.svelte'; import { isRouterMode, serverError } from '$lib/stores/server.svelte';
import { ModelsSelectorDropdown, ModelsSelectorSheet } from '$lib/components/app'; import { ModelsSelectorDropdown, ModelsSelectorSheet } from '$lib/components/app';
import { IsMobile } from '$lib/hooks/is-mobile.svelte'; import { isMobile } from '$lib/stores/viewport.svelte';
import { activeMessages } from '$lib/stores/conversations.svelte'; import { activeMessages } from '$lib/stores/conversations.svelte';
interface Props { interface Props {
@@ -152,8 +152,6 @@
let selectorModelRef: ModelsSelectorDropdown | ModelsSelectorSheet | undefined = let selectorModelRef: ModelsSelectorDropdown | ModelsSelectorSheet | undefined =
$state(undefined); $state(undefined);
let isMobile = new IsMobile();
export function open() { export function open() {
selectorModelRef?.open(); selectorModelRef?.open();
} }
@@ -1,4 +1,4 @@
import { IsMobile } from '$lib/hooks/is-mobile.svelte.js'; import { isMobile } from '$lib/stores/viewport.svelte.js';
import { getContext, setContext } from 'svelte'; import { getContext, setContext } from 'svelte';
import { SIDEBAR_KEYBOARD_SHORTCUT, SIDEBAR_MIN_WIDTH } from './constants.js'; import { SIDEBAR_KEYBOARD_SHORTCUT, SIDEBAR_MIN_WIDTH } from './constants.js';
@@ -27,19 +27,17 @@ class SidebarState {
sidebarWidth = $state(SIDEBAR_MIN_WIDTH); sidebarWidth = $state(SIDEBAR_MIN_WIDTH);
isResizing = $state(false); isResizing = $state(false);
setOpen: SidebarStateProps['setOpen']; setOpen: SidebarStateProps['setOpen'];
#isMobile: IsMobile;
state = $derived.by(() => (this.open ? 'expanded' : 'collapsed')); state = $derived.by(() => (this.open ? 'expanded' : 'collapsed'));
constructor(props: SidebarStateProps) { constructor(props: SidebarStateProps) {
this.setOpen = props.setOpen; this.setOpen = props.setOpen;
this.#isMobile = new IsMobile();
this.props = props; this.props = props;
} }
// Convenience getter for checking if the sidebar is mobile // Convenience getter for checking if the sidebar is mobile
// without this, we would need to use `sidebar.isMobile.current` everywhere // without this, we would need to use `sidebar.isMobile.current` everywhere
get isMobile() { get isMobile() {
return this.#isMobile.current; return isMobile.current;
} }
// Event handler to apply to the `<svelte:window>` // Event handler to apply to the `<svelte:window>`
@@ -1,8 +0,0 @@
import { DEFAULT_MOBILE_BREAKPOINT } from '$lib/constants';
import { MediaQuery } from 'svelte/reactivity';
export class IsMobile extends MediaQuery {
constructor(breakpoint: number = DEFAULT_MOBILE_BREAKPOINT) {
super(`max-width: ${breakpoint - 1}px`);
}
}
+2 -3
View File
@@ -41,8 +41,7 @@ import {
SETTINGS_KEYS, SETTINGS_KEYS,
USER_OVERRIDES_LOCALSTORAGE_KEY USER_OVERRIDES_LOCALSTORAGE_KEY
} from '$lib/constants'; } from '$lib/constants';
import { isMobile } from '$lib/stores/viewport.svelte';
import { IsMobile } from '$lib/hooks/is-mobile.svelte';
import { ParameterSyncService } from '$lib/services/parameter-sync.service'; import { ParameterSyncService } from '$lib/services/parameter-sync.service';
import { serverStore } from '$lib/stores/server.svelte'; import { serverStore } from '$lib/stores/server.svelte';
import { import {
@@ -132,7 +131,7 @@ class SettingsStore {
// Default sendOnEnter to false on mobile when the user has no saved preference // Default sendOnEnter to false on mobile when the user has no saved preference
if (!(SETTINGS_KEYS.SEND_ON_ENTER in savedVal)) { if (!(SETTINGS_KEYS.SEND_ON_ENTER in savedVal)) {
if (new IsMobile().current) { if (isMobile.current) {
this.config[SETTINGS_KEYS.SEND_ON_ENTER] = false; this.config[SETTINGS_KEYS.SEND_ON_ENTER] = false;
} }
} }
@@ -0,0 +1,9 @@
import { browser } from '$app/environment';
import { DEFAULT_MOBILE_BREAKPOINT } from '$lib/constants/viewport';
import { MediaQuery } from 'svelte/reactivity';
export const viewport = $state({
width: browser ? window.innerWidth : 0
});
export const isMobile = new MediaQuery(`max-width: ${DEFAULT_MOBILE_BREAKPOINT - 1}px`);
+3 -3
View File
@@ -26,18 +26,18 @@
import { modelsStore } from '$lib/stores/models.svelte'; import { modelsStore } from '$lib/stores/models.svelte';
import { mcpStore } from '$lib/stores/mcp.svelte'; import { mcpStore } from '$lib/stores/mcp.svelte';
import { TOOLTIP_DELAY_DURATION } from '$lib/constants'; import { TOOLTIP_DELAY_DURATION } from '$lib/constants';
import { IsMobile } from '$lib/hooks/is-mobile.svelte';
import { useKeyboardShortcuts } from '$lib/hooks/use-keyboard-shortcuts.svelte'; import { useKeyboardShortcuts } from '$lib/hooks/use-keyboard-shortcuts.svelte';
import { useSettingsNavigation } from '$lib/hooks/use-settings-navigation.svelte'; import { useSettingsNavigation } from '$lib/hooks/use-settings-navigation.svelte';
import { conversations } from '$lib/stores/conversations.svelte'; import { conversations } from '$lib/stores/conversations.svelte';
import { isMobile } from '$lib/stores/viewport.svelte';
let { children } = $props(); let { children } = $props();
let alwaysShowSidebarOnDesktop = $derived(config().alwaysShowSidebarOnDesktop); let alwaysShowSidebarOnDesktop = $derived(config().alwaysShowSidebarOnDesktop);
let isMobile = new IsMobile();
let isDesktop = $derived(!isMobile.current); let isDesktop = $derived(!isMobile.current);
let sidebarOpen = $state(false); let sidebarOpen = $state(false);
let mounted = $state(false); let mounted = $state(false);
let innerHeight = $state<number | undefined>(); let innerHeight = $state<number | undefined>();
let innerWidth = $state(browser ? window.innerWidth : 0);
let chatSidebar: let chatSidebar:
| { | {
@@ -278,4 +278,4 @@
</Sidebar.Provider> </Sidebar.Provider>
</Tooltip.Provider> </Tooltip.Provider>
<svelte:window onkeydown={handleKeydown} bind:innerHeight /> <svelte:window onkeydown={handleKeydown} bind:innerHeight bind:innerWidth />