@findlane/browser 0.9.0 → 0.11.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/README.md +1 -1
- package/dist/index.d.ts +6 -0
- package/dist/index.js +9 -2
- package/dist/react/index.d.ts +1 -0
- package/dist/react/index.js +1 -0
- package/dist/react/product-view.d.ts +10 -0
- package/dist/react/product-view.js +19 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -42,7 +42,7 @@ import "@findlane/browser/react/styles.css" // optional
|
|
|
42
42
|
<SearchBox publicId="pub_your_public_id" getHref={(hit) => hit.record.url} action="/search" />
|
|
43
43
|
```
|
|
44
44
|
|
|
45
|
-
`SearchDialog` is the same search box in a dialog that ⌘K opens, as on docs sites. A results page shares its state through `SearchProvider`, with `RefinementList`, `SortSelect`, `Hits`, `LoadMore`, and hooks such as `useRefinementList` and `useRange` for your own markup. With `routing`, the search is kept in the URL. `@findlane/browser/react/ssr` renders the first results on the server. Docs: https://findlane.dev/docs/react
|
|
45
|
+
`SearchDialog` is the same search box in a dialog that ⌘K opens, as on docs sites. A results page shares its state through `SearchProvider`, with `RefinementList`, `SortSelect`, `Hits`, `LoadMore`, and hooks such as `useRefinementList` and `useRange` for your own markup. With `routing`, the search is kept in the URL. `@findlane/browser/react/ssr` renders the first results on the server. On a product page, `useProductView(objectID)`, or `<ProductView>` in a server component, sends the page's view for the index's Events page. Docs: https://findlane.dev/docs/react
|
|
46
46
|
|
|
47
47
|
## Search requests
|
|
48
48
|
|
package/dist/index.d.ts
CHANGED
|
@@ -149,6 +149,8 @@ export type ClickDetails = {
|
|
|
149
149
|
/** Events from the page, sent in the background: a batch a second, and what's left when the page is
|
|
150
150
|
* hidden or closed. They carry no cookie or ID. */
|
|
151
151
|
export type BrowserEvents = {
|
|
152
|
+
/** A product's page was viewed. A repeat of the last view within 5 seconds is the same page view
|
|
153
|
+
* (React's double mount in development, a re-render), so it isn't sent. */
|
|
152
154
|
view(objectID: string): void;
|
|
153
155
|
click(objectID: string, details?: ClickDetails): void;
|
|
154
156
|
addToCart(items: EventItem[], options?: {
|
|
@@ -177,6 +179,10 @@ export type BrowserClientOptions = {
|
|
|
177
179
|
/** Whether `events` sends anything: true (the default), false, or "afterConsent" to hold events until
|
|
178
180
|
* `events.consent(true)`. */
|
|
179
181
|
events?: boolean | "afterConsent";
|
|
182
|
+
/** Where events go: the API, unless you relay them from a route on your own domain (the server SDK's
|
|
183
|
+
* `events.proxy`), such as "/findlane/events". Ad blockers block beacons to other sites, not to the
|
|
184
|
+
* page's own. */
|
|
185
|
+
eventsUrl?: string;
|
|
180
186
|
};
|
|
181
187
|
export declare class SearchBrowserClient {
|
|
182
188
|
private readonly url;
|
package/dist/index.js
CHANGED
|
@@ -13,6 +13,7 @@ function eventSender(url, fetcher, mode) {
|
|
|
13
13
|
let allowed = mode === "afterConsent" ? undefined : mode;
|
|
14
14
|
let timer;
|
|
15
15
|
let listening = false;
|
|
16
|
+
let lastView = { objectID: "", at: 0 };
|
|
16
17
|
const flush = () => {
|
|
17
18
|
clearTimeout(timer);
|
|
18
19
|
timer = undefined;
|
|
@@ -37,7 +38,13 @@ function eventSender(url, fetcher, mode) {
|
|
|
37
38
|
timer ??= setTimeout(flush, 1000);
|
|
38
39
|
};
|
|
39
40
|
return {
|
|
40
|
-
view
|
|
41
|
+
view(objectID) {
|
|
42
|
+
const now = Date.now();
|
|
43
|
+
if (objectID === lastView.objectID && now - lastView.at < 5000)
|
|
44
|
+
return;
|
|
45
|
+
lastView = { objectID, at: now };
|
|
46
|
+
add({ type: "view", objectID });
|
|
47
|
+
},
|
|
41
48
|
click: (objectID, details) => add({ type: "click", objectID, ...details }),
|
|
42
49
|
addToCart: (items, options) => add({ type: "addToCart", items, ...options }),
|
|
43
50
|
purchase: (order) => add({ type: "purchase", ...order }),
|
|
@@ -61,7 +68,7 @@ export class SearchBrowserClient {
|
|
|
61
68
|
throw new Error("A valid public index ID (pub_…) is required");
|
|
62
69
|
this.url = `${(options.baseUrl ?? defaultBaseUrl).replace(/\/$/, "")}/api/public/indexes/${options.publicId}`;
|
|
63
70
|
this.fetcher = options.fetch ?? ((input, init) => globalThis.fetch(input, init));
|
|
64
|
-
this.events = eventSender(`${this.url}/events`, options.fetch, options.events ?? true);
|
|
71
|
+
this.events = eventSender(options.eventsUrl ?? `${this.url}/events`, options.fetch, options.events ?? true);
|
|
65
72
|
}
|
|
66
73
|
async post(operation, request, signal) {
|
|
67
74
|
const response = await this.fetcher(`${this.url}/${operation}`, {
|
package/dist/react/index.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ export { useSearch, type UseSearchOptions, type UseSearchResult } from "./use-se
|
|
|
3
3
|
export { useAutocomplete, type AutocompleteOptions } from "./use-autocomplete.js";
|
|
4
4
|
export { useRecommendations, type UseRecommendationsOptions, type UseRecommendationsResult } from "./use-recommendations.js";
|
|
5
5
|
export { Recommendations, type RecommendationsProps } from "./recommendations.js";
|
|
6
|
+
export { ProductView, useProductView } from "./product-view.js";
|
|
6
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";
|
|
7
8
|
export { CurrentRefinements, DefaultHit, DidYouMean, Hits, LoadMore, RefinementList, SearchBox, SearchInput, SortSelect, Stats, type HitsProps, type RefinementListProps, type SearchBoxProps, } from "./components.js";
|
|
8
9
|
export { SearchDialog, type SearchDialogProps } from "./dialog.js";
|
package/dist/react/index.js
CHANGED
|
@@ -5,6 +5,7 @@ export { useSearch } from "./use-search.js";
|
|
|
5
5
|
export { useAutocomplete } from "./use-autocomplete.js";
|
|
6
6
|
export { useRecommendations } from "./use-recommendations.js";
|
|
7
7
|
export { Recommendations } from "./recommendations.js";
|
|
8
|
+
export { ProductView, useProductView } from "./product-view.js";
|
|
8
9
|
export { SearchProvider, useCurrentRefinements, useDidYouMean, useGeoSearch, useHits, useLoadMore, usePagination, useRange, useRefinementList, useSearchBox, useSearchState, useSortBy, useToggle, } from "./search-provider.js";
|
|
9
10
|
export { CurrentRefinements, DefaultHit, DidYouMean, Hits, LoadMore, RefinementList, SearchBox, SearchInput, SortSelect, Stats, } from "./components.js";
|
|
10
11
|
export { SearchDialog } from "./dialog.js";
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type ClientOptions } from "./client.js";
|
|
2
|
+
/** Sends a view of the product when its page shows, and again when `objectID` changes, for the index's
|
|
3
|
+
* Events page and popularity. Nothing is sent while `objectID` is empty. The client's `events` option
|
|
4
|
+
* turns views off, or holds them until consent. */
|
|
5
|
+
export declare function useProductView(objectID: string | null | undefined, options?: ClientOptions): void;
|
|
6
|
+
/** useProductView as a component that renders nothing, for pages that can't call hooks, such as a
|
|
7
|
+
* Next.js server component. */
|
|
8
|
+
export declare function ProductView({ objectID, ...options }: ClientOptions & {
|
|
9
|
+
objectID: string;
|
|
10
|
+
}): null;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { useEffect } from "react";
|
|
3
|
+
import { useClient } from "./client.js";
|
|
4
|
+
/** Sends a view of the product when its page shows, and again when `objectID` changes, for the index's
|
|
5
|
+
* Events page and popularity. Nothing is sent while `objectID` is empty. The client's `events` option
|
|
6
|
+
* turns views off, or holds them until consent. */
|
|
7
|
+
export function useProductView(objectID, options = {}) {
|
|
8
|
+
const client = useClient(options);
|
|
9
|
+
useEffect(() => {
|
|
10
|
+
if (objectID)
|
|
11
|
+
client.events.view(objectID);
|
|
12
|
+
}, [client, objectID]);
|
|
13
|
+
}
|
|
14
|
+
/** useProductView as a component that renders nothing, for pages that can't call hooks, such as a
|
|
15
|
+
* Next.js server component. */
|
|
16
|
+
export function ProductView({ objectID, ...options }) {
|
|
17
|
+
useProductView(objectID, options);
|
|
18
|
+
return null;
|
|
19
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@findlane/browser",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.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",
|