@findlane/browser 0.3.0 → 0.4.0

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,6 +8,9 @@ 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
16
  /** Up to 1,000. */
@@ -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;
@@ -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 !== "");
@@ -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). */
@@ -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.0",
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",