@findlane/browser 0.3.0 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -8,9 +8,12 @@ export type SearchRequest = {
8
8
  /** Facets (also in `facets`) counted without the filter on their own field, so a list of choices
9
9
  * keeps its other values, with counts, after one is chosen. */
10
10
  disjunctiveFacets?: string[];
11
+ /** Number fields (up to eight) whose smallest and largest values across the matches to return in
12
+ * `facetStats`, such as a price slider's bounds. Each field's own filter is left out. */
13
+ facetStats?: string[];
11
14
  /** Hits per request: 1–50 (default 20). */
12
15
  limit?: number;
13
- /** Up to 1,000. */
16
+ /** Up to 5,000. */
14
17
  offset?: number;
15
18
  attributesToRetrieve?: string[];
16
19
  /** "page" (default) counts only the returned page when a query matches more; "exact" counts every match. */
@@ -37,6 +40,12 @@ export type SearchResponse<T> = {
37
40
  totalRelation?: "eq" | "gte";
38
41
  hasMore?: boolean;
39
42
  facets: Record<string, Record<string, number>>;
43
+ /** With `facetStats`: each field's smallest and largest value across the matches, without the field's
44
+ * own filter. A field no match has is left out. */
45
+ facetStats?: Record<string, {
46
+ min: number;
47
+ max: number;
48
+ }>;
40
49
  tookMs: number;
41
50
  sort?: string;
42
51
  /** With the index's `didYouMean` setting: the query with the words typo tolerance corrected, when the
@@ -12,7 +12,7 @@ export function searchKey(request) {
12
12
  for (const [key, value] of Object.entries(defaults))
13
13
  if (normal[key] === value)
14
14
  delete normal[key];
15
- for (const key of ["facets", "disjunctiveFacets"]) {
15
+ for (const key of ["facets", "disjunctiveFacets", "facetStats"]) {
16
16
  const list = normal[key];
17
17
  if (Array.isArray(list))
18
18
  normal[key] = list.length ? [...list].sort() : undefined;
@@ -72,9 +72,13 @@ export type Range = {
72
72
  gte?: number | string;
73
73
  lte?: number | string;
74
74
  };
75
- /** A number or date range filter. List the field in `filterFields` to keep it in the URL. */
75
+ /** A number or date range filter. List the field in `filterFields` to keep it in the URL, and in
76
+ * `facetStats` for `min` and `max`: the smallest and largest values across the matches, without
77
+ * this range, such as a slider's bounds. */
76
78
  export declare function useRange(field: string): {
77
79
  range: Range | undefined;
80
+ min: number | undefined;
81
+ max: number | undefined;
78
82
  /** Leave a bound out (or empty) for no limit on that side. */
79
83
  setRange: (range: Range | undefined) => void;
80
84
  clear: () => void;
@@ -110,7 +114,7 @@ export declare function useLoadMore(): {
110
114
  shown: number;
111
115
  total: number;
112
116
  };
113
- /** Numbered pages, from 0. The API returns hits up to the 1,000th offset, so pages stop there. */
117
+ /** Numbered pages, from 0. The API returns hits up to the 5,000th offset, so pages stop there. */
114
118
  export declare function usePagination(): {
115
119
  page: number;
116
120
  pages: number;
@@ -142,12 +142,17 @@ export function useRefinementList(field) {
142
142
  clear: () => store.update(withRefinement(field, [])),
143
143
  };
144
144
  }
145
- /** A number or date range filter. List the field in `filterFields` to keep it in the URL. */
145
+ /** A number or date range filter. List the field in `filterFields` to keep it in the URL, and in
146
+ * `facetStats` for `min` and `max`: the smallest and largest values across the matches, without
147
+ * this range, such as a slider's bounds. */
146
148
  export function useRange(field) {
147
- const [{ state }, store] = useSnapshot();
149
+ const [{ state, results }, store] = useSnapshot();
148
150
  const value = state.filters[field];
151
+ const stats = results?.response.facetStats?.[field];
149
152
  return {
150
153
  range: isRange(value) ? value : undefined,
154
+ min: stats?.min,
155
+ max: stats?.max,
151
156
  /** Leave a bound out (or empty) for no limit on that side. */
152
157
  setRange: (range) => {
153
158
  const bounds = Object.entries(range ?? {}).filter(([, bound]) => bound !== undefined && bound !== "");
@@ -203,7 +208,7 @@ export function useLoadMore() {
203
208
  total: results?.response.total ?? 0,
204
209
  };
205
210
  }
206
- /** Numbered pages, from 0. The API returns hits up to the 1,000th offset, so pages stop there. */
211
+ /** Numbered pages, from 0. The API returns hits up to the 5,000th offset, so pages stop there. */
207
212
  export function usePagination() {
208
213
  const [{ state, results }, store] = useSnapshot();
209
214
  const pageSize = store.settings.pageSize ?? 20;
@@ -18,6 +18,9 @@ export type SearchConfig = {
18
18
  facets?: string[];
19
19
  /** Fields that useRange and useToggle filter, so the URL can carry them. */
20
20
  filterFields?: string[];
21
+ /** Number fields whose smallest and largest values across the matches useRange returns as `min`
22
+ * and `max`, such as a price slider's bounds. */
23
+ facetStats?: string[];
21
24
  /** Filters always applied, such as `{ published: true }`. They aren't shown as refinements. */
22
25
  filters?: Record<string, unknown>;
23
26
  /** Hits per page: 1–50 (default 20). */
@@ -30,7 +33,7 @@ export type SearchConfig = {
30
33
  };
31
34
  export declare const emptyState: SearchState;
32
35
  /** The API's largest offset. */
33
- export declare const maxOffset = 1000;
36
+ export declare const maxOffset = 5000;
34
37
  /** The request behind a state. A facet with selections is counted without them
35
38
  * (`disjunctiveFacets`), so its other values stay listed and can be added. */
36
39
  export declare function planSearch(config: SearchConfig, state: SearchState): SearchRequest;
@@ -1,6 +1,6 @@
1
1
  export const emptyState = { q: "", refinements: {}, filters: {}, page: 0 };
2
2
  /** The API's largest offset. */
3
- export const maxOffset = 1000;
3
+ export const maxOffset = 5000;
4
4
  /** The request behind a state. A facet with selections is counted without them
5
5
  * (`disjunctiveFacets`), so its other values stay listed and can be added. */
6
6
  export function planSearch(config, state) {
@@ -19,6 +19,7 @@ export function planSearch(config, state) {
19
19
  ...(Object.keys(filters).length ? { filters } : {}),
20
20
  ...(facets.length ? { facets } : {}),
21
21
  ...(disjunctive.length ? { disjunctiveFacets: disjunctive } : {}),
22
+ ...(config.facetStats?.length ? { facetStats: config.facetStats } : {}),
22
23
  limit: pageSize,
23
24
  ...(state.page > 0 ? { offset: Math.min(state.page * pageSize, maxOffset) } : {}),
24
25
  ...(config.fields ? { attributesToRetrieve: config.fields } : {}),
@@ -116,7 +116,7 @@ export class SearchStore {
116
116
  const { results, loadingMore, pending } = this.snapshot;
117
117
  if (!results?.hasMore || loadingMore || pending)
118
118
  return;
119
- const { facets: _, disjunctiveFacets: __, facetMode: ___, ...request } = results.request;
119
+ const { facets: _, disjunctiveFacets: __, facetMode: ___, facetStats: ____, ...request } = results.request;
120
120
  const offset = (request.offset ?? 0) + results.hits.length;
121
121
  const run = this.run;
122
122
  this.set({ loadingMore: true });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@findlane/browser",
3
- "version": "0.3.0",
3
+ "version": "0.4.1",
4
4
  "description": "Findlane browser SDK: fast, typo-tolerant public search from the browser with an index's public ID, plus React hooks and components.",
5
5
  "keywords": ["findlane", "search", "full-text search", "typo tolerance", "instant search", "ecommerce search", "browser", "react", "autocomplete"],
6
6
  "license": "MIT",