refactor(typography): lazy getTypographySettingsStore + fix effect leak
Convert the eager typographySettingsStore singleton to getTypographySettingsStore() (+ __resetTypographySettingsStore for tests) and update barrels and consumers (TypographyMenu, FontSampler, SampleList, ComparisonView Line/SliderArea, comparisonStore + its mock). Fix a latent leak while here: the store ran $effect.root and discarded the returned cleanup, so its storage-sync effects outlived every instance. Capture the disposer and expose destroy(), which __reset now calls.
This commit is contained in:
@@ -1,9 +1,9 @@
|
||||
export {
|
||||
createTypographySettingsStore,
|
||||
getTypographySettingsStore,
|
||||
MULTIPLIER_L,
|
||||
MULTIPLIER_M,
|
||||
MULTIPLIER_S,
|
||||
type TypographySettingsStore,
|
||||
typographySettingsStore,
|
||||
} from './model';
|
||||
export { TypographyMenu } from './ui';
|
||||
|
||||
@@ -5,6 +5,6 @@ export {
|
||||
} from './const/const';
|
||||
export {
|
||||
createTypographySettingsStore,
|
||||
getTypographySettingsStore,
|
||||
type TypographySettingsStore,
|
||||
typographySettingsStore,
|
||||
} from './store/typographySettingsStore/typographySettingsStore.svelte';
|
||||
|
||||
+33
-6
@@ -94,6 +94,12 @@ export class TypographySettingsStore {
|
||||
* The underlying font size before responsive scaling is applied
|
||||
*/
|
||||
#baseSize = $state(DEFAULT_FONT_SIZE);
|
||||
/**
|
||||
* Disposes the $effect.root that backs the storage-sync effects.
|
||||
* $effect.root lives outside component lifecycle, so callers must invoke
|
||||
* destroy() to avoid leaking the subscriptions.
|
||||
*/
|
||||
#disposeEffects: () => void;
|
||||
|
||||
constructor(configs: ControlModel<ControlId>[], storage: PersistentStore<TypographySettings>) {
|
||||
this.#storage = storage;
|
||||
@@ -117,7 +123,7 @@ export class TypographySettingsStore {
|
||||
|
||||
// The Sync Effect (UI -> Storage)
|
||||
// We access .value explicitly to ensure Svelte 5 tracks the dependency
|
||||
$effect.root(() => {
|
||||
this.#disposeEffects = $effect.root(() => {
|
||||
$effect(() => {
|
||||
// EXPLICIT DEPENDENCIES: Accessing these triggers the effect
|
||||
const fontSize = this.#baseSize;
|
||||
@@ -155,6 +161,13 @@ export class TypographySettingsStore {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Tears down the storage-sync effects. Call on unmount / store disposal.
|
||||
*/
|
||||
destroy(): void {
|
||||
this.#disposeEffects();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets initial value for a control from storage or defaults
|
||||
*/
|
||||
@@ -336,10 +349,24 @@ export function createTypographySettingsStore(
|
||||
return new TypographySettingsStore(configs, storage);
|
||||
}
|
||||
|
||||
export type TypographySettingsStoreInstance = ReturnType<typeof createTypographySettingsStore>;
|
||||
|
||||
let _typographySettingsStore: TypographySettingsStoreInstance | undefined;
|
||||
|
||||
/**
|
||||
* App-wide typography settings singleton, keyed for the comparison view.
|
||||
* App-wide typography settings store, keyed for the comparison view.
|
||||
* Created on first access so its persistent-store sync effects aren't set up
|
||||
* at module load.
|
||||
*/
|
||||
export const typographySettingsStore = createTypographySettingsStore(
|
||||
DEFAULT_TYPOGRAPHY_CONTROLS_DATA,
|
||||
COMPARISON_STORAGE_KEY,
|
||||
);
|
||||
export function getTypographySettingsStore(): TypographySettingsStoreInstance {
|
||||
return (_typographySettingsStore ??= createTypographySettingsStore(
|
||||
DEFAULT_TYPOGRAPHY_CONTROLS_DATA,
|
||||
COMPARISON_STORAGE_KEY,
|
||||
));
|
||||
}
|
||||
|
||||
// test-only reset, so specs don't share persisted typography state or leak effects
|
||||
export function __resetTypographySettingsStore() {
|
||||
_typographySettingsStore?.destroy();
|
||||
_typographySettingsStore = undefined;
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ import {
|
||||
MULTIPLIER_L,
|
||||
MULTIPLIER_M,
|
||||
MULTIPLIER_S,
|
||||
typographySettingsStore,
|
||||
getTypographySettingsStore,
|
||||
} from '../../model';
|
||||
|
||||
interface Props {
|
||||
@@ -46,6 +46,7 @@ interface Props {
|
||||
let { class: className, hidden = false, open = $bindable(false) }: Props = $props();
|
||||
|
||||
const responsive = getContext<ResponsiveManager>('responsive');
|
||||
const typographySettingsStore = getTypographySettingsStore();
|
||||
|
||||
/**
|
||||
* Sets the common font size multiplier based on the current responsive state.
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
type FontLoadStatus,
|
||||
type UnifiedFont,
|
||||
} from '$entities/Font';
|
||||
import { typographySettingsStore } from '$features/AdjustTypography/model';
|
||||
import { getTypographySettingsStore } from '$features/AdjustTypography/model';
|
||||
import {
|
||||
Badge,
|
||||
ContentEditable,
|
||||
@@ -43,6 +43,8 @@ interface Props {
|
||||
|
||||
let { font, status, text = $bindable(), index = 0 }: Props = $props();
|
||||
|
||||
const typographySettingsStore = getTypographySettingsStore();
|
||||
|
||||
// Extract provider badge with fallback
|
||||
const providerBadge = $derived(
|
||||
font.providerBadge
|
||||
|
||||
Reference in New Issue
Block a user