chore(SetupFont): rename controlManager to typographySettingsStore for better semantic
This commit is contained in:
@@ -0,0 +1,256 @@
|
||||
/**
|
||||
* Typography control manager
|
||||
*
|
||||
* Manages a collection of typography controls (font size, weight, line height,
|
||||
* letter spacing) with persistent storage. Supports responsive scaling
|
||||
* through a multiplier system.
|
||||
*
|
||||
* The font size control uses a multiplier system to allow responsive scaling
|
||||
* while preserving the user's base size preference. The multiplier is applied
|
||||
* when displaying/editing, but the base size is what's stored.
|
||||
*/
|
||||
|
||||
import {
|
||||
type ControlDataModel,
|
||||
type ControlModel,
|
||||
type PersistentStore,
|
||||
type TypographyControl,
|
||||
createPersistentStore,
|
||||
createTypographyControl,
|
||||
} from '$shared/lib';
|
||||
import { SvelteMap } from 'svelte/reactivity';
|
||||
import {
|
||||
type ControlId,
|
||||
DEFAULT_FONT_SIZE,
|
||||
DEFAULT_FONT_WEIGHT,
|
||||
DEFAULT_LETTER_SPACING,
|
||||
DEFAULT_LINE_HEIGHT,
|
||||
} from '../../model';
|
||||
|
||||
type ControlOnlyFields<T extends string = string> = Omit<ControlModel<T>, keyof ControlDataModel>;
|
||||
|
||||
/**
|
||||
* A control with its instance
|
||||
*/
|
||||
export interface Control extends ControlOnlyFields<ControlId> {
|
||||
instance: TypographyControl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Storage schema for typography settings
|
||||
*/
|
||||
export interface TypographySettings {
|
||||
fontSize: number;
|
||||
fontWeight: number;
|
||||
lineHeight: number;
|
||||
letterSpacing: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Typography control manager class
|
||||
*
|
||||
* Manages multiple typography controls with persistent storage and
|
||||
* responsive scaling support for font size.
|
||||
*/
|
||||
export class TypographySettingsManager {
|
||||
/** Map of controls keyed by ID */
|
||||
#controls = new SvelteMap<string, Control>();
|
||||
/** Responsive multiplier for font size display */
|
||||
#multiplier = $state(1);
|
||||
/** Persistent storage for settings */
|
||||
#storage: PersistentStore<TypographySettings>;
|
||||
/** Base font size (user preference, unscaled) */
|
||||
#baseSize = $state(DEFAULT_FONT_SIZE);
|
||||
|
||||
constructor(configs: ControlModel<ControlId>[], storage: PersistentStore<TypographySettings>) {
|
||||
this.#storage = storage;
|
||||
|
||||
// Initial Load
|
||||
const saved = storage.value;
|
||||
this.#baseSize = saved.fontSize;
|
||||
|
||||
// Setup Controls
|
||||
configs.forEach(config => {
|
||||
const initialValue = this.#getInitialValue(config.id, saved);
|
||||
|
||||
this.#controls.set(config.id, {
|
||||
...config,
|
||||
instance: createTypographyControl({
|
||||
...config,
|
||||
value: initialValue,
|
||||
}),
|
||||
});
|
||||
});
|
||||
|
||||
// The Sync Effect (UI -> Storage)
|
||||
// We access .value explicitly to ensure Svelte 5 tracks the dependency
|
||||
$effect.root(() => {
|
||||
$effect(() => {
|
||||
// EXPLICIT DEPENDENCIES: Accessing these triggers the effect
|
||||
const fontSize = this.#baseSize;
|
||||
const fontWeight = this.#controls.get('font_weight')?.instance.value ?? DEFAULT_FONT_WEIGHT;
|
||||
const lineHeight = this.#controls.get('line_height')?.instance.value ?? DEFAULT_LINE_HEIGHT;
|
||||
const letterSpacing = this.#controls.get('letter_spacing')?.instance.value ?? DEFAULT_LETTER_SPACING;
|
||||
|
||||
// Syncing back to storage
|
||||
this.#storage.value = {
|
||||
fontSize,
|
||||
fontWeight,
|
||||
lineHeight,
|
||||
letterSpacing,
|
||||
};
|
||||
});
|
||||
|
||||
// The Font Size Proxy Effect
|
||||
// This handles the "Multiplier" logic specifically for the Font Size Control
|
||||
$effect(() => {
|
||||
const ctrl = this.#controls.get('font_size')?.instance;
|
||||
if (!ctrl) return;
|
||||
|
||||
// If the user moves the slider/clicks buttons in the UI:
|
||||
// We update the baseSize (User Intent)
|
||||
const currentDisplayValue = ctrl.value;
|
||||
const calculatedBase = currentDisplayValue / this.#multiplier;
|
||||
|
||||
// Only update if the difference is significant (prevents rounding jitter)
|
||||
if (Math.abs(this.#baseSize - calculatedBase) > 0.01) {
|
||||
this.#baseSize = calculatedBase;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets initial value for a control from storage or defaults
|
||||
*/
|
||||
#getInitialValue(id: string, saved: TypographySettings): number {
|
||||
if (id === 'font_size') return saved.fontSize * this.#multiplier;
|
||||
if (id === 'font_weight') return saved.fontWeight;
|
||||
if (id === 'line_height') return saved.lineHeight;
|
||||
if (id === 'letter_spacing') return saved.letterSpacing;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/** Current multiplier for responsive scaling */
|
||||
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.
|
||||
*/
|
||||
set multiplier(value: number) {
|
||||
if (this.#multiplier === value) return;
|
||||
this.#multiplier = value;
|
||||
|
||||
// When multiplier changes, we must update the Font Size Control's display value
|
||||
const ctrl = this.#controls.get('font_size')?.instance;
|
||||
if (ctrl) {
|
||||
ctrl.value = this.#baseSize * this.#multiplier;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The scaled size for CSS usage
|
||||
* Returns baseSize * multiplier for actual rendering
|
||||
*/
|
||||
get renderedSize() {
|
||||
return this.#baseSize * this.#multiplier;
|
||||
}
|
||||
|
||||
/** The base size (User Preference) */
|
||||
get baseSize() {
|
||||
return this.#baseSize;
|
||||
}
|
||||
|
||||
set baseSize(val: number) {
|
||||
this.#baseSize = val;
|
||||
const ctrl = this.#controls.get('font_size')?.instance;
|
||||
if (ctrl) ctrl.value = val * this.#multiplier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Getters for controls
|
||||
*/
|
||||
get controls() {
|
||||
return Array.from(this.#controls.values());
|
||||
}
|
||||
|
||||
get weightControl() {
|
||||
return this.#controls.get('font_weight')?.instance;
|
||||
}
|
||||
|
||||
get sizeControl() {
|
||||
return this.#controls.get('font_size')?.instance;
|
||||
}
|
||||
|
||||
get heightControl() {
|
||||
return this.#controls.get('line_height')?.instance;
|
||||
}
|
||||
|
||||
get spacingControl() {
|
||||
return this.#controls.get('letter_spacing')?.instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Getters for values (besides font-size)
|
||||
*/
|
||||
get weight() {
|
||||
return this.#controls.get('font_weight')?.instance.value ?? DEFAULT_FONT_WEIGHT;
|
||||
}
|
||||
|
||||
get height() {
|
||||
return this.#controls.get('line_height')?.instance.value ?? DEFAULT_LINE_HEIGHT;
|
||||
}
|
||||
|
||||
get spacing() {
|
||||
return this.#controls.get('letter_spacing')?.instance.value ?? DEFAULT_LETTER_SPACING;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset all controls to default values
|
||||
*/
|
||||
reset() {
|
||||
this.#storage.clear();
|
||||
const defaults = this.#storage.value;
|
||||
|
||||
this.#baseSize = defaults.fontSize;
|
||||
|
||||
// Reset all control instances
|
||||
this.#controls.forEach(c => {
|
||||
if (c.id === 'font_size') {
|
||||
c.instance.value = defaults.fontSize * this.#multiplier;
|
||||
} else {
|
||||
// Map storage key to control id
|
||||
const key = c.id.replace('_', '') as keyof TypographySettings;
|
||||
// Simplified for brevity, you'd map these properly:
|
||||
if (c.id === 'font_weight') c.instance.value = defaults.fontWeight;
|
||||
if (c.id === 'line_height') c.instance.value = defaults.lineHeight;
|
||||
if (c.id === 'letter_spacing') c.instance.value = defaults.letterSpacing;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a typography control manager
|
||||
*
|
||||
* @param configs - Array of control configurations
|
||||
* @param storageId - Persistent storage identifier
|
||||
* @returns Typography control manager instance
|
||||
*/
|
||||
export function createTypographySettingsManager(
|
||||
configs: ControlModel<ControlId>[],
|
||||
storageId: string = 'glyphdiff:typography',
|
||||
) {
|
||||
const storage = createPersistentStore<TypographySettings>(storageId, {
|
||||
fontSize: DEFAULT_FONT_SIZE,
|
||||
fontWeight: DEFAULT_FONT_WEIGHT,
|
||||
lineHeight: DEFAULT_LINE_HEIGHT,
|
||||
letterSpacing: DEFAULT_LETTER_SPACING,
|
||||
});
|
||||
return new TypographySettingsManager(configs, storage);
|
||||
}
|
||||
Reference in New Issue
Block a user