@findlane/browser 0.19.0 → 0.21.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 +24 -0
- package/dist/index.js +1 -0
- package/dist/react/components.d.ts +11 -3
- package/dist/react/components.js +24 -5
- package/dist/react/index.d.ts +1 -1
- package/dist/react/index.js +1 -1
- package/dist/react/search-provider.d.ts +5 -0
- package/dist/react/search-provider.js +2 -0
- package/dist/react/store.d.ts +5 -0
- package/dist/react/store.js +5 -0
- package/dist/react/use-autocomplete.js +7 -0
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -54,6 +54,11 @@ export type SearchRequest = {
|
|
|
54
54
|
/** Contexts the page sends, which the index's rules may fire on, such as "category-page" or
|
|
55
55
|
* "black-friday": a name or up to 10, each 1 to 64 letters, digits, `_`, `:` or `-` (0.19.0). */
|
|
56
56
|
context?: string | string[];
|
|
57
|
+
/** Records matching these filters come first, after the index's rank first, in searches by relevance: filters as
|
|
58
|
+
* `filters` takes them, or a list of up to 10, each counting once, so records matching more come first (0.20.0). */
|
|
59
|
+
boost?: SearchRequest["filters"] | SearchRequest["filters"][];
|
|
60
|
+
/** Records matching these come last, the same way (0.20.0). */
|
|
61
|
+
bury?: SearchRequest["filters"] | SearchRequest["filters"][];
|
|
57
62
|
};
|
|
58
63
|
/** A point in degrees: lat from -90 to 90, lng from -180 to 180. */
|
|
59
64
|
export type GeoPoint = {
|
|
@@ -122,6 +127,11 @@ export type SearchResponse<T> = {
|
|
|
122
127
|
appliedRules?: AppliedRule[];
|
|
123
128
|
/** When a rule replaced words: the query that was searched. */
|
|
124
129
|
rewrittenQuery?: string;
|
|
130
|
+
/** Banners of the rules that fired, in their order (0.20.0): show each at its `position` among the hits (from
|
|
131
|
+
* 1; the top when left out), linking to `link`. */
|
|
132
|
+
banners?: Banner[];
|
|
133
|
+
/** A page a rule sends this search to, such as "/pages/returns" (0.20.0): go there instead of showing results. */
|
|
134
|
+
redirect?: string;
|
|
125
135
|
/** With the index's related results on (0.17.0): when the query's words found nothing (`hits` empty, `total`
|
|
126
136
|
* 0), the products closest to it in meaning, best first, with their similarity as `score`. Show them under
|
|
127
137
|
* a heading of their own, such as "No exact matches. Related products". */
|
|
@@ -135,6 +145,14 @@ export type AppliedRule = {
|
|
|
135
145
|
id: string;
|
|
136
146
|
userData?: unknown;
|
|
137
147
|
};
|
|
148
|
+
/** A rule's banner: an image, where it links, its text, its place among the hits from 1, and the rule's ID. */
|
|
149
|
+
export type Banner = {
|
|
150
|
+
image: string;
|
|
151
|
+
link?: string;
|
|
152
|
+
title?: string;
|
|
153
|
+
position?: number;
|
|
154
|
+
rule: string;
|
|
155
|
+
};
|
|
138
156
|
/** A corrected query. `applied`: the results are already for `q` (a search's "Showing results for");
|
|
139
157
|
* otherwise searching `q` finds results that the query didn't ("Did you mean"). `changes` lists each
|
|
140
158
|
* changed part of the query as typed. */
|
|
@@ -277,6 +295,12 @@ export type BrowserEvents = {
|
|
|
277
295
|
position?: number;
|
|
278
296
|
kind?: "typed" | "popular" | "recent";
|
|
279
297
|
}): void;
|
|
298
|
+
/** A rule's banner was clicked: its rule (each banner in a response says which), the search's query, and
|
|
299
|
+
* its place. The Merchandising page shows each banner's clicks beside how often it was shown. */
|
|
300
|
+
bannerClick(rule: string, details?: {
|
|
301
|
+
query?: string;
|
|
302
|
+
position?: number;
|
|
303
|
+
}): void;
|
|
280
304
|
addToCart(items: EventItem[], options?: {
|
|
281
305
|
currency?: string;
|
|
282
306
|
}): void;
|
package/dist/index.js
CHANGED
|
@@ -137,6 +137,7 @@ function eventSender(url, fetcher, mode, ids) {
|
|
|
137
137
|
},
|
|
138
138
|
click: (objectID, details) => add({ type: "click", objectID, ...details }),
|
|
139
139
|
suggestion: (query, details) => add({ type: "suggestion", query, ...details }),
|
|
140
|
+
bannerClick: (rule, details) => add({ type: "bannerClick", rule, ...details }),
|
|
140
141
|
addToCart: (items, options) => add({ type: "addToCart", items, ...options }),
|
|
141
142
|
purchase: (order) => add({ type: "purchase", ...order }),
|
|
142
143
|
consent(granted) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type MouseEvent, type ReactNode } from "react";
|
|
2
|
-
import { type DidYouMean as Correction } from "../index.js";
|
|
2
|
+
import { type Banner, type DidYouMean as Correction } from "../index.js";
|
|
3
3
|
import { type Refinement } from "./search-provider.js";
|
|
4
4
|
import type { Hit } from "./state.js";
|
|
5
5
|
import { useAutocomplete, type AutocompleteOptions } from "./use-autocomplete.js";
|
|
@@ -54,6 +54,9 @@ export declare function SearchInput({ placeholder, label, className, autoFocus }
|
|
|
54
54
|
export type HitsProps<T> = {
|
|
55
55
|
/** A hit's content; DefaultHit shows an image and a title. */
|
|
56
56
|
render?: (hit: Hit<T>, index: number) => ReactNode;
|
|
57
|
+
/** A rule's banner, shown at its place among the hits; DefaultBanner shows its image, linked, with its
|
|
58
|
+
* title (0.20.0). */
|
|
59
|
+
renderBanner?: (banner: Banner) => ReactNode;
|
|
57
60
|
/** Shown when nothing matches (default "No results for “…”"). */
|
|
58
61
|
empty?: (query: string) => ReactNode;
|
|
59
62
|
/** The heading over related results, shown when nothing matched but the index's related results found
|
|
@@ -65,8 +68,13 @@ export type HitsProps<T> = {
|
|
|
65
68
|
};
|
|
66
69
|
/** Whether a click was on a link inside the element that handled it, such as a hit's link. */
|
|
67
70
|
export declare function onLink(event: MouseEvent<HTMLElement>): boolean;
|
|
68
|
-
/**
|
|
69
|
-
export declare function
|
|
71
|
+
/** A rule's banner: its image, linked when it has a link, with its title. */
|
|
72
|
+
export declare function DefaultBanner({ banner }: {
|
|
73
|
+
banner: Banner;
|
|
74
|
+
}): import("react").JSX.Element;
|
|
75
|
+
/** The results. Following a link in a hit sends a click event, with its place and the query. Rules'
|
|
76
|
+
* banners show at their places among the first page's hits, and following a banner's link sends a banner click. */
|
|
77
|
+
export declare function Hits<T = Record<string, unknown>>({ render, renderBanner, empty, relatedTitle, loading, className }: HitsProps<T>): import("react").JSX.Element;
|
|
70
78
|
export type RefinementListProps = {
|
|
71
79
|
field: string;
|
|
72
80
|
/** The heading (default the field). */
|
package/dist/react/components.js
CHANGED
|
@@ -73,9 +73,15 @@ export function onLink(event) {
|
|
|
73
73
|
const link = event.target.closest?.("a[href]");
|
|
74
74
|
return Boolean(link && event.currentTarget.contains(link));
|
|
75
75
|
}
|
|
76
|
-
/**
|
|
77
|
-
export function
|
|
78
|
-
const {
|
|
76
|
+
/** A rule's banner: its image, linked when it has a link, with its title. */
|
|
77
|
+
export function DefaultBanner({ banner }) {
|
|
78
|
+
const content = (_jsxs(_Fragment, { children: [_jsx("img", { src: banner.image, alt: banner.title ?? "", loading: "lazy" }), banner.title && _jsx("span", { children: banner.title })] }));
|
|
79
|
+
return banner.link ? _jsx("a", { href: banner.link, children: content }) : content;
|
|
80
|
+
}
|
|
81
|
+
/** The results. Following a link in a hit sends a click event, with its place and the query. Rules'
|
|
82
|
+
* banners show at their places among the first page's hits, and following a banner's link sends a banner click. */
|
|
83
|
+
export function Hits({ render, renderBanner, empty, relatedTitle, loading = null, className }) {
|
|
84
|
+
const { hits, related, isLoaded, isPending, error, click, bannerClick, response, request } = useHits();
|
|
79
85
|
const { query } = useSearchBox();
|
|
80
86
|
if (error && !isLoaded)
|
|
81
87
|
return _jsx("p", { "data-findlane-note": "", children: messageFor(error) });
|
|
@@ -85,12 +91,25 @@ export function Hits({ render, empty, relatedTitle, loading = null, className })
|
|
|
85
91
|
return _jsx("p", { "data-findlane-empty": "", children: empty ? empty(query) : query ? _jsxs(_Fragment, { children: ["No results for \u201C", query, "\u201D"] }) : "No results" });
|
|
86
92
|
// Nothing matched, but related results found products close in meaning: those, under their own heading.
|
|
87
93
|
const shown = hits.length ? hits : related;
|
|
94
|
+
// Banners go before the hit at their place, on the first page; past the last hit, after it.
|
|
95
|
+
const banners = hits.length && !request?.offset ? response?.banners ?? [] : [];
|
|
96
|
+
const bannerAt = (place) => banners.filter((banner) => Math.min(Math.max(banner.position ?? 1, 1), shown.length) === place);
|
|
97
|
+
// Following a banner's link sends a banner click, as a hit's sends a click.
|
|
98
|
+
const bannerItems = (place) => bannerAt(place).map((banner, at) => {
|
|
99
|
+
const clicked = (event) => { if (!event.defaultPrevented && onLink(event))
|
|
100
|
+
bannerClick(banner); };
|
|
101
|
+
return (_jsx("li", { "data-findlane-banner": "", onClick: clicked, onAuxClick: (event) => { if (event.button === 1)
|
|
102
|
+
clicked(event); }, children: renderBanner ? renderBanner(banner) : _jsx(DefaultBanner, { banner: banner }) }, `banner-${banner.rule}-${at}`));
|
|
103
|
+
});
|
|
88
104
|
const list = (_jsx("ol", { className: className, "data-findlane-hits": "", "data-findlane-related": !hits.length || undefined, "data-pending": isPending || undefined, children: shown.map((hit, index) => {
|
|
89
105
|
// A click, and a middle click that opens the link in a new tab.
|
|
90
106
|
const clicked = (event) => { if (!event.defaultPrevented && onLink(event))
|
|
91
107
|
click(hit); };
|
|
92
|
-
return
|
|
93
|
-
|
|
108
|
+
return [
|
|
109
|
+
...bannerItems(index + 1),
|
|
110
|
+
_jsx("li", { onClick: clicked, onAuxClick: (event) => { if (event.button === 1)
|
|
111
|
+
clicked(event); }, children: render ? render(hit, index) : _jsx(DefaultHit, { hit: hit }) }, hit.id),
|
|
112
|
+
];
|
|
94
113
|
}) }));
|
|
95
114
|
if (hits.length)
|
|
96
115
|
return list;
|
package/dist/react/index.d.ts
CHANGED
|
@@ -5,7 +5,7 @@ export { useRecommendations, type UseRecommendationsOptions, type UseRecommendat
|
|
|
5
5
|
export { Recommendations, type RecommendationsProps } from "./recommendations.js";
|
|
6
6
|
export { ProductView, useProductView } from "./product-view.js";
|
|
7
7
|
export { SearchProvider, useCurrentRefinements, useDidYouMean, useGeoSearch, useHits, useLoadMore, usePagination, useRange, useRefinementList, useSearchBox, useSearchState, useSortBy, useToggle, type Range, type Refinement, type SearchProviderProps, } from "./search-provider.js";
|
|
8
|
-
export { CurrentRefinements, DefaultHit, DidYouMean, Hits, LoadMore, RefinementList, SearchBox, SearchInput, SortSelect, Stats, type HitsProps, type RefinementListProps, type SearchBoxProps, type SuggestionLabels, } from "./components.js";
|
|
8
|
+
export { CurrentRefinements, DefaultBanner, DefaultHit, DidYouMean, Hits, LoadMore, RefinementList, SearchBox, SearchInput, SortSelect, Stats, type HitsProps, type RefinementListProps, type SearchBoxProps, type SuggestionLabels, } from "./components.js";
|
|
9
9
|
export { SearchDialog, type SearchDialogProps } from "./dialog.js";
|
|
10
10
|
export { Highlight, Snippet, excerpt, highlightParts, type HighlightProps } from "./highlight.js";
|
|
11
11
|
export { messageFor } from "./messages.js";
|
package/dist/react/index.js
CHANGED
|
@@ -7,7 +7,7 @@ export { useRecommendations } from "./use-recommendations.js";
|
|
|
7
7
|
export { Recommendations } from "./recommendations.js";
|
|
8
8
|
export { ProductView, useProductView } from "./product-view.js";
|
|
9
9
|
export { SearchProvider, useCurrentRefinements, useDidYouMean, useGeoSearch, useHits, useLoadMore, usePagination, useRange, useRefinementList, useSearchBox, useSearchState, useSortBy, useToggle, } from "./search-provider.js";
|
|
10
|
-
export { CurrentRefinements, DefaultHit, DidYouMean, Hits, LoadMore, RefinementList, SearchBox, SearchInput, SortSelect, Stats, } from "./components.js";
|
|
10
|
+
export { CurrentRefinements, DefaultBanner, DefaultHit, DidYouMean, Hits, LoadMore, RefinementList, SearchBox, SearchInput, SortSelect, Stats, } from "./components.js";
|
|
11
11
|
export { SearchDialog } from "./dialog.js";
|
|
12
12
|
export { Highlight, Snippet, excerpt, highlightParts } from "./highlight.js";
|
|
13
13
|
export { messageFor } from "./messages.js";
|
|
@@ -54,6 +54,11 @@ export declare function useHits<T = Record<string, unknown>>(): {
|
|
|
54
54
|
click: (hit: {
|
|
55
55
|
id: string;
|
|
56
56
|
}) => void;
|
|
57
|
+
/** Sends a click on a rule's banner. Hits does it for links in a banner; call it from your own banner. */
|
|
58
|
+
bannerClick: (banner: {
|
|
59
|
+
rule: string;
|
|
60
|
+
position?: number;
|
|
61
|
+
}) => void;
|
|
57
62
|
};
|
|
58
63
|
/** Did you mean, with the index's didYouMean setting: the query the results are for when typo tolerance
|
|
59
64
|
* corrected some words (`applied`, "Showing results for"), or, when nothing matched, a query that
|
|
@@ -99,6 +99,8 @@ export function useHits() {
|
|
|
99
99
|
/** Sends a click event for a hit, with its place and the query, for click-through in Analytics.
|
|
100
100
|
* Hits does it for links in a hit; call it from a hit rendered without a link. */
|
|
101
101
|
click: (hit) => store.click(hit.id),
|
|
102
|
+
/** Sends a click on a rule's banner. Hits does it for links in a banner; call it from your own banner. */
|
|
103
|
+
bannerClick: (banner) => store.bannerClick(banner),
|
|
102
104
|
};
|
|
103
105
|
}
|
|
104
106
|
/** Did you mean, with the index's didYouMean setting: the query the results are for when typo tolerance
|
package/dist/react/store.d.ts
CHANGED
|
@@ -65,6 +65,11 @@ export declare class SearchStore<T = unknown> {
|
|
|
65
65
|
/** Sends a click on a result: its place in the results, from 1, and the query they're for. A related
|
|
66
66
|
* result's click says so (list "related"), with its place among them. */
|
|
67
67
|
click(objectID: string): void;
|
|
68
|
+
/** Sends a click on a rule's banner, with the query it showed for. */
|
|
69
|
+
bannerClick(banner: {
|
|
70
|
+
rule: string;
|
|
71
|
+
position?: number;
|
|
72
|
+
}): void;
|
|
68
73
|
/** Searches unless the results already answer the current state; with a state, such as the
|
|
69
74
|
* URL's, adopts it first. */
|
|
70
75
|
start(state?: SearchState): void;
|
package/dist/react/store.js
CHANGED
|
@@ -77,6 +77,11 @@ export class SearchStore {
|
|
|
77
77
|
...(index >= 0 ? { position: (results.request.offset ?? 0) + index + 1 } : {}),
|
|
78
78
|
...(related >= 0 ? { position: related + 1, list: "related" } : {}) });
|
|
79
79
|
}
|
|
80
|
+
/** Sends a click on a rule's banner, with the query it showed for. */
|
|
81
|
+
bannerClick(banner) {
|
|
82
|
+
this.client.events.bannerClick(banner.rule, { query: this.snapshot.results?.request.q ?? "",
|
|
83
|
+
...(banner.position ? { position: banner.position } : {}) });
|
|
84
|
+
}
|
|
80
85
|
/** Searches unless the results already answer the current state; with a state, such as the
|
|
81
86
|
* URL's, adopts it first. */
|
|
82
87
|
start(state) {
|
|
@@ -185,6 +185,13 @@ export function useAutocomplete(options = {}) {
|
|
|
185
185
|
event.preventDefault();
|
|
186
186
|
choose(item.index);
|
|
187
187
|
}
|
|
188
|
+
else if (search.data?.redirect && !search.isPending) {
|
|
189
|
+
// A rule sends this search to a page, such as "returns" to the returns page (0.20.0).
|
|
190
|
+
event.preventDefault();
|
|
191
|
+
remember(query);
|
|
192
|
+
close();
|
|
193
|
+
window.location.assign(search.data.redirect);
|
|
194
|
+
}
|
|
188
195
|
else {
|
|
189
196
|
remember(query);
|
|
190
197
|
if (onSubmit) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@findlane/browser",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.21.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",
|