@findlane/browser 0.2.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 +30 -0
- package/dist/index.js +13 -4
- package/dist/react/cache.js +1 -1
- package/dist/react/components.d.ts +10 -0
- package/dist/react/components.js +11 -1
- package/dist/react/index.d.ts +2 -2
- package/dist/react/index.js +2 -2
- package/dist/react/search-provider.d.ts +16 -1
- package/dist/react/search-provider.js +33 -2
- package/dist/react/state.d.ts +3 -0
- package/dist/react/state.js +1 -0
- package/dist/react/store.d.ts +3 -1
- package/dist/react/store.js +5 -1
- package/dist/react/styles.css +15 -0
- package/package.json +1 -1
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,8 +40,28 @@ 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;
|
|
51
|
+
/** With the index's `didYouMean` setting: the query with the words typo tolerance corrected, when the
|
|
52
|
+
* results matched some words only by typo ("Showing results for …"), else null. */
|
|
53
|
+
didYouMean?: DidYouMean | null;
|
|
54
|
+
};
|
|
55
|
+
/** A corrected query. `applied`: the results are already for `q` (a search's "Showing results for");
|
|
56
|
+
* otherwise searching `q` finds results that the query didn't ("Did you mean"). `changes` lists each
|
|
57
|
+
* changed part of the query as typed. */
|
|
58
|
+
export type DidYouMean = {
|
|
59
|
+
q: string;
|
|
60
|
+
applied: boolean;
|
|
61
|
+
changes: {
|
|
62
|
+
from: string;
|
|
63
|
+
to: string;
|
|
64
|
+
}[];
|
|
42
65
|
};
|
|
43
66
|
export declare class FindlaneError extends Error {
|
|
44
67
|
status: number;
|
|
@@ -56,7 +79,14 @@ export declare class SearchBrowserClient {
|
|
|
56
79
|
private readonly url;
|
|
57
80
|
private readonly fetcher;
|
|
58
81
|
constructor(options: BrowserClientOptions);
|
|
82
|
+
private post;
|
|
59
83
|
search<T = Record<string, unknown>>(request: SearchRequest, options?: {
|
|
60
84
|
signal?: AbortSignal;
|
|
61
85
|
}): Promise<SearchResponse<T>>;
|
|
86
|
+
/** Did you mean: for a search that found nothing, a query that finds something, or null. Takes the same
|
|
87
|
+
* request (its q and filters matter). Needs the index's `didYouMean` setting; not counted as a search.
|
|
88
|
+
* Ask once the shopper stops typing, not for every keystroke. */
|
|
89
|
+
suggest(request: SearchRequest, options?: {
|
|
90
|
+
signal?: AbortSignal;
|
|
91
|
+
}): Promise<DidYouMean | null>;
|
|
62
92
|
}
|
package/dist/index.js
CHANGED
|
@@ -14,19 +14,28 @@ export class SearchBrowserClient {
|
|
|
14
14
|
constructor(options) {
|
|
15
15
|
if (!/^pub_[a-f0-9]{32}$/.test(options.publicId))
|
|
16
16
|
throw new Error("A valid public index ID (pub_…) is required");
|
|
17
|
-
this.url = `${(options.baseUrl ?? defaultBaseUrl).replace(/\/$/, "")}/api/public/indexes/${options.publicId}
|
|
17
|
+
this.url = `${(options.baseUrl ?? defaultBaseUrl).replace(/\/$/, "")}/api/public/indexes/${options.publicId}`;
|
|
18
18
|
this.fetcher = options.fetch ?? ((input, init) => globalThis.fetch(input, init));
|
|
19
19
|
}
|
|
20
|
-
async
|
|
21
|
-
const response = await this.fetcher(this.url
|
|
20
|
+
async post(operation, request, signal) {
|
|
21
|
+
const response = await this.fetcher(`${this.url}/${operation}`, {
|
|
22
22
|
method: "POST",
|
|
23
23
|
headers: { "content-type": "application/json" },
|
|
24
24
|
body: JSON.stringify(request),
|
|
25
|
-
signal
|
|
25
|
+
signal,
|
|
26
26
|
});
|
|
27
27
|
const data = await response.json();
|
|
28
28
|
if (!response.ok)
|
|
29
29
|
throw new FindlaneError(response.status, data.error ?? `HTTP ${response.status}`);
|
|
30
30
|
return data;
|
|
31
31
|
}
|
|
32
|
+
search(request, options = {}) {
|
|
33
|
+
return this.post("search", request, options.signal);
|
|
34
|
+
}
|
|
35
|
+
/** Did you mean: for a search that found nothing, a query that finds something, or null. Takes the same
|
|
36
|
+
* request (its q and filters matter). Needs the index's `didYouMean` setting; not counted as a search.
|
|
37
|
+
* Ask once the shopper stops typing, not for every keystroke. */
|
|
38
|
+
async suggest(request, options = {}) {
|
|
39
|
+
return (await this.post("suggest", request, options.signal)).didYouMean;
|
|
40
|
+
}
|
|
32
41
|
}
|
package/dist/react/cache.js
CHANGED
|
@@ -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;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type ReactNode } from "react";
|
|
2
|
+
import type { DidYouMean as Correction } from "../index.js";
|
|
2
3
|
import { type Refinement } from "./search-provider.js";
|
|
3
4
|
import type { Hit } from "./state.js";
|
|
4
5
|
import { useAutocomplete, type AutocompleteOptions } from "./use-autocomplete.js";
|
|
@@ -80,6 +81,15 @@ export declare function LoadMore({ label, className }: {
|
|
|
80
81
|
label?: string;
|
|
81
82
|
className?: string;
|
|
82
83
|
}): import("react").JSX.Element | null;
|
|
84
|
+
/** "Showing results for …" when typo tolerance corrected the query, and "Did you mean …?", a button that
|
|
85
|
+
* searches for it, when nothing matched. Needs the index's didYouMean setting; otherwise shows nothing. */
|
|
86
|
+
export declare function DidYouMean({ delay, format, className }: {
|
|
87
|
+
/** How long the visitor must stop typing before a suggestion is asked for (default 400 ms). */
|
|
88
|
+
delay?: number;
|
|
89
|
+
/** The line's content (default "Showing results for …" or "Did you mean …?"). */
|
|
90
|
+
format?: (didYouMean: Correction, apply: () => void) => ReactNode;
|
|
91
|
+
className?: string;
|
|
92
|
+
}): import("react").JSX.Element | null;
|
|
83
93
|
/** "1,234 results". */
|
|
84
94
|
export declare function Stats({ format, className }: {
|
|
85
95
|
format?: (total: number) => ReactNode;
|
package/dist/react/components.js
CHANGED
|
@@ -3,7 +3,7 @@ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-run
|
|
|
3
3
|
import { useState } from "react";
|
|
4
4
|
import { Highlight } from "./highlight.js";
|
|
5
5
|
import { messageFor } from "./messages.js";
|
|
6
|
-
import { useCurrentRefinements, useHits, useLoadMore, useRefinementList, useSearchBox, useSortBy, } from "./search-provider.js";
|
|
6
|
+
import { useCurrentRefinements, useDidYouMean, useHits, useLoadMore, useRefinementList, useSearchBox, useSortBy, } from "./search-provider.js";
|
|
7
7
|
import { useAutocomplete } from "./use-autocomplete.js";
|
|
8
8
|
// The components render plain elements marked with data-findlane-* attributes and no styles of
|
|
9
9
|
// their own; styles.css gives them a default look.
|
|
@@ -74,6 +74,16 @@ export function LoadMore({ label = "Show more", className }) {
|
|
|
74
74
|
return null;
|
|
75
75
|
return (_jsxs("div", { className: className, "data-findlane-load-more": "", children: [_jsx("button", { type: "button", onClick: loadMore, disabled: isLoading, children: isLoading ? "Loading…" : label }), _jsxs("span", { children: [number(shown), " of ", number(total)] })] }));
|
|
76
76
|
}
|
|
77
|
+
/** "Showing results for …" when typo tolerance corrected the query, and "Did you mean …?", a button that
|
|
78
|
+
* searches for it, when nothing matched. Needs the index's didYouMean setting; otherwise shows nothing. */
|
|
79
|
+
export function DidYouMean({ delay, format, className }) {
|
|
80
|
+
const { didYouMean, apply } = useDidYouMean({ delay });
|
|
81
|
+
if (!didYouMean)
|
|
82
|
+
return null;
|
|
83
|
+
return (_jsx("p", { className: className, "data-findlane-did-you-mean": "", children: format ? format(didYouMean, apply) : didYouMean.applied
|
|
84
|
+
? _jsxs(_Fragment, { children: ["Showing results for ", _jsx("b", { children: didYouMean.q })] })
|
|
85
|
+
: _jsxs(_Fragment, { children: ["Did you mean ", _jsx("button", { type: "button", onClick: apply, children: didYouMean.q }), "?"] }) }));
|
|
86
|
+
}
|
|
77
87
|
/** "1,234 results". */
|
|
78
88
|
export function Stats({ format, className }) {
|
|
79
89
|
const { total, totalRelation, isLoaded } = useHits();
|
package/dist/react/index.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
export { FindlaneProvider, useClient, type ClientOptions } from "./client.js";
|
|
2
2
|
export { useSearch, type UseSearchOptions, type UseSearchResult } from "./use-search.js";
|
|
3
3
|
export { useAutocomplete, type AutocompleteOptions } from "./use-autocomplete.js";
|
|
4
|
-
export { SearchProvider, useCurrentRefinements, useHits, useLoadMore, usePagination, useRange, useRefinementList, useSearchBox, useSearchState, useSortBy, useToggle, type Range, type Refinement, type SearchProviderProps, } from "./search-provider.js";
|
|
5
|
-
export { CurrentRefinements, DefaultHit, Hits, LoadMore, RefinementList, SearchBox, SearchInput, SortSelect, Stats, type HitsProps, type RefinementListProps, type SearchBoxProps, } from "./components.js";
|
|
4
|
+
export { SearchProvider, useCurrentRefinements, useDidYouMean, useHits, useLoadMore, usePagination, useRange, useRefinementList, useSearchBox, useSearchState, useSortBy, useToggle, type Range, type Refinement, type SearchProviderProps, } from "./search-provider.js";
|
|
5
|
+
export { CurrentRefinements, DefaultHit, DidYouMean, Hits, LoadMore, RefinementList, SearchBox, SearchInput, SortSelect, Stats, type HitsProps, type RefinementListProps, type SearchBoxProps, } from "./components.js";
|
|
6
6
|
export { SearchDialog, type SearchDialogProps } from "./dialog.js";
|
|
7
7
|
export { Highlight, Snippet, excerpt, highlightParts, type HighlightProps } from "./highlight.js";
|
|
8
8
|
export { messageFor } from "./messages.js";
|
package/dist/react/index.js
CHANGED
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
export { FindlaneProvider, useClient } from "./client.js";
|
|
4
4
|
export { useSearch } from "./use-search.js";
|
|
5
5
|
export { useAutocomplete } from "./use-autocomplete.js";
|
|
6
|
-
export { SearchProvider, useCurrentRefinements, useHits, useLoadMore, usePagination, useRange, useRefinementList, useSearchBox, useSearchState, useSortBy, useToggle, } from "./search-provider.js";
|
|
7
|
-
export { CurrentRefinements, DefaultHit, Hits, LoadMore, RefinementList, SearchBox, SearchInput, SortSelect, Stats, } from "./components.js";
|
|
6
|
+
export { SearchProvider, useCurrentRefinements, useDidYouMean, useHits, useLoadMore, usePagination, useRange, useRefinementList, useSearchBox, useSearchState, useSortBy, useToggle, } from "./search-provider.js";
|
|
7
|
+
export { CurrentRefinements, DefaultHit, DidYouMean, Hits, LoadMore, RefinementList, SearchBox, SearchInput, SortSelect, Stats, } from "./components.js";
|
|
8
8
|
export { SearchDialog } from "./dialog.js";
|
|
9
9
|
export { Highlight, Snippet, excerpt, highlightParts } from "./highlight.js";
|
|
10
10
|
export { messageFor } from "./messages.js";
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type ReactNode } from "react";
|
|
2
|
+
import type { DidYouMean } from "../index.js";
|
|
2
3
|
import { type ClientOptions } from "./client.js";
|
|
3
4
|
import { type ServerResults, type StateChange } from "./store.js";
|
|
4
5
|
import { type SearchConfig, type SearchState } from "./state.js";
|
|
@@ -45,6 +46,16 @@ export declare function useHits<T = Record<string, unknown>>(): {
|
|
|
45
46
|
isPending: boolean;
|
|
46
47
|
error: unknown;
|
|
47
48
|
};
|
|
49
|
+
/** Did you mean, with the index's didYouMean setting: the query the results are for when typo tolerance
|
|
50
|
+
* corrected some words (`applied`, "Showing results for"), or, when nothing matched, a query that
|
|
51
|
+
* finds results, asked for once the visitor has stopped typing for `delay` ms. `apply` searches for it.
|
|
52
|
+
* Searches carry `didYouMean` only when the setting is on, so other indexes are never asked. */
|
|
53
|
+
export declare function useDidYouMean({ delay }?: {
|
|
54
|
+
delay?: number;
|
|
55
|
+
}): {
|
|
56
|
+
didYouMean: DidYouMean | null;
|
|
57
|
+
apply: () => void;
|
|
58
|
+
};
|
|
48
59
|
/** A facet's values with their counts, most common first. Selected values stay listed (at 0 when the
|
|
49
60
|
* query no longer matches them), so they can be removed. */
|
|
50
61
|
export declare function useRefinementList(field: string): {
|
|
@@ -61,9 +72,13 @@ export type Range = {
|
|
|
61
72
|
gte?: number | string;
|
|
62
73
|
lte?: number | string;
|
|
63
74
|
};
|
|
64
|
-
/** 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. */
|
|
65
78
|
export declare function useRange(field: string): {
|
|
66
79
|
range: Range | undefined;
|
|
80
|
+
min: number | undefined;
|
|
81
|
+
max: number | undefined;
|
|
67
82
|
/** Leave a bound out (or empty) for no limit on that side. */
|
|
68
83
|
setRange: (range: Range | undefined) => void;
|
|
69
84
|
clear: () => void;
|
|
@@ -94,6 +94,32 @@ export function useHits() {
|
|
|
94
94
|
error,
|
|
95
95
|
};
|
|
96
96
|
}
|
|
97
|
+
/** Did you mean, with the index's didYouMean setting: the query the results are for when typo tolerance
|
|
98
|
+
* corrected some words (`applied`, "Showing results for"), or, when nothing matched, a query that
|
|
99
|
+
* finds results, asked for once the visitor has stopped typing for `delay` ms. `apply` searches for it.
|
|
100
|
+
* Searches carry `didYouMean` only when the setting is on, so other indexes are never asked. */
|
|
101
|
+
export function useDidYouMean({ delay = 400 } = {}) {
|
|
102
|
+
const [{ results, pending }, store] = useSnapshot();
|
|
103
|
+
const [suggested, setSuggested] = useState(null);
|
|
104
|
+
const empty = Boolean(results && !pending && !results.response.total && results.request.q?.trim() &&
|
|
105
|
+
results.response.didYouMean !== undefined);
|
|
106
|
+
const key = results?.key;
|
|
107
|
+
useEffect(() => {
|
|
108
|
+
if (!empty || !results)
|
|
109
|
+
return;
|
|
110
|
+
const controller = new AbortController();
|
|
111
|
+
const timer = setTimeout(() => store.suggest(results.request, controller.signal)
|
|
112
|
+
.then((didYouMean) => setSuggested({ key: results.key, didYouMean }), () => undefined), delay);
|
|
113
|
+
return () => { clearTimeout(timer); controller.abort(); };
|
|
114
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
115
|
+
}, [store, key, empty, delay]);
|
|
116
|
+
const didYouMean = results?.response.didYouMean ?? (empty && suggested?.key === key ? suggested.didYouMean : null);
|
|
117
|
+
return {
|
|
118
|
+
didYouMean,
|
|
119
|
+
apply: () => { if (didYouMean)
|
|
120
|
+
store.update((current) => ({ ...current, q: didYouMean.q, page: 0 })); },
|
|
121
|
+
};
|
|
122
|
+
}
|
|
97
123
|
/** A facet's values with their counts, most common first. Selected values stay listed (at 0 when the
|
|
98
124
|
* query no longer matches them), so they can be removed. */
|
|
99
125
|
export function useRefinementList(field) {
|
|
@@ -116,12 +142,17 @@ export function useRefinementList(field) {
|
|
|
116
142
|
clear: () => store.update(withRefinement(field, [])),
|
|
117
143
|
};
|
|
118
144
|
}
|
|
119
|
-
/** 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. */
|
|
120
148
|
export function useRange(field) {
|
|
121
|
-
const [{ state }, store] = useSnapshot();
|
|
149
|
+
const [{ state, results }, store] = useSnapshot();
|
|
122
150
|
const value = state.filters[field];
|
|
151
|
+
const stats = results?.response.facetStats?.[field];
|
|
123
152
|
return {
|
|
124
153
|
range: isRange(value) ? value : undefined,
|
|
154
|
+
min: stats?.min,
|
|
155
|
+
max: stats?.max,
|
|
125
156
|
/** Leave a bound out (or empty) for no limit on that side. */
|
|
126
157
|
setRange: (range) => {
|
|
127
158
|
const bounds = Object.entries(range ?? {}).filter(([, bound]) => bound !== undefined && bound !== "");
|
package/dist/react/state.d.ts
CHANGED
|
@@ -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). */
|
package/dist/react/state.js
CHANGED
|
@@ -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 } : {}),
|
package/dist/react/store.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { SearchBrowserClient, SearchRequest, SearchResponse } from "../index.js";
|
|
1
|
+
import type { DidYouMean, SearchBrowserClient, SearchRequest, SearchResponse } from "../index.js";
|
|
2
2
|
import { type Hit, type SearchConfig, type SearchState } from "./state.js";
|
|
3
3
|
/** Results computed on the server (see ssr.ts), so the first render needn't search again. */
|
|
4
4
|
export type ServerResults<T = unknown> = {
|
|
@@ -60,6 +60,8 @@ export declare class SearchStore<T = unknown> {
|
|
|
60
60
|
stop(): void;
|
|
61
61
|
private resultsFor;
|
|
62
62
|
private search;
|
|
63
|
+
/** A did-you-mean suggestion for a request that found nothing. */
|
|
64
|
+
suggest(request: SearchRequest, signal: AbortSignal): Promise<DidYouMean | null>;
|
|
63
65
|
/** Adds the next page's hits to the current ones. */
|
|
64
66
|
loadMore(): void;
|
|
65
67
|
}
|
package/dist/react/store.js
CHANGED
|
@@ -107,12 +107,16 @@ export class SearchStore {
|
|
|
107
107
|
else
|
|
108
108
|
go();
|
|
109
109
|
}
|
|
110
|
+
/** A did-you-mean suggestion for a request that found nothing. */
|
|
111
|
+
suggest(request, signal) {
|
|
112
|
+
return this.client.suggest(request, { signal });
|
|
113
|
+
}
|
|
110
114
|
/** Adds the next page's hits to the current ones. */
|
|
111
115
|
loadMore() {
|
|
112
116
|
const { results, loadingMore, pending } = this.snapshot;
|
|
113
117
|
if (!results?.hasMore || loadingMore || pending)
|
|
114
118
|
return;
|
|
115
|
-
const { facets: _, disjunctiveFacets: __, facetMode: ___, ...request } = results.request;
|
|
119
|
+
const { facets: _, disjunctiveFacets: __, facetMode: ___, facetStats: ____, ...request } = results.request;
|
|
116
120
|
const offset = (request.offset ?? 0) + results.hits.length;
|
|
117
121
|
const run = this.run;
|
|
118
122
|
this.set({ loadingMore: true });
|
package/dist/react/styles.css
CHANGED
|
@@ -239,6 +239,21 @@
|
|
|
239
239
|
cursor: pointer;
|
|
240
240
|
}
|
|
241
241
|
|
|
242
|
+
:where([data-findlane-did-you-mean]) {
|
|
243
|
+
margin: 0;
|
|
244
|
+
color: var(--findlane-muted, color-mix(in srgb, currentColor 60%, transparent));
|
|
245
|
+
}
|
|
246
|
+
:where([data-findlane-did-you-mean]) :where(button) {
|
|
247
|
+
padding: 0;
|
|
248
|
+
font: inherit;
|
|
249
|
+
font-weight: 600;
|
|
250
|
+
color: var(--findlane-accent, #2f5bff);
|
|
251
|
+
background: none;
|
|
252
|
+
border: 0;
|
|
253
|
+
text-decoration: underline;
|
|
254
|
+
cursor: pointer;
|
|
255
|
+
}
|
|
256
|
+
|
|
242
257
|
:where([data-findlane-stats]) {
|
|
243
258
|
margin: 0;
|
|
244
259
|
color: var(--findlane-muted, color-mix(in srgb, currentColor 60%, transparent));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@findlane/browser",
|
|
3
|
-
"version": "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",
|