refactor(sample-list): replace layoutManager singleton with lazy accessor

Convert the eager layoutManager singleton to getLayoutManager() (+ __resetLayoutManager
for tests), so its persisted layout preference is read on first access rather than at
module load. Update the model barrels and consumers (LayoutSwitch, SampleListSection,
SampleList) with $derived reads; the LayoutSwitch test resolves via the accessor.
This commit is contained in:
Ilia Mashkov
2026-06-02 09:09:20 +03:00
parent b3bc40b76c
commit 7ddf232e3a
7 changed files with 42 additions and 13 deletions
+1 -1
View File
@@ -1,2 +1,2 @@
export { layoutManager } from './stores'; export { getLayoutManager } from './stores';
export type { LayoutMode } from './stores'; export type { LayoutMode } from './stores';
+1 -1
View File
@@ -1,2 +1,2 @@
export { layoutManager } from './layoutStore/layoutStore.svelte'; export { getLayoutManager } from './layoutStore/layoutStore.svelte';
export type { LayoutMode } from './layoutStore/layoutStore.svelte'; export type { LayoutMode } from './layoutStore/layoutStore.svelte';
@@ -146,10 +146,20 @@ class LayoutManager {
} }
} }
let _layoutManager: LayoutManager | undefined;
/** /**
* Singleton layout manager instance * App-wide layout manager, created on first access. Lazy so its persisted
* layout preference isn't read at module load.
*/ */
export const layoutManager = new LayoutManager(); export function getLayoutManager(): LayoutManager {
return (_layoutManager ??= new LayoutManager());
}
// test-only reset, so specs don't share persisted layout state
export function __resetLayoutManager() {
_layoutManager = undefined;
}
// Export class for testing purposes // Export class for testing purposes
export { LayoutManager }; export { LayoutManager };
@@ -7,7 +7,7 @@ import { ButtonGroup } from '$shared/ui';
import { IconButton } from '$shared/ui'; import { IconButton } from '$shared/ui';
import GridIcon from '@lucide/svelte/icons/layout-grid'; import GridIcon from '@lucide/svelte/icons/layout-grid';
import ListIcon from '@lucide/svelte/icons/stretch-horizontal'; import ListIcon from '@lucide/svelte/icons/stretch-horizontal';
import { layoutManager } from '../../model'; import { getLayoutManager } from '../../model';
interface Props { interface Props {
/** /**
@@ -18,18 +18,21 @@ interface Props {
const { class: className }: Props = $props(); const { class: className }: Props = $props();
const layoutManager = getLayoutManager();
const mode = $derived(layoutManager.mode);
function handleClick() { function handleClick() {
layoutManager.toggleMode(); layoutManager.toggleMode();
} }
</script> </script>
<ButtonGroup class={className}> <ButtonGroup class={className}>
<IconButton active={layoutManager.mode === 'list'} onclick={handleClick}> <IconButton active={mode === 'list'} onclick={handleClick}>
{#snippet icon()} {#snippet icon()}
<ListIcon class="size-4" /> <ListIcon class="size-4" />
{/snippet} {/snippet}
</IconButton> </IconButton>
<IconButton active={layoutManager.mode === 'grid'} onclick={handleClick}> <IconButton active={mode === 'grid'} onclick={handleClick}>
{#snippet icon()} {#snippet icon()}
<GridIcon class="size-4" /> <GridIcon class="size-4" />
{/snippet} {/snippet}
@@ -4,14 +4,23 @@ import {
screen, screen,
waitFor, waitFor,
} from '@testing-library/svelte'; } from '@testing-library/svelte';
import { layoutManager } from '../../model'; import { afterEach } from 'vitest';
import { getLayoutManager } from '../../model';
import { __resetLayoutManager } from '../../model/stores/layoutStore/layoutStore.svelte';
import LayoutSwitch from './LayoutSwitch.svelte'; import LayoutSwitch from './LayoutSwitch.svelte';
describe('LayoutSwitch', () => { describe('LayoutSwitch', () => {
let layoutManager: ReturnType<typeof getLayoutManager>;
beforeEach(() => { beforeEach(() => {
layoutManager = getLayoutManager();
layoutManager.reset(); layoutManager.reset();
}); });
afterEach(() => {
__resetLayoutManager();
});
describe('Rendering', () => { describe('Rendering', () => {
it('renders two icon buttons', () => { it('renders two icon buttons', () => {
render(LayoutSwitch); render(LayoutSwitch);
@@ -20,11 +20,15 @@ import {
import { FontSampler } from '$features/DisplayFont'; import { FontSampler } from '$features/DisplayFont';
import { throttle } from '$shared/lib/utils'; import { throttle } from '$shared/lib/utils';
import { Skeleton } from '$shared/ui'; import { Skeleton } from '$shared/ui';
import { layoutManager } from '../../model'; import { getLayoutManager } from '../../model';
const fontCatalog = getFontCatalog(); const fontCatalog = getFontCatalog();
const typographySettingsStore = getTypographySettingsStore(); const typographySettingsStore = getTypographySettingsStore();
const fontLifecycleManager = getFontLifecycleManager(); const fontLifecycleManager = getFontLifecycleManager();
const layoutManager = getLayoutManager();
const columns = $derived(layoutManager.columns);
const gap = $derived(layoutManager.gap);
// FontSampler chrome heights — derived from Tailwind classes in FontSampler.svelte. // FontSampler chrome heights — derived from Tailwind classes in FontSampler.svelte.
// Header: py-3 (12+12px padding) + ~32px content row ≈ 56px. // Header: py-3 (12+12px padding) + ~32px content row ≈ 56px.
@@ -114,8 +118,8 @@ const fontRowHeight = $derived.by(() =>
itemHeight={fontRowHeight} itemHeight={fontRowHeight}
useWindowScroll={true} useWindowScroll={true}
weight={typographySettingsStore.weight} weight={typographySettingsStore.weight}
columns={layoutManager.columns} {columns}
gap={layoutManager.gap} {gap}
{skeleton} {skeleton}
> >
{#snippet children({ item: font, index })} {#snippet children({ item: font, index })}
@@ -12,7 +12,7 @@ import {
Section, Section,
} from '$shared/ui'; } from '$shared/ui';
import { getContext } from 'svelte'; import { getContext } from 'svelte';
import { layoutManager } from '../../model'; import { getLayoutManager } from '../../model';
import LayoutSwitch from '../LayoutSwitch/LayoutSwitch.svelte'; import LayoutSwitch from '../LayoutSwitch/LayoutSwitch.svelte';
import SampleList from '../SampleList/SampleList.svelte'; import SampleList from '../SampleList/SampleList.svelte';
@@ -29,6 +29,9 @@ const responsive = getContext<ResponsiveManager>('responsive');
const fontCatalog = getFontCatalog(); const fontCatalog = getFontCatalog();
const total = $derived<number>(fontCatalog?.pagination?.total); const total = $derived<number>(fontCatalog?.pagination?.total);
const layoutManager = getLayoutManager();
const mode = $derived(layoutManager.mode);
</script> </script>
<NavigationWrapper index={2} title="Samples"> <NavigationWrapper index={2} title="Samples">
@@ -46,7 +49,7 @@ const total = $derived<number>(fontCatalog?.pagination?.total);
<div class="flex items-center gap-3 md:gap-4"> <div class="flex items-center gap-3 md:gap-4">
<div class="hidden md:flex items-center gap-2 mr-4"> <div class="hidden md:flex items-center gap-2 mr-4">
<Label variant="muted" size="sm">view_mode: </Label> <Label variant="muted" size="sm">view_mode: </Label>
<Label variant="default" size="sm" bold>{layoutManager.mode}</Label> <Label variant="default" size="sm" bold>{mode}</Label>
</div> </div>
<LayoutSwitch /> <LayoutSwitch />
</div> </div>