Chore/architecture refactoring #42
@@ -667,10 +667,10 @@ export const MOCK_STORES = {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* Create a mock FontStore object
|
* Create a mock FontCatalogStore object
|
||||||
* Matches FontStore's public API for Storybook use
|
* Matches FontCatalogStore's public API for Storybook use
|
||||||
*/
|
*/
|
||||||
fontStore: (config: {
|
fontCatalogStore: (config: {
|
||||||
/**
|
/**
|
||||||
* Preset font list
|
* Preset font list
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { TextLayoutEngine } from '$shared/lib';
|
import { TextLayoutEngine } from '$shared/lib';
|
||||||
import { generateFontKey } from '../../model/store/appliedFontsStore/utils/generateFontKey/generateFontKey';
|
import { generateFontKey } from '../../model/store/fontLifecycleManager/utils/generateFontKey/generateFontKey';
|
||||||
import type {
|
import type {
|
||||||
FontLoadStatus,
|
FontLoadStatus,
|
||||||
UnifiedFont,
|
UnifiedFont,
|
||||||
@@ -41,7 +41,7 @@ export interface FontRowSizeResolverOptions {
|
|||||||
/**
|
/**
|
||||||
* Returns the font load status for a given font key (`'{id}@{weight}'` or `'{id}@vf'`).
|
* Returns the font load status for a given font key (`'{id}@{weight}'` or `'{id}@vf'`).
|
||||||
*
|
*
|
||||||
* In production: `(key) => appliedFontsManager.statuses.get(key)`.
|
* In production: `(key) => fontLifecycleManager.statuses.get(key)`.
|
||||||
* Injected for testability — avoids a module-level singleton dependency in tests.
|
* Injected for testability — avoids a module-level singleton dependency in tests.
|
||||||
* The call to `.get()` on a `SvelteMap` must happen inside a `$derived.by` context
|
* The call to `.get()` on a `SvelteMap` must happen inside a `$derived.by` context
|
||||||
* for reactivity to work. This is satisfied when `itemHeight` is called by
|
* for reactivity to work. This is satisfied when `itemHeight` is called by
|
||||||
@@ -108,7 +108,7 @@ export function createFontRowSizeResolver(options: FontRowSizeResolverOptions):
|
|||||||
// generateFontKey: '{id}@{weight}' for static fonts, '{id}@vf' for variable fonts.
|
// generateFontKey: '{id}@{weight}' for static fonts, '{id}@vf' for variable fonts.
|
||||||
const fontKey = generateFontKey({ id: font.id, weight, isVariable: font.features?.isVariable });
|
const fontKey = generateFontKey({ id: font.id, weight, isVariable: font.features?.isVariable });
|
||||||
|
|
||||||
// Reading via getStatus() allows the caller to pass appliedFontsManager.statuses.get(),
|
// Reading via getStatus() allows the caller to pass fontLifecycleManager.statuses.get(),
|
||||||
// which creates a Svelte 5 reactive dependency when called inside $derived.by.
|
// which creates a Svelte 5 reactive dependency when called inside $derived.by.
|
||||||
const status = options.getStatus(fontKey);
|
const status = options.getStatus(fontKey);
|
||||||
if (status !== 'loaded') {
|
if (status !== 'loaded') {
|
||||||
|
|||||||
+9
-9
@@ -17,7 +17,7 @@ import {
|
|||||||
generateMockFonts,
|
generateMockFonts,
|
||||||
} from '../../../lib/mocks/fonts.mock';
|
} from '../../../lib/mocks/fonts.mock';
|
||||||
import type { UnifiedFont } from '../../types';
|
import type { UnifiedFont } from '../../types';
|
||||||
import { FontStore } from './fontStore.svelte';
|
import { FontCatalogStore } from './fontCatalogStore.svelte';
|
||||||
|
|
||||||
vi.mock('$shared/api/queryClient', () => ({
|
vi.mock('$shared/api/queryClient', () => ({
|
||||||
queryClient: new QueryClient({
|
queryClient: new QueryClient({
|
||||||
@@ -44,7 +44,7 @@ const makeResponse = (
|
|||||||
});
|
});
|
||||||
|
|
||||||
function makeStore(params = {}) {
|
function makeStore(params = {}) {
|
||||||
return new FontStore({ limit: 10, ...params });
|
return new FontCatalogStore({ limit: 10, ...params });
|
||||||
}
|
}
|
||||||
|
|
||||||
async function fetchedStore(params = {}, fonts = generateMockFonts(5), meta: Parameters<typeof makeResponse>[1] = {}) {
|
async function fetchedStore(params = {}, fonts = generateMockFonts(5), meta: Parameters<typeof makeResponse>[1] = {}) {
|
||||||
@@ -55,7 +55,7 @@ async function fetchedStore(params = {}, fonts = generateMockFonts(5), meta: Par
|
|||||||
return store;
|
return store;
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('FontStore', () => {
|
describe('FontCatalogStore', () => {
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
queryClient.clear();
|
queryClient.clear();
|
||||||
vi.resetAllMocks();
|
vi.resetAllMocks();
|
||||||
@@ -69,7 +69,7 @@ describe('FontStore', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('defaults limit to 50 when not provided', () => {
|
it('defaults limit to 50 when not provided', () => {
|
||||||
const store = new FontStore();
|
const store = new FontCatalogStore();
|
||||||
expect(store.params.limit).toBe(50);
|
expect(store.params.limit).toBe(50);
|
||||||
store.destroy();
|
store.destroy();
|
||||||
});
|
});
|
||||||
@@ -390,11 +390,11 @@ describe('FontStore', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('nextPage', () => {
|
describe('nextPage', () => {
|
||||||
let store: FontStore;
|
let store: FontCatalogStore;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
fetch.mockResolvedValue(makeResponse(generateMockFonts(10), { total: 30, limit: 10, offset: 0 }));
|
fetch.mockResolvedValue(makeResponse(generateMockFonts(10), { total: 30, limit: 10, offset: 0 }));
|
||||||
store = new FontStore({ limit: 10 });
|
store = new FontCatalogStore({ limit: 10 });
|
||||||
await store.refetch();
|
await store.refetch();
|
||||||
flushSync();
|
flushSync();
|
||||||
});
|
});
|
||||||
@@ -415,7 +415,7 @@ describe('FontStore', () => {
|
|||||||
// Set up a store where all fonts fit in one page (hasMore = false)
|
// Set up a store where all fonts fit in one page (hasMore = false)
|
||||||
queryClient.clear();
|
queryClient.clear();
|
||||||
fetch.mockResolvedValue(makeResponse(generateMockFonts(10), { total: 10, limit: 10, offset: 0 }));
|
fetch.mockResolvedValue(makeResponse(generateMockFonts(10), { total: 10, limit: 10, offset: 0 }));
|
||||||
store = new FontStore({ limit: 10 });
|
store = new FontCatalogStore({ limit: 10 });
|
||||||
await store.refetch();
|
await store.refetch();
|
||||||
flushSync();
|
flushSync();
|
||||||
|
|
||||||
@@ -454,7 +454,7 @@ describe('FontStore', () => {
|
|||||||
describe('getCachedData / setQueryData', () => {
|
describe('getCachedData / setQueryData', () => {
|
||||||
it('getCachedData returns undefined before any fetch', () => {
|
it('getCachedData returns undefined before any fetch', () => {
|
||||||
queryClient.clear();
|
queryClient.clear();
|
||||||
const store = new FontStore({ limit: 10 });
|
const store = new FontCatalogStore({ limit: 10 });
|
||||||
expect(store.getCachedData()).toBeUndefined();
|
expect(store.getCachedData()).toBeUndefined();
|
||||||
store.destroy();
|
store.destroy();
|
||||||
});
|
});
|
||||||
@@ -502,7 +502,7 @@ describe('FontStore', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('filter shortcut methods', () => {
|
describe('filter shortcut methods', () => {
|
||||||
let store: FontStore;
|
let store: FontCatalogStore;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
store = makeStore();
|
store = makeStore();
|
||||||
+4
-4
@@ -25,7 +25,7 @@ type FontStoreParams = Omit<ProxyFontsParams, 'offset'>;
|
|||||||
|
|
||||||
type FontStoreResult = InfiniteQueryObserverResult<InfiniteData<ProxyFontsResponse, PageParam>, Error>;
|
type FontStoreResult = InfiniteQueryObserverResult<InfiniteData<ProxyFontsResponse, PageParam>, Error>;
|
||||||
|
|
||||||
export class FontStore {
|
export class FontCatalogStore {
|
||||||
#params = $state<FontStoreParams>({ limit: 50 });
|
#params = $state<FontStoreParams>({ limit: 50 });
|
||||||
#result = $state<FontStoreResult>({} as FontStoreResult);
|
#result = $state<FontStoreResult>({} as FontStoreResult);
|
||||||
#observer: InfiniteQueryObserver<
|
#observer: InfiniteQueryObserver<
|
||||||
@@ -459,8 +459,8 @@ export class FontStore {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createFontStore(params: FontStoreParams = {}): FontStore {
|
export function createFontCatalogStore(params: FontStoreParams = {}): FontCatalogStore {
|
||||||
return new FontStore(params);
|
return new FontCatalogStore(params);
|
||||||
}
|
}
|
||||||
|
|
||||||
export const fontStore = new FontStore({ limit: 50 });
|
export const fontCatalogStore = new FontCatalogStore({ limit: 50 });
|
||||||
+4
-4
@@ -17,7 +17,7 @@ import { FontBufferCache } from './utils/fontBufferCache/FontBufferCache';
|
|||||||
import { FontEvictionPolicy } from './utils/fontEvictionPolicy/FontEvictionPolicy';
|
import { FontEvictionPolicy } from './utils/fontEvictionPolicy/FontEvictionPolicy';
|
||||||
import { FontLoadQueue } from './utils/fontLoadQueue/FontLoadQueue';
|
import { FontLoadQueue } from './utils/fontLoadQueue/FontLoadQueue';
|
||||||
|
|
||||||
interface AppliedFontsManagerDeps {
|
interface FontLifecycleManagerDeps {
|
||||||
cache?: FontBufferCache;
|
cache?: FontBufferCache;
|
||||||
eviction?: FontEvictionPolicy;
|
eviction?: FontEvictionPolicy;
|
||||||
queue?: FontLoadQueue;
|
queue?: FontLoadQueue;
|
||||||
@@ -46,7 +46,7 @@ interface AppliedFontsManagerDeps {
|
|||||||
*
|
*
|
||||||
* **Browser APIs Used:** `scheduler.yield()`, `isInputPending()`, `requestIdleCallback`, Cache API, Network Information API
|
* **Browser APIs Used:** `scheduler.yield()`, `isInputPending()`, `requestIdleCallback`, Cache API, Network Information API
|
||||||
*/
|
*/
|
||||||
export class AppliedFontsManager {
|
export class FontLifecycleManager {
|
||||||
// Injected collaborators - each handles one concern for better testability
|
// Injected collaborators - each handles one concern for better testability
|
||||||
readonly #cache: FontBufferCache;
|
readonly #cache: FontBufferCache;
|
||||||
readonly #eviction: FontEvictionPolicy;
|
readonly #eviction: FontEvictionPolicy;
|
||||||
@@ -78,7 +78,7 @@ export class AppliedFontsManager {
|
|||||||
// Starts periodic cleanup timer (browser-only).
|
// Starts periodic cleanup timer (browser-only).
|
||||||
constructor(
|
constructor(
|
||||||
{ cache = new FontBufferCache(), eviction = new FontEvictionPolicy(), queue = new FontLoadQueue() }:
|
{ cache = new FontBufferCache(), eviction = new FontEvictionPolicy(), queue = new FontLoadQueue() }:
|
||||||
AppliedFontsManagerDeps = {},
|
FontLifecycleManagerDeps = {},
|
||||||
) {
|
) {
|
||||||
// Inject collaborators - defaults provided for production, fakes for testing
|
// Inject collaborators - defaults provided for production, fakes for testing
|
||||||
this.#cache = cache;
|
this.#cache = cache;
|
||||||
@@ -396,4 +396,4 @@ export class AppliedFontsManager {
|
|||||||
/**
|
/**
|
||||||
* Singleton instance — use throughout the application for unified font loading state.
|
* Singleton instance — use throughout the application for unified font loading state.
|
||||||
*/
|
*/
|
||||||
export const appliedFontsManager = new AppliedFontsManager();
|
export const fontLifecycleManager = new FontLifecycleManager();
|
||||||
+8
-8
@@ -1,8 +1,8 @@
|
|||||||
/**
|
/**
|
||||||
* @vitest-environment jsdom
|
* @vitest-environment jsdom
|
||||||
*/
|
*/
|
||||||
import { AppliedFontsManager } from './appliedFontsStore.svelte';
|
|
||||||
import { FontFetchError } from './errors';
|
import { FontFetchError } from './errors';
|
||||||
|
import { FontLifecycleManager } from './fontLifecycleManager.svelte';
|
||||||
import { FontEvictionPolicy } from './utils/fontEvictionPolicy/FontEvictionPolicy';
|
import { FontEvictionPolicy } from './utils/fontEvictionPolicy/FontEvictionPolicy';
|
||||||
|
|
||||||
class FakeBufferCache {
|
class FakeBufferCache {
|
||||||
@@ -32,8 +32,8 @@ const makeConfig = (id: string, overrides: Partial<{ weight: number; isVariable:
|
|||||||
...overrides,
|
...overrides,
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('AppliedFontsManager', () => {
|
describe('FontLifecycleManager', () => {
|
||||||
let manager: AppliedFontsManager;
|
let manager: FontLifecycleManager;
|
||||||
let eviction: FontEvictionPolicy;
|
let eviction: FontEvictionPolicy;
|
||||||
let mockFontFaceSet: { add: ReturnType<typeof vi.fn>; delete: ReturnType<typeof vi.fn> };
|
let mockFontFaceSet: { add: ReturnType<typeof vi.fn>; delete: ReturnType<typeof vi.fn> };
|
||||||
|
|
||||||
@@ -55,7 +55,7 @@ describe('AppliedFontsManager', () => {
|
|||||||
});
|
});
|
||||||
vi.stubGlobal('FontFace', MockFontFace);
|
vi.stubGlobal('FontFace', MockFontFace);
|
||||||
|
|
||||||
manager = new AppliedFontsManager({ cache: new FakeBufferCache() as any, eviction });
|
manager = new FontLifecycleManager({ cache: new FakeBufferCache() as any, eviction });
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
@@ -101,7 +101,7 @@ describe('AppliedFontsManager', () => {
|
|||||||
|
|
||||||
it('skips fonts that have exhausted retries', async () => {
|
it('skips fonts that have exhausted retries', async () => {
|
||||||
const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
||||||
const failManager = new AppliedFontsManager({ cache: new FailingBufferCache() as any, eviction });
|
const failManager = new FontLifecycleManager({ cache: new FailingBufferCache() as any, eviction });
|
||||||
|
|
||||||
// exhaust all 3 retries
|
// exhaust all 3 retries
|
||||||
for (let i = 0; i < 3; i++) {
|
for (let i = 0; i < 3; i++) {
|
||||||
@@ -160,7 +160,7 @@ describe('AppliedFontsManager', () => {
|
|||||||
describe('Phase 1 — fetch', () => {
|
describe('Phase 1 — fetch', () => {
|
||||||
it('sets status to error on fetch failure', async () => {
|
it('sets status to error on fetch failure', async () => {
|
||||||
const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
||||||
const failManager = new AppliedFontsManager({ cache: new FailingBufferCache() as any, eviction });
|
const failManager = new FontLifecycleManager({ cache: new FailingBufferCache() as any, eviction });
|
||||||
|
|
||||||
failManager.touch([makeConfig('broken')]);
|
failManager.touch([makeConfig('broken')]);
|
||||||
await vi.advanceTimersByTimeAsync(50);
|
await vi.advanceTimersByTimeAsync(50);
|
||||||
@@ -171,7 +171,7 @@ describe('AppliedFontsManager', () => {
|
|||||||
|
|
||||||
it('logs a console error on fetch failure', async () => {
|
it('logs a console error on fetch failure', async () => {
|
||||||
const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
||||||
const failManager = new AppliedFontsManager({ cache: new FailingBufferCache() as any, eviction });
|
const failManager = new FontLifecycleManager({ cache: new FailingBufferCache() as any, eviction });
|
||||||
|
|
||||||
failManager.touch([makeConfig('broken')]);
|
failManager.touch([makeConfig('broken')]);
|
||||||
await vi.advanceTimersByTimeAsync(50);
|
await vi.advanceTimersByTimeAsync(50);
|
||||||
@@ -189,7 +189,7 @@ describe('AppliedFontsManager', () => {
|
|||||||
evict() {},
|
evict() {},
|
||||||
clear() {},
|
clear() {},
|
||||||
};
|
};
|
||||||
const abortManager = new AppliedFontsManager({ cache: abortingCache as any, eviction });
|
const abortManager = new FontLifecycleManager({ cache: abortingCache as any, eviction });
|
||||||
|
|
||||||
abortManager.touch([makeConfig('aborted')]);
|
abortManager.touch([makeConfig('aborted')]);
|
||||||
await vi.advanceTimersByTimeAsync(50);
|
await vi.advanceTimersByTimeAsync(50);
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
// Applied fonts manager
|
// Font lifecycle manager (browser-side load + cache + eviction)
|
||||||
export * from './appliedFontsStore/appliedFontsStore.svelte';
|
export * from './fontLifecycleManager/fontLifecycleManager.svelte';
|
||||||
|
|
||||||
// Single FontStore
|
// Paginated catalog
|
||||||
export {
|
export {
|
||||||
createFontStore,
|
createFontCatalogStore,
|
||||||
FontStore,
|
FontCatalogStore,
|
||||||
fontStore,
|
fontCatalogStore,
|
||||||
} from './fontStore/fontStore.svelte';
|
} from './fontCatalogStore/fontCatalogStore.svelte';
|
||||||
|
|||||||
@@ -23,5 +23,5 @@ export type {
|
|||||||
FontCollectionState,
|
FontCollectionState,
|
||||||
} from './store';
|
} from './store';
|
||||||
|
|
||||||
export * from './store/appliedFonts';
|
export * from './store/fontLifecycle';
|
||||||
export * from './typography';
|
export * from './typography';
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ const fontArialBold = mockUnifiedFont({ id: 'arial-bold', name: 'Arial' });
|
|||||||
docs: {
|
docs: {
|
||||||
description: {
|
description: {
|
||||||
story:
|
story:
|
||||||
'Font that has never been loaded by appliedFontsManager. The component renders in its pending state: blurred, scaled down, and semi-transparent.',
|
'Font that has never been loaded by fontLifecycleManager. The component renders in its pending state: blurred, scaled down, and semi-transparent.',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
@@ -58,7 +58,7 @@ const fontArialBold = mockUnifiedFont({ id: 'arial-bold', name: 'Arial' });
|
|||||||
docs: {
|
docs: {
|
||||||
description: {
|
description: {
|
||||||
story:
|
story:
|
||||||
'Uses Arial, a system font available in all browsers. Because appliedFontsManager has not loaded it via FontFace, the manager status may remain pending — meaning the blur/scale state may still show. In a real app the manager would load the font and transition to the revealed state.',
|
'Uses Arial, a system font available in all browsers. Because fontLifecycleManager has not loaded it via FontFace, the manager status may remain pending — meaning the blur/scale state may still show. In a real app the manager would load the font and transition to the revealed state.',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
@@ -77,7 +77,7 @@ const fontArialBold = mockUnifiedFont({ id: 'arial-bold', name: 'Arial' });
|
|||||||
docs: {
|
docs: {
|
||||||
description: {
|
description: {
|
||||||
story:
|
story:
|
||||||
'Demonstrates passing a custom weight (700). The weight is forwarded to appliedFontsManager for font resolution; visually identical to the loaded state story until the manager confirms the font.',
|
'Demonstrates passing a custom weight (700). The weight is forwarded to fontLifecycleManager for font resolution; visually identical to the loaded state story until the manager confirms the font.',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import type { Snippet } from 'svelte';
|
|||||||
import {
|
import {
|
||||||
DEFAULT_FONT_WEIGHT,
|
DEFAULT_FONT_WEIGHT,
|
||||||
type UnifiedFont,
|
type UnifiedFont,
|
||||||
appliedFontsManager,
|
fontLifecycleManager,
|
||||||
} from '../../model';
|
} from '../../model';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@@ -46,7 +46,7 @@ let {
|
|||||||
}: Props = $props();
|
}: Props = $props();
|
||||||
|
|
||||||
const status = $derived(
|
const status = $derived(
|
||||||
appliedFontsManager.getFontStatus(
|
fontLifecycleManager.getFontStatus(
|
||||||
font.id,
|
font.id,
|
||||||
weight,
|
weight,
|
||||||
font.features?.isVariable,
|
font.features?.isVariable,
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ const { Story } = defineMeta({
|
|||||||
docs: {
|
docs: {
|
||||||
description: {
|
description: {
|
||||||
component:
|
component:
|
||||||
'Virtualized font list backed by the `fontStore` singleton. Handles font loading registration (pin/touch) for visible items and triggers infinite scroll pagination via `fontStore.nextPage()`. Because the component reads directly from the `fontStore` singleton, stories render against a live (but empty/loading) store — no font data will appear unless the API is reachable from the Storybook host.',
|
'Virtualized font list backed by the `fontCatalogStore` singleton. Handles font loading registration (pin/touch) for visible items and triggers infinite scroll pagination via `fontCatalogStore.nextPage()`. Because the component reads directly from the `fontCatalogStore` singleton, stories render against a live (but empty/loading) store — no font data will appear unless the API is reachable from the Storybook host.',
|
||||||
},
|
},
|
||||||
story: { inline: false },
|
story: { inline: false },
|
||||||
},
|
},
|
||||||
@@ -33,7 +33,7 @@ import type { ComponentProps } from 'svelte';
|
|||||||
docs: {
|
docs: {
|
||||||
description: {
|
description: {
|
||||||
story:
|
story:
|
||||||
'Skeleton state shown while `fontStore.fonts` is empty and `fontStore.isLoading` is true. In a real session the skeleton fades out once the first page loads.',
|
'Skeleton state shown while `fontCatalogStore.fonts` is empty and `fontCatalogStore.isLoading` is true. In a real session the skeleton fades out once the first page loads.',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
@@ -63,7 +63,7 @@ import type { ComponentProps } from 'svelte';
|
|||||||
docs: {
|
docs: {
|
||||||
description: {
|
description: {
|
||||||
story:
|
story:
|
||||||
'No `skeleton` snippet provided. When `fontStore.fonts` is empty the underlying VirtualList renders its empty state directly.',
|
'No `skeleton` snippet provided. When `fontCatalogStore.fonts` is empty the underlying VirtualList renders its empty state directly.',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
@@ -86,7 +86,7 @@ import type { ComponentProps } from 'svelte';
|
|||||||
docs: {
|
docs: {
|
||||||
description: {
|
description: {
|
||||||
story:
|
story:
|
||||||
'Demonstrates how to configure a `children` snippet for item rendering. The list will be empty because `fontStore` is not populated in Storybook, but the template shows the expected slot shape: `{ item: UnifiedFont }`.',
|
'Demonstrates how to configure a `children` snippet for item rendering. The list will be empty because `fontCatalogStore` is not populated in Storybook, but the template shows the expected slot shape: `{ item: UnifiedFont }`.',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -18,8 +18,8 @@ import { getFontUrl } from '../../lib';
|
|||||||
import {
|
import {
|
||||||
type FontLoadRequestConfig,
|
type FontLoadRequestConfig,
|
||||||
type UnifiedFont,
|
type UnifiedFont,
|
||||||
appliedFontsManager,
|
fontCatalogStore,
|
||||||
fontStore,
|
fontLifecycleManager,
|
||||||
} from '../../model';
|
} from '../../model';
|
||||||
|
|
||||||
interface Props extends
|
interface Props extends
|
||||||
@@ -51,13 +51,13 @@ let {
|
|||||||
}: Props = $props();
|
}: Props = $props();
|
||||||
|
|
||||||
const isLoading = $derived(
|
const isLoading = $derived(
|
||||||
fontStore.isFetching || fontStore.isLoading,
|
fontCatalogStore.isFetching || fontCatalogStore.isLoading,
|
||||||
);
|
);
|
||||||
|
|
||||||
let visibleFonts = $state<UnifiedFont[]>([]);
|
let visibleFonts = $state<UnifiedFont[]>([]);
|
||||||
let isCatchingUp = $state(false);
|
let isCatchingUp = $state(false);
|
||||||
|
|
||||||
const showInitialSkeleton = $derived(!!skeleton && isLoading && fontStore.fonts.length === 0);
|
const showInitialSkeleton = $derived(!!skeleton && isLoading && fontCatalogStore.fonts.length === 0);
|
||||||
const showCatchupSkeleton = $derived(!!skeleton && isCatchingUp);
|
const showCatchupSkeleton = $derived(!!skeleton && isCatchingUp);
|
||||||
|
|
||||||
function handleInternalVisibleChange(items: UnifiedFont[]) {
|
function handleInternalVisibleChange(items: UnifiedFont[]) {
|
||||||
@@ -68,23 +68,23 @@ function handleInternalVisibleChange(items: UnifiedFont[]) {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle jump scroll — batch-load all missing pages then re-enable font loading.
|
* Handle jump scroll — batch-load all missing pages then re-enable font loading.
|
||||||
* Suppresses appliedFontsManager.touch() during catch-up to avoid loading
|
* Suppresses fontLifecycleManager.touch() during catch-up to avoid loading
|
||||||
* font files for thousands of intermediate fonts.
|
* font files for thousands of intermediate fonts.
|
||||||
*/
|
*/
|
||||||
async function handleJump(targetIndex: number) {
|
async function handleJump(targetIndex: number) {
|
||||||
if (isCatchingUp || !fontStore.pagination.hasMore) {
|
if (isCatchingUp || !fontCatalogStore.pagination.hasMore) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
isCatchingUp = true;
|
isCatchingUp = true;
|
||||||
try {
|
try {
|
||||||
await fontStore.fetchAllPagesTo(targetIndex);
|
await fontCatalogStore.fetchAllPagesTo(targetIndex);
|
||||||
} finally {
|
} finally {
|
||||||
isCatchingUp = false;
|
isCatchingUp = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const debouncedTouch = debounce((configs: FontLoadRequestConfig[]) => {
|
const debouncedTouch = debounce((configs: FontLoadRequestConfig[]) => {
|
||||||
appliedFontsManager.touch(configs);
|
fontLifecycleManager.touch(configs);
|
||||||
}, 150);
|
}, 150);
|
||||||
|
|
||||||
// Re-touch whenever visible set or weight changes — fixes weight-change gap
|
// Re-touch whenever visible set or weight changes — fixes weight-change gap
|
||||||
@@ -111,11 +111,11 @@ $effect(() => {
|
|||||||
const w = weight;
|
const w = weight;
|
||||||
const fonts = visibleFonts;
|
const fonts = visibleFonts;
|
||||||
for (const f of fonts) {
|
for (const f of fonts) {
|
||||||
appliedFontsManager.pin(f.id, w, f.features?.isVariable);
|
fontLifecycleManager.pin(f.id, w, f.features?.isVariable);
|
||||||
}
|
}
|
||||||
return () => {
|
return () => {
|
||||||
for (const f of fonts) {
|
for (const f of fonts) {
|
||||||
appliedFontsManager.unpin(f.id, w, f.features?.isVariable);
|
fontLifecycleManager.unpin(f.id, w, f.features?.isVariable);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
@@ -125,12 +125,12 @@ $effect(() => {
|
|||||||
*/
|
*/
|
||||||
function loadMore() {
|
function loadMore() {
|
||||||
if (
|
if (
|
||||||
!fontStore.pagination.hasMore
|
!fontCatalogStore.pagination.hasMore
|
||||||
|| fontStore.isFetching
|
|| fontCatalogStore.isFetching
|
||||||
) {
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
fontStore.nextPage();
|
fontCatalogStore.nextPage();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -140,12 +140,12 @@ function loadMore() {
|
|||||||
* of the loaded items. Only fetches if there are more pages available.
|
* of the loaded items. Only fetches if there are more pages available.
|
||||||
*/
|
*/
|
||||||
function handleNearBottom(_lastVisibleIndex: number) {
|
function handleNearBottom(_lastVisibleIndex: number) {
|
||||||
const { hasMore } = fontStore.pagination;
|
const { hasMore } = fontCatalogStore.pagination;
|
||||||
|
|
||||||
// VirtualList already checks if we're near the bottom of loaded items.
|
// VirtualList already checks if we're near the bottom of loaded items.
|
||||||
// Guard isCatchingUp: fetchAllPagesTo bypasses TQ so isFetching stays false
|
// Guard isCatchingUp: fetchAllPagesTo bypasses TQ so isFetching stays false
|
||||||
// during batch catch-up, which would otherwise let nextPage() race with it.
|
// during batch catch-up, which would otherwise let nextPage() race with it.
|
||||||
if (hasMore && !fontStore.isFetching && !isCatchingUp) {
|
if (hasMore && !fontCatalogStore.isFetching && !isCatchingUp) {
|
||||||
loadMore();
|
loadMore();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -160,8 +160,8 @@ function handleNearBottom(_lastVisibleIndex: number) {
|
|||||||
{:else}
|
{:else}
|
||||||
<!-- VirtualList persists during pagination - no destruction/recreation -->
|
<!-- VirtualList persists during pagination - no destruction/recreation -->
|
||||||
<VirtualList
|
<VirtualList
|
||||||
items={fontStore.fonts}
|
items={fontCatalogStore.fonts}
|
||||||
total={fontStore.pagination.total}
|
total={fontCatalogStore.pagination.total}
|
||||||
isLoading={isLoading || isCatchingUp}
|
isLoading={isLoading || isCatchingUp}
|
||||||
onVisibleItemsChange={handleInternalVisibleChange}
|
onVisibleItemsChange={handleInternalVisibleChange}
|
||||||
onNearBottom={handleNearBottom}
|
onNearBottom={handleNearBottom}
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ export {
|
|||||||
} from './store/appliedFilterStore/appliedFilterStore.svelte';
|
} from './store/appliedFilterStore/appliedFilterStore.svelte';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Side-effect import: installs the global appliedFilterStore+sortStore → fontStore
|
* Side-effect import: installs the global appliedFilterStore+sortStore → fontCatalogStore
|
||||||
* bridge on first import of this feature barrel. No exports.
|
* bridge on first import of this feature barrel. No exports.
|
||||||
*/
|
*/
|
||||||
import './store/bindings.svelte';
|
import './store/bindings.svelte';
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/**
|
/**
|
||||||
* Bridges feature-level UI state (appliedFilterStore + sortStore) to the
|
* Bridges feature-level UI state (appliedFilterStore + sortStore) to the
|
||||||
* entity-level fontStore query params.
|
* entity-level fontCatalogStore query params.
|
||||||
*
|
*
|
||||||
* Centralizing this here means consumers (Search, FontSearch,
|
* Centralizing this here means consumers (Search, FontSearch,
|
||||||
* FilterControls, etc.) bind to the manager/store directly without
|
* FilterControls, etc.) bind to the manager/store directly without
|
||||||
@@ -9,7 +9,7 @@
|
|||||||
* observer, so it lives at module scope, not in any individual widget.
|
* observer, so it lives at module scope, not in any individual widget.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { fontStore } from '$entities/Font';
|
import { fontCatalogStore } from '$entities/Font';
|
||||||
import { untrack } from 'svelte';
|
import { untrack } from 'svelte';
|
||||||
import { mapAppliedFiltersToParams } from '../../lib/mapper/mapAppliedFiltersToParams';
|
import { mapAppliedFiltersToParams } from '../../lib/mapper/mapAppliedFiltersToParams';
|
||||||
import { appliedFilterStore } from './appliedFilterStore/appliedFilterStore.svelte';
|
import { appliedFilterStore } from './appliedFilterStore/appliedFilterStore.svelte';
|
||||||
@@ -42,20 +42,20 @@ $effect.root(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mirror filter selections + debounced search query into fontStore params.
|
* Mirror filter selections + debounced search query into fontCatalogStore params.
|
||||||
* untrack the write so fontStore's internal $state reads don't feed back
|
* untrack the write so fontCatalogStore's internal $state reads don't feed back
|
||||||
* into this effect's dependency graph.
|
* into this effect's dependency graph.
|
||||||
*/
|
*/
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
const params = mapAppliedFiltersToParams(appliedFilterStore);
|
const params = mapAppliedFiltersToParams(appliedFilterStore);
|
||||||
untrack(() => fontStore.setParams(params));
|
untrack(() => fontCatalogStore.setParams(params));
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mirror sort selection into fontStore.
|
* Mirror sort selection into fontCatalogStore.
|
||||||
*/
|
*/
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
const apiSort = sortStore.apiValue;
|
const apiSort = sortStore.apiValue;
|
||||||
untrack(() => fontStore.setSort(apiSort));
|
untrack(() => fontCatalogStore.setSort(apiSort));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ export interface VirtualizerOptions {
|
|||||||
* when those values change, `offsets` and `totalSize` recompute instantly.
|
* when those values change, `offsets` and `totalSize` recompute instantly.
|
||||||
*
|
*
|
||||||
* For font preview rows, pass a closure that reads
|
* For font preview rows, pass a closure that reads
|
||||||
* `appliedFontsManager.statuses` so the virtualizer recalculates heights
|
* `fontLifecycleManager.statuses` so the virtualizer recalculates heights
|
||||||
* as fonts finish loading, eliminating the DOM-measurement snap on load.
|
* as fonts finish loading, eliminating the DOM-measurement snap on load.
|
||||||
*/
|
*/
|
||||||
estimateSize: (index: number) => number;
|
estimateSize: (index: number) => number;
|
||||||
|
|||||||
@@ -16,8 +16,8 @@
|
|||||||
import {
|
import {
|
||||||
type FontLoadRequestConfig,
|
type FontLoadRequestConfig,
|
||||||
type UnifiedFont,
|
type UnifiedFont,
|
||||||
appliedFontsManager,
|
fontCatalogStore,
|
||||||
fontStore,
|
fontLifecycleManager,
|
||||||
getFontUrl,
|
getFontUrl,
|
||||||
} from '$entities/Font';
|
} from '$entities/Font';
|
||||||
import { typographySettingsStore } from '$features/AdjustTypography/model';
|
import { typographySettingsStore } from '$features/AdjustTypography/model';
|
||||||
@@ -140,7 +140,7 @@ export class ComparisonStore {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (configs.length > 0) {
|
if (configs.length > 0) {
|
||||||
appliedFontsManager.touch(configs);
|
fontLifecycleManager.touch(configs);
|
||||||
this.#checkFontsLoaded();
|
this.#checkFontsLoaded();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -151,7 +151,7 @@ export class ComparisonStore {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const fonts = fontStore.fonts;
|
const fonts = fontCatalogStore.fonts;
|
||||||
if (fonts.length >= 2) {
|
if (fonts.length >= 2) {
|
||||||
untrack(() => {
|
untrack(() => {
|
||||||
const id1 = fonts[0].id;
|
const id1 = fonts[0].id;
|
||||||
@@ -168,17 +168,17 @@ export class ComparisonStore {
|
|||||||
const fb = this.#fontB;
|
const fb = this.#fontB;
|
||||||
const w = typographySettingsStore.weight;
|
const w = typographySettingsStore.weight;
|
||||||
if (fa) {
|
if (fa) {
|
||||||
appliedFontsManager.pin(fa.id, w, fa.features?.isVariable);
|
fontLifecycleManager.pin(fa.id, w, fa.features?.isVariable);
|
||||||
}
|
}
|
||||||
if (fb) {
|
if (fb) {
|
||||||
appliedFontsManager.pin(fb.id, w, fb.features?.isVariable);
|
fontLifecycleManager.pin(fb.id, w, fb.features?.isVariable);
|
||||||
}
|
}
|
||||||
return () => {
|
return () => {
|
||||||
if (fa) {
|
if (fa) {
|
||||||
appliedFontsManager.unpin(fa.id, w, fa.features?.isVariable);
|
fontLifecycleManager.unpin(fa.id, w, fa.features?.isVariable);
|
||||||
}
|
}
|
||||||
if (fb) {
|
if (fb) {
|
||||||
appliedFontsManager.unpin(fb.id, w, fb.features?.isVariable);
|
fontLifecycleManager.unpin(fb.id, w, fb.features?.isVariable);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -55,8 +55,8 @@ vi.mock('$entities/Font', async importOriginal => {
|
|||||||
const actual = await importOriginal<typeof import('$entities/Font')>();
|
const actual = await importOriginal<typeof import('$entities/Font')>();
|
||||||
return {
|
return {
|
||||||
...actual,
|
...actual,
|
||||||
fontStore: { fonts: [] },
|
fontCatalogStore: { fonts: [] },
|
||||||
appliedFontsManager: {
|
fontLifecycleManager: {
|
||||||
touch: vi.fn(),
|
touch: vi.fn(),
|
||||||
pin: vi.fn(),
|
pin: vi.fn(),
|
||||||
unpin: vi.fn(),
|
unpin: vi.fn(),
|
||||||
@@ -85,8 +85,8 @@ vi.mock('$features/AdjustTypography/model', () => ({
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
import {
|
import {
|
||||||
appliedFontsManager,
|
fontCatalogStore,
|
||||||
fontStore,
|
fontLifecycleManager,
|
||||||
} from '$entities/Font';
|
} from '$entities/Font';
|
||||||
import * as proxyFonts from '$entities/Font/api/proxy/proxyFonts';
|
import * as proxyFonts from '$entities/Font/api/proxy/proxyFonts';
|
||||||
import { ComparisonStore } from './comparisonStore.svelte';
|
import { ComparisonStore } from './comparisonStore.svelte';
|
||||||
@@ -100,7 +100,7 @@ describe('ComparisonStore', () => {
|
|||||||
vi.clearAllMocks();
|
vi.clearAllMocks();
|
||||||
mockStorage._value = { fontAId: null, fontBId: null };
|
mockStorage._value = { fontAId: null, fontBId: null };
|
||||||
mockStorage._clear.mockClear();
|
mockStorage._clear.mockClear();
|
||||||
(fontStore as any).fonts = [];
|
(fontCatalogStore as any).fonts = [];
|
||||||
|
|
||||||
// Default: fetchFontsByIds returns empty so tests that don't care don't hang
|
// Default: fetchFontsByIds returns empty so tests that don't care don't hang
|
||||||
vi.spyOn(proxyFonts, 'fetchFontsByIds').mockResolvedValue([]);
|
vi.spyOn(proxyFonts, 'fetchFontsByIds').mockResolvedValue([]);
|
||||||
@@ -155,7 +155,7 @@ describe('ComparisonStore', () => {
|
|||||||
|
|
||||||
describe('Default Fallbacks', () => {
|
describe('Default Fallbacks', () => {
|
||||||
it('should update storage with default IDs when storage is empty', async () => {
|
it('should update storage with default IDs when storage is empty', async () => {
|
||||||
(fontStore as any).fonts = [mockFontA, mockFontB];
|
(fontCatalogStore as any).fonts = [mockFontA, mockFontB];
|
||||||
vi.spyOn(proxyFonts, 'fetchFontsByIds').mockResolvedValue([mockFontA, mockFontB]);
|
vi.spyOn(proxyFonts, 'fetchFontsByIds').mockResolvedValue([mockFontA, mockFontB]);
|
||||||
|
|
||||||
new ComparisonStore();
|
new ComparisonStore();
|
||||||
@@ -212,12 +212,12 @@ describe('ComparisonStore', () => {
|
|||||||
new ComparisonStore();
|
new ComparisonStore();
|
||||||
|
|
||||||
await vi.waitFor(() => {
|
await vi.waitFor(() => {
|
||||||
expect(appliedFontsManager.pin).toHaveBeenCalledWith(
|
expect(fontLifecycleManager.pin).toHaveBeenCalledWith(
|
||||||
mockFontA.id,
|
mockFontA.id,
|
||||||
400,
|
400,
|
||||||
mockFontA.features?.isVariable,
|
mockFontA.features?.isVariable,
|
||||||
);
|
);
|
||||||
expect(appliedFontsManager.pin).toHaveBeenCalledWith(
|
expect(fontLifecycleManager.pin).toHaveBeenCalledWith(
|
||||||
mockFontB.id,
|
mockFontB.id,
|
||||||
400,
|
400,
|
||||||
mockFontB.features?.isVariable,
|
mockFontB.features?.isVariable,
|
||||||
@@ -238,12 +238,12 @@ describe('ComparisonStore', () => {
|
|||||||
store.fontA = mockFontC;
|
store.fontA = mockFontC;
|
||||||
|
|
||||||
await vi.waitFor(() => {
|
await vi.waitFor(() => {
|
||||||
expect(appliedFontsManager.unpin).toHaveBeenCalledWith(
|
expect(fontLifecycleManager.unpin).toHaveBeenCalledWith(
|
||||||
mockFontA.id,
|
mockFontA.id,
|
||||||
400,
|
400,
|
||||||
mockFontA.features?.isVariable,
|
mockFontA.features?.isVariable,
|
||||||
);
|
);
|
||||||
expect(appliedFontsManager.pin).toHaveBeenCalledWith(
|
expect(fontLifecycleManager.pin).toHaveBeenCalledWith(
|
||||||
mockFontC.id,
|
mockFontC.id,
|
||||||
400,
|
400,
|
||||||
mockFontC.features?.isVariable,
|
mockFontC.features?.isVariable,
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ import {
|
|||||||
FontVirtualList,
|
FontVirtualList,
|
||||||
type UnifiedFont,
|
type UnifiedFont,
|
||||||
VIRTUAL_INDEX_NOT_LOADED,
|
VIRTUAL_INDEX_NOT_LOADED,
|
||||||
appliedFontsManager,
|
fontCatalogStore,
|
||||||
fontStore,
|
fontLifecycleManager,
|
||||||
} from '$entities/Font';
|
} from '$entities/Font';
|
||||||
import { getSkeletonWidth } from '$shared/lib/utils';
|
import { getSkeletonWidth } from '$shared/lib/utils';
|
||||||
import {
|
import {
|
||||||
@@ -36,7 +36,7 @@ function getVirtualIndex(fontId: string | undefined): number {
|
|||||||
if (!fontId) {
|
if (!fontId) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
const idx = fontStore.fonts.findIndex(f => f.id === fontId);
|
const idx = fontCatalogStore.fonts.findIndex(f => f.id === fontId);
|
||||||
if (idx === -1) {
|
if (idx === -1) {
|
||||||
return VIRTUAL_INDEX_NOT_LOADED;
|
return VIRTUAL_INDEX_NOT_LOADED;
|
||||||
}
|
}
|
||||||
@@ -77,11 +77,11 @@ function handleSelect(font: UnifiedFont) {
|
|||||||
/**
|
/**
|
||||||
* Returns true once the font file is loaded (or errored) and safe to render.
|
* Returns true once the font file is loaded (or errored) and safe to render.
|
||||||
* Called inside the template — Svelte 5 tracks the $state reads inside
|
* Called inside the template — Svelte 5 tracks the $state reads inside
|
||||||
* appliedFontsManager.getFontStatus(), so each row re-renders reactively
|
* fontLifecycleManager.getFontStatus(), so each row re-renders reactively
|
||||||
* when its file arrives.
|
* when its file arrives.
|
||||||
*/
|
*/
|
||||||
function isFontReady(font: UnifiedFont): boolean {
|
function isFontReady(font: UnifiedFont): boolean {
|
||||||
const status = appliedFontsManager.getFontStatus(
|
const status = fontLifecycleManager.getFontStatus(
|
||||||
font.id,
|
font.id,
|
||||||
DEFAULT_FONT_WEIGHT,
|
DEFAULT_FONT_WEIGHT,
|
||||||
font.features?.isVariable,
|
font.features?.isVariable,
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
Component: Search
|
Component: Search
|
||||||
Typeface search input for the comparison view.
|
Typeface search input for the comparison view.
|
||||||
Writes through appliedFilterStore; the global bridge in $features/FilterAndSortFonts
|
Writes through appliedFilterStore; the global bridge in $features/FilterAndSortFonts
|
||||||
propagates the value into fontStore.
|
propagates the value into fontCatalogStore.
|
||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { appliedFilterStore } from '$features/FilterAndSortFonts';
|
import { appliedFilterStore } from '$features/FilterAndSortFonts';
|
||||||
|
|||||||
@@ -7,9 +7,9 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {
|
import {
|
||||||
FontVirtualList,
|
FontVirtualList,
|
||||||
appliedFontsManager,
|
|
||||||
createFontRowSizeResolver,
|
createFontRowSizeResolver,
|
||||||
fontStore,
|
fontCatalogStore,
|
||||||
|
fontLifecycleManager,
|
||||||
} from '$entities/Font';
|
} from '$entities/Font';
|
||||||
import {
|
import {
|
||||||
TypographyMenu,
|
TypographyMenu,
|
||||||
@@ -57,17 +57,17 @@ const checkPosition = throttle(() => {
|
|||||||
}, 100);
|
}, 100);
|
||||||
|
|
||||||
// Resolver recreated when typography values change. The returned closure reads
|
// Resolver recreated when typography values change. The returned closure reads
|
||||||
// appliedFontsManager.statuses (a SvelteMap) on every call, so any font status
|
// fontLifecycleManager.statuses (a SvelteMap) on every call, so any font status
|
||||||
// change triggers a full offsets recompute in createVirtualizer — no DOM snap.
|
// change triggers a full offsets recompute in createVirtualizer — no DOM snap.
|
||||||
const fontRowHeight = $derived.by(() =>
|
const fontRowHeight = $derived.by(() =>
|
||||||
createFontRowSizeResolver({
|
createFontRowSizeResolver({
|
||||||
getFonts: () => fontStore.fonts,
|
getFonts: () => fontCatalogStore.fonts,
|
||||||
getWeight: () => typographySettingsStore.weight,
|
getWeight: () => typographySettingsStore.weight,
|
||||||
getPreviewText: () => text,
|
getPreviewText: () => text,
|
||||||
getContainerWidth: () => containerWidth,
|
getContainerWidth: () => containerWidth,
|
||||||
getFontSizePx: () => typographySettingsStore.renderedSize,
|
getFontSizePx: () => typographySettingsStore.renderedSize,
|
||||||
getLineHeightPx: () => typographySettingsStore.height * typographySettingsStore.renderedSize,
|
getLineHeightPx: () => typographySettingsStore.height * typographySettingsStore.renderedSize,
|
||||||
getStatus: key => appliedFontsManager.statuses.get(key),
|
getStatus: key => fontLifecycleManager.statuses.get(key),
|
||||||
contentHorizontalPadding: SAMPLER_CONTENT_PADDING_X,
|
contentHorizontalPadding: SAMPLER_CONTENT_PADDING_X,
|
||||||
chromeHeight: SAMPLER_CHROME_HEIGHT,
|
chromeHeight: SAMPLER_CHROME_HEIGHT,
|
||||||
fallbackHeight: SAMPLER_FALLBACK_HEIGHT,
|
fallbackHeight: SAMPLER_FALLBACK_HEIGHT,
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { NavigationWrapper } from '$entities/Breadcrumb';
|
import { NavigationWrapper } from '$entities/Breadcrumb';
|
||||||
import { fontStore } from '$entities/Font';
|
import { fontCatalogStore } from '$entities/Font';
|
||||||
import type { ResponsiveManager } from '$shared/lib';
|
import type { ResponsiveManager } from '$shared/lib';
|
||||||
import { cn } from '$shared/lib';
|
import { cn } from '$shared/lib';
|
||||||
import {
|
import {
|
||||||
@@ -36,7 +36,7 @@ const responsive = getContext<ResponsiveManager>('responsive');
|
|||||||
id="sample_set"
|
id="sample_set"
|
||||||
title="Sample Set"
|
title="Sample Set"
|
||||||
headerTitle="visual_output"
|
headerTitle="visual_output"
|
||||||
headerSubtitle="items_total: {fontStore.pagination.total ?? 0}"
|
headerSubtitle="items_total: {fontCatalogStore.pagination.total ?? 0}"
|
||||||
headerAction={registerAction}
|
headerAction={registerAction}
|
||||||
>
|
>
|
||||||
{#snippet headerContent()}
|
{#snippet headerContent()}
|
||||||
|
|||||||
Reference in New Issue
Block a user