refactor(Font): consolidate API layer and update type structure
This commit is contained in:
@@ -1,161 +0,0 @@
|
||||
/**
|
||||
* Fontshare API client
|
||||
*
|
||||
* Handles API requests to Fontshare API for fetching font metadata.
|
||||
* Provides error handling, pagination support, and type-safe responses.
|
||||
*
|
||||
* Pagination: The Fontshare API DOES support pagination via `page` and `limit` parameters.
|
||||
* However, the current implementation uses `fetchAllFontshareFonts()` to fetch all fonts upfront.
|
||||
* For future optimization, consider implementing incremental pagination for large datasets.
|
||||
*
|
||||
* @see https://fontshare.com
|
||||
*/
|
||||
|
||||
import { api } from '$shared/api/api';
|
||||
import { buildQueryString } from '$shared/lib/utils';
|
||||
import type { QueryParams } from '$shared/lib/utils';
|
||||
import type {
|
||||
FontshareApiModel,
|
||||
FontshareFont,
|
||||
} from '../../model/types/fontshare';
|
||||
|
||||
/**
|
||||
* Fontshare API parameters
|
||||
*/
|
||||
export interface FontshareParams extends QueryParams {
|
||||
/**
|
||||
* Filter by categories (e.g., ["Sans", "Serif", "Display"])
|
||||
*/
|
||||
categories?: string[];
|
||||
/**
|
||||
* Filter by tags (e.g., ["Magazines", "Branding", "Logos"])
|
||||
*/
|
||||
tags?: string[];
|
||||
/**
|
||||
* Page number for pagination (1-indexed)
|
||||
*/
|
||||
page?: number;
|
||||
/**
|
||||
* Number of items per page
|
||||
*/
|
||||
limit?: number;
|
||||
/**
|
||||
* Search query to filter fonts
|
||||
*/
|
||||
q?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fontshare API response wrapper
|
||||
* Re-exported from model/types/fontshare for backward compatibility
|
||||
*/
|
||||
export type FontshareResponse = FontshareApiModel;
|
||||
|
||||
/**
|
||||
* Fetch fonts from Fontshare API
|
||||
*
|
||||
* @param params - Query parameters for filtering fonts
|
||||
* @returns Promise resolving to Fontshare API response
|
||||
* @throws ApiError when request fails
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* // Fetch all Sans category fonts
|
||||
* const response = await fetchFontshareFonts({
|
||||
* categories: ['Sans'],
|
||||
* limit: 50
|
||||
* });
|
||||
*
|
||||
* // Fetch fonts with specific tags
|
||||
* const response = await fetchFontshareFonts({
|
||||
* tags: ['Branding', 'Logos']
|
||||
* });
|
||||
*
|
||||
* // Search fonts
|
||||
* const response = await fetchFontshareFonts({
|
||||
* search: 'Satoshi'
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
export async function fetchFontshareFonts(
|
||||
params: FontshareParams = {},
|
||||
): Promise<FontshareResponse> {
|
||||
const queryString = buildQueryString(params);
|
||||
const url = `https://api.fontshare.com/v2/fonts${queryString}`;
|
||||
|
||||
try {
|
||||
const response = await api.get<FontshareResponse>(url);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
// Re-throw ApiError with context
|
||||
if (error instanceof Error) {
|
||||
throw error;
|
||||
}
|
||||
throw new Error(`Failed to fetch Fontshare fonts: ${String(error)}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch font by slug
|
||||
* Convenience function for fetching a single font
|
||||
*
|
||||
* @param slug - Font slug (e.g., "satoshi", "general-sans")
|
||||
* @returns Promise resolving to Fontshare font item
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* const satoshi = await fetchFontshareFontBySlug('satoshi');
|
||||
* ```
|
||||
*/
|
||||
export async function fetchFontshareFontBySlug(
|
||||
slug: string,
|
||||
): Promise<FontshareFont | undefined> {
|
||||
const response = await fetchFontshareFonts();
|
||||
return response.fonts.find(font => font.slug === slug);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch all fonts from Fontshare
|
||||
* Convenience function for fetching all available fonts
|
||||
* Uses pagination to get all items
|
||||
*
|
||||
* @returns Promise resolving to all Fontshare fonts
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* const allFonts = await fetchAllFontshareFonts();
|
||||
* console.log(`Found ${allFonts.fonts.length} fonts`);
|
||||
* ```
|
||||
*/
|
||||
export async function fetchAllFontshareFonts(
|
||||
params: FontshareParams = {},
|
||||
): Promise<FontshareResponse> {
|
||||
const allFonts: FontshareFont[] = [];
|
||||
let page = 1;
|
||||
const limit = 100; // Max items per page
|
||||
|
||||
while (true) {
|
||||
const response = await fetchFontshareFonts({
|
||||
...params,
|
||||
page,
|
||||
limit,
|
||||
});
|
||||
|
||||
allFonts.push(...response.fonts);
|
||||
|
||||
// Check if we've fetched all items
|
||||
if (response.fonts.length < limit) {
|
||||
break;
|
||||
}
|
||||
|
||||
page++;
|
||||
}
|
||||
|
||||
// Return first response with all items combined
|
||||
const firstResponse = await fetchFontshareFonts({ ...params, page: 1, limit });
|
||||
|
||||
return {
|
||||
...firstResponse,
|
||||
fonts: allFonts,
|
||||
};
|
||||
}
|
||||
@@ -1,127 +0,0 @@
|
||||
/**
|
||||
* Google Fonts API client
|
||||
*
|
||||
* Handles API requests to Google Fonts API for fetching font metadata.
|
||||
* Provides error handling, retry logic, and type-safe responses.
|
||||
*
|
||||
* Pagination: The Google Fonts API does NOT support pagination parameters.
|
||||
* All fonts matching the query are returned in a single response.
|
||||
* Use category, subset, or sort filters to reduce the result set if needed.
|
||||
*
|
||||
* @see https://developers.google.com/fonts/docs/developer_api
|
||||
*/
|
||||
|
||||
import { api } from '$shared/api/api';
|
||||
import { buildQueryString } from '$shared/lib/utils';
|
||||
import type { QueryParams } from '$shared/lib/utils';
|
||||
import type {
|
||||
FontItem,
|
||||
GoogleFontsApiModel,
|
||||
} from '../../model/types/google';
|
||||
|
||||
/**
|
||||
* Google Fonts API parameters
|
||||
*/
|
||||
export interface GoogleFontsParams extends QueryParams {
|
||||
/**
|
||||
* Google Fonts API key (required for Google Fonts API v1)
|
||||
*/
|
||||
key?: string;
|
||||
/**
|
||||
* Font family name (to fetch specific font)
|
||||
*/
|
||||
family?: string;
|
||||
/**
|
||||
* Font category filter (e.g., "sans-serif", "serif", "display")
|
||||
*/
|
||||
category?: string;
|
||||
/**
|
||||
* Character subset filter (e.g., "latin", "latin-ext", "cyrillic")
|
||||
*/
|
||||
subset?: string;
|
||||
/**
|
||||
* Sort order for results
|
||||
*/
|
||||
sort?: 'alpha' | 'date' | 'popularity' | 'style' | 'trending';
|
||||
/**
|
||||
* Cap the number of fonts returned
|
||||
*/
|
||||
capability?: 'VF' | 'WOFF2';
|
||||
}
|
||||
|
||||
/**
|
||||
* Google Fonts API response wrapper
|
||||
* Re-exported from model/types/google for backward compatibility
|
||||
*/
|
||||
export type GoogleFontsResponse = GoogleFontsApiModel;
|
||||
|
||||
/**
|
||||
* Simplified font item from Google Fonts API
|
||||
* Re-exported from model/types/google for backward compatibility
|
||||
*/
|
||||
export type GoogleFontItem = FontItem;
|
||||
|
||||
/**
|
||||
* Google Fonts API base URL
|
||||
* Note: Google Fonts API v1 requires an API key. For development/testing without a key,
|
||||
* fonts may not load properly.
|
||||
*/
|
||||
const GOOGLE_FONTS_API_URL = 'https://www.googleapis.com/webfonts/v1/webfonts' as const;
|
||||
|
||||
/**
|
||||
* Fetch fonts from Google Fonts API
|
||||
*
|
||||
* @param params - Query parameters for filtering fonts
|
||||
* @returns Promise resolving to Google Fonts API response
|
||||
* @throws ApiError when request fails
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* // Fetch all sans-serif fonts sorted by popularity
|
||||
* const response = await fetchGoogleFonts({
|
||||
* category: 'sans-serif',
|
||||
* sort: 'popularity'
|
||||
* });
|
||||
*
|
||||
* // Fetch specific font family
|
||||
* const robotoResponse = await fetchGoogleFonts({
|
||||
* family: 'Roboto'
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
export async function fetchGoogleFonts(
|
||||
params: GoogleFontsParams = {},
|
||||
): Promise<GoogleFontsResponse> {
|
||||
const queryString = buildQueryString(params);
|
||||
const url = `${GOOGLE_FONTS_API_URL}${queryString}`;
|
||||
|
||||
try {
|
||||
const response = await api.get<GoogleFontsResponse>(url);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
// Re-throw ApiError with context
|
||||
if (error instanceof Error) {
|
||||
throw error;
|
||||
}
|
||||
throw new Error(`Failed to fetch Google Fonts: ${String(error)}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch font by family name
|
||||
* Convenience function for fetching a single font
|
||||
*
|
||||
* @param family - Font family name (e.g., "Roboto")
|
||||
* @returns Promise resolving to Google Font item
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* const roboto = await fetchGoogleFontFamily('Roboto');
|
||||
* ```
|
||||
*/
|
||||
export async function fetchGoogleFontFamily(
|
||||
family: string,
|
||||
): Promise<GoogleFontItem | undefined> {
|
||||
const response = await fetchGoogleFonts({ family });
|
||||
return response.items.find(item => item.family === family);
|
||||
}
|
||||
@@ -4,7 +4,7 @@
|
||||
* Exports API clients and normalization utilities
|
||||
*/
|
||||
|
||||
// Proxy API (PRIMARY - NEW)
|
||||
// Proxy API (primary)
|
||||
export {
|
||||
fetchFontsByIds,
|
||||
fetchProxyFontById,
|
||||
@@ -14,25 +14,3 @@ export type {
|
||||
ProxyFontsParams,
|
||||
ProxyFontsResponse,
|
||||
} from './proxy/proxyFonts';
|
||||
|
||||
// Google Fonts API (DEPRECATED - kept for backward compatibility)
|
||||
export {
|
||||
fetchGoogleFontFamily,
|
||||
fetchGoogleFonts,
|
||||
} from './google/googleFonts';
|
||||
export type {
|
||||
GoogleFontItem,
|
||||
GoogleFontsParams,
|
||||
GoogleFontsResponse,
|
||||
} from './google/googleFonts';
|
||||
|
||||
// Fontshare API (DEPRECATED - kept for backward compatibility)
|
||||
export {
|
||||
fetchAllFontshareFonts,
|
||||
fetchFontshareFontBySlug,
|
||||
fetchFontshareFonts,
|
||||
} from './fontshare/fontshare';
|
||||
export type {
|
||||
FontshareParams,
|
||||
FontshareResponse,
|
||||
} from './fontshare/fontshare';
|
||||
|
||||
@@ -1,83 +0,0 @@
|
||||
/**
|
||||
* Proxy API filters
|
||||
*
|
||||
* Fetches filter metadata from GlyphDiff proxy API.
|
||||
* Provides type-safe response handling.
|
||||
*
|
||||
* @see https://api.glyphdiff.com/api/v1/filters
|
||||
*/
|
||||
|
||||
import { api } from '$shared/api/api';
|
||||
|
||||
/**
|
||||
* Filter metadata type from backend
|
||||
*/
|
||||
export interface FilterMetadata {
|
||||
/** Filter ID (e.g., "providers", "categories", "subsets") */
|
||||
id: string;
|
||||
|
||||
/** Display name (e.g., "Font Providers", "Categories", "Character Subsets") */
|
||||
name: string;
|
||||
|
||||
/** Filter description */
|
||||
description: string;
|
||||
|
||||
/** Filter type */
|
||||
type: 'enum' | 'string' | 'array';
|
||||
|
||||
/** Available filter options */
|
||||
options: FilterOption[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter option type
|
||||
*/
|
||||
export interface FilterOption {
|
||||
/** Option ID (e.g., "google", "serif", "latin") */
|
||||
id: string;
|
||||
|
||||
/** Display name (e.g., "Google Fonts", "Serif", "Latin") */
|
||||
name: string;
|
||||
|
||||
/** Option value (e.g., "google", "serif", "latin") */
|
||||
value: string;
|
||||
|
||||
/** Number of fonts with this value */
|
||||
count: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Proxy filters API response
|
||||
*/
|
||||
export interface ProxyFiltersResponse {
|
||||
/** Array of filter metadata */
|
||||
filters: FilterMetadata[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch filters from proxy API
|
||||
*
|
||||
* @returns Promise resolving to array of filter metadata
|
||||
* @throws ApiError when request fails
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* // Fetch all filters
|
||||
* const filters = await fetchProxyFilters();
|
||||
*
|
||||
* console.log(filters); // [
|
||||
* // { id: "providers", name: "Font Providers", options: [...] },
|
||||
* // { id: "categories", name: "Categories", options: [...] },
|
||||
* // { id: "subsets", name: "Character Subsets", options: [...] }
|
||||
* // ]
|
||||
* ```
|
||||
*/
|
||||
export async function fetchProxyFilters(): Promise<FilterMetadata[]> {
|
||||
const response = await api.get<FilterMetadata[]>('/api/v1/filters');
|
||||
|
||||
if (!response.data || !Array.isArray(response.data)) {
|
||||
throw new Error('Proxy API returned invalid response');
|
||||
}
|
||||
|
||||
return response.data;
|
||||
}
|
||||
171
src/entities/Font/api/proxy/proxyFonts.test.ts
Normal file
171
src/entities/Font/api/proxy/proxyFonts.test.ts
Normal file
@@ -0,0 +1,171 @@
|
||||
/**
|
||||
* Tests for proxy API client
|
||||
*/
|
||||
|
||||
import {
|
||||
beforeEach,
|
||||
describe,
|
||||
expect,
|
||||
test,
|
||||
vi,
|
||||
} from 'vitest';
|
||||
import type { UnifiedFont } from '../../model/types';
|
||||
import type { ProxyFontsResponse } from './proxyFonts';
|
||||
|
||||
vi.mock('$shared/api/api', () => ({
|
||||
api: {
|
||||
get: vi.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
import { api } from '$shared/api/api';
|
||||
import {
|
||||
fetchFontsByIds,
|
||||
fetchProxyFontById,
|
||||
fetchProxyFonts,
|
||||
} from './proxyFonts';
|
||||
|
||||
const PROXY_API_URL = 'https://api.glyphdiff.com/api/v1/fonts';
|
||||
|
||||
function createMockFont(overrides: Partial<UnifiedFont> = {}): UnifiedFont {
|
||||
return {
|
||||
id: 'roboto',
|
||||
family: 'Roboto',
|
||||
provider: 'google',
|
||||
category: 'sans-serif',
|
||||
variants: [],
|
||||
subsets: [],
|
||||
...overrides,
|
||||
} as UnifiedFont;
|
||||
}
|
||||
|
||||
function mockApiGet<T>(data: T) {
|
||||
vi.mocked(api.get).mockResolvedValueOnce({ data, status: 200 });
|
||||
}
|
||||
|
||||
describe('proxyFonts', () => {
|
||||
beforeEach(() => {
|
||||
vi.mocked(api.get).mockReset();
|
||||
});
|
||||
|
||||
describe('fetchProxyFonts', () => {
|
||||
test('should fetch fonts with no params', async () => {
|
||||
const mockResponse: ProxyFontsResponse = {
|
||||
fonts: [createMockFont()],
|
||||
total: 1,
|
||||
limit: 50,
|
||||
offset: 0,
|
||||
};
|
||||
mockApiGet(mockResponse);
|
||||
|
||||
const result = await fetchProxyFonts();
|
||||
|
||||
expect(api.get).toHaveBeenCalledWith(PROXY_API_URL);
|
||||
expect(result).toEqual(mockResponse);
|
||||
});
|
||||
|
||||
test('should build URL with query params', async () => {
|
||||
const mockResponse: ProxyFontsResponse = {
|
||||
fonts: [createMockFont()],
|
||||
total: 1,
|
||||
limit: 20,
|
||||
offset: 0,
|
||||
};
|
||||
mockApiGet(mockResponse);
|
||||
|
||||
await fetchProxyFonts({ provider: 'google', category: 'sans-serif', limit: 20, offset: 0 });
|
||||
|
||||
const calledUrl = vi.mocked(api.get).mock.calls[0][0];
|
||||
expect(calledUrl).toContain('provider=google');
|
||||
expect(calledUrl).toContain('category=sans-serif');
|
||||
expect(calledUrl).toContain('limit=20');
|
||||
expect(calledUrl).toContain('offset=0');
|
||||
});
|
||||
|
||||
test('should throw on invalid response (missing fonts array)', async () => {
|
||||
mockApiGet({ total: 0 });
|
||||
|
||||
await expect(fetchProxyFonts()).rejects.toThrow('Proxy API returned invalid response');
|
||||
});
|
||||
|
||||
test('should throw on null response data', async () => {
|
||||
vi.mocked(api.get).mockResolvedValueOnce({ data: null, status: 200 });
|
||||
|
||||
await expect(fetchProxyFonts()).rejects.toThrow('Proxy API returned invalid response');
|
||||
});
|
||||
});
|
||||
|
||||
describe('fetchProxyFontById', () => {
|
||||
test('should return font matching the ID', async () => {
|
||||
const targetFont = createMockFont({ id: 'satoshi', name: 'Satoshi' });
|
||||
const mockResponse: ProxyFontsResponse = {
|
||||
fonts: [createMockFont(), targetFont],
|
||||
total: 2,
|
||||
limit: 1000,
|
||||
offset: 0,
|
||||
};
|
||||
mockApiGet(mockResponse);
|
||||
|
||||
const result = await fetchProxyFontById('satoshi');
|
||||
|
||||
expect(result).toEqual(targetFont);
|
||||
});
|
||||
|
||||
test('should return undefined when font not found', async () => {
|
||||
const mockResponse: ProxyFontsResponse = {
|
||||
fonts: [createMockFont()],
|
||||
total: 1,
|
||||
limit: 1000,
|
||||
offset: 0,
|
||||
};
|
||||
mockApiGet(mockResponse);
|
||||
|
||||
const result = await fetchProxyFontById('nonexistent');
|
||||
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
|
||||
test('should search with the ID as query param', async () => {
|
||||
const mockResponse: ProxyFontsResponse = {
|
||||
fonts: [],
|
||||
total: 0,
|
||||
limit: 1000,
|
||||
offset: 0,
|
||||
};
|
||||
mockApiGet(mockResponse);
|
||||
|
||||
await fetchProxyFontById('Roboto');
|
||||
|
||||
const calledUrl = vi.mocked(api.get).mock.calls[0][0];
|
||||
expect(calledUrl).toContain('limit=1000');
|
||||
expect(calledUrl).toContain('q=Roboto');
|
||||
});
|
||||
});
|
||||
|
||||
describe('fetchFontsByIds', () => {
|
||||
test('should return empty array for empty input', async () => {
|
||||
const result = await fetchFontsByIds([]);
|
||||
|
||||
expect(result).toEqual([]);
|
||||
expect(api.get).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('should call batch endpoint with comma-separated IDs', async () => {
|
||||
const fonts = [createMockFont({ id: 'roboto' }), createMockFont({ id: 'satoshi' })];
|
||||
mockApiGet(fonts);
|
||||
|
||||
const result = await fetchFontsByIds(['roboto', 'satoshi']);
|
||||
|
||||
expect(api.get).toHaveBeenCalledWith(`${PROXY_API_URL}/batch?ids=roboto,satoshi`);
|
||||
expect(result).toEqual(fonts);
|
||||
});
|
||||
|
||||
test('should return empty array when response data is nullish', async () => {
|
||||
vi.mocked(api.get).mockResolvedValueOnce({ data: null, status: 200 });
|
||||
|
||||
const result = await fetchFontsByIds(['roboto']);
|
||||
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,4 +1,4 @@
|
||||
// Proxy API (PRIMARY)
|
||||
// Proxy API (primary)
|
||||
export {
|
||||
fetchFontsByIds,
|
||||
fetchProxyFontById,
|
||||
@@ -9,32 +9,9 @@ export type {
|
||||
ProxyFontsResponse,
|
||||
} from './api/proxy/proxyFonts';
|
||||
|
||||
// Fontshare API (DEPRECATED)
|
||||
export {
|
||||
fetchAllFontshareFonts,
|
||||
fetchFontshareFontBySlug,
|
||||
fetchFontshareFonts,
|
||||
} from './api/fontshare/fontshare';
|
||||
export type {
|
||||
FontshareParams,
|
||||
FontshareResponse,
|
||||
} from './api/fontshare/fontshare';
|
||||
|
||||
// Google Fonts API (DEPRECATED)
|
||||
export {
|
||||
fetchGoogleFontFamily,
|
||||
fetchGoogleFonts,
|
||||
} from './api/google/googleFonts';
|
||||
export type {
|
||||
GoogleFontItem,
|
||||
GoogleFontsParams,
|
||||
GoogleFontsResponse,
|
||||
} from './api/google/googleFonts';
|
||||
export {
|
||||
normalizeFontshareFont,
|
||||
normalizeFontshareFonts,
|
||||
normalizeGoogleFont,
|
||||
normalizeGoogleFonts,
|
||||
} from './lib/normalize/normalize';
|
||||
export type {
|
||||
// Domain types
|
||||
@@ -65,8 +42,6 @@ export type {
|
||||
FontVariant,
|
||||
FontWeight,
|
||||
FontWeightItalic,
|
||||
// Google Fonts API types
|
||||
GoogleFontsApiModel,
|
||||
// Normalization types
|
||||
UnifiedFont,
|
||||
UnifiedFontVariant,
|
||||
|
||||
@@ -3,13 +3,31 @@ import type {
|
||||
UnifiedFont,
|
||||
} from '../../model';
|
||||
|
||||
/** Valid font weight values (100-900 in increments of 100) */
|
||||
const SIZES = [100, 200, 300, 400, 500, 600, 700, 800, 900];
|
||||
|
||||
/**
|
||||
* Constructs a URL for a font based on the provided font and weight.
|
||||
* @param font - The font object.
|
||||
* @param weight - The weight of the font.
|
||||
* @returns The URL for the font.
|
||||
* Gets the URL for a font file at a specific weight
|
||||
*
|
||||
* Constructs the appropriate URL for loading a font file based on
|
||||
* the font object and requested weight. Handles variable fonts and
|
||||
* provides fallbacks for static fonts.
|
||||
*
|
||||
* @param font - Unified font object containing style URLs
|
||||
* @param weight - Font weight (100-900)
|
||||
* @returns URL string for the font file, or undefined if not found
|
||||
* @throws Error if weight is not a valid value (100-900)
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* const url = getFontUrl(roboto, 700); // Returns URL for Roboto Bold
|
||||
*
|
||||
* // Variable fonts: backend maps weight to VF URL
|
||||
* const vfUrl = getFontUrl(inter, 450); // Returns variable font URL
|
||||
*
|
||||
* // Fallback for missing weights
|
||||
* const fallback = getFontUrl(font, 900); // Falls back to regular/400 if 900 missing
|
||||
* ```
|
||||
*/
|
||||
export function getFontUrl(font: UnifiedFont, weight: number): string | undefined {
|
||||
if (!SIZES.includes(weight)) {
|
||||
@@ -18,12 +36,11 @@ export function getFontUrl(font: UnifiedFont, weight: number): string | undefine
|
||||
|
||||
const weightKey = weight.toString() as FontWeight;
|
||||
|
||||
// 1. Try exact match (Backend now maps "100".."900" to VF URL if variable)
|
||||
// Try exact match (backend maps weight to VF URL for variable fonts)
|
||||
if (font.styles.variants?.[weightKey]) {
|
||||
return font.styles.variants[weightKey];
|
||||
}
|
||||
|
||||
// 2. Fallbacks for Static Fonts (if exact weight missing)
|
||||
// Try 'regular' or '400' as safe defaults
|
||||
// Fallbacks for static fonts when exact weight is missing
|
||||
return font.styles.regular || font.styles.variants?.['400'] || font.styles.variants?.['regular'];
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
/**
|
||||
* ============================================================================
|
||||
* MOCK FONT FILTER DATA
|
||||
* ============================================================================
|
||||
* Mock font filter data
|
||||
*
|
||||
* Factory functions and preset mock data for font-related filters.
|
||||
* Used in Storybook stories for font filtering components.
|
||||
@@ -36,9 +34,7 @@ import type {
|
||||
import type { Property } from '$shared/lib';
|
||||
import { createFilter } from '$shared/lib';
|
||||
|
||||
// ============================================================================
|
||||
// TYPE DEFINITIONS
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Options for creating a mock filter
|
||||
@@ -60,9 +56,7 @@ export interface MockFilters {
|
||||
subsets: ReturnType<typeof createFilter<FontSubset>>;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// FONT CATEGORIES
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Google Fonts categories
|
||||
@@ -98,9 +92,7 @@ export const UNIFIED_CATEGORIES: Property<FontCategory>[] = [
|
||||
{ id: 'monospace', name: 'Monospace', value: 'monospace' },
|
||||
];
|
||||
|
||||
// ============================================================================
|
||||
// FONT SUBSETS
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Common font subsets
|
||||
@@ -114,9 +106,7 @@ export const FONT_SUBSETS: Property<FontSubset>[] = [
|
||||
{ id: 'devanagari', name: 'Devanagari', value: 'devanagari' },
|
||||
];
|
||||
|
||||
// ============================================================================
|
||||
// FONT PROVIDERS
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Font providers
|
||||
@@ -126,9 +116,7 @@ export const FONT_PROVIDERS: Property<FontProvider>[] = [
|
||||
{ id: 'fontshare', name: 'Fontshare', value: 'fontshare' },
|
||||
];
|
||||
|
||||
// ============================================================================
|
||||
// FILTER FACTORIES
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Create a mock filter from properties
|
||||
@@ -172,9 +160,7 @@ export function createProvidersFilter(options?: { selected?: FontProvider[] }) {
|
||||
return createFilter<FontProvider>({ properties });
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// PRESET FILTERS
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Preset mock filters - use these directly in stories
|
||||
@@ -251,9 +237,7 @@ export const MOCK_FILTERS_ALL_SELECTED: MockFilters = {
|
||||
}),
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// GENERIC FILTER MOCKS
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Create a mock filter with generic string properties
|
||||
|
||||
@@ -50,9 +50,7 @@ import type {
|
||||
UnifiedFont,
|
||||
} from '$entities/Font/model/types';
|
||||
|
||||
// ============================================================================
|
||||
// GOOGLE FONTS MOCKS
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Options for creating a mock Google Font
|
||||
@@ -186,9 +184,7 @@ export const GOOGLE_FONTS: Record<string, GoogleFontItem> = {
|
||||
}),
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// FONTHARE MOCKS
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Options for creating a mock Fontshare font
|
||||
@@ -399,9 +395,7 @@ export const FONTHARE_FONTS: Record<string, FontshareFont> = {
|
||||
}),
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// UNIFIED FONT MOCKS
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Options for creating a mock UnifiedFont
|
||||
|
||||
@@ -35,9 +35,7 @@ import {
|
||||
generateMockFonts,
|
||||
} from './fonts.mock';
|
||||
|
||||
// ============================================================================
|
||||
// TANSTACK QUERY MOCK TYPES
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Mock TanStack Query state
|
||||
@@ -83,9 +81,7 @@ export interface MockQueryObserverResult<TData = unknown, TError = Error> {
|
||||
isPaused?: boolean;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// TANSTACK QUERY MOCK FACTORIES
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Create a mock query state for TanStack Query
|
||||
@@ -142,9 +138,7 @@ export function createSuccessState<TData>(data: TData): MockQueryObserverResult<
|
||||
return createMockQueryState<TData>({ status: 'success', data, error: undefined });
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// FONT STORE MOCKS
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Mock UnifiedFontStore state
|
||||
@@ -332,9 +326,7 @@ export const MOCK_FONT_STORE_STATES = {
|
||||
}),
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// MOCK STORE OBJECT
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Create a mock store object that mimics TanStack Query behavior
|
||||
@@ -469,9 +461,7 @@ export const MOCK_STORES = {
|
||||
},
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// REACTIVE STATE MOCKS
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Create a reactive state object using Svelte 5 runes pattern
|
||||
@@ -525,9 +515,7 @@ export function createMockComparisonStore(config: {
|
||||
};
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// MOCK DATA GENERATORS
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Generate paginated font data
|
||||
|
||||
@@ -7,41 +7,64 @@ import {
|
||||
} from '@tanstack/query-core';
|
||||
import type { UnifiedFont } from '../types';
|
||||
|
||||
/** */
|
||||
/**
|
||||
* Base class for font stores using TanStack Query
|
||||
*
|
||||
* Provides reactive font data fetching with caching, automatic refetching,
|
||||
* and parameter binding. Extended by UnifiedFontStore for provider-agnostic
|
||||
* font fetching.
|
||||
*
|
||||
* @template TParams - Type of query parameters
|
||||
*/
|
||||
export abstract class BaseFontStore<TParams extends Record<string, any>> {
|
||||
/**
|
||||
* Cleanup function for effects
|
||||
* Call destroy() to remove effects and prevent memory leaks
|
||||
*/
|
||||
cleanup: () => void;
|
||||
|
||||
/** Reactive parameter bindings from external sources */
|
||||
#bindings = $state<(() => Partial<TParams>)[]>([]);
|
||||
/** Internal parameter state */
|
||||
#internalParams = $state<TParams>({} as TParams);
|
||||
|
||||
/**
|
||||
* Merged params from internal state and all bindings
|
||||
* Automatically updates when bindings or internal params change
|
||||
*/
|
||||
params = $derived.by(() => {
|
||||
let merged = { ...this.#internalParams };
|
||||
|
||||
// Loop through every "Cable" plugged into the store
|
||||
// Loop through every "Cable" plugged into the store
|
||||
// Merge all binding results into params
|
||||
for (const getter of this.#bindings) {
|
||||
const bindingResult = getter();
|
||||
merged = { ...merged, ...bindingResult };
|
||||
}
|
||||
|
||||
return merged as TParams;
|
||||
});
|
||||
|
||||
/** TanStack Query result state */
|
||||
protected result = $state<QueryObserverResult<UnifiedFont[], Error>>({} as any);
|
||||
/** TanStack Query observer instance */
|
||||
protected observer: QueryObserver<UnifiedFont[], Error>;
|
||||
/** Shared query client */
|
||||
protected qc = queryClient;
|
||||
|
||||
/**
|
||||
* Creates a new base font store
|
||||
* @param initialParams - Initial query parameters
|
||||
*/
|
||||
constructor(initialParams: TParams) {
|
||||
this.#internalParams = initialParams;
|
||||
|
||||
this.observer = new QueryObserver(this.qc, this.getOptions());
|
||||
|
||||
// Sync TanStack -> Svelte State
|
||||
// Sync TanStack Query state -> Svelte state
|
||||
this.observer.subscribe(r => {
|
||||
this.result = r;
|
||||
});
|
||||
|
||||
// Sync Svelte State -> TanStack Options
|
||||
// Sync Svelte state changes -> TanStack Query options
|
||||
this.cleanup = $effect.root(() => {
|
||||
$effect(() => {
|
||||
this.observer.setOptions(this.getOptions());
|
||||
@@ -50,11 +73,21 @@ export abstract class BaseFontStore<TParams extends Record<string, any>> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Mandatory: Child must define how to fetch data and what the key is.
|
||||
* Must be implemented by child class
|
||||
* Returns the query key for TanStack Query caching
|
||||
*/
|
||||
protected abstract getQueryKey(params: TParams): QueryKey;
|
||||
|
||||
/**
|
||||
* Must be implemented by child class
|
||||
* Fetches font data from API
|
||||
*/
|
||||
protected abstract fetchFn(params: TParams): Promise<UnifiedFont[]>;
|
||||
|
||||
/**
|
||||
* Gets TanStack Query options
|
||||
* @param params - Query parameters (defaults to current params)
|
||||
*/
|
||||
protected getOptions(params = this.params): QueryObserverOptions<UnifiedFont[], Error> {
|
||||
return {
|
||||
queryKey: this.getQueryKey(params),
|
||||
@@ -64,25 +97,36 @@ export abstract class BaseFontStore<TParams extends Record<string, any>> {
|
||||
};
|
||||
}
|
||||
|
||||
// --- Common Getters ---
|
||||
/** Array of fonts (empty array if loading/error) */
|
||||
get fonts() {
|
||||
return this.result.data ?? [];
|
||||
}
|
||||
|
||||
/** Whether currently fetching initial data */
|
||||
get isLoading() {
|
||||
return this.result.isLoading;
|
||||
}
|
||||
|
||||
/** Whether any fetch is in progress (including refetches) */
|
||||
get isFetching() {
|
||||
return this.result.isFetching;
|
||||
}
|
||||
|
||||
/** Whether last fetch resulted in an error */
|
||||
get isError() {
|
||||
return this.result.isError;
|
||||
}
|
||||
|
||||
/** Whether no fonts are loaded (not loading and empty array) */
|
||||
get isEmpty() {
|
||||
return !this.isLoading && this.fonts.length === 0;
|
||||
}
|
||||
|
||||
// --- Common Actions ---
|
||||
|
||||
/**
|
||||
* Add a reactive parameter binding
|
||||
* @param getter - Function that returns partial params to merge
|
||||
* @returns Unbind function to remove the binding
|
||||
*/
|
||||
addBinding(getter: () => Partial<TParams>) {
|
||||
this.#bindings.push(getter);
|
||||
|
||||
@@ -91,9 +135,14 @@ export abstract class BaseFontStore<TParams extends Record<string, any>> {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Update query parameters
|
||||
* @param newParams - Partial params to merge with existing
|
||||
*/
|
||||
setParams(newParams: Partial<TParams>) {
|
||||
this.#internalParams = { ...this.params, ...newParams };
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidate cache and refetch
|
||||
*/
|
||||
@@ -101,19 +150,22 @@ export abstract class BaseFontStore<TParams extends Record<string, any>> {
|
||||
this.qc.invalidateQueries({ queryKey: this.getQueryKey(this.params) });
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up effects and observers
|
||||
*/
|
||||
destroy() {
|
||||
this.cleanup();
|
||||
}
|
||||
|
||||
/**
|
||||
* Manually refetch
|
||||
* Manually trigger a refetch
|
||||
*/
|
||||
async refetch() {
|
||||
await this.observer.refetch();
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefetch with different params (for hover states, pagination, etc.)
|
||||
* Prefetch data with different parameters
|
||||
*/
|
||||
async prefetch(params: TParams) {
|
||||
await this.qc.prefetchQuery(this.getOptions(params));
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
/**
|
||||
* ============================================================================
|
||||
* UNIFIED FONT STORE TYPES
|
||||
* ============================================================================
|
||||
*
|
||||
* Type definitions for the unified font store infrastructure.
|
||||
* Provides types for filters, sorting, and fetch parameters.
|
||||
*/
|
||||
|
||||
import type {
|
||||
FontshareParams,
|
||||
GoogleFontsParams,
|
||||
} from '$entities/Font/api';
|
||||
import type {
|
||||
FontCategory,
|
||||
FontProvider,
|
||||
FontSubset,
|
||||
} from '$entities/Font/model/types/common';
|
||||
|
||||
/**
|
||||
* Sort configuration
|
||||
*/
|
||||
export interface FontSort {
|
||||
field: 'name' | 'popularity' | 'category' | 'date';
|
||||
direction: 'asc' | 'desc';
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch params for unified API
|
||||
*/
|
||||
export interface FetchFontsParams {
|
||||
providers?: FontProvider[];
|
||||
categories?: FontCategory[];
|
||||
subsets?: FontSubset[];
|
||||
search?: string;
|
||||
sort?: FontSort;
|
||||
forceRefetch?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provider-specific params union
|
||||
*/
|
||||
export type ProviderParams = GoogleFontsParams | FontshareParams;
|
||||
@@ -43,7 +43,7 @@ import { BaseFontStore } from './baseFontStore.svelte';
|
||||
* });
|
||||
*
|
||||
* // Update parameters
|
||||
* store.setCategory('serif');
|
||||
* store.setCategories(['serif']);
|
||||
* store.nextPage();
|
||||
* ```
|
||||
*/
|
||||
@@ -108,17 +108,21 @@ export class UnifiedFontStore extends BaseFontStore<ProxyFontsParams> {
|
||||
this.#filterCleanup = $effect.root(() => {
|
||||
$effect(() => {
|
||||
const filterParams = JSON.stringify({
|
||||
provider: this.params.provider,
|
||||
category: this.params.category,
|
||||
subset: this.params.subset,
|
||||
providers: this.params.providers,
|
||||
categories: this.params.categories,
|
||||
subsets: this.params.subsets,
|
||||
q: this.params.q,
|
||||
});
|
||||
|
||||
// If filters changed, reset offset to 0
|
||||
// If filters changed, reset offset and invalidate cache
|
||||
if (filterParams !== this.#previousFilterParams) {
|
||||
if (this.#previousFilterParams && this.params.offset !== 0) {
|
||||
if (this.#previousFilterParams) {
|
||||
if (this.params.offset !== 0) {
|
||||
this.setParams({ offset: 0 });
|
||||
}
|
||||
this.#accumulatedFonts = [];
|
||||
this.invalidate();
|
||||
}
|
||||
this.#previousFilterParams = filterParams;
|
||||
}
|
||||
});
|
||||
@@ -170,7 +174,7 @@ export class UnifiedFontStore extends BaseFontStore<ProxyFontsParams> {
|
||||
}
|
||||
|
||||
protected getOptions(params = this.params): QueryObserverOptions<UnifiedFont[], Error> {
|
||||
const hasFilters = !!(params.q || params.provider || params.category || params.subset);
|
||||
const hasFilters = !!(params.q || params.providers || params.categories || params.subsets);
|
||||
return {
|
||||
queryKey: this.getQueryKey(params),
|
||||
queryFn: () => this.fetchFn(params),
|
||||
@@ -221,8 +225,6 @@ export class UnifiedFontStore extends BaseFontStore<ProxyFontsParams> {
|
||||
return response.fonts;
|
||||
}
|
||||
|
||||
// --- Getters (proxied from BaseFontStore) ---
|
||||
|
||||
/**
|
||||
* Get all accumulated fonts (for infinite scroll)
|
||||
*/
|
||||
@@ -258,27 +260,25 @@ export class UnifiedFontStore extends BaseFontStore<ProxyFontsParams> {
|
||||
return !this.isLoading && this.fonts.length === 0;
|
||||
}
|
||||
|
||||
// --- Provider-specific shortcuts ---
|
||||
|
||||
/**
|
||||
* Set provider filter
|
||||
* Set providers filter
|
||||
*/
|
||||
setProvider(provider: 'google' | 'fontshare' | undefined) {
|
||||
this.setParams({ provider });
|
||||
setProviders(providers: ProxyFontsParams['providers']) {
|
||||
this.setParams({ providers });
|
||||
}
|
||||
|
||||
/**
|
||||
* Set category filter
|
||||
* Set categories filter
|
||||
*/
|
||||
setCategory(category: ProxyFontsParams['category']) {
|
||||
this.setParams({ category });
|
||||
setCategories(categories: ProxyFontsParams['categories']) {
|
||||
this.setParams({ categories });
|
||||
}
|
||||
|
||||
/**
|
||||
* Set subset filter
|
||||
* Set subsets filter
|
||||
*/
|
||||
setSubset(subset: ProxyFontsParams['subset']) {
|
||||
this.setParams({ subset });
|
||||
setSubsets(subsets: ProxyFontsParams['subsets']) {
|
||||
this.setParams({ subsets });
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -295,8 +295,6 @@ export class UnifiedFontStore extends BaseFontStore<ProxyFontsParams> {
|
||||
this.setParams({ sort });
|
||||
}
|
||||
|
||||
// --- Pagination methods ---
|
||||
|
||||
/**
|
||||
* Go to next page
|
||||
*/
|
||||
@@ -337,8 +335,6 @@ export class UnifiedFontStore extends BaseFontStore<ProxyFontsParams> {
|
||||
this.setParams({ limit });
|
||||
}
|
||||
|
||||
// --- Category shortcuts (for convenience) ---
|
||||
|
||||
get sansSerifFonts() {
|
||||
return this.fonts.filter(f => f.category === 'sans-serif');
|
||||
}
|
||||
|
||||
@@ -1,50 +1,60 @@
|
||||
/**
|
||||
* ============================================================================
|
||||
* DOMAIN TYPES
|
||||
* ============================================================================
|
||||
* Common font domain types
|
||||
*
|
||||
* Shared types for font entities across providers (Google, Fontshare).
|
||||
* Includes categories, subsets, weights, and filter types.
|
||||
*/
|
||||
|
||||
import type { FontCategory as FontshareFontCategory } from './fontshare';
|
||||
import type { FontCategory as GoogleFontCategory } from './google';
|
||||
|
||||
/**
|
||||
* Font category
|
||||
* Unified font category across all providers
|
||||
*/
|
||||
export type FontCategory = GoogleFontCategory | FontshareFontCategory;
|
||||
|
||||
/**
|
||||
* Font provider
|
||||
* Font provider identifier
|
||||
*/
|
||||
export type FontProvider = 'google' | 'fontshare';
|
||||
|
||||
/**
|
||||
* Font subset
|
||||
* Character subset support
|
||||
*/
|
||||
export type FontSubset = 'latin' | 'latin-ext' | 'cyrillic' | 'greek' | 'arabic' | 'devanagari';
|
||||
|
||||
/**
|
||||
* Filter state
|
||||
* Combined filter state for font queries
|
||||
*/
|
||||
export interface FontFilters {
|
||||
/** Selected font providers */
|
||||
providers: FontProvider[];
|
||||
/** Selected font categories */
|
||||
categories: FontCategory[];
|
||||
/** Selected character subsets */
|
||||
subsets: FontSubset[];
|
||||
}
|
||||
|
||||
/** Filter group identifier */
|
||||
export type FilterGroup = 'providers' | 'categories' | 'subsets';
|
||||
|
||||
/** Filter type including search query */
|
||||
export type FilterType = FilterGroup | 'searchQuery';
|
||||
|
||||
/**
|
||||
* Standard font weights
|
||||
* Numeric font weights (100-900)
|
||||
*/
|
||||
export type FontWeight = '100' | '200' | '300' | '400' | '500' | '600' | '700' | '800' | '900';
|
||||
|
||||
/**
|
||||
* Italic variant format: e.g., "100italic", "400italic", "700italic"
|
||||
* Italic variant with weight: "100italic", "400italic", "700italic", etc.
|
||||
*/
|
||||
export type FontWeightItalic = `${FontWeight}italic`;
|
||||
|
||||
/**
|
||||
* All possible font variants
|
||||
* All possible font variant identifiers
|
||||
*
|
||||
* Includes:
|
||||
* - Numeric weights: "400", "700", etc.
|
||||
* - Italic variants: "400italic", "700italic", etc.
|
||||
* - Legacy names: "regular", "italic", "bold", "bolditalic"
|
||||
|
||||
@@ -46,6 +46,13 @@ export interface FontMetadata {
|
||||
lastModified?: string;
|
||||
/** Popularity rank (if available from provider) */
|
||||
popularity?: number;
|
||||
/**
|
||||
* Normalized popularity score (0-100)
|
||||
*
|
||||
* Normalized across all fonts for consistent ranking
|
||||
* Higher values indicate more popular fonts
|
||||
*/
|
||||
popularityScore?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -79,6 +86,13 @@ export interface UnifiedFont {
|
||||
name: string;
|
||||
/** Font provider (google | fontshare) */
|
||||
provider: FontProvider;
|
||||
/**
|
||||
* Provider badge display name
|
||||
*
|
||||
* Human-readable provider name for UI display
|
||||
* e.g., "Google Fonts" or "Fontshare"
|
||||
*/
|
||||
providerBadge?: string;
|
||||
/** Font category classification */
|
||||
category: FontCategory;
|
||||
/** Supported character subsets */
|
||||
|
||||
@@ -16,19 +16,20 @@ import {
|
||||
|
||||
interface Props {
|
||||
/**
|
||||
* Applied font
|
||||
* Font to apply
|
||||
*/
|
||||
font: UnifiedFont;
|
||||
/**
|
||||
* Font weight
|
||||
* @default 400
|
||||
*/
|
||||
weight?: number;
|
||||
/**
|
||||
* Additional classes
|
||||
* CSS classes
|
||||
*/
|
||||
className?: string;
|
||||
/**
|
||||
* Children
|
||||
* Content snippet
|
||||
*/
|
||||
children?: Snippet;
|
||||
}
|
||||
@@ -44,7 +45,7 @@ const status = $derived(
|
||||
appliedFontsManager.getFontStatus(
|
||||
font.id,
|
||||
weight,
|
||||
font.features.isVariable,
|
||||
font.features?.isVariable,
|
||||
),
|
||||
);
|
||||
|
||||
|
||||
@@ -28,11 +28,11 @@ interface Props extends
|
||||
>
|
||||
{
|
||||
/**
|
||||
* Callback for when visible items change
|
||||
* Visible items callback
|
||||
*/
|
||||
onVisibleItemsChange?: (items: UnifiedFont[]) => void;
|
||||
/**
|
||||
* Weight of the font
|
||||
* Font weight
|
||||
*/
|
||||
weight: number;
|
||||
/**
|
||||
@@ -69,6 +69,7 @@ function handleInternalVisibleChange(visibleItems: UnifiedFont[]) {
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Auto-register fonts with the manager
|
||||
appliedFontsManager.touch(configs);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user