@uniformdev/search 0.0.3 → 0.0.6

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/react.mjs CHANGED
@@ -104,22 +104,26 @@ var SearchProvider = ({
104
104
  }, {}),
105
105
  [filterBy, urlParams]
106
106
  );
107
+ const buildFilters = useCallback(
108
+ (excludeField) => Object.entries(selectedFilters).reduce((acc, [fieldKey, values]) => {
109
+ var _a2;
110
+ if (excludeField && fieldKey === excludeField) return acc;
111
+ if (!values || values.length === 0) return acc;
112
+ const filterType = (_a2 = filterBy.find((f) => f.fieldKey === fieldKey)) == null ? void 0 : _a2.type;
113
+ if (filterType === "range") {
114
+ const [min, max] = values;
115
+ return { ...acc, [`${fieldKey}[gte]`]: min, [`${fieldKey}[lte]`]: max };
116
+ }
117
+ return { ...acc, [`${fieldKey}[in]`]: values };
118
+ }, {}),
119
+ [selectedFilters, filterBy]
120
+ );
121
+ const currentFilters = useMemo(() => buildFilters(), [buildFilters]);
107
122
  useEffect(() => {
108
123
  const requestId = ++searchRequestIdRef.current;
109
124
  const doFetch = async () => {
110
125
  var _a2, _b2;
111
126
  const currentOrderByQuery = (urlParams == null ? void 0 : urlParams[UNIFORM_SEARCH_ORDER_BY_KEY]) || defaultOrderByQuery;
112
- const buildFilters = (excludeField) => Object.entries(selectedFilters).reduce((acc, [fieldKey, values]) => {
113
- var _a3;
114
- if (excludeField && fieldKey === excludeField) return acc;
115
- if (!values || values.length === 0) return acc;
116
- const filterType = (_a3 = filterBy.find((f) => f.fieldKey === fieldKey)) == null ? void 0 : _a3.type;
117
- if (filterType === "range") {
118
- const [min, max] = values;
119
- return { ...acc, [`${fieldKey}[gte]`]: min, [`${fieldKey}[lte]`]: max };
120
- }
121
- return { ...acc, [`${fieldKey}[in]`]: values };
122
- }, {});
123
127
  const selectedFacetFields = Object.keys(selectedFilters).filter((k) => {
124
128
  var _a3;
125
129
  return ((_a3 = selectedFilters[k]) == null ? void 0 : _a3.length) > 0;
@@ -167,6 +171,7 @@ var SearchProvider = ({
167
171
  doFetch().catch((error) => console.error("Search fetch error:", error)).finally(() => setIsLoading(false));
168
172
  }, [
169
173
  baseFilterString,
174
+ buildFilters,
170
175
  defaultOrderByQuery,
171
176
  facetBy,
172
177
  filterBy,
@@ -307,6 +312,11 @@ var SearchProvider = ({
307
312
  setOrderBy,
308
313
  registerOrderBy,
309
314
  unregisterOrderBy,
315
+ performSearch,
316
+ queryBy,
317
+ locale,
318
+ baseFilterString,
319
+ currentFilters,
310
320
  filterOptions: filterBy,
311
321
  setFilterOptions,
312
322
  registerFilterOption,
@@ -317,11 +327,16 @@ var SearchProvider = ({
317
327
  formatResultsSummary
318
328
  }),
319
329
  [
330
+ baseFilterString,
320
331
  clearFilters,
332
+ currentFilters,
321
333
  defaultOrderByQuery,
322
334
  entries,
323
335
  facets,
324
336
  filterBy,
337
+ locale,
338
+ performSearch,
339
+ queryBy,
325
340
  setFilterOptions,
326
341
  registerFilterOption,
327
342
  unregisterFilterOption,
@@ -358,14 +373,335 @@ function useSearch() {
358
373
  return context;
359
374
  }
360
375
 
376
+ // src/react/useAutocomplete.ts
377
+ import {
378
+ useCallback as useCallback2,
379
+ useEffect as useEffect2,
380
+ useId,
381
+ useMemo as useMemo2,
382
+ useRef as useRef2,
383
+ useState as useState2
384
+ } from "react";
385
+ var NO_ACTIVE_INDEX = -1;
386
+ var DEFAULT_PER_PAGE = 6;
387
+ var DEFAULT_MIN_QUERY_LENGTH = 1;
388
+ var DEFAULT_DEBOUNCE_MS = 150;
389
+ function useAutocomplete(options) {
390
+ var _a;
391
+ const {
392
+ performSearch,
393
+ queryBy,
394
+ baseFilterBy,
395
+ filters,
396
+ locale,
397
+ perPage = DEFAULT_PER_PAGE,
398
+ minQueryLength = DEFAULT_MIN_QUERY_LENGTH,
399
+ debounceMs = DEFAULT_DEBOUNCE_MS,
400
+ openOnFocus = false,
401
+ onSelect,
402
+ onSubmit,
403
+ maxTypos,
404
+ minLengthFor1Typo,
405
+ minLengthFor2Typos,
406
+ typoFallbackThreshold,
407
+ prioritizeExactMatch
408
+ } = options;
409
+ const baseId = useId();
410
+ const listboxId = `${baseId}-listbox`;
411
+ const labelId = `${baseId}-label`;
412
+ const inputId = `${baseId}-input`;
413
+ const optionId = useCallback2((index) => `${baseId}-option-${index}`, [baseId]);
414
+ const [query, setQueryState] = useState2("");
415
+ const [suggestions, setSuggestions] = useState2([]);
416
+ const [isLoading, setIsLoading] = useState2(false);
417
+ const [isOpen, setIsOpen] = useState2(false);
418
+ const [activeIndex, setActiveIndex] = useState2(NO_ACTIVE_INDEX);
419
+ const debounceRef = useRef2(null);
420
+ const requestIdRef = useRef2(0);
421
+ const queryByString = useMemo2(() => (queryBy == null ? void 0 : queryBy.length) ? queryBy.join(",") : void 0, [queryBy]);
422
+ const fetchConfigRef = useRef2({
423
+ performSearch,
424
+ queryByString,
425
+ baseFilterBy,
426
+ filters,
427
+ locale,
428
+ perPage,
429
+ maxTypos,
430
+ minLengthFor1Typo,
431
+ minLengthFor2Typos,
432
+ typoFallbackThreshold,
433
+ prioritizeExactMatch
434
+ });
435
+ fetchConfigRef.current = {
436
+ performSearch,
437
+ queryByString,
438
+ baseFilterBy,
439
+ filters,
440
+ locale,
441
+ perPage,
442
+ maxTypos,
443
+ minLengthFor1Typo,
444
+ minLengthFor2Typos,
445
+ typoFallbackThreshold,
446
+ prioritizeExactMatch
447
+ };
448
+ const runSearch = useCallback2((value) => {
449
+ const requestId = ++requestIdRef.current;
450
+ const cfg = fetchConfigRef.current;
451
+ setIsLoading(true);
452
+ cfg.performSearch({
453
+ page: 0,
454
+ perPage: cfg.perPage,
455
+ queryBy: cfg.queryByString,
456
+ baseFilterBy: cfg.baseFilterBy,
457
+ filters: cfg.filters,
458
+ search: value,
459
+ locale: cfg.locale,
460
+ maxTypos: cfg.maxTypos,
461
+ minLengthFor1Typo: cfg.minLengthFor1Typo,
462
+ minLengthFor2Typos: cfg.minLengthFor2Typos,
463
+ typoFallbackThreshold: cfg.typoFallbackThreshold,
464
+ prioritizeExactMatch: cfg.prioritizeExactMatch
465
+ }).then((result) => {
466
+ var _a2, _b;
467
+ if (requestId !== requestIdRef.current) return;
468
+ const items = (_b = (_a2 = result == null ? void 0 : result.data) == null ? void 0 : _a2.items) != null ? _b : [];
469
+ setSuggestions(items);
470
+ setActiveIndex(NO_ACTIVE_INDEX);
471
+ setIsOpen(items.length > 0);
472
+ }).catch((error) => {
473
+ if (requestId !== requestIdRef.current) return;
474
+ console.error("Autocomplete fetch error:", error);
475
+ setSuggestions([]);
476
+ }).finally(() => {
477
+ if (requestId !== requestIdRef.current) return;
478
+ setIsLoading(false);
479
+ });
480
+ }, []);
481
+ const setQuery = useCallback2(
482
+ (value) => {
483
+ setQueryState(value);
484
+ if (debounceRef.current) clearTimeout(debounceRef.current);
485
+ if (value.trim().length < minQueryLength) {
486
+ requestIdRef.current++;
487
+ setSuggestions([]);
488
+ setActiveIndex(NO_ACTIVE_INDEX);
489
+ setIsOpen(false);
490
+ setIsLoading(false);
491
+ return;
492
+ }
493
+ debounceRef.current = setTimeout(() => runSearch(value), debounceMs);
494
+ },
495
+ [debounceMs, minQueryLength, runSearch]
496
+ );
497
+ useEffect2(() => {
498
+ return () => {
499
+ if (debounceRef.current) clearTimeout(debounceRef.current);
500
+ };
501
+ }, []);
502
+ const open = useCallback2(() => {
503
+ if (suggestions.length > 0) setIsOpen(true);
504
+ }, [suggestions.length]);
505
+ const close = useCallback2(() => {
506
+ setIsOpen(false);
507
+ setActiveIndex(NO_ACTIVE_INDEX);
508
+ }, []);
509
+ const clear = useCallback2(() => {
510
+ if (debounceRef.current) clearTimeout(debounceRef.current);
511
+ requestIdRef.current++;
512
+ setQueryState("");
513
+ setSuggestions([]);
514
+ setActiveIndex(NO_ACTIVE_INDEX);
515
+ setIsOpen(false);
516
+ setIsLoading(false);
517
+ }, []);
518
+ const selectItem = useCallback2(
519
+ (item) => {
520
+ close();
521
+ onSelect == null ? void 0 : onSelect(item);
522
+ },
523
+ [close, onSelect]
524
+ );
525
+ const activeItem = activeIndex >= 0 ? (_a = suggestions[activeIndex]) != null ? _a : null : null;
526
+ const handleKeyDown = useCallback2(
527
+ (event) => {
528
+ const count = suggestions.length;
529
+ switch (event.key) {
530
+ case "ArrowDown": {
531
+ event.preventDefault();
532
+ if (!isOpen) {
533
+ open();
534
+ return;
535
+ }
536
+ if (count === 0) return;
537
+ setActiveIndex((prev) => prev + 1 > count - 1 ? NO_ACTIVE_INDEX : prev + 1);
538
+ break;
539
+ }
540
+ case "ArrowUp": {
541
+ event.preventDefault();
542
+ if (!isOpen) {
543
+ open();
544
+ return;
545
+ }
546
+ if (count === 0) return;
547
+ setActiveIndex((prev) => prev <= NO_ACTIVE_INDEX ? count - 1 : prev - 1);
548
+ break;
549
+ }
550
+ case "Home": {
551
+ if (!isOpen || count === 0) return;
552
+ event.preventDefault();
553
+ setActiveIndex(0);
554
+ break;
555
+ }
556
+ case "End": {
557
+ if (!isOpen || count === 0) return;
558
+ event.preventDefault();
559
+ setActiveIndex(count - 1);
560
+ break;
561
+ }
562
+ case "Enter": {
563
+ if (isOpen && activeIndex >= 0 && suggestions[activeIndex]) {
564
+ event.preventDefault();
565
+ selectItem(suggestions[activeIndex]);
566
+ } else {
567
+ close();
568
+ onSubmit == null ? void 0 : onSubmit(query);
569
+ }
570
+ break;
571
+ }
572
+ case "Escape": {
573
+ if (isOpen) {
574
+ event.preventDefault();
575
+ close();
576
+ }
577
+ break;
578
+ }
579
+ default:
580
+ break;
581
+ }
582
+ },
583
+ [activeIndex, close, isOpen, onSubmit, open, query, selectItem, suggestions]
584
+ );
585
+ const getLabelProps = useCallback2(() => ({ id: labelId, htmlFor: inputId }), [labelId, inputId]);
586
+ const getInputProps = useCallback2(
587
+ (userProps = {}) => {
588
+ var _a2, _b;
589
+ return {
590
+ ...userProps,
591
+ id: (_a2 = userProps.id) != null ? _a2 : inputId,
592
+ role: "combobox",
593
+ "aria-autocomplete": "list",
594
+ "aria-expanded": isOpen,
595
+ "aria-controls": listboxId,
596
+ "aria-activedescendant": activeIndex >= 0 ? optionId(activeIndex) : void 0,
597
+ "aria-labelledby": (_b = userProps["aria-labelledby"]) != null ? _b : labelId,
598
+ autoComplete: "off",
599
+ value: query,
600
+ onChange: (event) => {
601
+ var _a3;
602
+ setQuery(event.target.value);
603
+ (_a3 = userProps.onChange) == null ? void 0 : _a3.call(userProps, event);
604
+ },
605
+ onKeyDown: (event) => {
606
+ var _a3;
607
+ handleKeyDown(event);
608
+ (_a3 = userProps.onKeyDown) == null ? void 0 : _a3.call(userProps, event);
609
+ },
610
+ onFocus: (event) => {
611
+ var _a3;
612
+ if (openOnFocus) open();
613
+ (_a3 = userProps.onFocus) == null ? void 0 : _a3.call(userProps, event);
614
+ },
615
+ onBlur: (event) => {
616
+ var _a3;
617
+ close();
618
+ (_a3 = userProps.onBlur) == null ? void 0 : _a3.call(userProps, event);
619
+ }
620
+ };
621
+ },
622
+ [
623
+ activeIndex,
624
+ close,
625
+ handleKeyDown,
626
+ inputId,
627
+ isOpen,
628
+ labelId,
629
+ listboxId,
630
+ open,
631
+ openOnFocus,
632
+ optionId,
633
+ query,
634
+ setQuery
635
+ ]
636
+ );
637
+ const getListboxProps = useCallback2(
638
+ (userProps = {}) => {
639
+ var _a2;
640
+ return {
641
+ ...userProps,
642
+ id: listboxId,
643
+ role: "listbox",
644
+ "aria-labelledby": (_a2 = userProps["aria-labelledby"]) != null ? _a2 : labelId
645
+ };
646
+ },
647
+ [labelId, listboxId]
648
+ );
649
+ const getItemProps = useCallback2(
650
+ ({
651
+ item,
652
+ index,
653
+ userProps = {}
654
+ }) => ({
655
+ ...userProps,
656
+ id: optionId(index),
657
+ role: "option",
658
+ "aria-selected": index === activeIndex,
659
+ onMouseMove: (event) => {
660
+ var _a2;
661
+ if (index !== activeIndex) setActiveIndex(index);
662
+ (_a2 = userProps.onMouseMove) == null ? void 0 : _a2.call(userProps, event);
663
+ },
664
+ // Prevent the input from blurring (which would close the panel) before the click lands.
665
+ onMouseDown: (event) => {
666
+ var _a2;
667
+ event.preventDefault();
668
+ (_a2 = userProps.onMouseDown) == null ? void 0 : _a2.call(userProps, event);
669
+ },
670
+ onClick: (event) => {
671
+ var _a2;
672
+ selectItem(item);
673
+ (_a2 = userProps.onClick) == null ? void 0 : _a2.call(userProps, event);
674
+ }
675
+ }),
676
+ [activeIndex, optionId, selectItem]
677
+ );
678
+ return {
679
+ query,
680
+ setQuery,
681
+ suggestions,
682
+ isLoading,
683
+ isOpen,
684
+ activeIndex,
685
+ activeItem,
686
+ open,
687
+ close,
688
+ clear,
689
+ selectItem,
690
+ getLabelProps,
691
+ getInputProps,
692
+ getListboxProps,
693
+ getItemProps
694
+ };
695
+ }
696
+
361
697
  // src/react/usePagination.ts
362
- import { useMemo as useMemo2 } from "react";
698
+ import { useMemo as useMemo3 } from "react";
363
699
  var DOTS = "...";
364
700
  var range = (start, end) => {
365
701
  const length = end - start + 1;
366
702
  return Array.from({ length }, (_, idx) => idx + start);
367
703
  };
368
- var usePagination = ({ totalCount, perPage, siblingCount = 1, currentPage }) => useMemo2(() => {
704
+ var usePagination = ({ totalCount, perPage, siblingCount = 1, currentPage }) => useMemo3(() => {
369
705
  const totalPageCount = Math.ceil(totalCount / perPage);
370
706
  const totalPageNumbers = siblingCount + 5;
371
707
  if (totalPageNumbers >= totalPageCount) {
@@ -394,7 +730,7 @@ var usePagination = ({ totalCount, perPage, siblingCount = 1, currentPage }) =>
394
730
  }, [totalCount, perPage, siblingCount, currentPage]);
395
731
 
396
732
  // src/react/useSearchPagination.ts
397
- import { useMemo as useMemo3, useCallback as useCallback2 } from "react";
733
+ import { useMemo as useMemo4, useCallback as useCallback3 } from "react";
398
734
  function useSearchPagination(siblingCount) {
399
735
  const { page, pageSize, results, setPage, isLoading } = useSearch();
400
736
  const pages = usePagination({
@@ -403,23 +739,23 @@ function useSearchPagination(siblingCount) {
403
739
  perPage: pageSize,
404
740
  siblingCount
405
741
  }) || [];
406
- const lastPage = useMemo3(
742
+ const lastPage = useMemo4(
407
743
  () => Number(pages[pages.length - 1]) || 0,
408
744
  [pages]
409
745
  );
410
- const goToPage = useCallback2(
746
+ const goToPage = useCallback3(
411
747
  (p) => {
412
748
  if (!isLoading) setPage(p);
413
749
  },
414
750
  [isLoading, setPage]
415
751
  );
416
- const goToPrev = useCallback2(
752
+ const goToPrev = useCallback3(
417
753
  () => {
418
754
  if (page > 0 && !isLoading) setPage(page - 1);
419
755
  },
420
756
  [page, isLoading, setPage]
421
757
  );
422
- const goToNext = useCallback2(
758
+ const goToNext = useCallback3(
423
759
  () => {
424
760
  if (page < lastPage && !isLoading) setPage(page + 1);
425
761
  },
@@ -500,6 +836,7 @@ export {
500
836
  SearchItemUrlResolverProvider,
501
837
  SearchProvider,
502
838
  createDefaultUrlResolver,
839
+ useAutocomplete,
503
840
  usePagination,
504
841
  useSearch,
505
842
  useSearchPagination,