Chore/architecture refactoring #42
+9
-1
@@ -28,6 +28,14 @@ import {
|
|||||||
} from '$shared/lib';
|
} from '$shared/lib';
|
||||||
import { SvelteMap } from 'svelte/reactivity';
|
import { SvelteMap } from 'svelte/reactivity';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Epsilon for detecting "significant" base-size changes when reconciling
|
||||||
|
* the multiplier-derived display value back to the underlying baseSize.
|
||||||
|
* Differences below this threshold are treated as rounding jitter and
|
||||||
|
* skipped to avoid spurious storage writes.
|
||||||
|
*/
|
||||||
|
const BASE_SIZE_EPSILON = 0.01;
|
||||||
|
|
||||||
type ControlOnlyFields<T extends string = string> = Omit<ControlModel<T>, keyof ControlDataModel>;
|
type ControlOnlyFields<T extends string = string> = Omit<ControlModel<T>, keyof ControlDataModel>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -139,7 +147,7 @@ export class TypographySettingsStore {
|
|||||||
const calculatedBase = currentDisplayValue / this.#multiplier;
|
const calculatedBase = currentDisplayValue / this.#multiplier;
|
||||||
|
|
||||||
// Only update if the difference is significant (prevents rounding jitter)
|
// Only update if the difference is significant (prevents rounding jitter)
|
||||||
if (Math.abs(this.#baseSize - calculatedBase) > 0.01) {
|
if (Math.abs(this.#baseSize - calculatedBase) > BASE_SIZE_EPSILON) {
|
||||||
this.#baseSize = calculatedBase;
|
this.#baseSize = calculatedBase;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
+17
-2
@@ -4,6 +4,20 @@ import {
|
|||||||
prepareWithSegments,
|
prepareWithSegments,
|
||||||
} from '@chenglou/pretext';
|
} from '@chenglou/pretext';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Width of the character morph "halo" around the slider thumb, in percent
|
||||||
|
* of container width. Characters within this window get partial blending
|
||||||
|
* instead of a hard A→B flip.
|
||||||
|
*/
|
||||||
|
const CHAR_PROXIMITY_RANGE_PCT = 5;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default render size in px when callers omit the `size` arg on `layout()`.
|
||||||
|
* Kept as a local constant to avoid pulling `$entities/Font` into
|
||||||
|
* `$shared/lib` (would create an FSD-illegal upward import cycle).
|
||||||
|
*/
|
||||||
|
const DEFAULT_RENDER_SIZE_PX = 16;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A single laid-out line produced by dual-font comparison layout.
|
* A single laid-out line produced by dual-font comparison layout.
|
||||||
*
|
*
|
||||||
@@ -129,7 +143,7 @@ export class CharacterComparisonEngine {
|
|||||||
width: number,
|
width: number,
|
||||||
lineHeight: number,
|
lineHeight: number,
|
||||||
spacing: number = 0,
|
spacing: number = 0,
|
||||||
size: number = 16,
|
size: number = DEFAULT_RENDER_SIZE_PX,
|
||||||
): ComparisonResult {
|
): ComparisonResult {
|
||||||
if (!text) {
|
if (!text) {
|
||||||
return { lines: [], totalHeight: 0 };
|
return { lines: [], totalHeight: 0 };
|
||||||
@@ -260,7 +274,7 @@ export class CharacterComparisonEngine {
|
|||||||
const chars = line.chars;
|
const chars = line.chars;
|
||||||
const n = chars.length;
|
const n = chars.length;
|
||||||
const sliderX = (sliderPos / 100) * containerWidth;
|
const sliderX = (sliderPos / 100) * containerWidth;
|
||||||
const range = 5;
|
const range = CHAR_PROXIMITY_RANGE_PCT;
|
||||||
// Prefix sums of widthA (left chars will be past → use widthA).
|
// Prefix sums of widthA (left chars will be past → use widthA).
|
||||||
// Suffix sums of widthB (right chars will not be past → use widthB).
|
// Suffix sums of widthB (right chars will not be past → use widthB).
|
||||||
// This lets us compute, for each char i, what the total line width and
|
// This lets us compute, for each char i, what the total line width and
|
||||||
@@ -291,6 +305,7 @@ export class CharacterComparisonEngine {
|
|||||||
const totalRendered = chars.reduce((s, c, i) => s + (isPastArr[i] ? c.widthA : c.widthB), 0);
|
const totalRendered = chars.reduce((s, c, i) => s + (isPastArr[i] ? c.widthA : c.widthB), 0);
|
||||||
const xOffset = (containerWidth - totalRendered) / 2;
|
const xOffset = (containerWidth - totalRendered) / 2;
|
||||||
let currentX = xOffset;
|
let currentX = xOffset;
|
||||||
|
|
||||||
return chars.map((char, i) => {
|
return chars.map((char, i) => {
|
||||||
const isPast = isPastArr[i] === 1;
|
const isPast = isPastArr[i] === 1;
|
||||||
const charWidth = isPast ? char.widthA : char.widthB;
|
const charWidth = isPast ? char.widthA : char.widthB;
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
import { debounce } from '$shared/lib/utils';
|
import { debounce } from '$shared/lib/utils';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default debounce delay used when no wait is provided. Picked to feel
|
||||||
|
* snappy for typing while still coalescing API-bound side effects.
|
||||||
|
*/
|
||||||
|
export const DEFAULT_DEBOUNCE_MS = 300;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates reactive state with immediate and debounced values.
|
* Creates reactive state with immediate and debounced values.
|
||||||
*
|
*
|
||||||
@@ -23,7 +29,7 @@ import { debounce } from '$shared/lib/utils';
|
|||||||
* <p>Searching: {search.debounced}</p>
|
* <p>Searching: {search.debounced}</p>
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
export function createDebouncedState<T>(initialValue: T, wait: number = 300) {
|
export function createDebouncedState<T>(initialValue: T, wait: number = DEFAULT_DEBOUNCE_MS) {
|
||||||
let immediate = $state(initialValue);
|
let immediate = $state(initialValue);
|
||||||
let debounced = $state(initialValue);
|
let debounced = $state(initialValue);
|
||||||
|
|
||||||
|
|||||||
@@ -34,6 +34,13 @@ import { Spring } from 'svelte/motion';
|
|||||||
*/
|
*/
|
||||||
const PERSPECTIVE_SPRING_CONFIG = { stiffness: 0.2, damping: 0.8 } as const;
|
const PERSPECTIVE_SPRING_CONFIG = { stiffness: 0.2, damping: 0.8 } as const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Halfway threshold on the 0→1 spring value. Above flips `isBack`,
|
||||||
|
* below flips `isFront`. Picking 0.5 means both states flip at the
|
||||||
|
* exact midpoint of the animation.
|
||||||
|
*/
|
||||||
|
const PERSPECTIVE_TOGGLE_THRESHOLD = 0.5;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Configuration options for perspective effects
|
* Configuration options for perspective effects
|
||||||
*/
|
*/
|
||||||
@@ -107,7 +114,7 @@ export class PerspectiveManager {
|
|||||||
* Content should appear blurred, scaled down, and less interactive
|
* Content should appear blurred, scaled down, and less interactive
|
||||||
* when this is true. Derived from spring value > 0.5.
|
* when this is true. Derived from spring value > 0.5.
|
||||||
*/
|
*/
|
||||||
isBack = $derived(this.spring.current > 0.5);
|
isBack = $derived(this.spring.current > PERSPECTIVE_TOGGLE_THRESHOLD);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reactive state: true when in front position
|
* Reactive state: true when in front position
|
||||||
@@ -115,7 +122,7 @@ export class PerspectiveManager {
|
|||||||
* Content should be fully visible, sharp, and interactive
|
* Content should be fully visible, sharp, and interactive
|
||||||
* when this is true. Derived from spring value < 0.5.
|
* when this is true. Derived from spring value < 0.5.
|
||||||
*/
|
*/
|
||||||
isFront = $derived(this.spring.current < 0.5);
|
isFront = $derived(this.spring.current < PERSPECTIVE_TOGGLE_THRESHOLD);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Internal configuration with defaults applied
|
* Internal configuration with defaults applied
|
||||||
|
|||||||
@@ -4,6 +4,12 @@
|
|||||||
* Used to render visible items with absolute positioning based on computed offsets.
|
* Used to render visible items with absolute positioning based on computed offsets.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Minimum height delta (in px) required to commit a re-measured row height.
|
||||||
|
* Sub-pixel diffs are treated as measurement noise to avoid spurious re-flows.
|
||||||
|
*/
|
||||||
|
const MEASUREMENT_EPSILON_PX = 0.5;
|
||||||
|
|
||||||
export interface VirtualItem {
|
export interface VirtualItem {
|
||||||
/**
|
/**
|
||||||
* Index of the item in the data array
|
* Index of the item in the data array
|
||||||
@@ -381,8 +387,8 @@ export function createVirtualizer<T>(
|
|||||||
if (!isNaN(index)) {
|
if (!isNaN(index)) {
|
||||||
const oldHeight = measuredSizes[index];
|
const oldHeight = measuredSizes[index];
|
||||||
|
|
||||||
// Only update if the height difference is significant (> 0.5px)
|
// Only update if the height difference is significant
|
||||||
if (oldHeight === undefined || Math.abs(oldHeight - height) > 0.5) {
|
if (oldHeight === undefined || Math.abs(oldHeight - height) > MEASUREMENT_EPSILON_PX) {
|
||||||
measurementBuffer[index] = height;
|
measurementBuffer[index] = height;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user