From 5028447384240df2a899c01a08be78fc433b11f1 Mon Sep 17 00:00:00 2001 From: Aleksander Grygier Date: Wed, 20 May 2026 10:52:00 +0200 Subject: [PATCH] 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 --- .../ChatFormActionAdd/ChatFormActionsAdd.svelte | 4 +--- .../ChatForm/ChatFormActions/ChatFormActionModels.svelte | 4 +--- tools/ui/src/lib/components/ui/sidebar/context.svelte.ts | 6 ++---- tools/ui/src/lib/hooks/is-mobile.svelte.ts | 8 -------- tools/ui/src/lib/stores/settings.svelte.ts | 5 ++--- tools/ui/src/lib/stores/viewport.svelte.ts | 9 +++++++++ tools/ui/src/routes/+layout.svelte | 6 +++--- 7 files changed, 18 insertions(+), 24 deletions(-) delete mode 100644 tools/ui/src/lib/hooks/is-mobile.svelte.ts create mode 100644 tools/ui/src/lib/stores/viewport.svelte.ts diff --git a/tools/ui/src/lib/components/app/chat/ChatForm/ChatFormActions/ChatFormActionAdd/ChatFormActionsAdd.svelte b/tools/ui/src/lib/components/app/chat/ChatForm/ChatFormActions/ChatFormActionAdd/ChatFormActionsAdd.svelte index 54ddcf9b0..6a91bf905 100644 --- a/tools/ui/src/lib/components/app/chat/ChatForm/ChatFormActions/ChatFormActionAdd/ChatFormActionsAdd.svelte +++ b/tools/ui/src/lib/components/app/chat/ChatForm/ChatFormActions/ChatFormActionAdd/ChatFormActionsAdd.svelte @@ -1,5 +1,5 @@ {#if isMobile.current} diff --git a/tools/ui/src/lib/components/app/chat/ChatForm/ChatFormActions/ChatFormActionModels.svelte b/tools/ui/src/lib/components/app/chat/ChatForm/ChatFormActions/ChatFormActionModels.svelte index 297020605..07f079f5b 100644 --- a/tools/ui/src/lib/components/app/chat/ChatForm/ChatFormActions/ChatFormActionModels.svelte +++ b/tools/ui/src/lib/components/app/chat/ChatForm/ChatFormActions/ChatFormActionModels.svelte @@ -3,7 +3,7 @@ import { modelsStore, modelOptions, selectedModelId } from '$lib/stores/models.svelte'; import { isRouterMode, serverError } from '$lib/stores/server.svelte'; 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'; interface Props { @@ -152,8 +152,6 @@ let selectorModelRef: ModelsSelectorDropdown | ModelsSelectorSheet | undefined = $state(undefined); - let isMobile = new IsMobile(); - export function open() { selectorModelRef?.open(); } diff --git a/tools/ui/src/lib/components/ui/sidebar/context.svelte.ts b/tools/ui/src/lib/components/ui/sidebar/context.svelte.ts index 9d49ee1f0..2fa5cc25d 100644 --- a/tools/ui/src/lib/components/ui/sidebar/context.svelte.ts +++ b/tools/ui/src/lib/components/ui/sidebar/context.svelte.ts @@ -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 { SIDEBAR_KEYBOARD_SHORTCUT, SIDEBAR_MIN_WIDTH } from './constants.js'; @@ -27,19 +27,17 @@ class SidebarState { sidebarWidth = $state(SIDEBAR_MIN_WIDTH); isResizing = $state(false); setOpen: SidebarStateProps['setOpen']; - #isMobile: IsMobile; state = $derived.by(() => (this.open ? 'expanded' : 'collapsed')); constructor(props: SidebarStateProps) { this.setOpen = props.setOpen; - this.#isMobile = new IsMobile(); this.props = props; } // Convenience getter for checking if the sidebar is mobile // without this, we would need to use `sidebar.isMobile.current` everywhere get isMobile() { - return this.#isMobile.current; + return isMobile.current; } // Event handler to apply to the `` diff --git a/tools/ui/src/lib/hooks/is-mobile.svelte.ts b/tools/ui/src/lib/hooks/is-mobile.svelte.ts deleted file mode 100644 index 6454fc5b5..000000000 --- a/tools/ui/src/lib/hooks/is-mobile.svelte.ts +++ /dev/null @@ -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`); - } -} diff --git a/tools/ui/src/lib/stores/settings.svelte.ts b/tools/ui/src/lib/stores/settings.svelte.ts index 58eea1ee6..8d3c711b8 100644 --- a/tools/ui/src/lib/stores/settings.svelte.ts +++ b/tools/ui/src/lib/stores/settings.svelte.ts @@ -41,8 +41,7 @@ import { SETTINGS_KEYS, USER_OVERRIDES_LOCALSTORAGE_KEY } from '$lib/constants'; - -import { IsMobile } from '$lib/hooks/is-mobile.svelte'; +import { isMobile } from '$lib/stores/viewport.svelte'; import { ParameterSyncService } from '$lib/services/parameter-sync.service'; import { serverStore } from '$lib/stores/server.svelte'; import { @@ -132,7 +131,7 @@ class SettingsStore { // Default sendOnEnter to false on mobile when the user has no saved preference if (!(SETTINGS_KEYS.SEND_ON_ENTER in savedVal)) { - if (new IsMobile().current) { + if (isMobile.current) { this.config[SETTINGS_KEYS.SEND_ON_ENTER] = false; } } diff --git a/tools/ui/src/lib/stores/viewport.svelte.ts b/tools/ui/src/lib/stores/viewport.svelte.ts new file mode 100644 index 000000000..dac241a01 --- /dev/null +++ b/tools/ui/src/lib/stores/viewport.svelte.ts @@ -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`); diff --git a/tools/ui/src/routes/+layout.svelte b/tools/ui/src/routes/+layout.svelte index 0610b07ae..2f1f52497 100644 --- a/tools/ui/src/routes/+layout.svelte +++ b/tools/ui/src/routes/+layout.svelte @@ -26,18 +26,18 @@ import { modelsStore } from '$lib/stores/models.svelte'; import { mcpStore } from '$lib/stores/mcp.svelte'; 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 { useSettingsNavigation } from '$lib/hooks/use-settings-navigation.svelte'; import { conversations } from '$lib/stores/conversations.svelte'; + import { isMobile } from '$lib/stores/viewport.svelte'; let { children } = $props(); let alwaysShowSidebarOnDesktop = $derived(config().alwaysShowSidebarOnDesktop); - let isMobile = new IsMobile(); let isDesktop = $derived(!isMobile.current); let sidebarOpen = $state(false); let mounted = $state(false); let innerHeight = $state(); + let innerWidth = $state(browser ? window.innerWidth : 0); let chatSidebar: | { @@ -278,4 +278,4 @@ - +