@findlane/browser 0.16.1 → 0.18.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 +39 -10
- package/dist/react/components.d.ts +4 -1
- package/dist/react/components.js +9 -4
- package/dist/react/highlight.js +1 -1
- package/dist/react/search-provider.d.ts +2 -0
- package/dist/react/search-provider.js +2 -0
- package/dist/react/store.d.ts +2 -1
- package/dist/react/store.js +5 -2
- package/dist/react/use-autocomplete.d.ts +1 -7
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -46,6 +46,11 @@ export type SearchRequest = {
|
|
|
46
46
|
/** Only records inside a box, in degrees, such as a map's visible area. West greater than east crosses the
|
|
47
47
|
* antimeridian. */
|
|
48
48
|
within?: GeoBox;
|
|
49
|
+
/** With the index's related results on: false asks for none (0.17.0). */
|
|
50
|
+
related?: boolean;
|
|
51
|
+
/** With the index's `distinct` setting, which shows each product once, such as one of its colours: false
|
|
52
|
+
* shows every record, a number 1 to 4 how many of each group's records. */
|
|
53
|
+
distinct?: boolean | number;
|
|
49
54
|
};
|
|
50
55
|
/** A point in degrees: lat from -90 to 90, lng from -180 to 180. */
|
|
51
56
|
export type GeoPoint = {
|
|
@@ -59,17 +64,34 @@ export type GeoBox = {
|
|
|
59
64
|
east: number;
|
|
60
65
|
west: number;
|
|
61
66
|
};
|
|
67
|
+
/** A result. */
|
|
68
|
+
export type SearchHit<T> = {
|
|
69
|
+
id: string;
|
|
70
|
+
score: number | null;
|
|
71
|
+
record: T;
|
|
72
|
+
/** With `near`: meters from the point. */
|
|
73
|
+
distance?: number;
|
|
74
|
+
/** With `highlight`: matched searchable fields by path; list fields give one string per value. */
|
|
75
|
+
highlights?: Record<string, string | string[]>;
|
|
76
|
+
/** With the index's `distinct` setting: the hit's group (its value of the group field, null for a record
|
|
77
|
+
* without one) and how many of the group's records matched. */
|
|
78
|
+
group?: {
|
|
79
|
+
value: string | number | null;
|
|
80
|
+
matched: number;
|
|
81
|
+
};
|
|
82
|
+
/** With the index's `variants` setting: which item of the record's variants list the search matched, by
|
|
83
|
+
* its words or by filters on the variants' fields, such as the flavour a shopper typed. Absent when none
|
|
84
|
+
* stands out. */
|
|
85
|
+
variant?: {
|
|
86
|
+
index: number;
|
|
87
|
+
};
|
|
88
|
+
};
|
|
62
89
|
export type SearchResponse<T> = {
|
|
63
|
-
hits:
|
|
64
|
-
|
|
65
|
-
score: number | null;
|
|
66
|
-
record: T;
|
|
67
|
-
/** With `near`: meters from the point. */
|
|
68
|
-
distance?: number;
|
|
69
|
-
/** With `highlight`: matched searchable fields by path; list fields give one string per value. */
|
|
70
|
-
highlights?: Record<string, string | string[]>;
|
|
71
|
-
}[];
|
|
90
|
+
hits: SearchHit<T>[];
|
|
91
|
+
/** The matches; with the index's `distinct` setting, the groups. */
|
|
72
92
|
total: number;
|
|
93
|
+
/** With `distinct`: the matching records, which `total` counts as groups. */
|
|
94
|
+
totalRecords?: number;
|
|
73
95
|
/** "gte" when `total` is a lower bound (page count mode). */
|
|
74
96
|
totalRelation?: "eq" | "gte";
|
|
75
97
|
hasMore?: boolean;
|
|
@@ -95,6 +117,13 @@ export type SearchResponse<T> = {
|
|
|
95
117
|
appliedRules?: AppliedRule[];
|
|
96
118
|
/** When a rule replaced words: the query that was searched. */
|
|
97
119
|
rewrittenQuery?: string;
|
|
120
|
+
/** With the index's related results on (0.17.0): when the query's words found nothing (`hits` empty, `total`
|
|
121
|
+
* 0), the products closest to it in meaning, best first, with their similarity as `score`. Show them under
|
|
122
|
+
* a heading of their own, such as "No exact matches. Related products". */
|
|
123
|
+
related?: {
|
|
124
|
+
hits: SearchHit<T>[];
|
|
125
|
+
total: number;
|
|
126
|
+
};
|
|
98
127
|
};
|
|
99
128
|
/** A query rule a search fired, with its `userData`, if it has any. */
|
|
100
129
|
export type AppliedRule = {
|
|
@@ -227,7 +256,7 @@ export type EventItem = {
|
|
|
227
256
|
export type ClickDetails = {
|
|
228
257
|
position?: number;
|
|
229
258
|
query?: string;
|
|
230
|
-
list?: "goesWith" | "similar" | "together";
|
|
259
|
+
list?: "goesWith" | "similar" | "together" | "related";
|
|
231
260
|
from?: string;
|
|
232
261
|
};
|
|
233
262
|
/** Events from the page, sent in the background: a batch a second, and what's left when the page is
|
|
@@ -56,6 +56,9 @@ export type HitsProps<T> = {
|
|
|
56
56
|
render?: (hit: Hit<T>, index: number) => ReactNode;
|
|
57
57
|
/** Shown when nothing matches (default "No results for “…”"). */
|
|
58
58
|
empty?: (query: string) => ReactNode;
|
|
59
|
+
/** The heading over related results, shown when nothing matched but the index's related results found
|
|
60
|
+
* products close in meaning (default "No exact matches. Related products"). */
|
|
61
|
+
relatedTitle?: (query: string) => ReactNode;
|
|
59
62
|
/** Shown before the first results arrive. */
|
|
60
63
|
loading?: ReactNode;
|
|
61
64
|
className?: string;
|
|
@@ -63,7 +66,7 @@ export type HitsProps<T> = {
|
|
|
63
66
|
/** Whether a click was on a link inside the element that handled it, such as a hit's link. */
|
|
64
67
|
export declare function onLink(event: MouseEvent<HTMLElement>): boolean;
|
|
65
68
|
/** The results. Following a link in a hit sends a click event, with its place and the query. */
|
|
66
|
-
export declare function Hits<T = Record<string, unknown>>({ render, empty, loading, className }: HitsProps<T>): import("react").JSX.Element;
|
|
69
|
+
export declare function Hits<T = Record<string, unknown>>({ render, empty, relatedTitle, loading, className }: HitsProps<T>): import("react").JSX.Element;
|
|
67
70
|
export type RefinementListProps = {
|
|
68
71
|
field: string;
|
|
69
72
|
/** The heading (default the field). */
|
package/dist/react/components.js
CHANGED
|
@@ -74,22 +74,27 @@ export function onLink(event) {
|
|
|
74
74
|
return Boolean(link && event.currentTarget.contains(link));
|
|
75
75
|
}
|
|
76
76
|
/** The results. Following a link in a hit sends a click event, with its place and the query. */
|
|
77
|
-
export function Hits({ render, empty, loading = null, className }) {
|
|
78
|
-
const { hits, isLoaded, isPending, error, click } = useHits();
|
|
77
|
+
export function Hits({ render, empty, relatedTitle, loading = null, className }) {
|
|
78
|
+
const { hits, related, isLoaded, isPending, error, click } = useHits();
|
|
79
79
|
const { query } = useSearchBox();
|
|
80
80
|
if (error && !isLoaded)
|
|
81
81
|
return _jsx("p", { "data-findlane-note": "", children: messageFor(error) });
|
|
82
82
|
if (!isLoaded)
|
|
83
83
|
return _jsx(_Fragment, { children: loading });
|
|
84
|
-
if (!hits.length)
|
|
84
|
+
if (!hits.length && !related.length)
|
|
85
85
|
return _jsx("p", { "data-findlane-empty": "", children: empty ? empty(query) : query ? _jsxs(_Fragment, { children: ["No results for \u201C", query, "\u201D"] }) : "No results" });
|
|
86
|
-
|
|
86
|
+
// Nothing matched, but related results found products close in meaning: those, under their own heading.
|
|
87
|
+
const shown = hits.length ? hits : related;
|
|
88
|
+
const list = (_jsx("ol", { className: className, "data-findlane-hits": "", "data-findlane-related": !hits.length || undefined, "data-pending": isPending || undefined, children: shown.map((hit, index) => {
|
|
87
89
|
// A click, and a middle click that opens the link in a new tab.
|
|
88
90
|
const clicked = (event) => { if (!event.defaultPrevented && onLink(event))
|
|
89
91
|
click(hit); };
|
|
90
92
|
return (_jsx("li", { onClick: clicked, onAuxClick: (event) => { if (event.button === 1)
|
|
91
93
|
clicked(event); }, children: render ? render(hit, index) : _jsx(DefaultHit, { hit: hit }) }, hit.id));
|
|
92
94
|
}) }));
|
|
95
|
+
if (hits.length)
|
|
96
|
+
return list;
|
|
97
|
+
return (_jsxs(_Fragment, { children: [_jsx("p", { "data-findlane-related-title": "", children: relatedTitle ? relatedTitle(query) : "No exact matches. Related products" }), list] }));
|
|
93
98
|
}
|
|
94
99
|
/** Checkboxes for a facet's values, with counts. */
|
|
95
100
|
export function RefinementList({ field, label, limit = 8, format, className }) {
|
package/dist/react/highlight.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { Fragment } from "react";
|
|
3
|
-
// The API escapes highlighted values (
|
|
3
|
+
// The API escapes highlighted values (the engine's, engine/src/present.rs) and wraps matched words in
|
|
4
4
|
// <mark>. Splitting on the tags and decoding the five escapes gives text React can render, so
|
|
5
5
|
// nothing is inserted as HTML.
|
|
6
6
|
const escapes = { "&": "&", "<": "<", ">": ">", """: '"', "'": "'" };
|
|
@@ -35,6 +35,8 @@ export declare function useSearchBox(): {
|
|
|
35
35
|
export declare function useHits<T = Record<string, unknown>>(): {
|
|
36
36
|
hits: import("./state.js").Hit<T>[];
|
|
37
37
|
total: number;
|
|
38
|
+
/** With the index's related results on, when nothing matched: the products closest in meaning. */
|
|
39
|
+
related: import("../index.js").SearchHit<T>[];
|
|
38
40
|
/** "gte" when `total` is a lower bound (with `countMode: "page"`). */
|
|
39
41
|
totalRelation: "eq" | "gte";
|
|
40
42
|
response: import("../index.js").SearchResponse<T> | null;
|
|
@@ -82,6 +82,8 @@ export function useHits() {
|
|
|
82
82
|
return {
|
|
83
83
|
hits: results?.hits ?? [],
|
|
84
84
|
total: results?.response.total ?? 0,
|
|
85
|
+
/** With the index's related results on, when nothing matched: the products closest in meaning. */
|
|
86
|
+
related: results?.response.related?.hits ?? [],
|
|
85
87
|
/** "gte" when `total` is a lower bound (with `countMode: "page"`). */
|
|
86
88
|
totalRelation: results?.response.totalRelation ?? "eq",
|
|
87
89
|
response: results?.response ?? null,
|
package/dist/react/store.d.ts
CHANGED
|
@@ -62,7 +62,8 @@ export declare class SearchStore<T = unknown> {
|
|
|
62
62
|
get settings(): SearchConfig;
|
|
63
63
|
/** Opens the connection to the API before the first search needs it (the client's warm). */
|
|
64
64
|
warm(): void;
|
|
65
|
-
/** Sends a click on a result: its place in the results, from 1, and the query they're for.
|
|
65
|
+
/** Sends a click on a result: its place in the results, from 1, and the query they're for. A related
|
|
66
|
+
* result's click says so (list "related"), with its place among them. */
|
|
66
67
|
click(objectID: string): void;
|
|
67
68
|
/** Searches unless the results already answer the current state; with a state, such as the
|
|
68
69
|
* URL's, adopts it first. */
|
package/dist/react/store.js
CHANGED
|
@@ -67,12 +67,15 @@ export class SearchStore {
|
|
|
67
67
|
get settings() { return this.config; }
|
|
68
68
|
/** Opens the connection to the API before the first search needs it (the client's warm). */
|
|
69
69
|
warm() { this.client.warm(); }
|
|
70
|
-
/** Sends a click on a result: its place in the results, from 1, and the query they're for.
|
|
70
|
+
/** Sends a click on a result: its place in the results, from 1, and the query they're for. A related
|
|
71
|
+
* result's click says so (list "related"), with its place among them. */
|
|
71
72
|
click(objectID) {
|
|
72
73
|
const results = this.snapshot.results;
|
|
73
74
|
const index = results ? results.hits.findIndex((hit) => hit.id === objectID) : -1;
|
|
75
|
+
const related = index < 0 && results ? (results.response.related?.hits ?? []).findIndex((hit) => hit.id === objectID) : -1;
|
|
74
76
|
this.client.events.click(objectID, { query: results?.request.q ?? "",
|
|
75
|
-
...(index >= 0 ? { position: (results.request.offset ?? 0) + index + 1 } : {})
|
|
77
|
+
...(index >= 0 ? { position: (results.request.offset ?? 0) + index + 1 } : {}),
|
|
78
|
+
...(related >= 0 ? { position: related + 1, list: "related" } : {}) });
|
|
76
79
|
}
|
|
77
80
|
/** Searches unless the results already answer the current state; with a state, such as the
|
|
78
81
|
* URL's, adopts it first. */
|
|
@@ -63,13 +63,7 @@ export declare function fillTemplate<T>(template: string, hit: Hit<T>): string |
|
|
|
63
63
|
export declare function useAutocomplete<T = Record<string, unknown>>(options?: AutocompleteOptions<T>): {
|
|
64
64
|
query: string;
|
|
65
65
|
setQuery: (value: string) => void;
|
|
66
|
-
hits:
|
|
67
|
-
id: string;
|
|
68
|
-
score: number | null;
|
|
69
|
-
record: T;
|
|
70
|
-
distance?: number;
|
|
71
|
-
highlights?: Record<string, string | string[]>;
|
|
72
|
-
}[];
|
|
66
|
+
hits: import("../index.js").SearchHit<T>[];
|
|
73
67
|
total: number;
|
|
74
68
|
response: import("../index.js").SearchResponse<T> | null;
|
|
75
69
|
error: unknown;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@findlane/browser",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.18.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",
|