diff --git a/src/entities/Font/model/store/fontStore/fontStore.svelte.spec.ts b/src/entities/Font/model/store/fontStore/fontStore.svelte.spec.ts index 7229645..d571544 100644 --- a/src/entities/Font/model/store/fontStore/fontStore.svelte.spec.ts +++ b/src/entities/Font/model/store/fontStore/fontStore.svelte.spec.ts @@ -1,7 +1,4 @@ -import { - type InfiniteData, - QueryClient, -} from '@tanstack/query-core'; +import { QueryClient } from '@tanstack/query-core'; import { flushSync } from 'svelte'; import { afterEach, diff --git a/src/entities/Font/model/store/fontStore/fontStore.svelte.ts b/src/entities/Font/model/store/fontStore/fontStore.svelte.ts index fa1111f..78ff5f3 100644 --- a/src/entities/Font/model/store/fontStore/fontStore.svelte.ts +++ b/src/entities/Font/model/store/fontStore/fontStore.svelte.ts @@ -47,26 +47,49 @@ export class FontStore { return this.#params; } get fonts(): UnifiedFont[] { - return []; + return this.#result.data?.pages.flatMap((p: FontPage) => p.fonts) ?? []; } get isLoading(): boolean { - return false; + return this.#result.isLoading; } get isFetching(): boolean { - return false; + return this.#result.isFetching; } get isError(): boolean { - return false; + return this.#result.isError; } + get error(): Error | null { - return null; + return this.#result.error ?? null; } + // isEmpty is false during loading/fetching so the UI never flashes "no results" + // while a fetch is in progress. The !isFetching guard is specifically for the filter-change + // transition: fonts clear synchronously → isFetching becomes true → isEmpty stays false. get isEmpty(): boolean { - return false; + return !this.isLoading && !this.isFetching && this.fonts.length === 0; } get pagination() { - return { total: 0, limit: 50, offset: 0, hasMore: false, page: 1, totalPages: 0 }; + const pages = this.#result.data?.pages; + const last = pages?.at(-1); + if (!last) { + return { + total: 0, + limit: this.#params.limit ?? 50, + offset: 0, + hasMore: false, + page: 1, + totalPages: 0, + }; + } + return { + total: last.total, + limit: last.limit, + offset: last.offset, + hasMore: this.#result.hasNextPage, + page: pages!.length, + totalPages: Math.ceil(last.total / last.limit), + }; } // -- Lifecycle -- @@ -124,17 +147,30 @@ export class FontStore { // -- Private helpers (TypeScript-private so tests can spy via `as any`) -- private buildQueryKey(params: FontStoreParams): readonly unknown[] { - return ['fonts', params]; + const normalized = Object.entries(params).reduce>((acc, [k, v]) => { + if (v === undefined || v === '' || (Array.isArray(v) && v.length === 0)) return acc; + return { ...acc, [k]: v }; + }, {}); + return ['fonts', normalized] as const; } private buildOptions(params = this.#params) { + const hasFilters = !!( + params.q + || (Array.isArray(params.providers) && params.providers.length > 0) + || (Array.isArray(params.categories) && params.categories.length > 0) + || (Array.isArray(params.subsets) && params.subsets.length > 0) + ); return { queryKey: this.buildQueryKey(params), queryFn: ({ pageParam }: QueryFunctionContext) => this.fetchPage({ ...this.#params, ...pageParam }), initialPageParam: { offset: 0 } as PageParam, - getNextPageParam: (_lastPage: FontPage): PageParam | undefined => undefined, - staleTime: 5 * 60 * 1000, + getNextPageParam: (lastPage: FontPage): PageParam | undefined => { + const next = lastPage.offset + lastPage.limit; + return next < lastPage.total ? { offset: next } : undefined; + }, + staleTime: hasFilters ? 0 : 5 * 60 * 1000, gcTime: 10 * 60 * 1000, }; }