@headband/react 0.1.1 → 0.2.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/LICENSE +21 -0
- package/README.md +249 -159
- package/dist/index.d.mts +168 -5
- package/dist/index.d.ts +168 -5
- package/dist/index.js +280 -103
- package/dist/index.mjs +268 -109
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -32,7 +32,21 @@ __export(index_exports, {
|
|
|
32
32
|
SearchBox: () => SearchBox,
|
|
33
33
|
SortBy: () => SortBy,
|
|
34
34
|
Stats: () => Stats,
|
|
35
|
+
andFilters: () => andFilters,
|
|
36
|
+
assignExperiment: () => assignExperiment,
|
|
37
|
+
buildExperimentConfigPath: () => buildExperimentConfigPath,
|
|
38
|
+
chooseExperimentVariant: () => chooseExperimentVariant,
|
|
39
|
+
combineFilters: () => combineFilters,
|
|
40
|
+
experimentSearchMetadata: () => experimentSearchMetadata,
|
|
41
|
+
filterEq: () => filterEq,
|
|
42
|
+
filterExists: () => filterExists,
|
|
43
|
+
filterIn: () => filterIn,
|
|
44
|
+
filterNotEq: () => filterNotEq,
|
|
45
|
+
filterRange: () => filterRange,
|
|
35
46
|
headband: () => headband,
|
|
47
|
+
isRunnableExperiment: () => isRunnableExperiment,
|
|
48
|
+
orFilters: () => orFilters,
|
|
49
|
+
quoteFilterValue: () => quoteFilterValue,
|
|
36
50
|
useHeadbandContext: () => useHeadbandContext,
|
|
37
51
|
useHighlight: () => useHighlight,
|
|
38
52
|
useHits: () => useHits,
|
|
@@ -46,6 +60,70 @@ __export(index_exports, {
|
|
|
46
60
|
});
|
|
47
61
|
module.exports = __toCommonJS(index_exports);
|
|
48
62
|
|
|
63
|
+
// src/experiments.ts
|
|
64
|
+
function stableHash(input) {
|
|
65
|
+
let hash = 2166136261;
|
|
66
|
+
for (let i = 0; i < input.length; i += 1) {
|
|
67
|
+
hash ^= input.charCodeAt(i);
|
|
68
|
+
hash = Math.imul(hash, 16777619);
|
|
69
|
+
}
|
|
70
|
+
return hash >>> 0;
|
|
71
|
+
}
|
|
72
|
+
function bucket(input, max) {
|
|
73
|
+
if (max <= 0) return 0;
|
|
74
|
+
return stableHash(input) % max;
|
|
75
|
+
}
|
|
76
|
+
function assignmentSeed(config, subjectId, scope) {
|
|
77
|
+
return `${config.assignmentSalt || config.experimentKey}:${config.experimentKey}:${scope}:${subjectId}`;
|
|
78
|
+
}
|
|
79
|
+
function buildExperimentConfigPath(query = {}) {
|
|
80
|
+
const params = new URLSearchParams();
|
|
81
|
+
if (query.projectId) params.set("projectId", query.projectId);
|
|
82
|
+
if (query.indexUid) params.set("indexUid", query.indexUid);
|
|
83
|
+
if (query.experimentKey) params.set("experimentKey", query.experimentKey);
|
|
84
|
+
const search = params.toString();
|
|
85
|
+
return `/v1/experiments/config${search ? `?${search}` : ""}`;
|
|
86
|
+
}
|
|
87
|
+
function isRunnableExperiment(config) {
|
|
88
|
+
return config.status === "active" && !config.killSwitch && config.trafficPercent > 0 && config.variants.length > 0;
|
|
89
|
+
}
|
|
90
|
+
function chooseExperimentVariant(config, subjectId) {
|
|
91
|
+
if (!isRunnableExperiment(config)) return null;
|
|
92
|
+
const trafficBucket = bucket(assignmentSeed(config, subjectId, "traffic"), 1e4) / 100;
|
|
93
|
+
if (trafficBucket >= config.trafficPercent) return null;
|
|
94
|
+
const totalWeight = config.variants.reduce(
|
|
95
|
+
(sum, variant) => sum + Math.max(0, variant.weight),
|
|
96
|
+
0
|
|
97
|
+
);
|
|
98
|
+
if (totalWeight <= 0) return null;
|
|
99
|
+
const variantBucket = bucket(assignmentSeed(config, subjectId, "variant"), totalWeight);
|
|
100
|
+
let cursor = 0;
|
|
101
|
+
for (const variant of config.variants) {
|
|
102
|
+
cursor += Math.max(0, variant.weight);
|
|
103
|
+
if (variantBucket < cursor) return variant;
|
|
104
|
+
}
|
|
105
|
+
return config.variants[config.variants.length - 1] ?? null;
|
|
106
|
+
}
|
|
107
|
+
function assignExperiment(config, subjectId, options = {}) {
|
|
108
|
+
const variant = chooseExperimentVariant(config, subjectId);
|
|
109
|
+
if (!variant) return null;
|
|
110
|
+
return {
|
|
111
|
+
experimentKey: config.experimentKey,
|
|
112
|
+
variant: variant.key,
|
|
113
|
+
indexUid: variant.indexUid,
|
|
114
|
+
sessionId: options.sessionId ?? subjectId,
|
|
115
|
+
config
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
function experimentSearchMetadata(assignment) {
|
|
119
|
+
if (!assignment) return void 0;
|
|
120
|
+
return {
|
|
121
|
+
experimentKey: assignment.experimentKey,
|
|
122
|
+
variant: assignment.variant,
|
|
123
|
+
...assignment.sessionId ? { sessionId: assignment.sessionId } : {}
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
49
127
|
// src/client.ts
|
|
50
128
|
var HeadbandError = class extends Error {
|
|
51
129
|
constructor(message, status, code) {
|
|
@@ -57,22 +135,20 @@ var HeadbandError = class extends Error {
|
|
|
57
135
|
};
|
|
58
136
|
function headband(host, apiKey) {
|
|
59
137
|
const baseUrl = host.replace(/\/+$/, "");
|
|
60
|
-
async function request(path,
|
|
138
|
+
async function request(path, options = {}) {
|
|
139
|
+
const method = options.method ?? "POST";
|
|
61
140
|
let response;
|
|
62
141
|
try {
|
|
63
142
|
response = await fetch(`${baseUrl}${path}`, {
|
|
64
|
-
method
|
|
143
|
+
method,
|
|
65
144
|
headers: {
|
|
66
|
-
"Content-Type": "application/json",
|
|
145
|
+
...method === "POST" ? { "Content-Type": "application/json" } : {},
|
|
67
146
|
Authorization: `Bearer ${apiKey}`
|
|
68
147
|
},
|
|
69
|
-
body: JSON.stringify(body)
|
|
148
|
+
...method === "POST" ? { body: JSON.stringify(options.body ?? {}) } : {}
|
|
70
149
|
});
|
|
71
150
|
} catch (err) {
|
|
72
|
-
throw new HeadbandError(
|
|
73
|
-
err instanceof Error ? err.message : "Network request failed",
|
|
74
|
-
0
|
|
75
|
-
);
|
|
151
|
+
throw new HeadbandError(err instanceof Error ? err.message : "Network request failed", 0);
|
|
76
152
|
}
|
|
77
153
|
if (!response.ok) {
|
|
78
154
|
let errorBody = {};
|
|
@@ -91,17 +167,102 @@ function headband(host, apiKey) {
|
|
|
91
167
|
return {
|
|
92
168
|
search(index, query, params) {
|
|
93
169
|
return request("/v1/search", {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
170
|
+
body: {
|
|
171
|
+
index,
|
|
172
|
+
q: query,
|
|
173
|
+
...params
|
|
174
|
+
}
|
|
97
175
|
});
|
|
98
176
|
},
|
|
99
177
|
multiSearch(queries) {
|
|
100
|
-
return request("/v1/multi-search", { queries });
|
|
178
|
+
return request("/v1/multi-search", { body: { queries } });
|
|
179
|
+
},
|
|
180
|
+
async getExperimentConfigs(query) {
|
|
181
|
+
const response = await request(
|
|
182
|
+
buildExperimentConfigPath(query),
|
|
183
|
+
{ method: "GET" }
|
|
184
|
+
);
|
|
185
|
+
return { experiments: response.data?.experiments ?? [] };
|
|
186
|
+
},
|
|
187
|
+
async assignExperiment(query) {
|
|
188
|
+
const response = await request(
|
|
189
|
+
"/v1/experiments/assign",
|
|
190
|
+
{ body: query }
|
|
191
|
+
);
|
|
192
|
+
if (!response.data) {
|
|
193
|
+
throw new HeadbandError("Experiment assignment response missing data", 500);
|
|
194
|
+
}
|
|
195
|
+
return response.data;
|
|
196
|
+
},
|
|
197
|
+
async recordExperimentEvent(event) {
|
|
198
|
+
const response = await request("/v1/experiments/events", {
|
|
199
|
+
body: event
|
|
200
|
+
});
|
|
201
|
+
if (!response.data) {
|
|
202
|
+
throw new HeadbandError("Experiment event response missing data", 500);
|
|
203
|
+
}
|
|
204
|
+
return response.data;
|
|
205
|
+
},
|
|
206
|
+
warm(target, opts) {
|
|
207
|
+
return request("/v1/warm", {
|
|
208
|
+
body: { ...target, ...opts?.level ? { level: opts.level } : {} }
|
|
209
|
+
});
|
|
101
210
|
}
|
|
102
211
|
};
|
|
103
212
|
}
|
|
104
213
|
|
|
214
|
+
// src/filters.ts
|
|
215
|
+
function quoteFilterValue(value) {
|
|
216
|
+
if (typeof value === "number" || typeof value === "boolean") return String(value);
|
|
217
|
+
return JSON.stringify(value);
|
|
218
|
+
}
|
|
219
|
+
function filterEq(attribute, value) {
|
|
220
|
+
return `${attribute} = ${quoteFilterValue(value)}`;
|
|
221
|
+
}
|
|
222
|
+
function filterNotEq(attribute, value) {
|
|
223
|
+
return `${attribute} != ${quoteFilterValue(value)}`;
|
|
224
|
+
}
|
|
225
|
+
function filterIn(attribute, values) {
|
|
226
|
+
return `${attribute} IN [${values.map(quoteFilterValue).join(", ")}]`;
|
|
227
|
+
}
|
|
228
|
+
function filterExists(attribute) {
|
|
229
|
+
return `${attribute} EXISTS`;
|
|
230
|
+
}
|
|
231
|
+
function filterRange(attribute, range) {
|
|
232
|
+
const parts = [];
|
|
233
|
+
const gte = range.gte ?? range.min;
|
|
234
|
+
const lte = range.lte ?? range.max;
|
|
235
|
+
if (range.gt !== void 0) parts.push(`${attribute} > ${range.gt}`);
|
|
236
|
+
if (gte !== void 0) parts.push(`${attribute} >= ${gte}`);
|
|
237
|
+
if (range.lt !== void 0) parts.push(`${attribute} < ${range.lt}`);
|
|
238
|
+
if (lte !== void 0) parts.push(`${attribute} <= ${lte}`);
|
|
239
|
+
return parts.length ? andFilters(...parts) : void 0;
|
|
240
|
+
}
|
|
241
|
+
function normalizeFilter(input) {
|
|
242
|
+
if (!input) return [];
|
|
243
|
+
return (Array.isArray(input) ? input : [input]).filter((part) => Boolean(part));
|
|
244
|
+
}
|
|
245
|
+
function andFilters(...filters) {
|
|
246
|
+
const parts = filters.flatMap(normalizeFilter);
|
|
247
|
+
if (parts.length === 0) return void 0;
|
|
248
|
+
if (parts.length === 1) return parts[0];
|
|
249
|
+
return parts.map((part) => `(${part})`).join(" AND ");
|
|
250
|
+
}
|
|
251
|
+
function orFilters(...filters) {
|
|
252
|
+
const parts = filters.flatMap(normalizeFilter);
|
|
253
|
+
if (parts.length === 0) return void 0;
|
|
254
|
+
if (parts.length === 1) return parts[0];
|
|
255
|
+
return parts.map((part) => `(${part})`).join(" OR ");
|
|
256
|
+
}
|
|
257
|
+
function combineFilters(baseFilter, generatedFilter) {
|
|
258
|
+
const base = normalizeFilter(baseFilter);
|
|
259
|
+
const generated = normalizeFilter(generatedFilter);
|
|
260
|
+
const combined = [...base, ...generated];
|
|
261
|
+
if (combined.length === 0) return void 0;
|
|
262
|
+
if (combined.length === 1) return combined[0];
|
|
263
|
+
return combined;
|
|
264
|
+
}
|
|
265
|
+
|
|
105
266
|
// src/provider.tsx
|
|
106
267
|
var import_react = require("react");
|
|
107
268
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
@@ -109,9 +270,7 @@ var HeadbandContext = (0, import_react.createContext)(null);
|
|
|
109
270
|
function useHeadbandContext() {
|
|
110
271
|
const ctx = (0, import_react.useContext)(HeadbandContext);
|
|
111
272
|
if (!ctx) {
|
|
112
|
-
throw new Error(
|
|
113
|
-
"useHeadbandContext must be used within a <HeadbandProvider>."
|
|
114
|
-
);
|
|
273
|
+
throw new Error("useHeadbandContext must be used within a <HeadbandProvider>.");
|
|
115
274
|
}
|
|
116
275
|
return ctx;
|
|
117
276
|
}
|
|
@@ -121,18 +280,19 @@ function buildFilterString(refinements, ranges) {
|
|
|
121
280
|
if (values.size === 0) return;
|
|
122
281
|
if (values.size === 1) {
|
|
123
282
|
const [v] = values;
|
|
124
|
-
parts.push(
|
|
283
|
+
parts.push(filterEq(attribute, v));
|
|
125
284
|
} else {
|
|
126
|
-
|
|
127
|
-
parts.push(`${attribute} IN [${list}]`);
|
|
285
|
+
parts.push(filterIn(attribute, Array.from(values)));
|
|
128
286
|
}
|
|
129
287
|
});
|
|
130
288
|
ranges.forEach((range, attribute) => {
|
|
131
289
|
if (range.min !== void 0) {
|
|
132
|
-
|
|
290
|
+
const generated = filterRange(attribute, { min: range.min });
|
|
291
|
+
if (generated) parts.push(generated);
|
|
133
292
|
}
|
|
134
293
|
if (range.max !== void 0) {
|
|
135
|
-
|
|
294
|
+
const generated = filterRange(attribute, { max: range.max });
|
|
295
|
+
if (generated) parts.push(generated);
|
|
136
296
|
}
|
|
137
297
|
});
|
|
138
298
|
return parts.length > 0 ? parts.join(" AND ") : void 0;
|
|
@@ -141,96 +301,92 @@ function HeadbandProvider({
|
|
|
141
301
|
client,
|
|
142
302
|
index,
|
|
143
303
|
initialQuery = "",
|
|
304
|
+
experiment = null,
|
|
305
|
+
baseFilter,
|
|
306
|
+
mode,
|
|
144
307
|
children
|
|
145
308
|
}) {
|
|
146
309
|
const [query, setQueryRaw] = (0, import_react.useState)(initialQuery);
|
|
147
310
|
const [results, setResults] = (0, import_react.useState)(null);
|
|
148
311
|
const [isLoading, setIsLoading] = (0, import_react.useState)(false);
|
|
149
312
|
const [error, setError] = (0, import_react.useState)(null);
|
|
150
|
-
const [refinements, setRefinements] = (0, import_react.useState)(
|
|
151
|
-
() => /* @__PURE__ */ new Map()
|
|
152
|
-
);
|
|
313
|
+
const [refinements, setRefinements] = (0, import_react.useState)(() => /* @__PURE__ */ new Map());
|
|
153
314
|
const [ranges, setRanges] = (0, import_react.useState)(() => /* @__PURE__ */ new Map());
|
|
154
315
|
const [sortBy, setSortBy] = (0, import_react.useState)(null);
|
|
155
316
|
const [page, setPage] = (0, import_react.useState)(0);
|
|
156
317
|
const [hitsPerPage, setHitsPerPage] = (0, import_react.useState)(20);
|
|
157
318
|
const [facets, setFacets] = (0, import_react.useState)(() => /* @__PURE__ */ new Set());
|
|
158
|
-
const timerRef = (0, import_react.useRef)(null);
|
|
159
319
|
const requestIdRef = (0, import_react.useRef)(0);
|
|
160
|
-
const
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
const
|
|
165
|
-
|
|
320
|
+
const generatedFilter = (0, import_react.useMemo)(
|
|
321
|
+
() => buildFilterString(refinements, ranges),
|
|
322
|
+
[refinements, ranges]
|
|
323
|
+
);
|
|
324
|
+
const filter = (0, import_react.useMemo)(
|
|
325
|
+
() => combineFilters(baseFilter, generatedFilter),
|
|
326
|
+
[baseFilter, generatedFilter]
|
|
327
|
+
);
|
|
328
|
+
const facetList = (0, import_react.useMemo)(() => facets.size > 0 ? Array.from(facets) : void 0, [facets]);
|
|
329
|
+
const sortList = (0, import_react.useMemo)(() => sortBy ? [sortBy] : void 0, [sortBy]);
|
|
166
330
|
const executeSearch = (0, import_react.useCallback)(() => {
|
|
167
|
-
const
|
|
331
|
+
const requestId = ++requestIdRef.current;
|
|
168
332
|
setIsLoading(true);
|
|
169
333
|
setError(null);
|
|
170
|
-
const filter = buildFilterString(refinementsRef.current, rangesRef.current);
|
|
171
|
-
const facetList = facetsRef.current.size > 0 ? Array.from(facetsRef.current) : void 0;
|
|
172
|
-
const sortList = sortBy ? [sortBy] : void 0;
|
|
173
334
|
const params = {
|
|
174
335
|
limit: hitsPerPage,
|
|
175
336
|
offset: page * hitsPerPage,
|
|
176
337
|
...filter ? { filter } : {},
|
|
177
338
|
...facetList ? { facets: facetList } : {},
|
|
178
339
|
...sortList ? { sort: sortList } : {},
|
|
340
|
+
...experiment ? experiment : {},
|
|
341
|
+
...mode ? { mode } : {},
|
|
179
342
|
attributesToHighlight: ["*"]
|
|
180
343
|
};
|
|
181
344
|
client.search(index, query, params).then((res) => {
|
|
182
|
-
if (
|
|
183
|
-
|
|
184
|
-
setIsLoading(false);
|
|
345
|
+
if (requestId !== requestIdRef.current) {
|
|
346
|
+
return;
|
|
185
347
|
}
|
|
348
|
+
setResults(res);
|
|
349
|
+
setIsLoading(false);
|
|
186
350
|
}).catch((err) => {
|
|
187
|
-
if (
|
|
188
|
-
|
|
189
|
-
setIsLoading(false);
|
|
351
|
+
if (requestId !== requestIdRef.current) {
|
|
352
|
+
return;
|
|
190
353
|
}
|
|
354
|
+
setError(err instanceof Error ? err : new Error(String(err)));
|
|
355
|
+
setIsLoading(false);
|
|
191
356
|
});
|
|
192
|
-
}, [client, index, query,
|
|
357
|
+
}, [client, index, query, filter, facetList, sortList, experiment, mode, page, hitsPerPage]);
|
|
193
358
|
(0, import_react.useEffect)(() => {
|
|
194
|
-
|
|
195
|
-
clearTimeout(timerRef.current);
|
|
196
|
-
}
|
|
197
|
-
timerRef.current = setTimeout(executeSearch, 200);
|
|
359
|
+
const timer = setTimeout(executeSearch, 200);
|
|
198
360
|
return () => {
|
|
199
|
-
|
|
361
|
+
clearTimeout(timer);
|
|
200
362
|
};
|
|
201
|
-
}, [executeSearch
|
|
363
|
+
}, [executeSearch]);
|
|
202
364
|
const setQuery = (0, import_react.useCallback)((q) => {
|
|
203
365
|
setQueryRaw(q);
|
|
204
366
|
setPage(0);
|
|
205
367
|
}, []);
|
|
206
|
-
const toggleRefinement = (0, import_react.useCallback)(
|
|
207
|
-
(
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
return next;
|
|
229
|
-
});
|
|
230
|
-
setPage(0);
|
|
231
|
-
},
|
|
232
|
-
[]
|
|
233
|
-
);
|
|
368
|
+
const toggleRefinement = (0, import_react.useCallback)((attribute, value2) => {
|
|
369
|
+
setRefinements((prev) => {
|
|
370
|
+
const next = new Map(prev);
|
|
371
|
+
const current = new Set(next.get(attribute) ?? []);
|
|
372
|
+
if (current.has(value2)) {
|
|
373
|
+
current.delete(value2);
|
|
374
|
+
} else {
|
|
375
|
+
current.add(value2);
|
|
376
|
+
}
|
|
377
|
+
next.set(attribute, current);
|
|
378
|
+
return next;
|
|
379
|
+
});
|
|
380
|
+
setPage(0);
|
|
381
|
+
}, []);
|
|
382
|
+
const setRange = (0, import_react.useCallback)((attribute, range) => {
|
|
383
|
+
setRanges((prev) => {
|
|
384
|
+
const next = new Map(prev);
|
|
385
|
+
next.set(attribute, range);
|
|
386
|
+
return next;
|
|
387
|
+
});
|
|
388
|
+
setPage(0);
|
|
389
|
+
}, []);
|
|
234
390
|
const clearRange = (0, import_react.useCallback)((attribute) => {
|
|
235
391
|
setRanges((prev) => {
|
|
236
392
|
const next = new Map(prev);
|
|
@@ -286,7 +442,18 @@ function HeadbandProvider({
|
|
|
286
442
|
unregisterFacet,
|
|
287
443
|
refresh
|
|
288
444
|
}),
|
|
289
|
-
[
|
|
445
|
+
[
|
|
446
|
+
setQuery,
|
|
447
|
+
setPage,
|
|
448
|
+
setSortBy,
|
|
449
|
+
setHitsPerPage,
|
|
450
|
+
toggleRefinement,
|
|
451
|
+
setRange,
|
|
452
|
+
clearRange,
|
|
453
|
+
registerFacet,
|
|
454
|
+
unregisterFacet,
|
|
455
|
+
refresh
|
|
456
|
+
]
|
|
290
457
|
);
|
|
291
458
|
const value = (0, import_react.useMemo)(
|
|
292
459
|
() => ({ client, index, state, actions }),
|
|
@@ -348,18 +515,19 @@ function useRefinementList({
|
|
|
348
515
|
actions.unregisterFacet(attribute);
|
|
349
516
|
};
|
|
350
517
|
}, [attribute, actions]);
|
|
351
|
-
const refined = state.refinements.get(attribute)
|
|
518
|
+
const refined = state.refinements.get(attribute);
|
|
352
519
|
const items = (0, import_react4.useMemo)(() => {
|
|
520
|
+
const activeRefinements = refined ?? /* @__PURE__ */ new Set();
|
|
353
521
|
const distribution = state.results?.facetDistribution?.[attribute] ?? {};
|
|
354
522
|
const allValues = /* @__PURE__ */ new Set([
|
|
355
523
|
...Object.keys(distribution),
|
|
356
|
-
...
|
|
524
|
+
...activeRefinements
|
|
357
525
|
]);
|
|
358
526
|
let result = Array.from(allValues).map(
|
|
359
527
|
(value) => ({
|
|
360
528
|
value,
|
|
361
529
|
count: distribution[value] ?? 0,
|
|
362
|
-
isRefined:
|
|
530
|
+
isRefined: activeRefinements.has(value)
|
|
363
531
|
})
|
|
364
532
|
);
|
|
365
533
|
if (sortOrder === "alpha") {
|
|
@@ -659,7 +827,11 @@ function SearchBox({
|
|
|
659
827
|
// src/components/Hits.tsx
|
|
660
828
|
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
661
829
|
function DefaultHit({ hit }) {
|
|
662
|
-
const
|
|
830
|
+
const rest = Object.fromEntries(
|
|
831
|
+
Object.entries(hit).filter(
|
|
832
|
+
([key]) => key !== "_formatted" && key !== "__data"
|
|
833
|
+
)
|
|
834
|
+
);
|
|
663
835
|
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "rounded-lg border border-gray-200 bg-white p-4 text-sm dark:border-gray-700 dark:bg-gray-900", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("pre", { className: "overflow-x-auto whitespace-pre-wrap text-xs text-gray-700 dark:text-gray-300", children: JSON.stringify(rest, null, 2) }) });
|
|
664
836
|
}
|
|
665
837
|
function DefaultEmpty() {
|
|
@@ -720,7 +892,7 @@ function RefinementList({
|
|
|
720
892
|
sortBy
|
|
721
893
|
});
|
|
722
894
|
const handleChange = (0, import_react11.useCallback)(
|
|
723
|
-
(value) => (
|
|
895
|
+
(value) => () => {
|
|
724
896
|
refine(value);
|
|
725
897
|
},
|
|
726
898
|
[refine]
|
|
@@ -960,17 +1132,15 @@ function RangeInput({
|
|
|
960
1132
|
maxPlaceholder = "Max"
|
|
961
1133
|
}) {
|
|
962
1134
|
const { min, max, stats, setRange, clear } = useRange({ attribute });
|
|
963
|
-
const
|
|
964
|
-
const
|
|
965
|
-
|
|
966
|
-
setLocalMin(min !== void 0 ? String(min) : "");
|
|
967
|
-
}, [min]);
|
|
968
|
-
(0, import_react14.useEffect)(() => {
|
|
969
|
-
setLocalMax(max !== void 0 ? String(max) : "");
|
|
970
|
-
}, [max]);
|
|
1135
|
+
const defaultMin = min !== void 0 ? String(min) : "";
|
|
1136
|
+
const defaultMax = max !== void 0 ? String(max) : "";
|
|
1137
|
+
const formKey = `${attribute}:${defaultMin}:${defaultMax}`;
|
|
971
1138
|
const handleSubmit = (0, import_react14.useCallback)(
|
|
972
1139
|
(e) => {
|
|
973
1140
|
e.preventDefault();
|
|
1141
|
+
const formData = new FormData(e.currentTarget);
|
|
1142
|
+
const localMin = String(formData.get("min") ?? "");
|
|
1143
|
+
const localMax = String(formData.get("max") ?? "");
|
|
974
1144
|
const parsedMin = localMin !== "" ? Number(localMin) : void 0;
|
|
975
1145
|
const parsedMax = localMax !== "" ? Number(localMax) : void 0;
|
|
976
1146
|
if (parsedMin === void 0 && parsedMax === void 0) {
|
|
@@ -982,15 +1152,7 @@ function RangeInput({
|
|
|
982
1152
|
});
|
|
983
1153
|
}
|
|
984
1154
|
},
|
|
985
|
-
[
|
|
986
|
-
);
|
|
987
|
-
const handleMinChange = (0, import_react14.useCallback)(
|
|
988
|
-
(e) => setLocalMin(e.target.value),
|
|
989
|
-
[]
|
|
990
|
-
);
|
|
991
|
-
const handleMaxChange = (0, import_react14.useCallback)(
|
|
992
|
-
(e) => setLocalMax(e.target.value),
|
|
993
|
-
[]
|
|
1155
|
+
[setRange, clear]
|
|
994
1156
|
);
|
|
995
1157
|
const containerClass = styled ? `space-y-2 ${className ?? ""}`.trim() : className ?? "";
|
|
996
1158
|
const inputClass = styled ? "w-full rounded border border-gray-200 bg-white px-2.5 py-1.5 text-sm outline-none transition-colors placeholder:text-gray-400 focus:border-indigo-500 focus:ring-2 focus:ring-indigo-500/20 dark:border-gray-700 dark:bg-gray-900 dark:text-white dark:placeholder:text-gray-500" : "";
|
|
@@ -1025,9 +1187,9 @@ function RangeInput({
|
|
|
1025
1187
|
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
1026
1188
|
"input",
|
|
1027
1189
|
{
|
|
1190
|
+
name: "min",
|
|
1028
1191
|
type: "number",
|
|
1029
|
-
|
|
1030
|
-
onChange: handleMinChange,
|
|
1192
|
+
defaultValue: defaultMin,
|
|
1031
1193
|
placeholder: minPlaceholder,
|
|
1032
1194
|
"aria-label": `Minimum ${attribute}`,
|
|
1033
1195
|
className: inputClass
|
|
@@ -1044,9 +1206,9 @@ function RangeInput({
|
|
|
1044
1206
|
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
1045
1207
|
"input",
|
|
1046
1208
|
{
|
|
1209
|
+
name: "max",
|
|
1047
1210
|
type: "number",
|
|
1048
|
-
|
|
1049
|
-
onChange: handleMaxChange,
|
|
1211
|
+
defaultValue: defaultMax,
|
|
1050
1212
|
placeholder: maxPlaceholder,
|
|
1051
1213
|
"aria-label": `Maximum ${attribute}`,
|
|
1052
1214
|
className: inputClass
|
|
@@ -1055,7 +1217,8 @@ function RangeInput({
|
|
|
1055
1217
|
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("button", { type: "submit", className: buttonClass, children: "Go" })
|
|
1056
1218
|
] })
|
|
1057
1219
|
]
|
|
1058
|
-
}
|
|
1220
|
+
},
|
|
1221
|
+
formKey
|
|
1059
1222
|
);
|
|
1060
1223
|
}
|
|
1061
1224
|
|
|
@@ -1116,7 +1279,21 @@ function PoweredBy({
|
|
|
1116
1279
|
SearchBox,
|
|
1117
1280
|
SortBy,
|
|
1118
1281
|
Stats,
|
|
1282
|
+
andFilters,
|
|
1283
|
+
assignExperiment,
|
|
1284
|
+
buildExperimentConfigPath,
|
|
1285
|
+
chooseExperimentVariant,
|
|
1286
|
+
combineFilters,
|
|
1287
|
+
experimentSearchMetadata,
|
|
1288
|
+
filterEq,
|
|
1289
|
+
filterExists,
|
|
1290
|
+
filterIn,
|
|
1291
|
+
filterNotEq,
|
|
1292
|
+
filterRange,
|
|
1119
1293
|
headband,
|
|
1294
|
+
isRunnableExperiment,
|
|
1295
|
+
orFilters,
|
|
1296
|
+
quoteFilterValue,
|
|
1120
1297
|
useHeadbandContext,
|
|
1121
1298
|
useHighlight,
|
|
1122
1299
|
useHits,
|