@headband/react 0.1.0 → 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 +279 -96
- package/dist/index.mjs +267 -102
- 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,90 +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);
|
|
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]);
|
|
160
330
|
const executeSearch = (0, import_react.useCallback)(() => {
|
|
161
|
-
const
|
|
331
|
+
const requestId = ++requestIdRef.current;
|
|
162
332
|
setIsLoading(true);
|
|
163
333
|
setError(null);
|
|
164
|
-
const filter = buildFilterString(refinements, ranges);
|
|
165
|
-
const facetList = facets.size > 0 ? Array.from(facets) : void 0;
|
|
166
|
-
const sortList = sortBy ? [sortBy] : void 0;
|
|
167
334
|
const params = {
|
|
168
335
|
limit: hitsPerPage,
|
|
169
336
|
offset: page * hitsPerPage,
|
|
170
337
|
...filter ? { filter } : {},
|
|
171
338
|
...facetList ? { facets: facetList } : {},
|
|
172
339
|
...sortList ? { sort: sortList } : {},
|
|
340
|
+
...experiment ? experiment : {},
|
|
341
|
+
...mode ? { mode } : {},
|
|
173
342
|
attributesToHighlight: ["*"]
|
|
174
343
|
};
|
|
175
344
|
client.search(index, query, params).then((res) => {
|
|
176
|
-
if (
|
|
177
|
-
|
|
178
|
-
setIsLoading(false);
|
|
345
|
+
if (requestId !== requestIdRef.current) {
|
|
346
|
+
return;
|
|
179
347
|
}
|
|
348
|
+
setResults(res);
|
|
349
|
+
setIsLoading(false);
|
|
180
350
|
}).catch((err) => {
|
|
181
|
-
if (
|
|
182
|
-
|
|
183
|
-
setIsLoading(false);
|
|
351
|
+
if (requestId !== requestIdRef.current) {
|
|
352
|
+
return;
|
|
184
353
|
}
|
|
354
|
+
setError(err instanceof Error ? err : new Error(String(err)));
|
|
355
|
+
setIsLoading(false);
|
|
185
356
|
});
|
|
186
|
-
}, [client, index, query,
|
|
357
|
+
}, [client, index, query, filter, facetList, sortList, experiment, mode, page, hitsPerPage]);
|
|
187
358
|
(0, import_react.useEffect)(() => {
|
|
188
|
-
|
|
189
|
-
clearTimeout(timerRef.current);
|
|
190
|
-
}
|
|
191
|
-
timerRef.current = setTimeout(executeSearch, 200);
|
|
359
|
+
const timer = setTimeout(executeSearch, 200);
|
|
192
360
|
return () => {
|
|
193
|
-
|
|
361
|
+
clearTimeout(timer);
|
|
194
362
|
};
|
|
195
363
|
}, [executeSearch]);
|
|
196
364
|
const setQuery = (0, import_react.useCallback)((q) => {
|
|
197
365
|
setQueryRaw(q);
|
|
198
366
|
setPage(0);
|
|
199
367
|
}, []);
|
|
200
|
-
const toggleRefinement = (0, import_react.useCallback)(
|
|
201
|
-
(
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
return next;
|
|
223
|
-
});
|
|
224
|
-
setPage(0);
|
|
225
|
-
},
|
|
226
|
-
[]
|
|
227
|
-
);
|
|
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
|
+
}, []);
|
|
228
390
|
const clearRange = (0, import_react.useCallback)((attribute) => {
|
|
229
391
|
setRanges((prev) => {
|
|
230
392
|
const next = new Map(prev);
|
|
@@ -280,7 +442,18 @@ function HeadbandProvider({
|
|
|
280
442
|
unregisterFacet,
|
|
281
443
|
refresh
|
|
282
444
|
}),
|
|
283
|
-
[
|
|
445
|
+
[
|
|
446
|
+
setQuery,
|
|
447
|
+
setPage,
|
|
448
|
+
setSortBy,
|
|
449
|
+
setHitsPerPage,
|
|
450
|
+
toggleRefinement,
|
|
451
|
+
setRange,
|
|
452
|
+
clearRange,
|
|
453
|
+
registerFacet,
|
|
454
|
+
unregisterFacet,
|
|
455
|
+
refresh
|
|
456
|
+
]
|
|
284
457
|
);
|
|
285
458
|
const value = (0, import_react.useMemo)(
|
|
286
459
|
() => ({ client, index, state, actions }),
|
|
@@ -342,18 +515,19 @@ function useRefinementList({
|
|
|
342
515
|
actions.unregisterFacet(attribute);
|
|
343
516
|
};
|
|
344
517
|
}, [attribute, actions]);
|
|
345
|
-
const refined = state.refinements.get(attribute)
|
|
518
|
+
const refined = state.refinements.get(attribute);
|
|
346
519
|
const items = (0, import_react4.useMemo)(() => {
|
|
520
|
+
const activeRefinements = refined ?? /* @__PURE__ */ new Set();
|
|
347
521
|
const distribution = state.results?.facetDistribution?.[attribute] ?? {};
|
|
348
522
|
const allValues = /* @__PURE__ */ new Set([
|
|
349
523
|
...Object.keys(distribution),
|
|
350
|
-
...
|
|
524
|
+
...activeRefinements
|
|
351
525
|
]);
|
|
352
526
|
let result = Array.from(allValues).map(
|
|
353
527
|
(value) => ({
|
|
354
528
|
value,
|
|
355
529
|
count: distribution[value] ?? 0,
|
|
356
|
-
isRefined:
|
|
530
|
+
isRefined: activeRefinements.has(value)
|
|
357
531
|
})
|
|
358
532
|
);
|
|
359
533
|
if (sortOrder === "alpha") {
|
|
@@ -653,7 +827,11 @@ function SearchBox({
|
|
|
653
827
|
// src/components/Hits.tsx
|
|
654
828
|
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
655
829
|
function DefaultHit({ hit }) {
|
|
656
|
-
const
|
|
830
|
+
const rest = Object.fromEntries(
|
|
831
|
+
Object.entries(hit).filter(
|
|
832
|
+
([key]) => key !== "_formatted" && key !== "__data"
|
|
833
|
+
)
|
|
834
|
+
);
|
|
657
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) }) });
|
|
658
836
|
}
|
|
659
837
|
function DefaultEmpty() {
|
|
@@ -714,7 +892,7 @@ function RefinementList({
|
|
|
714
892
|
sortBy
|
|
715
893
|
});
|
|
716
894
|
const handleChange = (0, import_react11.useCallback)(
|
|
717
|
-
(value) => (
|
|
895
|
+
(value) => () => {
|
|
718
896
|
refine(value);
|
|
719
897
|
},
|
|
720
898
|
[refine]
|
|
@@ -954,17 +1132,15 @@ function RangeInput({
|
|
|
954
1132
|
maxPlaceholder = "Max"
|
|
955
1133
|
}) {
|
|
956
1134
|
const { min, max, stats, setRange, clear } = useRange({ attribute });
|
|
957
|
-
const
|
|
958
|
-
const
|
|
959
|
-
|
|
960
|
-
setLocalMin(min !== void 0 ? String(min) : "");
|
|
961
|
-
}, [min]);
|
|
962
|
-
(0, import_react14.useEffect)(() => {
|
|
963
|
-
setLocalMax(max !== void 0 ? String(max) : "");
|
|
964
|
-
}, [max]);
|
|
1135
|
+
const defaultMin = min !== void 0 ? String(min) : "";
|
|
1136
|
+
const defaultMax = max !== void 0 ? String(max) : "";
|
|
1137
|
+
const formKey = `${attribute}:${defaultMin}:${defaultMax}`;
|
|
965
1138
|
const handleSubmit = (0, import_react14.useCallback)(
|
|
966
1139
|
(e) => {
|
|
967
1140
|
e.preventDefault();
|
|
1141
|
+
const formData = new FormData(e.currentTarget);
|
|
1142
|
+
const localMin = String(formData.get("min") ?? "");
|
|
1143
|
+
const localMax = String(formData.get("max") ?? "");
|
|
968
1144
|
const parsedMin = localMin !== "" ? Number(localMin) : void 0;
|
|
969
1145
|
const parsedMax = localMax !== "" ? Number(localMax) : void 0;
|
|
970
1146
|
if (parsedMin === void 0 && parsedMax === void 0) {
|
|
@@ -976,15 +1152,7 @@ function RangeInput({
|
|
|
976
1152
|
});
|
|
977
1153
|
}
|
|
978
1154
|
},
|
|
979
|
-
[
|
|
980
|
-
);
|
|
981
|
-
const handleMinChange = (0, import_react14.useCallback)(
|
|
982
|
-
(e) => setLocalMin(e.target.value),
|
|
983
|
-
[]
|
|
984
|
-
);
|
|
985
|
-
const handleMaxChange = (0, import_react14.useCallback)(
|
|
986
|
-
(e) => setLocalMax(e.target.value),
|
|
987
|
-
[]
|
|
1155
|
+
[setRange, clear]
|
|
988
1156
|
);
|
|
989
1157
|
const containerClass = styled ? `space-y-2 ${className ?? ""}`.trim() : className ?? "";
|
|
990
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" : "";
|
|
@@ -1019,9 +1187,9 @@ function RangeInput({
|
|
|
1019
1187
|
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
1020
1188
|
"input",
|
|
1021
1189
|
{
|
|
1190
|
+
name: "min",
|
|
1022
1191
|
type: "number",
|
|
1023
|
-
|
|
1024
|
-
onChange: handleMinChange,
|
|
1192
|
+
defaultValue: defaultMin,
|
|
1025
1193
|
placeholder: minPlaceholder,
|
|
1026
1194
|
"aria-label": `Minimum ${attribute}`,
|
|
1027
1195
|
className: inputClass
|
|
@@ -1038,9 +1206,9 @@ function RangeInput({
|
|
|
1038
1206
|
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
1039
1207
|
"input",
|
|
1040
1208
|
{
|
|
1209
|
+
name: "max",
|
|
1041
1210
|
type: "number",
|
|
1042
|
-
|
|
1043
|
-
onChange: handleMaxChange,
|
|
1211
|
+
defaultValue: defaultMax,
|
|
1044
1212
|
placeholder: maxPlaceholder,
|
|
1045
1213
|
"aria-label": `Maximum ${attribute}`,
|
|
1046
1214
|
className: inputClass
|
|
@@ -1049,7 +1217,8 @@ function RangeInput({
|
|
|
1049
1217
|
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("button", { type: "submit", className: buttonClass, children: "Go" })
|
|
1050
1218
|
] })
|
|
1051
1219
|
]
|
|
1052
|
-
}
|
|
1220
|
+
},
|
|
1221
|
+
formKey
|
|
1053
1222
|
);
|
|
1054
1223
|
}
|
|
1055
1224
|
|
|
@@ -1110,7 +1279,21 @@ function PoweredBy({
|
|
|
1110
1279
|
SearchBox,
|
|
1111
1280
|
SortBy,
|
|
1112
1281
|
Stats,
|
|
1282
|
+
andFilters,
|
|
1283
|
+
assignExperiment,
|
|
1284
|
+
buildExperimentConfigPath,
|
|
1285
|
+
chooseExperimentVariant,
|
|
1286
|
+
combineFilters,
|
|
1287
|
+
experimentSearchMetadata,
|
|
1288
|
+
filterEq,
|
|
1289
|
+
filterExists,
|
|
1290
|
+
filterIn,
|
|
1291
|
+
filterNotEq,
|
|
1292
|
+
filterRange,
|
|
1113
1293
|
headband,
|
|
1294
|
+
isRunnableExperiment,
|
|
1295
|
+
orFilters,
|
|
1296
|
+
quoteFilterValue,
|
|
1114
1297
|
useHeadbandContext,
|
|
1115
1298
|
useHighlight,
|
|
1116
1299
|
useHits,
|