chore: follow the general comments style

This commit is contained in:
Ilia Mashkov
2026-04-17 12:14:55 +03:00
parent 0ebf75b24e
commit cfaff46d59
56 changed files with 1600 additions and 499 deletions
@@ -1,10 +1,10 @@
/** @vitest-environment jsdom */
/**
* @vitest-environment jsdom
*/
import { AppliedFontsManager } from './appliedFontsStore.svelte';
import { FontFetchError } from './errors';
import { FontEvictionPolicy } from './utils/fontEvictionPolicy/FontEvictionPolicy';
// ── Fake collaborators ────────────────────────────────────────────────────────
class FakeBufferCache {
async get(_url: string): Promise<ArrayBuffer> {
return new ArrayBuffer(8);
@@ -13,7 +13,9 @@ class FakeBufferCache {
clear(): void {}
}
/** Throws {@link FontFetchError} on every `get()` — simulates network/HTTP failure. */
/**
* Throws {@link FontFetchError} on every `get()` — simulates network/HTTP failure.
*/
class FailingBufferCache {
async get(url: string): Promise<never> {
throw new FontFetchError(url, new Error('network error'), 500);
@@ -22,8 +24,6 @@ class FailingBufferCache {
clear(): void {}
}
// ── Helpers ───────────────────────────────────────────────────────────────────
const makeConfig = (id: string, overrides: Partial<{ weight: number; isVariable: boolean }> = {}) => ({
id,
name: id,
@@ -32,8 +32,6 @@ const makeConfig = (id: string, overrides: Partial<{ weight: number; isVariable:
...overrides,
});
// ── Suite ─────────────────────────────────────────────────────────────────────
describe('AppliedFontsManager', () => {
let manager: AppliedFontsManager;
let eviction: FontEvictionPolicy;
@@ -66,8 +64,6 @@ describe('AppliedFontsManager', () => {
vi.unstubAllGlobals();
});
// ── touch() ───────────────────────────────────────────────────────────────
describe('touch()', () => {
it('queues and loads a new font', async () => {
manager.touch([makeConfig('roboto')]);
@@ -131,8 +127,6 @@ describe('AppliedFontsManager', () => {
});
});
// ── queue processing ──────────────────────────────────────────────────────
describe('queue processing', () => {
it('filters non-critical weights in data-saver mode', async () => {
(navigator as any).connection = { saveData: true };
@@ -163,8 +157,6 @@ describe('AppliedFontsManager', () => {
});
});
// ── Phase 1: fetch ────────────────────────────────────────────────────────
describe('Phase 1 — fetch', () => {
it('sets status to error on fetch failure', async () => {
const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
@@ -209,8 +201,6 @@ describe('AppliedFontsManager', () => {
});
});
// ── Phase 2: parse ────────────────────────────────────────────────────────
describe('Phase 2 — parse', () => {
it('sets status to error on parse failure', async () => {
const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
@@ -241,8 +231,6 @@ describe('AppliedFontsManager', () => {
});
});
// ── #purgeUnused ──────────────────────────────────────────────────────────
describe('#purgeUnused', () => {
it('evicts fonts after TTL expires', async () => {
manager.touch([makeConfig('ephemeral')]);
@@ -300,8 +288,6 @@ describe('AppliedFontsManager', () => {
});
});
// ── destroy() ─────────────────────────────────────────────────────────────
describe('destroy()', () => {
it('clears all statuses', async () => {
manager.touch([makeConfig('roboto')]);
@@ -156,7 +156,9 @@ export class AppliedFontsManager {
}
}
/** Returns true if data-saver mode is enabled (defers non-critical weights). */
/**
* Returns true if data-saver mode is enabled (defers non-critical weights).
*/
#shouldDeferNonCritical(): boolean {
return (navigator as any).connection?.saveData === true;
}
@@ -188,13 +190,11 @@ export class AppliedFontsManager {
const concurrency = getEffectiveConcurrency();
const buffers = new Map<string, ArrayBuffer>();
// ==================== PHASE 1: Concurrent Fetching ====================
// Fetch multiple font files in parallel since network I/O is non-blocking
for (let i = 0; i < entries.length; i += concurrency) {
await this.#fetchChunk(entries.slice(i, i + concurrency), buffers);
}
// ==================== PHASE 2: Sequential Parsing ====================
// Parse buffers one at a time with periodic yields to avoid blocking UI
const hasInputPending = !!(navigator as any).scheduling?.isInputPending;
let lastYield = performance.now();
@@ -279,7 +279,9 @@ export class AppliedFontsManager {
}
}
/** Removes fonts unused within TTL (LRU-style cleanup). Runs every PURGE_INTERVAL. Pinned fonts are never evicted. */
/**
* Removes fonts unused within TTL (LRU-style cleanup). Runs every PURGE_INTERVAL. Pinned fonts are never evicted.
*/
#purgeUnused() {
const now = Date.now();
// Iterate through all tracked font keys
@@ -307,7 +309,9 @@ export class AppliedFontsManager {
}
}
/** Returns current loading status for a font, or undefined if never requested. */
/**
* Returns current loading status for a font, or undefined if never requested.
*/
getFontStatus(id: string, weight: number, isVariable = false) {
try {
return this.statuses.get(generateFontKey({ id, weight, isVariable }));
@@ -316,17 +320,23 @@ export class AppliedFontsManager {
}
}
/** Pins a font so it is never evicted by #purgeUnused(), regardless of TTL. */
/**
* Pins a font so it is never evicted by #purgeUnused(), regardless of TTL.
*/
pin(id: string, weight: number, isVariable = false): void {
this.#eviction.pin(generateFontKey({ id, weight, isVariable }));
}
/** Unpins a font, allowing it to be evicted by #purgeUnused() once its TTL expires. */
/**
* Unpins a font, allowing it to be evicted by #purgeUnused() once its TTL expires.
*/
unpin(id: string, weight: number, isVariable = false): void {
this.#eviction.unpin(generateFontKey({ id, weight, isVariable }));
}
/** Waits for all fonts to finish loading using document.fonts.ready. */
/**
* Waits for all fonts to finish loading using document.fonts.ready.
*/
async ready(): Promise<void> {
if (typeof document === 'undefined') {
return;
@@ -336,7 +346,9 @@ export class AppliedFontsManager {
} catch { /* document unloaded */ }
}
/** Aborts all operations, removes fonts from document, and clears state. Manager cannot be reused after. */
/**
* Aborts all operations, removes fonts from document, and clears state. Manager cannot be reused after.
*/
destroy() {
// Abort all in-flight network requests
this.#abortController.abort();
@@ -375,5 +387,7 @@ 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();
@@ -1,4 +1,6 @@
/** @vitest-environment jsdom */
/**
* @vitest-environment jsdom
*/
import { FontFetchError } from '../../errors';
import { FontBufferCache } from './FontBufferCache';
@@ -3,9 +3,13 @@ import { FontFetchError } from '../../errors';
type Fetcher = (url: string, init?: RequestInit) => Promise<Response>;
interface FontBufferCacheOptions {
/** Custom fetch implementation. Defaults to `globalThis.fetch`. Inject in tests for isolation. */
/**
* Custom fetch implementation. Defaults to `globalThis.fetch`. Inject in tests for isolation.
*/
fetcher?: Fetcher;
/** Cache API cache name. Defaults to `'font-cache-v1'`. */
/**
* Cache API cache name. Defaults to `'font-cache-v1'`.
*/
cacheName?: string;
}
@@ -85,12 +89,16 @@ export class FontBufferCache {
return buffer;
}
/** Removes a URL from the in-memory cache. Next call to `get()` will re-fetch. */
/**
* Removes a URL from the in-memory cache. Next call to `get()` will re-fetch.
*/
evict(url: string): void {
this.#buffersByUrl.delete(url);
}
/** Clears all in-memory cached buffers. */
/**
* Clears all in-memory cached buffers.
*/
clear(): void {
this.#buffersByUrl.clear();
}
@@ -1,5 +1,7 @@
interface FontEvictionPolicyOptions {
/** TTL in milliseconds. Defaults to 5 minutes. */
/**
* TTL in milliseconds. Defaults to 5 minutes.
*/
ttl?: number;
}
@@ -28,12 +30,16 @@ export class FontEvictionPolicy {
this.#usageTracker.set(key, now);
}
/** Pins a font key so it is never evicted regardless of TTL. */
/**
* Pins a font key so it is never evicted regardless of TTL.
*/
pin(key: string): void {
this.#pinnedFonts.add(key);
}
/** Unpins a font key, allowing it to be evicted once its TTL expires. */
/**
* Unpins a font key, allowing it to be evicted once its TTL expires.
*/
unpin(key: string): void {
this.#pinnedFonts.delete(key);
}
@@ -57,18 +63,24 @@ export class FontEvictionPolicy {
return now - lastUsed >= this.#TTL;
}
/** Returns an iterator over all tracked font keys. */
/**
* Returns an iterator over all tracked font keys.
*/
keys(): IterableIterator<string> {
return this.#usageTracker.keys();
}
/** Removes a font key from tracking. Called by the orchestrator after eviction. */
/**
* Removes a font key from tracking. Called by the orchestrator after eviction.
*/
remove(key: string): void {
this.#usageTracker.delete(key);
this.#pinnedFonts.delete(key);
}
/** Clears all usage timestamps and pinned keys. */
/**
* Clears all usage timestamps and pinned keys.
*/
clear(): void {
this.#usageTracker.clear();
this.#pinnedFonts.clear();
@@ -34,22 +34,30 @@ export class FontLoadQueue {
return entries;
}
/** Returns `true` if the key is currently in the queue. */
/**
* Returns `true` if the key is currently in the queue.
*/
has(key: string): boolean {
return this.#queue.has(key);
}
/** Increments the retry count for a font key. */
/**
* Increments the retry count for a font key.
*/
incrementRetry(key: string): void {
this.#retryCounts.set(key, (this.#retryCounts.get(key) ?? 0) + 1);
}
/** Returns `true` if the font has reached or exceeded the maximum retry limit. */
/**
* Returns `true` if the font has reached or exceeded the maximum retry limit.
*/
isMaxRetriesReached(key: string): boolean {
return (this.#retryCounts.get(key) ?? 0) >= this.#MAX_RETRIES;
}
/** Clears all queued fonts and resets all retry counts. */
/**
* Clears all queued fonts and resets all retry counts.
*/
clear(): void {
this.#queue.clear();
this.#retryCounts.clear();
@@ -1,4 +1,6 @@
/** @vitest-environment jsdom */
/**
* @vitest-environment jsdom
*/
import { FontParseError } from '../../errors';
import { loadFont } from './loadFont';
@@ -61,7 +61,6 @@ describe('FontStore', () => {
vi.resetAllMocks();
});
// -----------------------------------------------------------------------
describe('construction', () => {
it('stores initial params', () => {
const store = makeStore({ limit: 20 });
@@ -90,7 +89,6 @@ describe('FontStore', () => {
});
});
// -----------------------------------------------------------------------
describe('state after fetch', () => {
it('exposes loaded fonts', async () => {
const store = await fetchedStore({}, generateMockFonts(7));
@@ -129,7 +127,6 @@ describe('FontStore', () => {
});
});
// -----------------------------------------------------------------------
describe('error states', () => {
it('isError is false before any fetch', () => {
const store = makeStore();
@@ -178,7 +175,6 @@ describe('FontStore', () => {
});
});
// -----------------------------------------------------------------------
describe('font accumulation', () => {
it('replaces fonts when refetching the first page', async () => {
const store = makeStore();
@@ -212,7 +208,6 @@ describe('FontStore', () => {
});
});
// -----------------------------------------------------------------------
describe('pagination state', () => {
it('returns zero-value defaults before any fetch', () => {
const store = makeStore();
@@ -248,7 +243,6 @@ describe('FontStore', () => {
});
});
// -----------------------------------------------------------------------
describe('setParams', () => {
it('merges updates into existing params', () => {
const store = makeStore({ limit: 10 });
@@ -266,7 +260,6 @@ describe('FontStore', () => {
});
});
// -----------------------------------------------------------------------
describe('filter change resets', () => {
it('clears accumulated fonts when a filter changes', async () => {
const store = await fetchedStore({}, generateMockFonts(5));
@@ -302,7 +295,6 @@ describe('FontStore', () => {
});
});
// -----------------------------------------------------------------------
describe('staleTime in buildOptions', () => {
it('is 5 minutes with no active filters', () => {
const store = makeStore();
@@ -331,7 +323,6 @@ describe('FontStore', () => {
});
});
// -----------------------------------------------------------------------
describe('buildQueryKey', () => {
it('omits empty-string params', () => {
const store = makeStore();
@@ -366,7 +357,6 @@ describe('FontStore', () => {
});
});
// -----------------------------------------------------------------------
describe('destroy', () => {
it('does not throw', () => {
const store = makeStore();
@@ -380,7 +370,6 @@ describe('FontStore', () => {
});
});
// -----------------------------------------------------------------------
describe('refetch', () => {
it('triggers a fetch', async () => {
const store = makeStore();
@@ -400,7 +389,6 @@ describe('FontStore', () => {
});
});
// -----------------------------------------------------------------------
describe('nextPage', () => {
let store: FontStore;
@@ -437,7 +425,6 @@ describe('FontStore', () => {
});
});
// -----------------------------------------------------------------------
describe('prevPage and goToPage', () => {
it('prevPage is a no-op — infinite scroll does not support backward navigation', async () => {
const store = await fetchedStore({}, generateMockFonts(5));
@@ -454,7 +441,6 @@ describe('FontStore', () => {
});
});
// -----------------------------------------------------------------------
describe('prefetch', () => {
it('triggers a fetch for the provided params', async () => {
const store = makeStore();
@@ -465,7 +451,6 @@ describe('FontStore', () => {
});
});
// -----------------------------------------------------------------------
describe('getCachedData / setQueryData', () => {
it('getCachedData returns undefined before any fetch', () => {
queryClient.clear();
@@ -497,7 +482,6 @@ describe('FontStore', () => {
});
});
// -----------------------------------------------------------------------
describe('invalidate', () => {
it('calls invalidateQueries', async () => {
const store = await fetchedStore();
@@ -508,7 +492,6 @@ describe('FontStore', () => {
});
});
// -----------------------------------------------------------------------
describe('setLimit', () => {
it('updates the limit param', () => {
const store = makeStore({ limit: 10 });
@@ -518,7 +501,6 @@ describe('FontStore', () => {
});
});
// -----------------------------------------------------------------------
describe('filter shortcut methods', () => {
let store: FontStore;
@@ -561,7 +543,6 @@ describe('FontStore', () => {
});
});
// -----------------------------------------------------------------------
describe('category getters', () => {
it('each getter returns only fonts of that category', async () => {
const fonts = generateMixedCategoryFonts(2); // 2 of each category = 10 total
@@ -18,7 +18,9 @@ import type { UnifiedFont } from '../../types';
type PageParam = { offset: number };
/** Filter params + limit — offset is managed by TQ as a page param, not a user param. */
/**
* Filter params + limit — offset is managed by TQ as a page param, not a user param.
*/
type FontStoreParams = Omit<ProxyFontsParams, 'offset'>;
type FontStoreResult = InfiniteQueryObserverResult<InfiniteData<ProxyFontsResponse, PageParam>, Error>;
@@ -44,34 +46,53 @@ export class FontStore {
});
}
// -- Public state --
/**
* Current filter and limit configuration
*/
get params(): FontStoreParams {
return this.#params;
}
/**
* Flattened list of all fonts loaded across all pages (reactive)
*/
get fonts(): UnifiedFont[] {
return this.#result.data?.pages.flatMap((p: ProxyFontsResponse) => p.fonts) ?? [];
}
/**
* True if the first page is currently being fetched
*/
get isLoading(): boolean {
return this.#result.isLoading;
}
/**
* True if any background fetch is in progress (initial or pagination)
*/
get isFetching(): boolean {
return this.#result.isFetching;
}
/**
* True if the last fetch attempt resulted in an error
*/
get isError(): boolean {
return this.#result.isError;
}
/**
* Last caught error from the query observer
*/
get error(): Error | null {
return this.#result.error ?? null;
}
// isEmpty is false during loading/fetching so the UI never flashes "no results"
// while a fetch is in progress. The !isFetching guard is specifically for the filter-change
// transition: fonts clear synchronously → isFetching becomes true → isEmpty stays false.
/**
* True if no fonts were found for the current filter criteria
*/
get isEmpty(): boolean {
return !this.isLoading && !this.isFetching && this.fonts.length === 0;
}
/**
* Pagination metadata derived from the last loaded page
*/
get pagination() {
const pages = this.#result.data?.pages;
const last = pages?.at(-1);
@@ -95,37 +116,52 @@ export class FontStore {
};
}
// -- Lifecycle --
/**
* Cleans up subscriptions and destroys the observer
*/
destroy() {
this.#unsubscribe();
this.#observer.destroy();
}
// -- Param management --
/**
* Merge new parameters into existing state and trigger a refetch
*/
setParams(updates: Partial<FontStoreParams>) {
this.#params = { ...this.#params, ...updates };
this.#observer.setOptions(this.buildOptions());
}
/**
* Forcefully invalidate and refetch the current query from the network
*/
invalidate() {
this.#qc.invalidateQueries({ queryKey: this.buildQueryKey(this.#params) });
}
// -- Async operations --
/**
* Manually trigger a query refetch
*/
async refetch() {
await this.#observer.refetch();
}
/**
* Prime the cache with data for a specific parameter set
*/
async prefetch(params: FontStoreParams) {
await this.#qc.prefetchInfiniteQuery(this.buildOptions(params));
}
/**
* Abort any active network requests for this store
*/
cancel() {
this.#qc.cancelQueries({ queryKey: this.buildQueryKey(this.#params) });
}
/**
* Retrieve current font list from cache without triggering a fetch
*/
getCachedData(): UnifiedFont[] | undefined {
const data = this.#qc.getQueryData<InfiniteData<ProxyFontsResponse, PageParam>>(
this.buildQueryKey(this.#params),
@@ -134,6 +170,9 @@ export class FontStore {
return data.pages.flatMap(p => p.fonts);
}
/**
* Manually update the cached font data (useful for optimistic updates)
*/
setQueryData(updater: (old: UnifiedFont[] | undefined) => UnifiedFont[]) {
const key = this.buildQueryKey(this.#params);
this.#qc.setQueryData<InfiniteData<ProxyFontsResponse, PageParam>>(
@@ -164,56 +203,90 @@ export class FontStore {
);
}
// -- Filter shortcuts --
/**
* Shortcut to update provider filters
*/
setProviders(v: ProxyFontsParams['providers']) {
this.setParams({ providers: v });
}
/**
* Shortcut to update category filters
*/
setCategories(v: ProxyFontsParams['categories']) {
this.setParams({ categories: v });
}
/**
* Shortcut to update subset filters
*/
setSubsets(v: ProxyFontsParams['subsets']) {
this.setParams({ subsets: v });
}
/**
* Shortcut to update search query
*/
setSearch(v: string) {
this.setParams({ q: v || undefined });
}
/**
* Shortcut to update sort order
*/
setSort(v: ProxyFontsParams['sort']) {
this.setParams({ sort: v });
}
// -- Pagination navigation --
/**
* Fetch the next page of results if available
*/
async nextPage(): Promise<void> {
await this.#observer.fetchNextPage();
}
prevPage(): void {} // no-op: infinite scroll accumulates forward only; method kept for API compatibility
goToPage(_page: number): void {} // no-op
/**
* Backward pagination (no-op: infinite scroll accumulates forward only)
*/
prevPage(): void {}
/**
* Jump to specific page (no-op for infinite scroll)
*/
goToPage(_page: number): void {}
/**
* Update the number of items fetched per page
*/
setLimit(limit: number) {
this.setParams({ limit });
}
// -- Category views --
/**
* Derived list of sans-serif fonts in the current set
*/
get sansSerifFonts() {
return this.fonts.filter(f => f.category === 'sans-serif');
}
/**
* Derived list of serif fonts in the current set
*/
get serifFonts() {
return this.fonts.filter(f => f.category === 'serif');
}
/**
* Derived list of display fonts in the current set
*/
get displayFonts() {
return this.fonts.filter(f => f.category === 'display');
}
/**
* Derived list of handwriting fonts in the current set
*/
get handwritingFonts() {
return this.fonts.filter(f => f.category === 'handwriting');
}
/**
* Derived list of monospace fonts in the current set
*/
get monospaceFonts() {
return this.fonts.filter(f => f.category === 'monospace');
}
// -- Private helpers (TypeScript-private so tests can spy via `as any`) --
private buildQueryKey(params: FontStoreParams): readonly unknown[] {
const filtered: Record<string, any> = {};
+95 -34
View File
@@ -31,18 +31,28 @@ export type FontSubset = 'latin' | 'latin-ext' | 'cyrillic' | 'greek' | 'arabic'
* Combined filter state for font queries
*/
export interface FontFilters {
/** Selected font providers */
/**
* Active font providers to fetch from
*/
providers: FontProvider[];
/** Selected font categories */
/**
* Visual classifications (sans, serif, etc.)
*/
categories: FontCategory[];
/** Selected character subsets */
/**
* Character sets required for the sample text
*/
subsets: FontSubset[];
}
/** Filter group identifier */
/**
* Filter group identifier
*/
export type FilterGroup = 'providers' | 'categories' | 'subsets';
/** Filter type including search query */
/**
* Filter type including search query
*/
export type FilterType = FilterGroup | 'searchQuery';
/**
@@ -80,15 +90,25 @@ export type UnifiedFontVariant = FontVariant;
* Font style URLs
*/
export interface FontStyleUrls {
/** Regular weight URL */
/**
* URL for the regular (400) weight
*/
regular?: string;
/** Italic URL */
/**
* URL for the italic (400) style
*/
italic?: string;
/** Bold weight URL */
/**
* URL for the bold (700) weight
*/
bold?: string;
/** Bold italic URL */
/**
* URL for the bold-italic (700) style
*/
boldItalic?: string;
/** Additional variant mapping */
/**
* Mapping for all other numeric/custom variants
*/
variants?: Partial<Record<UnifiedFontVariant, string>>;
}
@@ -96,19 +116,24 @@ export interface FontStyleUrls {
* Font metadata
*/
export interface FontMetadata {
/** Timestamp when font was cached */
/**
* Epoch timestamp of last successful fetch
*/
cachedAt: number;
/** Font version from provider */
/**
* Semantic version string from upstream
*/
version?: string;
/** Last modified date from provider */
/**
* ISO date string of last remote update
*/
lastModified?: string;
/** Popularity rank (if available from provider) */
/**
* Raw ranking integer from provider
*/
popularity?: number;
/**
* Normalized popularity score (0-100)
*
* Normalized across all fonts for consistent ranking
* Higher values indicate more popular fonts
* Normalized score (0-100) used for global sorting
*/
popularityScore?: number;
}
@@ -117,17 +142,38 @@ export interface FontMetadata {
* Font features (variable fonts, axes, tags)
*/
export interface FontFeatures {
/** Whether this is a variable font */
/**
* Whether the font supports fluid weight/width axes
*/
isVariable?: boolean;
/** Variable font axes (for Fontshare) */
/**
* Definable axes for variable font interpolation
*/
axes?: Array<{
/**
* Human-readable axis name (e.g., 'Weight')
*/
name: string;
/**
* CSS property name (e.g., 'wght')
*/
property: string;
/**
* Default numeric value for the axis
*/
default: number;
/**
* Minimum inclusive bound
*/
min: number;
/**
* Maximum inclusive bound
*/
max: number;
}>;
/** Usage tags (for Fontshare) */
/**
* Descriptive keywords for search indexing
*/
tags?: string[];
}
@@ -138,29 +184,44 @@ export interface FontFeatures {
* for consistent font handling across the application.
*/
export interface UnifiedFont {
/** Unique identifier (Google: family name, Fontshare: slug) */
/**
* Unique ID (family name for Google, slug for Fontshare)
*/
id: string;
/** Font display name */
/**
* Canonical family name for CSS font-family
*/
name: string;
/** Font provider (google | fontshare) */
/**
* Upstream data source
*/
provider: FontProvider;
/**
* Provider badge display name
*
* Human-readable provider name for UI display
* e.g., "Google Fonts" or "Fontshare"
* Display label for provider badges
*/
providerBadge?: string;
/** Font category classification */
/**
* Primary typographic category
*/
category: FontCategory;
/** Supported character subsets */
/**
* All supported character sets
*/
subsets: FontSubset[];
/** Available font variants (weights, styles) */
/**
* List of available weights and styles
*/
variants: UnifiedFontVariant[];
/** URL mapping for font file downloads */
/**
* Remote assets for font loading
*/
styles: FontStyleUrls;
/** Additional metadata */
/**
* Technical metadata and rankings
*/
metadata: FontMetadata;
/** Advanced font features */
/**
* Variable font details and tags
*/
features: FontFeatures;
}
-9
View File
@@ -1,12 +1,3 @@
/**
* ============================================================================
* SINGLE EXPORT POINT
* ============================================================================
*
* This is the single export point for all Font types.
* All imports should use: `import { X } from '$entities/Font/model/types'`
*/
// Font domain and model types
export type {
FilterGroup,
+30 -18
View File
@@ -1,9 +1,3 @@
/**
* ============================================================================
* STORE TYPES
* ============================================================================
*/
import type {
FontCategory,
FontProvider,
@@ -12,37 +6,55 @@ import type {
} from './font';
/**
* Font collection state
* Global state for the local font collection
*/
export interface FontCollectionState {
/** All cached fonts */
/**
* Map of cached fonts indexed by their unique family ID
*/
fonts: Record<string, UnifiedFont>;
/** Active filters */
/**
* Set of active user-defined filters
*/
filters: FontCollectionFilters;
/** Sort configuration */
/**
* Current sorting parameters for the display list
*/
sort: FontCollectionSort;
}
/**
* Font collection filters
* Filter configuration for narrow collections
*/
export interface FontCollectionFilters {
/** Search query */
/**
* Partial family name to match against
*/
searchQuery: string;
/** Filter by providers */
/**
* Data sources (Google, Fontshare) to include
*/
providers?: FontProvider[];
/** Filter by categories */
/**
* Typographic categories (Serif, Sans, etc.) to include
*/
categories?: FontCategory[];
/** Filter by subsets */
/**
* Character sets (Latin, Cyrillic, etc.) to include
*/
subsets?: FontSubset[];
}
/**
* Font collection sort configuration
* Ordering configuration for the font list
*/
export interface FontCollectionSort {
/** Sort field */
/**
* The font property to order by
*/
field: 'name' | 'popularity' | 'category';
/** Sort direction */
/**
* The sort order (Ascending or Descending)
*/
direction: 'asc' | 'desc';
}