chore: enforce brackets for if clause and for/while loops
This commit is contained in:
@@ -31,7 +31,12 @@
|
|||||||
"importDeclaration.forceMultiLine": "whenMultiple",
|
"importDeclaration.forceMultiLine": "whenMultiple",
|
||||||
"importDeclaration.forceSingleLine": false,
|
"importDeclaration.forceSingleLine": false,
|
||||||
"exportDeclaration.forceMultiLine": "whenMultiple",
|
"exportDeclaration.forceMultiLine": "whenMultiple",
|
||||||
"exportDeclaration.forceSingleLine": false
|
"exportDeclaration.forceSingleLine": false,
|
||||||
|
"ifStatement.useBraces": "always",
|
||||||
|
"whileStatement.useBraces": "always",
|
||||||
|
"forStatement.useBraces": "always",
|
||||||
|
"forInStatement.useBraces": "always",
|
||||||
|
"forOfStatement.useBraces": "always"
|
||||||
},
|
},
|
||||||
"json": {
|
"json": {
|
||||||
"indentWidth": 2,
|
"indentWidth": 2,
|
||||||
|
|||||||
@@ -105,13 +105,17 @@ class ScrollBreadcrumbsStore {
|
|||||||
* (fires as soon as any part of element crosses viewport edge).
|
* (fires as soon as any part of element crosses viewport edge).
|
||||||
*/
|
*/
|
||||||
#initObserver(): void {
|
#initObserver(): void {
|
||||||
if (this.#observer) return;
|
if (this.#observer) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this.#observer = new IntersectionObserver(
|
this.#observer = new IntersectionObserver(
|
||||||
entries => {
|
entries => {
|
||||||
for (const entry of entries) {
|
for (const entry of entries) {
|
||||||
const item = this.#items.find(i => i.element === entry.target);
|
const item = this.#items.find(i => i.element === entry.target);
|
||||||
if (!item) continue;
|
if (!item) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (!entry.isIntersecting && this.#isScrollingDown) {
|
if (!entry.isIntersecting && this.#isScrollingDown) {
|
||||||
// Element exited viewport while scrolling DOWN - add to breadcrumbs
|
// Element exited viewport while scrolling DOWN - add to breadcrumbs
|
||||||
@@ -199,7 +203,9 @@ class ScrollBreadcrumbsStore {
|
|||||||
* @param offset - Scroll offset in pixels (for sticky headers)
|
* @param offset - Scroll offset in pixels (for sticky headers)
|
||||||
*/
|
*/
|
||||||
add(item: BreadcrumbItem, offset = 0): void {
|
add(item: BreadcrumbItem, offset = 0): void {
|
||||||
if (this.#items.find(i => i.index === item.index)) return;
|
if (this.#items.find(i => i.index === item.index)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this.#scrollOffset = offset;
|
this.#scrollOffset = offset;
|
||||||
this.#items.push(item);
|
this.#items.push(item);
|
||||||
@@ -216,7 +222,9 @@ class ScrollBreadcrumbsStore {
|
|||||||
*/
|
*/
|
||||||
remove(index: number): void {
|
remove(index: number): void {
|
||||||
const item = this.#items.find(i => i.index === index);
|
const item = this.#items.find(i => i.index === index);
|
||||||
if (!item) return;
|
if (!item) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this.#observer?.unobserve(item.element);
|
this.#observer?.unobserve(item.element);
|
||||||
this.#items = this.#items.filter(i => i.index !== index);
|
this.#items = this.#items.filter(i => i.index !== index);
|
||||||
@@ -237,7 +245,9 @@ class ScrollBreadcrumbsStore {
|
|||||||
*/
|
*/
|
||||||
scrollTo(index: number, container: HTMLElement | Window = window): void {
|
scrollTo(index: number, container: HTMLElement | Window = window): void {
|
||||||
const item = this.#items.find(i => i.index === index);
|
const item = this.#items.find(i => i.index === index);
|
||||||
if (!item) return;
|
if (!item) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const rect = item.element.getBoundingClientRect();
|
const rect = item.element.getBoundingClientRect();
|
||||||
const scrollTop = container === window ? window.scrollY : (container as HTMLElement).scrollTop;
|
const scrollTop = container === window ? window.scrollY : (container as HTMLElement).scrollTop;
|
||||||
|
|||||||
@@ -26,7 +26,9 @@ class MockIntersectionObserver implements IntersectionObserver {
|
|||||||
|
|
||||||
constructor(callback: IntersectionObserverCallback, options?: IntersectionObserverInit) {
|
constructor(callback: IntersectionObserverCallback, options?: IntersectionObserverInit) {
|
||||||
this.callbacks.push(callback);
|
this.callbacks.push(callback);
|
||||||
if (options?.rootMargin) this.rootMargin = options.rootMargin;
|
if (options?.rootMargin) {
|
||||||
|
this.rootMargin = options.rootMargin;
|
||||||
|
}
|
||||||
if (options?.threshold) {
|
if (options?.threshold) {
|
||||||
this.thresholds = Array.isArray(options.threshold) ? options.threshold : [options.threshold];
|
this.thresholds = Array.isArray(options.threshold) ? options.threshold : [options.threshold];
|
||||||
}
|
}
|
||||||
@@ -120,7 +122,9 @@ describe('ScrollBreadcrumbsStore', () => {
|
|||||||
(event: string, listener: EventListenerOrEventListenerObject) => {
|
(event: string, listener: EventListenerOrEventListenerObject) => {
|
||||||
if (event === 'scroll') {
|
if (event === 'scroll') {
|
||||||
const index = scrollListeners.indexOf(listener as () => void);
|
const index = scrollListeners.indexOf(listener as () => void);
|
||||||
if (index > -1) scrollListeners.splice(index, 1);
|
if (index > -1) {
|
||||||
|
scrollListeners.splice(index, 1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return undefined;
|
return undefined;
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -197,7 +197,9 @@ export async function fetchProxyFontById(
|
|||||||
* @returns Promise resolving to an array of fonts
|
* @returns Promise resolving to an array of fonts
|
||||||
*/
|
*/
|
||||||
export async function fetchFontsByIds(ids: string[]): Promise<UnifiedFont[]> {
|
export async function fetchFontsByIds(ids: string[]): Promise<UnifiedFont[]> {
|
||||||
if (ids.length === 0) return [];
|
if (ids.length === 0) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
const queryString = ids.join(',');
|
const queryString = ids.join(',');
|
||||||
const url = `${PROXY_API_URL}/batch?ids=${queryString}`;
|
const url = `${PROXY_API_URL}/batch?ids=${queryString}`;
|
||||||
|
|||||||
@@ -529,8 +529,12 @@ export function createMockStore<T>(config: {
|
|||||||
* Returns semantic status string
|
* Returns semantic status string
|
||||||
*/
|
*/
|
||||||
get status() {
|
get status() {
|
||||||
if (isLoading) return 'pending';
|
if (isLoading) {
|
||||||
if (isError) return 'error';
|
return 'pending';
|
||||||
|
}
|
||||||
|
if (isError) {
|
||||||
|
return 'error';
|
||||||
|
}
|
||||||
return 'success';
|
return 'success';
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -93,12 +93,16 @@ export function createFontRowSizeResolver(options: FontRowSizeResolverOptions):
|
|||||||
return function resolveRowHeight(rowIndex: number): number {
|
return function resolveRowHeight(rowIndex: number): number {
|
||||||
const fonts = options.getFonts();
|
const fonts = options.getFonts();
|
||||||
const font = fonts[rowIndex];
|
const font = fonts[rowIndex];
|
||||||
if (!font) return options.fallbackHeight;
|
if (!font) {
|
||||||
|
return options.fallbackHeight;
|
||||||
|
}
|
||||||
|
|
||||||
const containerWidth = options.getContainerWidth();
|
const containerWidth = options.getContainerWidth();
|
||||||
const previewText = options.getPreviewText();
|
const previewText = options.getPreviewText();
|
||||||
|
|
||||||
if (containerWidth <= 0 || !previewText) return options.fallbackHeight;
|
if (containerWidth <= 0 || !previewText) {
|
||||||
|
return options.fallbackHeight;
|
||||||
|
}
|
||||||
|
|
||||||
const weight = options.getWeight();
|
const weight = options.getWeight();
|
||||||
// generateFontKey: '{id}@{weight}' for static fonts, '{id}@vf' for variable fonts.
|
// generateFontKey: '{id}@{weight}' for static fonts, '{id}@vf' for variable fonts.
|
||||||
@@ -107,7 +111,9 @@ export function createFontRowSizeResolver(options: FontRowSizeResolverOptions):
|
|||||||
// Reading via getStatus() allows the caller to pass appliedFontsManager.statuses.get(),
|
// Reading via getStatus() allows the caller to pass appliedFontsManager.statuses.get(),
|
||||||
// which creates a Svelte 5 reactive dependency when called inside $derived.by.
|
// which creates a Svelte 5 reactive dependency when called inside $derived.by.
|
||||||
const status = options.getStatus(fontKey);
|
const status = options.getStatus(fontKey);
|
||||||
if (status !== 'loaded') return options.fallbackHeight;
|
if (status !== 'loaded') {
|
||||||
|
return options.fallbackHeight;
|
||||||
|
}
|
||||||
|
|
||||||
const fontSizePx = options.getFontSizePx();
|
const fontSizePx = options.getFontSizePx();
|
||||||
const lineHeightPx = options.getLineHeightPx();
|
const lineHeightPx = options.getLineHeightPx();
|
||||||
@@ -116,7 +122,9 @@ export function createFontRowSizeResolver(options: FontRowSizeResolverOptions):
|
|||||||
|
|
||||||
const cacheKey = `${fontCssString}|${previewText}|${contentWidth}|${lineHeightPx}`;
|
const cacheKey = `${fontCssString}|${previewText}|${contentWidth}|${lineHeightPx}`;
|
||||||
const cached = cache.get(cacheKey);
|
const cached = cache.get(cacheKey);
|
||||||
if (cached !== undefined) return cached;
|
if (cached !== undefined) {
|
||||||
|
return cached;
|
||||||
|
}
|
||||||
|
|
||||||
const { totalHeight } = engine.layout(previewText, fontCssString, contentWidth, lineHeightPx);
|
const { totalHeight } = engine.layout(previewText, fontCssString, contentWidth, lineHeightPx);
|
||||||
const result = totalHeight + options.chromeHeight;
|
const result = totalHeight + options.chromeHeight;
|
||||||
|
|||||||
@@ -246,12 +246,16 @@ export class AppliedFontsManager {
|
|||||||
);
|
);
|
||||||
|
|
||||||
for (const result of results) {
|
for (const result of results) {
|
||||||
if (result.ok) continue;
|
if (result.ok) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
const { key, config, reason } = result;
|
const { key, config, reason } = result;
|
||||||
const isAbort = reason instanceof FontFetchError
|
const isAbort = reason instanceof FontFetchError
|
||||||
&& reason.cause instanceof Error
|
&& reason.cause instanceof Error
|
||||||
&& reason.cause.name === 'AbortError';
|
&& reason.cause.name === 'AbortError';
|
||||||
if (isAbort) continue;
|
if (isAbort) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if (reason instanceof FontFetchError) {
|
if (reason instanceof FontFetchError) {
|
||||||
console.error(`Font fetch failed: ${config.name}`, reason);
|
console.error(`Font fetch failed: ${config.name}`, reason);
|
||||||
}
|
}
|
||||||
@@ -293,7 +297,9 @@ export class AppliedFontsManager {
|
|||||||
|
|
||||||
// Remove FontFace from document to free memory
|
// Remove FontFace from document to free memory
|
||||||
const font = this.#loadedFonts.get(key);
|
const font = this.#loadedFonts.get(key);
|
||||||
if (font) document.fonts.delete(font);
|
if (font) {
|
||||||
|
document.fonts.delete(font);
|
||||||
|
}
|
||||||
|
|
||||||
// Evict from cache and cleanup URL mapping
|
// Evict from cache and cleanup URL mapping
|
||||||
const url = this.#urlByKey.get(key);
|
const url = this.#urlByKey.get(key);
|
||||||
|
|||||||
@@ -15,7 +15,9 @@ import type { UnifiedFont } from '../../model/types';
|
|||||||
* Standalone function to avoid 'this' issues during construction.
|
* Standalone function to avoid 'this' issues during construction.
|
||||||
*/
|
*/
|
||||||
async function fetchAndSeed(ids: string[]): Promise<UnifiedFont[]> {
|
async function fetchAndSeed(ids: string[]): Promise<UnifiedFont[]> {
|
||||||
if (ids.length === 0) return [];
|
if (ids.length === 0) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
let response: UnifiedFont[];
|
let response: UnifiedFont[];
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -166,7 +166,9 @@ export class FontStore {
|
|||||||
const data = this.#qc.getQueryData<InfiniteData<ProxyFontsResponse, PageParam>>(
|
const data = this.#qc.getQueryData<InfiniteData<ProxyFontsResponse, PageParam>>(
|
||||||
this.buildQueryKey(this.#params),
|
this.buildQueryKey(this.#params),
|
||||||
);
|
);
|
||||||
if (!data) return undefined;
|
if (!data) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
return data.pages.flatMap(p => p.fonts);
|
return data.pages.flatMap(p => p.fonts);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -336,9 +338,15 @@ export class FontStore {
|
|||||||
throw new FontNetworkError(cause);
|
throw new FontNetworkError(cause);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!response) throw new FontResponseError('response', response);
|
if (!response) {
|
||||||
if (!response.fonts) throw new FontResponseError('response.fonts', response.fonts);
|
throw new FontResponseError('response', response);
|
||||||
if (!Array.isArray(response.fonts)) throw new FontResponseError('response.fonts', response.fonts);
|
}
|
||||||
|
if (!response.fonts) {
|
||||||
|
throw new FontResponseError('response.fonts', response.fonts);
|
||||||
|
}
|
||||||
|
if (!Array.isArray(response.fonts)) {
|
||||||
|
throw new FontResponseError('response.fonts', response.fonts);
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
fonts: response.fonts,
|
fonts: response.fonts,
|
||||||
|
|||||||
@@ -65,7 +65,9 @@ function handleInternalVisibleChange(items: UnifiedFont[]) {
|
|||||||
$effect(() => {
|
$effect(() => {
|
||||||
const configs: FontLoadRequestConfig[] = visibleFonts.flatMap(item => {
|
const configs: FontLoadRequestConfig[] = visibleFonts.flatMap(item => {
|
||||||
const url = getFontUrl(item, weight);
|
const url = getFontUrl(item, weight);
|
||||||
if (!url) return [];
|
if (!url) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
return [{ id: item.id, name: item.name, weight, url, isVariable: item.features?.isVariable }];
|
return [{ id: item.id, name: item.name, weight, url, isVariable: item.features?.isVariable }];
|
||||||
});
|
});
|
||||||
if (configs.length > 0) {
|
if (configs.length > 0) {
|
||||||
|
|||||||
@@ -128,7 +128,9 @@ export class TypographySettingsManager {
|
|||||||
// This handles the "Multiplier" logic specifically for the Font Size Control
|
// This handles the "Multiplier" logic specifically for the Font Size Control
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
const ctrl = this.#controls.get('font_size')?.instance;
|
const ctrl = this.#controls.get('font_size')?.instance;
|
||||||
if (!ctrl) return;
|
if (!ctrl) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// If the user moves the slider/clicks buttons in the UI:
|
// If the user moves the slider/clicks buttons in the UI:
|
||||||
// We update the baseSize (User Intent)
|
// We update the baseSize (User Intent)
|
||||||
@@ -147,10 +149,18 @@ export class TypographySettingsManager {
|
|||||||
* Gets initial value for a control from storage or defaults
|
* Gets initial value for a control from storage or defaults
|
||||||
*/
|
*/
|
||||||
#getInitialValue(id: string, saved: TypographySettings): number {
|
#getInitialValue(id: string, saved: TypographySettings): number {
|
||||||
if (id === 'font_size') return saved.fontSize * this.#multiplier;
|
if (id === 'font_size') {
|
||||||
if (id === 'font_weight') return saved.fontWeight;
|
return saved.fontSize * this.#multiplier;
|
||||||
if (id === 'line_height') return saved.lineHeight;
|
}
|
||||||
if (id === 'letter_spacing') return saved.letterSpacing;
|
if (id === 'font_weight') {
|
||||||
|
return saved.fontWeight;
|
||||||
|
}
|
||||||
|
if (id === 'line_height') {
|
||||||
|
return saved.lineHeight;
|
||||||
|
}
|
||||||
|
if (id === 'letter_spacing') {
|
||||||
|
return saved.letterSpacing;
|
||||||
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -165,7 +175,9 @@ export class TypographySettingsManager {
|
|||||||
* Updates the multiplier and recalculates dependent control values
|
* Updates the multiplier and recalculates dependent control values
|
||||||
*/
|
*/
|
||||||
set multiplier(value: number) {
|
set multiplier(value: number) {
|
||||||
if (this.#multiplier === value) return;
|
if (this.#multiplier === value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.#multiplier = value;
|
this.#multiplier = value;
|
||||||
|
|
||||||
// When multiplier changes, we must update the Font Size Control's display value
|
// When multiplier changes, we must update the Font Size Control's display value
|
||||||
@@ -192,7 +204,9 @@ export class TypographySettingsManager {
|
|||||||
set baseSize(val: number) {
|
set baseSize(val: number) {
|
||||||
this.#baseSize = val;
|
this.#baseSize = val;
|
||||||
const ctrl = this.#controls.get('font_size')?.instance;
|
const ctrl = this.#controls.get('font_size')?.instance;
|
||||||
if (ctrl) ctrl.value = val * this.#multiplier;
|
if (ctrl) {
|
||||||
|
ctrl.value = val * this.#multiplier;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -268,9 +282,15 @@ export class TypographySettingsManager {
|
|||||||
// Map storage key to control id
|
// Map storage key to control id
|
||||||
const key = c.id.replace('_', '') as keyof TypographySettings;
|
const key = c.id.replace('_', '') as keyof TypographySettings;
|
||||||
// Simplified for brevity, you'd map these properly:
|
// Simplified for brevity, you'd map these properly:
|
||||||
if (c.id === 'font_weight') c.instance.value = defaults.fontWeight;
|
if (c.id === 'font_weight') {
|
||||||
if (c.id === 'line_height') c.instance.value = defaults.lineHeight;
|
c.instance.value = defaults.fontWeight;
|
||||||
if (c.id === 'letter_spacing') c.instance.value = defaults.letterSpacing;
|
}
|
||||||
|
if (c.id === 'line_height') {
|
||||||
|
c.instance.value = defaults.lineHeight;
|
||||||
|
}
|
||||||
|
if (c.id === 'letter_spacing') {
|
||||||
|
c.instance.value = defaults.letterSpacing;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,7 +48,9 @@ let isOpen = $state(false);
|
|||||||
* Sets the common font size multiplier based on the current responsive state.
|
* Sets the common font size multiplier based on the current responsive state.
|
||||||
*/
|
*/
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
if (!responsive) return;
|
if (!responsive) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
switch (true) {
|
switch (true) {
|
||||||
case responsive.isMobile:
|
case responsive.isMobile:
|
||||||
typographySettingsStore.multiplier = MULTIPLIER_S;
|
typographySettingsStore.multiplier = MULTIPLIER_S;
|
||||||
|
|||||||
@@ -171,7 +171,9 @@ export class CharacterComparisonEngine {
|
|||||||
|
|
||||||
for (let sIdx = start.segmentIndex; sIdx <= end.segmentIndex; sIdx++) {
|
for (let sIdx = start.segmentIndex; sIdx <= end.segmentIndex; sIdx++) {
|
||||||
const segmentText = this.#preparedA!.segments[sIdx];
|
const segmentText = this.#preparedA!.segments[sIdx];
|
||||||
if (segmentText === undefined) continue;
|
if (segmentText === undefined) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
// PERFORMANCE: Reuse segmenter results if possible, but for now just optimize the loop
|
// PERFORMANCE: Reuse segmenter results if possible, but for now just optimize the loop
|
||||||
const graphemes = Array.from(this.#segmenter.segment(segmentText), s => s.segment);
|
const graphemes = Array.from(this.#segmenter.segment(segmentText), s => s.segment);
|
||||||
@@ -242,7 +244,9 @@ export class CharacterComparisonEngine {
|
|||||||
const line = this.#lastResult.lines[lineIndex];
|
const line = this.#lastResult.lines[lineIndex];
|
||||||
const char = line.chars[charIndex];
|
const char = line.chars[charIndex];
|
||||||
|
|
||||||
if (!char) return { proximity: 0, isPast: false };
|
if (!char) {
|
||||||
|
return { proximity: 0, isPast: false };
|
||||||
|
}
|
||||||
|
|
||||||
// Center the comparison on the unified width
|
// Center the comparison on the unified width
|
||||||
// In the UI, lines are centered. So we need to calculate the global X.
|
// In the UI, lines are centered. So we need to calculate the global X.
|
||||||
@@ -279,9 +283,15 @@ export class CharacterComparisonEngine {
|
|||||||
|
|
||||||
unified.breakableFitAdvances = intA.breakableFitAdvances.map((advA: number[] | null, i: number) => {
|
unified.breakableFitAdvances = intA.breakableFitAdvances.map((advA: number[] | null, i: number) => {
|
||||||
const advB = intB.breakableFitAdvances[i];
|
const advB = intB.breakableFitAdvances[i];
|
||||||
if (!advA && !advB) return null;
|
if (!advA && !advB) {
|
||||||
if (!advA) return advB;
|
return null;
|
||||||
if (!advB) return advA;
|
}
|
||||||
|
if (!advA) {
|
||||||
|
return advB;
|
||||||
|
}
|
||||||
|
if (!advB) {
|
||||||
|
return advA;
|
||||||
|
}
|
||||||
|
|
||||||
return advA.map((w: number, j: number) => Math.max(w, advB[j]));
|
return advA.map((w: number, j: number) => Math.max(w, advB[j]));
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -127,7 +127,9 @@ export class TextLayoutEngine {
|
|||||||
// Both cursors are grapheme-level: start is inclusive, end is exclusive.
|
// Both cursors are grapheme-level: start is inclusive, end is exclusive.
|
||||||
for (let sIdx = start.segmentIndex; sIdx <= end.segmentIndex; sIdx++) {
|
for (let sIdx = start.segmentIndex; sIdx <= end.segmentIndex; sIdx++) {
|
||||||
const segmentText = prepared.segments[sIdx];
|
const segmentText = prepared.segments[sIdx];
|
||||||
if (segmentText === undefined) continue;
|
if (segmentText === undefined) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
const graphemes = Array.from(this.#segmenter.segment(segmentText), s => s.segment);
|
const graphemes = Array.from(this.#segmenter.segment(segmentText), s => s.segment);
|
||||||
const advances = breakableFitAdvances[sIdx];
|
const advances = breakableFitAdvances[sIdx];
|
||||||
|
|||||||
@@ -150,7 +150,9 @@ export function createResponsiveManager(customBreakpoints?: Partial<Breakpoints>
|
|||||||
* @returns Cleanup function to remove listeners
|
* @returns Cleanup function to remove listeners
|
||||||
*/
|
*/
|
||||||
function init() {
|
function init() {
|
||||||
if (typeof window === 'undefined') return;
|
if (typeof window === 'undefined') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const handleResize = () => {
|
const handleResize = () => {
|
||||||
width = window.innerWidth;
|
width = window.innerWidth;
|
||||||
|
|||||||
@@ -175,7 +175,9 @@ export function createVirtualizer<T>(
|
|||||||
const { count, data } = options;
|
const { count, data } = options;
|
||||||
// Implicit dependency
|
// Implicit dependency
|
||||||
const v = _version;
|
const v = _version;
|
||||||
if (count === 0 || containerHeight === 0 || !data) return [];
|
if (count === 0 || containerHeight === 0 || !data) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
const overscan = options.overscan ?? 5;
|
const overscan = options.overscan ?? 5;
|
||||||
|
|
||||||
@@ -264,7 +266,9 @@ export function createVirtualizer<T>(
|
|||||||
containerHeight = window.innerHeight;
|
containerHeight = window.innerHeight;
|
||||||
|
|
||||||
const handleScroll = () => {
|
const handleScroll = () => {
|
||||||
if (rafId !== null) return;
|
if (rafId !== null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
rafId = requestAnimationFrame(() => {
|
rafId = requestAnimationFrame(() => {
|
||||||
// Get current position of element relative to viewport
|
// Get current position of element relative to viewport
|
||||||
@@ -323,7 +327,9 @@ export function createVirtualizer<T>(
|
|||||||
};
|
};
|
||||||
|
|
||||||
const resizeObserver = new ResizeObserver(([entry]) => {
|
const resizeObserver = new ResizeObserver(([entry]) => {
|
||||||
if (entry) containerHeight = entry.contentRect.height;
|
if (entry) {
|
||||||
|
containerHeight = entry.contentRect.height;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
node.addEventListener('scroll', handleScroll, { passive: true });
|
node.addEventListener('scroll', handleScroll, { passive: true });
|
||||||
@@ -423,7 +429,9 @@ export function createVirtualizer<T>(
|
|||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
function scrollToIndex(index: number, align: 'start' | 'center' | 'end' | 'auto' = 'auto') {
|
function scrollToIndex(index: number, align: 'start' | 'center' | 'end' | 'auto' = 'auto') {
|
||||||
if (!elementRef || index < 0 || index >= options.count) return;
|
if (!elementRef || index < 0 || index >= options.count) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const itemStart = offsets[index];
|
const itemStart = offsets[index];
|
||||||
const itemSize = measuredSizes[index] ?? options.estimateSize(index);
|
const itemSize = measuredSizes[index] ?? options.estimateSize(index);
|
||||||
@@ -431,16 +439,24 @@ export function createVirtualizer<T>(
|
|||||||
const { useWindowScroll } = optionsGetter();
|
const { useWindowScroll } = optionsGetter();
|
||||||
|
|
||||||
if (useWindowScroll) {
|
if (useWindowScroll) {
|
||||||
if (align === 'center') target = itemStart - window.innerHeight / 2 + itemSize / 2;
|
if (align === 'center') {
|
||||||
if (align === 'end') target = itemStart - window.innerHeight + itemSize;
|
target = itemStart - window.innerHeight / 2 + itemSize / 2;
|
||||||
|
}
|
||||||
|
if (align === 'end') {
|
||||||
|
target = itemStart - window.innerHeight + itemSize;
|
||||||
|
}
|
||||||
|
|
||||||
// Add container offset to target to get absolute document position
|
// Add container offset to target to get absolute document position
|
||||||
const absoluteTarget = target + elementOffsetTop;
|
const absoluteTarget = target + elementOffsetTop;
|
||||||
|
|
||||||
window.scrollTo({ top: absoluteTarget, behavior: 'smooth' });
|
window.scrollTo({ top: absoluteTarget, behavior: 'smooth' });
|
||||||
} else {
|
} else {
|
||||||
if (align === 'center') target = itemStart - containerHeight / 2 + itemSize / 2;
|
if (align === 'center') {
|
||||||
if (align === 'end') target = itemStart - containerHeight + itemSize;
|
target = itemStart - containerHeight / 2 + itemSize / 2;
|
||||||
|
}
|
||||||
|
if (align === 'end') {
|
||||||
|
target = itemStart - containerHeight + itemSize;
|
||||||
|
}
|
||||||
|
|
||||||
elementRef.scrollTo({ top: target, behavior: 'smooth' });
|
elementRef.scrollTo({ top: target, behavior: 'smooth' });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,9 @@ export function smoothScroll(node: HTMLAnchorElement) {
|
|||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
const hash = node.getAttribute('href');
|
const hash = node.getAttribute('href');
|
||||||
if (!hash || hash === '#') return;
|
if (!hash || hash === '#') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const targetElement = document.querySelector(hash);
|
const targetElement = document.querySelector(hash);
|
||||||
|
|
||||||
|
|||||||
@@ -35,7 +35,9 @@ export function throttle<T extends (...args: any[]) => any>(
|
|||||||
fn(...args);
|
fn(...args);
|
||||||
} else {
|
} else {
|
||||||
// Schedule for end of wait period (trailing edge)
|
// Schedule for end of wait period (trailing edge)
|
||||||
if (timeoutId) clearTimeout(timeoutId);
|
if (timeoutId) {
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
}
|
||||||
timeoutId = setTimeout(() => {
|
timeoutId = setTimeout(() => {
|
||||||
lastCall = Date.now();
|
lastCall = Date.now();
|
||||||
fn(...args);
|
fn(...args);
|
||||||
|
|||||||
@@ -66,7 +66,9 @@ let open = $state(false);
|
|||||||
// Smart value formatting matching the Figma design
|
// Smart value formatting matching the Figma design
|
||||||
const formattedValue = $derived(() => {
|
const formattedValue = $derived(() => {
|
||||||
const v = control.value;
|
const v = control.value;
|
||||||
if (Number.isInteger(v)) return String(v);
|
if (Number.isInteger(v)) {
|
||||||
|
return String(v);
|
||||||
|
}
|
||||||
return control.step < 0.1 ? v.toFixed(2) : v.toFixed(1);
|
return control.step < 0.1 ? v.toFixed(2) : v.toFixed(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -61,7 +61,9 @@ const style = $derived.by(() => {
|
|||||||
|
|
||||||
// Calculate horizontal constraints based on region
|
// Calculate horizontal constraints based on region
|
||||||
const regionStyleStr = $derived(() => {
|
const regionStyleStr = $derived(() => {
|
||||||
if (region === 'full') return '';
|
if (region === 'full') {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
const side = region === 'left' ? 'left' : 'right';
|
const side = region === 'left' ? 'left' : 'right';
|
||||||
return `position: absolute; ${side}: 0; width: ${regionWidth}%; top: 0; bottom: 0;`;
|
return `position: absolute; ${side}: 0; width: ${regionWidth}%; top: 0; bottom: 0;`;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -136,7 +136,9 @@ const estimatedTotalSize = $derived.by(() => {
|
|||||||
|
|
||||||
// Add estimated size for unloaded rows
|
// Add estimated size for unloaded rows
|
||||||
const unloadedRows = totalRows - rowCount;
|
const unloadedRows = totalRows - rowCount;
|
||||||
if (unloadedRows <= 0) return loadedSize;
|
if (unloadedRows <= 0) {
|
||||||
|
return loadedSize;
|
||||||
|
}
|
||||||
|
|
||||||
// Estimate the size of unloaded rows
|
// Estimate the size of unloaded rows
|
||||||
const estimateFn = typeof itemHeight === 'function'
|
const estimateFn = typeof itemHeight === 'function'
|
||||||
|
|||||||
@@ -95,16 +95,22 @@ export class ComparisonStore {
|
|||||||
// Effect 1: Sync batch results → fontA / fontB
|
// Effect 1: Sync batch results → fontA / fontB
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
const fonts = this.#batchStore.fonts;
|
const fonts = this.#batchStore.fonts;
|
||||||
if (fonts.length === 0) return;
|
if (fonts.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const { fontAId: aId, fontBId: bId } = storage.value;
|
const { fontAId: aId, fontBId: bId } = storage.value;
|
||||||
if (aId) {
|
if (aId) {
|
||||||
const fa = fonts.find(f => f.id === aId);
|
const fa = fonts.find(f => f.id === aId);
|
||||||
if (fa) this.#fontA = fa;
|
if (fa) {
|
||||||
|
this.#fontA = fa;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (bId) {
|
if (bId) {
|
||||||
const fb = fonts.find(f => f.id === bId);
|
const fb = fonts.find(f => f.id === bId);
|
||||||
if (fb) this.#fontB = fb;
|
if (fb) {
|
||||||
|
this.#fontB = fb;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -114,7 +120,9 @@ export class ComparisonStore {
|
|||||||
const fb = this.#fontB;
|
const fb = this.#fontB;
|
||||||
const weight = typographySettingsStore.weight;
|
const weight = typographySettingsStore.weight;
|
||||||
|
|
||||||
if (!fa || !fb) return;
|
if (!fa || !fb) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const configs: FontLoadRequestConfig[] = [];
|
const configs: FontLoadRequestConfig[] = [];
|
||||||
[fa, fb].forEach(f => {
|
[fa, fb].forEach(f => {
|
||||||
@@ -138,7 +146,9 @@ export class ComparisonStore {
|
|||||||
|
|
||||||
// Effect 3: Set default fonts when storage is empty
|
// Effect 3: Set default fonts when storage is empty
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
if (this.#fontA && this.#fontB) return;
|
if (this.#fontA && this.#fontB) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const fonts = fontStore.fonts;
|
const fonts = fontStore.fonts;
|
||||||
if (fonts.length >= 2) {
|
if (fonts.length >= 2) {
|
||||||
@@ -156,11 +166,19 @@ export class ComparisonStore {
|
|||||||
const fa = this.#fontA;
|
const fa = this.#fontA;
|
||||||
const fb = this.#fontB;
|
const fb = this.#fontB;
|
||||||
const w = typographySettingsStore.weight;
|
const w = typographySettingsStore.weight;
|
||||||
if (fa) appliedFontsManager.pin(fa.id, w, fa.features?.isVariable);
|
if (fa) {
|
||||||
if (fb) appliedFontsManager.pin(fb.id, w, fb.features?.isVariable);
|
appliedFontsManager.pin(fa.id, w, fa.features?.isVariable);
|
||||||
|
}
|
||||||
|
if (fb) {
|
||||||
|
appliedFontsManager.pin(fb.id, w, fb.features?.isVariable);
|
||||||
|
}
|
||||||
return () => {
|
return () => {
|
||||||
if (fa) appliedFontsManager.unpin(fa.id, w, fa.features?.isVariable);
|
if (fa) {
|
||||||
if (fb) appliedFontsManager.unpin(fb.id, w, fb.features?.isVariable);
|
appliedFontsManager.unpin(fa.id, w, fa.features?.isVariable);
|
||||||
|
}
|
||||||
|
if (fb) {
|
||||||
|
appliedFontsManager.unpin(fb.id, w, fb.features?.isVariable);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -183,7 +201,9 @@ export class ComparisonStore {
|
|||||||
const fontAName = this.#fontA?.name;
|
const fontAName = this.#fontA?.name;
|
||||||
const fontBName = this.#fontB?.name;
|
const fontBName = this.#fontB?.name;
|
||||||
|
|
||||||
if (!fontAName || !fontBName) return;
|
if (!fontAName || !fontBName) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const fontAString = `${weight} ${size}px "${fontAName}"`;
|
const fontAString = `${weight} ${size}px "${fontAName}"`;
|
||||||
const fontBString = `${weight} ${size}px "${fontBName}"`;
|
const fontBString = `${weight} ${size}px "${fontBName}"`;
|
||||||
|
|||||||
@@ -35,7 +35,9 @@ const displayChar = $derived(char === ' ' ? '\u00A0' : char);
|
|||||||
const targetFont = $derived(isPast ? fontA?.name ?? '' : fontB?.name ?? '');
|
const targetFont = $derived(isPast ? fontA?.name ?? '' : fontB?.name ?? '');
|
||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
if (!targetFont || slotFonts[slot] === targetFont) return;
|
if (!targetFont || slotFonts[slot] === targetFont) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const next = slot === 0 ? 1 : 0;
|
const next = slot === 0 ? 1 : 0;
|
||||||
slotFonts[next] = targetFont;
|
slotFonts[next] = targetFont;
|
||||||
slot = next;
|
slot = next;
|
||||||
|
|||||||
@@ -65,7 +65,9 @@ const sliderSpring = new Spring(50, {
|
|||||||
const sliderPos = $derived(sliderSpring.current);
|
const sliderPos = $derived(sliderSpring.current);
|
||||||
|
|
||||||
function handleMove(e: PointerEvent) {
|
function handleMove(e: PointerEvent) {
|
||||||
if (!isDragging || !container) return;
|
if (!isDragging || !container) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const rect = container.getBoundingClientRect();
|
const rect = container.getBoundingClientRect();
|
||||||
const x = Math.max(0, Math.min(e.clientX - rect.left, rect.width));
|
const x = Math.max(0, Math.min(e.clientX - rect.left, rect.width));
|
||||||
const percentage = (x / rect.width) * 100;
|
const percentage = (x / rect.width) * 100;
|
||||||
@@ -87,7 +89,9 @@ $effect(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
if (!responsive) return;
|
if (!responsive) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
switch (true) {
|
switch (true) {
|
||||||
case responsive.isMobile:
|
case responsive.isMobile:
|
||||||
typography.multiplier = 0.5;
|
typography.multiplier = 0.5;
|
||||||
@@ -143,7 +147,9 @@ $effect(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
if (typeof window === 'undefined') return;
|
if (typeof window === 'undefined') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const handleResize = () => {
|
const handleResize = () => {
|
||||||
if (container && fontA && fontB) {
|
if (container && fontA && fontB) {
|
||||||
const width = container.offsetWidth;
|
const width = container.offsetWidth;
|
||||||
|
|||||||
@@ -46,7 +46,9 @@ let isAboveMiddle = $state(false);
|
|||||||
let containerWidth = $state(0);
|
let containerWidth = $state(0);
|
||||||
|
|
||||||
const checkPosition = throttle(() => {
|
const checkPosition = throttle(() => {
|
||||||
if (!wrapper) return;
|
if (!wrapper) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const rect = wrapper.getBoundingClientRect();
|
const rect = wrapper.getBoundingClientRect();
|
||||||
const viewportMiddle = innerHeight / 2;
|
const viewportMiddle = innerHeight / 2;
|
||||||
|
|||||||
Reference in New Issue
Block a user