chore: follow the general comments style
This commit is contained in:
@@ -41,15 +41,25 @@ type ThemeSource = 'system' | 'user';
|
||||
*/
|
||||
class ThemeManager {
|
||||
// Private reactive state
|
||||
/** Current theme value ('light' or 'dark') */
|
||||
/**
|
||||
* Current theme value ('light' or 'dark')
|
||||
*/
|
||||
#theme = $state<Theme>('light');
|
||||
/** Whether theme is controlled by user or follows system */
|
||||
/**
|
||||
* Whether theme is controlled by user or follows system
|
||||
*/
|
||||
#source = $state<ThemeSource>('system');
|
||||
/** MediaQueryList for detecting system theme changes */
|
||||
/**
|
||||
* MediaQueryList for detecting system theme changes
|
||||
*/
|
||||
#mediaQuery: MediaQueryList | null = null;
|
||||
/** Persistent storage for user's theme preference */
|
||||
/**
|
||||
* Persistent storage for user's theme preference
|
||||
*/
|
||||
#store = createPersistentStore<Theme | null>('glyphdiff:theme', null);
|
||||
/** Bound handler for system theme change events */
|
||||
/**
|
||||
* Bound handler for system theme change events
|
||||
*/
|
||||
#systemChangeHandler = this.#onSystemChange.bind(this);
|
||||
|
||||
constructor() {
|
||||
@@ -64,22 +74,30 @@ class ThemeManager {
|
||||
}
|
||||
}
|
||||
|
||||
/** Current theme value */
|
||||
/**
|
||||
* Current theme value
|
||||
*/
|
||||
get value(): Theme {
|
||||
return this.#theme;
|
||||
}
|
||||
|
||||
/** Source of current theme ('system' or 'user') */
|
||||
/**
|
||||
* Source of current theme ('system' or 'user')
|
||||
*/
|
||||
get source(): ThemeSource {
|
||||
return this.#source;
|
||||
}
|
||||
|
||||
/** Whether dark theme is active */
|
||||
/**
|
||||
* Whether dark theme is active
|
||||
*/
|
||||
get isDark(): boolean {
|
||||
return this.#theme === 'dark';
|
||||
}
|
||||
|
||||
/** Whether theme is controlled by user (not following system) */
|
||||
/**
|
||||
* Whether theme is controlled by user (not following system)
|
||||
*/
|
||||
get isUserControlled(): boolean {
|
||||
return this.#source === 'user';
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
/** @vitest-environment jsdom */
|
||||
/**
|
||||
* @vitest-environment jsdom
|
||||
*/
|
||||
|
||||
// ============================================================
|
||||
// Mock MediaQueryListEvent for system theme change simulations
|
||||
// Note: Other mocks (ResizeObserver, localStorage, matchMedia) are set up in vitest.setup.unit.ts
|
||||
// ============================================================
|
||||
|
||||
class MockMediaQueryListEvent extends Event {
|
||||
matches: boolean;
|
||||
@@ -16,9 +16,7 @@ class MockMediaQueryListEvent extends Event {
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// NOW IT'S SAFE TO IMPORT
|
||||
// ============================================================
|
||||
|
||||
import {
|
||||
afterEach,
|
||||
|
||||
@@ -15,19 +15,29 @@ const PROXY_API_URL = 'https://api.glyphdiff.com/api/v1/filters' as const;
|
||||
* Filter metadata type from backend
|
||||
*/
|
||||
export interface FilterMetadata {
|
||||
/** Filter ID (e.g., "providers", "categories", "subsets") */
|
||||
/**
|
||||
* Filter ID (e.g., "providers", "categories", "subsets")
|
||||
*/
|
||||
id: string;
|
||||
|
||||
/** Display name (e.g., "Font Providers", "Categories", "Character Subsets") */
|
||||
/**
|
||||
* Display name (e.g., "Font Providers", "Categories", "Character Subsets")
|
||||
*/
|
||||
name: string;
|
||||
|
||||
/** Filter description */
|
||||
/**
|
||||
* Filter description
|
||||
*/
|
||||
description: string;
|
||||
|
||||
/** Filter type */
|
||||
/**
|
||||
* Filter type
|
||||
*/
|
||||
type: 'enum' | 'string' | 'array';
|
||||
|
||||
/** Available filter options */
|
||||
/**
|
||||
* Available filter options
|
||||
*/
|
||||
options: FilterOption[];
|
||||
}
|
||||
|
||||
@@ -35,16 +45,24 @@ export interface FilterMetadata {
|
||||
* Filter option type
|
||||
*/
|
||||
export interface FilterOption {
|
||||
/** Option ID (e.g., "google", "serif", "latin") */
|
||||
/**
|
||||
* Option ID (e.g., "google", "serif", "latin")
|
||||
*/
|
||||
id: string;
|
||||
|
||||
/** Display name (e.g., "Google Fonts", "Serif", "Latin") */
|
||||
/**
|
||||
* Display name (e.g., "Google Fonts", "Serif", "Latin")
|
||||
*/
|
||||
name: string;
|
||||
|
||||
/** Option value (e.g., "google", "serif", "latin") */
|
||||
/**
|
||||
* Option value (e.g., "google", "serif", "latin")
|
||||
*/
|
||||
value: string;
|
||||
|
||||
/** Number of fonts with this value */
|
||||
/**
|
||||
* Number of fonts with this value
|
||||
*/
|
||||
count: number;
|
||||
}
|
||||
|
||||
@@ -52,7 +70,9 @@ export interface FilterOption {
|
||||
* Proxy filters API response
|
||||
*/
|
||||
export interface ProxyFiltersResponse {
|
||||
/** Array of filter metadata */
|
||||
/**
|
||||
* Array of filter metadata
|
||||
*/
|
||||
filters: FilterMetadata[];
|
||||
}
|
||||
|
||||
|
||||
@@ -1,15 +1,56 @@
|
||||
export type {
|
||||
/**
|
||||
* Top-level configuration for all filters
|
||||
*/
|
||||
FilterConfig,
|
||||
/**
|
||||
* Configuration for a single grouping of filter properties
|
||||
*/
|
||||
FilterGroupConfig,
|
||||
} from './types/filter';
|
||||
|
||||
export { filtersStore } from './state/filters.svelte';
|
||||
export { filterManager } from './state/manager.svelte';
|
||||
|
||||
/**
|
||||
* Global reactive filter state
|
||||
*/
|
||||
export {
|
||||
/**
|
||||
* Low-level property selection store
|
||||
*/
|
||||
filtersStore,
|
||||
} from './state/filters.svelte';
|
||||
|
||||
/**
|
||||
* Main filter controller
|
||||
*/
|
||||
export {
|
||||
/**
|
||||
* High-level manager for syncing search and filters
|
||||
*/
|
||||
filterManager,
|
||||
} from './state/manager.svelte';
|
||||
|
||||
/**
|
||||
* Sorting logic
|
||||
*/
|
||||
export {
|
||||
/**
|
||||
* Map of human-readable labels to API sort keys
|
||||
*/
|
||||
SORT_MAP,
|
||||
/**
|
||||
* List of all available sort options for the UI
|
||||
*/
|
||||
SORT_OPTIONS,
|
||||
/**
|
||||
* Valid sort key values
|
||||
*/
|
||||
type SortApiValue,
|
||||
/**
|
||||
* UI model for a single sort option
|
||||
*/
|
||||
type SortOption,
|
||||
/**
|
||||
* Reactive store for the current sort selection
|
||||
*/
|
||||
sortStore,
|
||||
} from './store/sortStore.svelte';
|
||||
|
||||
@@ -32,13 +32,19 @@ import {
|
||||
* Provides reactive access to filter data
|
||||
*/
|
||||
class FiltersStore {
|
||||
/** TanStack Query result state */
|
||||
/**
|
||||
* TanStack Query result state
|
||||
*/
|
||||
protected result = $state<QueryObserverResult<FilterMetadata[], Error>>({} as any);
|
||||
|
||||
/** TanStack Query observer instance */
|
||||
/**
|
||||
* TanStack Query observer instance
|
||||
*/
|
||||
protected observer: QueryObserver<FilterMetadata[], Error>;
|
||||
|
||||
/** Shared query client */
|
||||
/**
|
||||
* Shared query client
|
||||
*/
|
||||
protected qc = queryClient;
|
||||
|
||||
/**
|
||||
|
||||
@@ -21,17 +21,23 @@ function createSortStore(initial: SortOption = 'Popularity') {
|
||||
let current = $state<SortOption>(initial);
|
||||
|
||||
return {
|
||||
/** Current display label (e.g. 'Popularity') */
|
||||
/**
|
||||
* Current display label (e.g. 'Popularity')
|
||||
*/
|
||||
get value() {
|
||||
return current;
|
||||
},
|
||||
|
||||
/** Mapped API value (e.g. 'popularity') */
|
||||
/**
|
||||
* Mapped API value (e.g. 'popularity')
|
||||
*/
|
||||
get apiValue(): SortApiValue {
|
||||
return SORT_MAP[current];
|
||||
},
|
||||
|
||||
/** Set the active sort option by its display label */
|
||||
/**
|
||||
* Set the active sort option by its display label
|
||||
*/
|
||||
set(option: SortOption) {
|
||||
current = option;
|
||||
},
|
||||
|
||||
@@ -1,12 +1,27 @@
|
||||
import type { Property } from '$shared/lib';
|
||||
|
||||
export interface FilterGroupConfig<TValue extends string> {
|
||||
/**
|
||||
* Unique identifier for the filter group (e.g. 'categories')
|
||||
*/
|
||||
id: string;
|
||||
/**
|
||||
* Human-readable label displayed in the UI header
|
||||
*/
|
||||
label: string;
|
||||
/**
|
||||
* List of toggleable properties within this group
|
||||
*/
|
||||
properties: Property<TValue>[];
|
||||
}
|
||||
|
||||
export interface FilterConfig<TValue extends string> {
|
||||
/**
|
||||
* Optional string to filter results by name
|
||||
*/
|
||||
queryValue?: string;
|
||||
/**
|
||||
* Collection of filter groups to display
|
||||
*/
|
||||
groups: FilterGroupConfig<TValue>[];
|
||||
}
|
||||
|
||||
@@ -30,9 +30,12 @@ import { SvelteMap } from 'svelte/reactivity';
|
||||
type ControlOnlyFields<T extends string = string> = Omit<ControlModel<T>, keyof ControlDataModel>;
|
||||
|
||||
/**
|
||||
* A control with its instance
|
||||
* A control with its associated instance
|
||||
*/
|
||||
export interface Control extends ControlOnlyFields<ControlId> {
|
||||
/**
|
||||
* The reactive typography control instance
|
||||
*/
|
||||
instance: TypographyControl;
|
||||
}
|
||||
|
||||
@@ -40,9 +43,21 @@ export interface Control extends ControlOnlyFields<ControlId> {
|
||||
* Storage schema for typography settings
|
||||
*/
|
||||
export interface TypographySettings {
|
||||
/**
|
||||
* Base font size (User preference, unscaled)
|
||||
*/
|
||||
fontSize: number;
|
||||
/**
|
||||
* Numeric font weight (100-900)
|
||||
*/
|
||||
fontWeight: number;
|
||||
/**
|
||||
* Line height multiplier (e.g. 1.5)
|
||||
*/
|
||||
lineHeight: number;
|
||||
/**
|
||||
* Letter spacing in em/px
|
||||
*/
|
||||
letterSpacing: number;
|
||||
}
|
||||
|
||||
@@ -53,13 +68,21 @@ export interface TypographySettings {
|
||||
* responsive scaling support for font size.
|
||||
*/
|
||||
export class TypographySettingsManager {
|
||||
/** Map of controls keyed by ID */
|
||||
/**
|
||||
* Internal map of reactive controls keyed by their identifier
|
||||
*/
|
||||
#controls = new SvelteMap<string, Control>();
|
||||
/** Responsive multiplier for font size display */
|
||||
/**
|
||||
* Global multiplier for responsive font size scaling
|
||||
*/
|
||||
#multiplier = $state(1);
|
||||
/** Persistent storage for settings */
|
||||
/**
|
||||
* LocalStorage-backed storage for persistence
|
||||
*/
|
||||
#storage: PersistentStore<TypographySettings>;
|
||||
/** Base font size (user preference, unscaled) */
|
||||
/**
|
||||
* The underlying font size before responsive scaling is applied
|
||||
*/
|
||||
#baseSize = $state(DEFAULT_FONT_SIZE);
|
||||
|
||||
constructor(configs: ControlModel<ControlId>[], storage: PersistentStore<TypographySettings>) {
|
||||
@@ -131,16 +154,15 @@ export class TypographySettingsManager {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/** Current multiplier for responsive scaling */
|
||||
/**
|
||||
* Active scaling factor for the rendered font size
|
||||
*/
|
||||
get multiplier() {
|
||||
return this.#multiplier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the multiplier and update font size display
|
||||
*
|
||||
* When multiplier changes, the font size control's display value
|
||||
* is updated to reflect the new scale while preserving base size.
|
||||
* Updates the multiplier and recalculates dependent control values
|
||||
*/
|
||||
set multiplier(value: number) {
|
||||
if (this.#multiplier === value) return;
|
||||
@@ -154,14 +176,15 @@ export class TypographySettingsManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* The scaled size for CSS usage
|
||||
* Returns baseSize * multiplier for actual rendering
|
||||
* The actual pixel value for CSS font-size (baseSize * multiplier)
|
||||
*/
|
||||
get renderedSize() {
|
||||
return this.#baseSize * this.#multiplier;
|
||||
}
|
||||
|
||||
/** The base size (User Preference) */
|
||||
/**
|
||||
* The raw font size preference before scaling
|
||||
*/
|
||||
get baseSize() {
|
||||
return this.#baseSize;
|
||||
}
|
||||
@@ -173,45 +196,63 @@ export class TypographySettingsManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* Getters for controls
|
||||
* List of all managed typography controls
|
||||
*/
|
||||
get controls() {
|
||||
return Array.from(this.#controls.values());
|
||||
}
|
||||
|
||||
/**
|
||||
* Reactive instance for weight manipulation
|
||||
*/
|
||||
get weightControl() {
|
||||
return this.#controls.get('font_weight')?.instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reactive instance for size manipulation
|
||||
*/
|
||||
get sizeControl() {
|
||||
return this.#controls.get('font_size')?.instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reactive instance for line-height manipulation
|
||||
*/
|
||||
get heightControl() {
|
||||
return this.#controls.get('line_height')?.instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reactive instance for letter-spacing manipulation
|
||||
*/
|
||||
get spacingControl() {
|
||||
return this.#controls.get('letter_spacing')?.instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Getters for values (besides font-size)
|
||||
* Current numeric font weight (reactive)
|
||||
*/
|
||||
get weight() {
|
||||
return this.#controls.get('font_weight')?.instance.value ?? DEFAULT_FONT_WEIGHT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Current numeric line height (reactive)
|
||||
*/
|
||||
get height() {
|
||||
return this.#controls.get('line_height')?.instance.value ?? DEFAULT_LINE_HEIGHT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Current numeric letter spacing (reactive)
|
||||
*/
|
||||
get spacing() {
|
||||
return this.#controls.get('letter_spacing')?.instance.value ?? DEFAULT_LETTER_SPACING;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset all controls to default values
|
||||
* Reset all controls to project-defined defaults
|
||||
*/
|
||||
reset() {
|
||||
this.#storage.clear();
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
/** @vitest-environment jsdom */
|
||||
/**
|
||||
* @vitest-environment jsdom
|
||||
*/
|
||||
import {
|
||||
DEFAULT_FONT_SIZE,
|
||||
DEFAULT_FONT_WEIGHT,
|
||||
|
||||
Reference in New Issue
Block a user