@uniformdev/search 0.0.4 → 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.js CHANGED
@@ -26,6 +26,7 @@ __export(react_exports, {
26
26
  SearchItemUrlResolverProvider: () => SearchItemUrlResolverProvider,
27
27
  SearchProvider: () => SearchProvider,
28
28
  createDefaultUrlResolver: () => createDefaultUrlResolver,
29
+ useAutocomplete: () => useAutocomplete,
29
30
  usePagination: () => usePagination,
30
31
  useSearch: () => useSearch,
31
32
  useSearchPagination: () => useSearchPagination,
@@ -190,22 +191,26 @@ var SearchProvider = ({
190
191
  }, {}),
191
192
  [filterBy, urlParams]
192
193
  );
194
+ const buildFilters = (0, import_react.useCallback)(
195
+ (excludeField) => Object.entries(selectedFilters).reduce((acc, [fieldKey, values]) => {
196
+ var _a2;
197
+ if (excludeField && fieldKey === excludeField) return acc;
198
+ if (!values || values.length === 0) return acc;
199
+ const filterType = (_a2 = filterBy.find((f) => f.fieldKey === fieldKey)) == null ? void 0 : _a2.type;
200
+ if (filterType === "range") {
201
+ const [min, max] = values;
202
+ return { ...acc, [`${fieldKey}[gte]`]: min, [`${fieldKey}[lte]`]: max };
203
+ }
204
+ return { ...acc, [`${fieldKey}[in]`]: values };
205
+ }, {}),
206
+ [selectedFilters, filterBy]
207
+ );
208
+ const currentFilters = (0, import_react.useMemo)(() => buildFilters(), [buildFilters]);
193
209
  (0, import_react.useEffect)(() => {
194
210
  const requestId = ++searchRequestIdRef.current;
195
211
  const doFetch = async () => {
196
212
  var _a2, _b2;
197
213
  const currentOrderByQuery = (urlParams == null ? void 0 : urlParams[UNIFORM_SEARCH_ORDER_BY_KEY]) || defaultOrderByQuery;
198
- const buildFilters = (excludeField) => Object.entries(selectedFilters).reduce((acc, [fieldKey, values]) => {
199
- var _a3;
200
- if (excludeField && fieldKey === excludeField) return acc;
201
- if (!values || values.length === 0) return acc;
202
- const filterType = (_a3 = filterBy.find((f) => f.fieldKey === fieldKey)) == null ? void 0 : _a3.type;
203
- if (filterType === "range") {
204
- const [min, max] = values;
205
- return { ...acc, [`${fieldKey}[gte]`]: min, [`${fieldKey}[lte]`]: max };
206
- }
207
- return { ...acc, [`${fieldKey}[in]`]: values };
208
- }, {});
209
214
  const selectedFacetFields = Object.keys(selectedFilters).filter((k) => {
210
215
  var _a3;
211
216
  return ((_a3 = selectedFilters[k]) == null ? void 0 : _a3.length) > 0;
@@ -253,6 +258,7 @@ var SearchProvider = ({
253
258
  doFetch().catch((error) => console.error("Search fetch error:", error)).finally(() => setIsLoading(false));
254
259
  }, [
255
260
  baseFilterString,
261
+ buildFilters,
256
262
  defaultOrderByQuery,
257
263
  facetBy,
258
264
  filterBy,
@@ -393,6 +399,11 @@ var SearchProvider = ({
393
399
  setOrderBy,
394
400
  registerOrderBy,
395
401
  unregisterOrderBy,
402
+ performSearch,
403
+ queryBy,
404
+ locale,
405
+ baseFilterString,
406
+ currentFilters,
396
407
  filterOptions: filterBy,
397
408
  setFilterOptions,
398
409
  registerFilterOption,
@@ -403,11 +414,16 @@ var SearchProvider = ({
403
414
  formatResultsSummary
404
415
  }),
405
416
  [
417
+ baseFilterString,
406
418
  clearFilters,
419
+ currentFilters,
407
420
  defaultOrderByQuery,
408
421
  entries,
409
422
  facets,
410
423
  filterBy,
424
+ locale,
425
+ performSearch,
426
+ queryBy,
411
427
  setFilterOptions,
412
428
  registerFilterOption,
413
429
  unregisterFilterOption,
@@ -444,14 +460,328 @@ function useSearch() {
444
460
  return context;
445
461
  }
446
462
 
447
- // src/react/usePagination.ts
463
+ // src/react/useAutocomplete.ts
448
464
  var import_react3 = require("react");
465
+ var NO_ACTIVE_INDEX = -1;
466
+ var DEFAULT_PER_PAGE = 6;
467
+ var DEFAULT_MIN_QUERY_LENGTH = 1;
468
+ var DEFAULT_DEBOUNCE_MS = 150;
469
+ function useAutocomplete(options) {
470
+ var _a;
471
+ const {
472
+ performSearch,
473
+ queryBy,
474
+ baseFilterBy,
475
+ filters,
476
+ locale,
477
+ perPage = DEFAULT_PER_PAGE,
478
+ minQueryLength = DEFAULT_MIN_QUERY_LENGTH,
479
+ debounceMs = DEFAULT_DEBOUNCE_MS,
480
+ openOnFocus = false,
481
+ onSelect,
482
+ onSubmit,
483
+ maxTypos,
484
+ minLengthFor1Typo,
485
+ minLengthFor2Typos,
486
+ typoFallbackThreshold,
487
+ prioritizeExactMatch
488
+ } = options;
489
+ const baseId = (0, import_react3.useId)();
490
+ const listboxId = `${baseId}-listbox`;
491
+ const labelId = `${baseId}-label`;
492
+ const inputId = `${baseId}-input`;
493
+ const optionId = (0, import_react3.useCallback)((index) => `${baseId}-option-${index}`, [baseId]);
494
+ const [query, setQueryState] = (0, import_react3.useState)("");
495
+ const [suggestions, setSuggestions] = (0, import_react3.useState)([]);
496
+ const [isLoading, setIsLoading] = (0, import_react3.useState)(false);
497
+ const [isOpen, setIsOpen] = (0, import_react3.useState)(false);
498
+ const [activeIndex, setActiveIndex] = (0, import_react3.useState)(NO_ACTIVE_INDEX);
499
+ const debounceRef = (0, import_react3.useRef)(null);
500
+ const requestIdRef = (0, import_react3.useRef)(0);
501
+ const queryByString = (0, import_react3.useMemo)(() => (queryBy == null ? void 0 : queryBy.length) ? queryBy.join(",") : void 0, [queryBy]);
502
+ const fetchConfigRef = (0, import_react3.useRef)({
503
+ performSearch,
504
+ queryByString,
505
+ baseFilterBy,
506
+ filters,
507
+ locale,
508
+ perPage,
509
+ maxTypos,
510
+ minLengthFor1Typo,
511
+ minLengthFor2Typos,
512
+ typoFallbackThreshold,
513
+ prioritizeExactMatch
514
+ });
515
+ fetchConfigRef.current = {
516
+ performSearch,
517
+ queryByString,
518
+ baseFilterBy,
519
+ filters,
520
+ locale,
521
+ perPage,
522
+ maxTypos,
523
+ minLengthFor1Typo,
524
+ minLengthFor2Typos,
525
+ typoFallbackThreshold,
526
+ prioritizeExactMatch
527
+ };
528
+ const runSearch = (0, import_react3.useCallback)((value) => {
529
+ const requestId = ++requestIdRef.current;
530
+ const cfg = fetchConfigRef.current;
531
+ setIsLoading(true);
532
+ cfg.performSearch({
533
+ page: 0,
534
+ perPage: cfg.perPage,
535
+ queryBy: cfg.queryByString,
536
+ baseFilterBy: cfg.baseFilterBy,
537
+ filters: cfg.filters,
538
+ search: value,
539
+ locale: cfg.locale,
540
+ maxTypos: cfg.maxTypos,
541
+ minLengthFor1Typo: cfg.minLengthFor1Typo,
542
+ minLengthFor2Typos: cfg.minLengthFor2Typos,
543
+ typoFallbackThreshold: cfg.typoFallbackThreshold,
544
+ prioritizeExactMatch: cfg.prioritizeExactMatch
545
+ }).then((result) => {
546
+ var _a2, _b;
547
+ if (requestId !== requestIdRef.current) return;
548
+ const items = (_b = (_a2 = result == null ? void 0 : result.data) == null ? void 0 : _a2.items) != null ? _b : [];
549
+ setSuggestions(items);
550
+ setActiveIndex(NO_ACTIVE_INDEX);
551
+ setIsOpen(items.length > 0);
552
+ }).catch((error) => {
553
+ if (requestId !== requestIdRef.current) return;
554
+ console.error("Autocomplete fetch error:", error);
555
+ setSuggestions([]);
556
+ }).finally(() => {
557
+ if (requestId !== requestIdRef.current) return;
558
+ setIsLoading(false);
559
+ });
560
+ }, []);
561
+ const setQuery = (0, import_react3.useCallback)(
562
+ (value) => {
563
+ setQueryState(value);
564
+ if (debounceRef.current) clearTimeout(debounceRef.current);
565
+ if (value.trim().length < minQueryLength) {
566
+ requestIdRef.current++;
567
+ setSuggestions([]);
568
+ setActiveIndex(NO_ACTIVE_INDEX);
569
+ setIsOpen(false);
570
+ setIsLoading(false);
571
+ return;
572
+ }
573
+ debounceRef.current = setTimeout(() => runSearch(value), debounceMs);
574
+ },
575
+ [debounceMs, minQueryLength, runSearch]
576
+ );
577
+ (0, import_react3.useEffect)(() => {
578
+ return () => {
579
+ if (debounceRef.current) clearTimeout(debounceRef.current);
580
+ };
581
+ }, []);
582
+ const open = (0, import_react3.useCallback)(() => {
583
+ if (suggestions.length > 0) setIsOpen(true);
584
+ }, [suggestions.length]);
585
+ const close = (0, import_react3.useCallback)(() => {
586
+ setIsOpen(false);
587
+ setActiveIndex(NO_ACTIVE_INDEX);
588
+ }, []);
589
+ const clear = (0, import_react3.useCallback)(() => {
590
+ if (debounceRef.current) clearTimeout(debounceRef.current);
591
+ requestIdRef.current++;
592
+ setQueryState("");
593
+ setSuggestions([]);
594
+ setActiveIndex(NO_ACTIVE_INDEX);
595
+ setIsOpen(false);
596
+ setIsLoading(false);
597
+ }, []);
598
+ const selectItem = (0, import_react3.useCallback)(
599
+ (item) => {
600
+ close();
601
+ onSelect == null ? void 0 : onSelect(item);
602
+ },
603
+ [close, onSelect]
604
+ );
605
+ const activeItem = activeIndex >= 0 ? (_a = suggestions[activeIndex]) != null ? _a : null : null;
606
+ const handleKeyDown = (0, import_react3.useCallback)(
607
+ (event) => {
608
+ const count = suggestions.length;
609
+ switch (event.key) {
610
+ case "ArrowDown": {
611
+ event.preventDefault();
612
+ if (!isOpen) {
613
+ open();
614
+ return;
615
+ }
616
+ if (count === 0) return;
617
+ setActiveIndex((prev) => prev + 1 > count - 1 ? NO_ACTIVE_INDEX : prev + 1);
618
+ break;
619
+ }
620
+ case "ArrowUp": {
621
+ event.preventDefault();
622
+ if (!isOpen) {
623
+ open();
624
+ return;
625
+ }
626
+ if (count === 0) return;
627
+ setActiveIndex((prev) => prev <= NO_ACTIVE_INDEX ? count - 1 : prev - 1);
628
+ break;
629
+ }
630
+ case "Home": {
631
+ if (!isOpen || count === 0) return;
632
+ event.preventDefault();
633
+ setActiveIndex(0);
634
+ break;
635
+ }
636
+ case "End": {
637
+ if (!isOpen || count === 0) return;
638
+ event.preventDefault();
639
+ setActiveIndex(count - 1);
640
+ break;
641
+ }
642
+ case "Enter": {
643
+ if (isOpen && activeIndex >= 0 && suggestions[activeIndex]) {
644
+ event.preventDefault();
645
+ selectItem(suggestions[activeIndex]);
646
+ } else {
647
+ close();
648
+ onSubmit == null ? void 0 : onSubmit(query);
649
+ }
650
+ break;
651
+ }
652
+ case "Escape": {
653
+ if (isOpen) {
654
+ event.preventDefault();
655
+ close();
656
+ }
657
+ break;
658
+ }
659
+ default:
660
+ break;
661
+ }
662
+ },
663
+ [activeIndex, close, isOpen, onSubmit, open, query, selectItem, suggestions]
664
+ );
665
+ const getLabelProps = (0, import_react3.useCallback)(() => ({ id: labelId, htmlFor: inputId }), [labelId, inputId]);
666
+ const getInputProps = (0, import_react3.useCallback)(
667
+ (userProps = {}) => {
668
+ var _a2, _b;
669
+ return {
670
+ ...userProps,
671
+ id: (_a2 = userProps.id) != null ? _a2 : inputId,
672
+ role: "combobox",
673
+ "aria-autocomplete": "list",
674
+ "aria-expanded": isOpen,
675
+ "aria-controls": listboxId,
676
+ "aria-activedescendant": activeIndex >= 0 ? optionId(activeIndex) : void 0,
677
+ "aria-labelledby": (_b = userProps["aria-labelledby"]) != null ? _b : labelId,
678
+ autoComplete: "off",
679
+ value: query,
680
+ onChange: (event) => {
681
+ var _a3;
682
+ setQuery(event.target.value);
683
+ (_a3 = userProps.onChange) == null ? void 0 : _a3.call(userProps, event);
684
+ },
685
+ onKeyDown: (event) => {
686
+ var _a3;
687
+ handleKeyDown(event);
688
+ (_a3 = userProps.onKeyDown) == null ? void 0 : _a3.call(userProps, event);
689
+ },
690
+ onFocus: (event) => {
691
+ var _a3;
692
+ if (openOnFocus) open();
693
+ (_a3 = userProps.onFocus) == null ? void 0 : _a3.call(userProps, event);
694
+ },
695
+ onBlur: (event) => {
696
+ var _a3;
697
+ close();
698
+ (_a3 = userProps.onBlur) == null ? void 0 : _a3.call(userProps, event);
699
+ }
700
+ };
701
+ },
702
+ [
703
+ activeIndex,
704
+ close,
705
+ handleKeyDown,
706
+ inputId,
707
+ isOpen,
708
+ labelId,
709
+ listboxId,
710
+ open,
711
+ openOnFocus,
712
+ optionId,
713
+ query,
714
+ setQuery
715
+ ]
716
+ );
717
+ const getListboxProps = (0, import_react3.useCallback)(
718
+ (userProps = {}) => {
719
+ var _a2;
720
+ return {
721
+ ...userProps,
722
+ id: listboxId,
723
+ role: "listbox",
724
+ "aria-labelledby": (_a2 = userProps["aria-labelledby"]) != null ? _a2 : labelId
725
+ };
726
+ },
727
+ [labelId, listboxId]
728
+ );
729
+ const getItemProps = (0, import_react3.useCallback)(
730
+ ({
731
+ item,
732
+ index,
733
+ userProps = {}
734
+ }) => ({
735
+ ...userProps,
736
+ id: optionId(index),
737
+ role: "option",
738
+ "aria-selected": index === activeIndex,
739
+ onMouseMove: (event) => {
740
+ var _a2;
741
+ if (index !== activeIndex) setActiveIndex(index);
742
+ (_a2 = userProps.onMouseMove) == null ? void 0 : _a2.call(userProps, event);
743
+ },
744
+ // Prevent the input from blurring (which would close the panel) before the click lands.
745
+ onMouseDown: (event) => {
746
+ var _a2;
747
+ event.preventDefault();
748
+ (_a2 = userProps.onMouseDown) == null ? void 0 : _a2.call(userProps, event);
749
+ },
750
+ onClick: (event) => {
751
+ var _a2;
752
+ selectItem(item);
753
+ (_a2 = userProps.onClick) == null ? void 0 : _a2.call(userProps, event);
754
+ }
755
+ }),
756
+ [activeIndex, optionId, selectItem]
757
+ );
758
+ return {
759
+ query,
760
+ setQuery,
761
+ suggestions,
762
+ isLoading,
763
+ isOpen,
764
+ activeIndex,
765
+ activeItem,
766
+ open,
767
+ close,
768
+ clear,
769
+ selectItem,
770
+ getLabelProps,
771
+ getInputProps,
772
+ getListboxProps,
773
+ getItemProps
774
+ };
775
+ }
776
+
777
+ // src/react/usePagination.ts
778
+ var import_react4 = require("react");
449
779
  var DOTS = "...";
450
780
  var range = (start, end) => {
451
781
  const length = end - start + 1;
452
782
  return Array.from({ length }, (_, idx) => idx + start);
453
783
  };
454
- var usePagination = ({ totalCount, perPage, siblingCount = 1, currentPage }) => (0, import_react3.useMemo)(() => {
784
+ var usePagination = ({ totalCount, perPage, siblingCount = 1, currentPage }) => (0, import_react4.useMemo)(() => {
455
785
  const totalPageCount = Math.ceil(totalCount / perPage);
456
786
  const totalPageNumbers = siblingCount + 5;
457
787
  if (totalPageNumbers >= totalPageCount) {
@@ -480,7 +810,7 @@ var usePagination = ({ totalCount, perPage, siblingCount = 1, currentPage }) =>
480
810
  }, [totalCount, perPage, siblingCount, currentPage]);
481
811
 
482
812
  // src/react/useSearchPagination.ts
483
- var import_react4 = require("react");
813
+ var import_react5 = require("react");
484
814
  function useSearchPagination(siblingCount) {
485
815
  const { page, pageSize, results, setPage, isLoading } = useSearch();
486
816
  const pages = usePagination({
@@ -489,23 +819,23 @@ function useSearchPagination(siblingCount) {
489
819
  perPage: pageSize,
490
820
  siblingCount
491
821
  }) || [];
492
- const lastPage = (0, import_react4.useMemo)(
822
+ const lastPage = (0, import_react5.useMemo)(
493
823
  () => Number(pages[pages.length - 1]) || 0,
494
824
  [pages]
495
825
  );
496
- const goToPage = (0, import_react4.useCallback)(
826
+ const goToPage = (0, import_react5.useCallback)(
497
827
  (p) => {
498
828
  if (!isLoading) setPage(p);
499
829
  },
500
830
  [isLoading, setPage]
501
831
  );
502
- const goToPrev = (0, import_react4.useCallback)(
832
+ const goToPrev = (0, import_react5.useCallback)(
503
833
  () => {
504
834
  if (page > 0 && !isLoading) setPage(page - 1);
505
835
  },
506
836
  [page, isLoading, setPage]
507
837
  );
508
- const goToNext = (0, import_react4.useCallback)(
838
+ const goToNext = (0, import_react5.useCallback)(
509
839
  () => {
510
840
  if (page < lastPage && !isLoading) setPage(page + 1);
511
841
  },
@@ -524,11 +854,11 @@ function useSearchPagination(siblingCount) {
524
854
  }
525
855
 
526
856
  // src/react/SearchItemUrlResolver.tsx
527
- var import_react5 = require("react");
857
+ var import_react6 = require("react");
528
858
  var import_jsx_runtime2 = require("react/jsx-runtime");
529
859
  var NOOP_RESOLVER = () => void 0;
530
- var UserUrlResolverContext = (0, import_react5.createContext)(null);
531
- var DefaultUrlResolverContext = (0, import_react5.createContext)(NOOP_RESOLVER);
860
+ var UserUrlResolverContext = (0, import_react6.createContext)(null);
861
+ var DefaultUrlResolverContext = (0, import_react6.createContext)(NOOP_RESOLVER);
532
862
  var SearchItemUrlResolverProvider = ({
533
863
  resolver,
534
864
  children
@@ -538,8 +868,8 @@ var SearchItemDefaultUrlResolverProvider = ({
538
868
  children
539
869
  }) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(DefaultUrlResolverContext.Provider, { value: resolver, children });
540
870
  function useUrlResolver() {
541
- const user = (0, import_react5.useContext)(UserUrlResolverContext);
542
- const def = (0, import_react5.useContext)(DefaultUrlResolverContext);
871
+ const user = (0, import_react6.useContext)(UserUrlResolverContext);
872
+ const def = (0, import_react6.useContext)(DefaultUrlResolverContext);
543
873
  return user != null ? user : def;
544
874
  }
545
875
  function resolveField(obj, path) {
@@ -587,6 +917,7 @@ function createDefaultUrlResolver(configs, options = {}) {
587
917
  SearchItemUrlResolverProvider,
588
918
  SearchProvider,
589
919
  createDefaultUrlResolver,
920
+ useAutocomplete,
590
921
  usePagination,
591
922
  useSearch,
592
923
  useSearchPagination,