@caratlogic/design-system 0.1.22 → 0.1.24

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.esm.js CHANGED
@@ -14890,9 +14890,15 @@ function useSearchableSelect({ selectedItem, onChange, getOptionKey, getOptionLa
14890
14890
  // ===== ASYNC MODE =====
14891
14891
  const fetchPageRef = useRef(fetchPage);
14892
14892
  fetchPageRef.current = fetchPage;
14893
+ // Bumped by every fresh (non-append) fetch. A response from an older search,
14894
+ // or a "load more" started before the search changed, is discarded so it
14895
+ // can't overwrite or pad the current results.
14896
+ const requestSeq = useRef(0);
14893
14897
  const runFetch = useCallback(async (p, searchTerm, append) => {
14894
14898
  if (!fetchPageRef.current)
14895
14899
  return;
14900
+ const seq = append ? requestSeq.current : ++requestSeq.current;
14901
+ const isStale = () => seq !== requestSeq.current;
14896
14902
  if (append)
14897
14903
  setLoadingMore(true);
14898
14904
  else
@@ -14903,6 +14909,8 @@ function useSearchableSelect({ selectedItem, onChange, getOptionKey, getOptionLa
14903
14909
  limit: pageSize,
14904
14910
  search: searchTerm,
14905
14911
  });
14912
+ if (isStale())
14913
+ return;
14906
14914
  setHasMore(res.hasMore);
14907
14915
  setPage(p);
14908
14916
  setItems((prev) => {
@@ -14933,12 +14941,16 @@ function useSearchableSelect({ selectedItem, onChange, getOptionKey, getOptionLa
14933
14941
  });
14934
14942
  }
14935
14943
  catch {
14944
+ if (isStale())
14945
+ return;
14936
14946
  setItems(selectedItem ? [selectedItem] : []);
14937
14947
  setHasMore(false);
14938
14948
  }
14939
14949
  finally {
14940
- setLoading(false);
14941
- setLoadingMore(false);
14950
+ if (!isStale()) {
14951
+ setLoading(false);
14952
+ setLoadingMore(false);
14953
+ }
14942
14954
  }
14943
14955
  }, [pageSize, selectedItem, getOptionKey]);
14944
14956
  // Debounced search effect (async mode).
@@ -15098,10 +15110,14 @@ function SearchableSelect({ selectedItem, onChange, getOptionKey, getOptionLabel
15098
15110
  ? String(getOptionLabel(selectedItem))
15099
15111
  : undefined, children: getOptionLabel(selectedItem) }));
15100
15112
  }, [renderTrigger, selectedItem, searchPlaceholder, getOptionLabel]);
15113
+ const [activeValue, setActiveValue] = useState('');
15101
15114
  return (jsxs(Popover, { open: open, onOpenChange: handleOpenChange, modal: modal, children: [jsx(PopoverTrigger, { asChild: true, children: jsxs(Button$1, { type: "button", variant: "outline", role: "combobox", "aria-expanded": open, "aria-label": ariaLabel, disabled: disabled, className: cn('w-full justify-start min-w-40 px-3 py-1.5',
15102
15115
  // This trigger is a form control, not an action, so it takes the
15103
15116
  // input disabled treatment instead of the Button's opacity fade.
15104
- 'disabled:bg-muted/50 disabled:text-label disabled:opacity-100', className), children: [jsxs("div", { className: "flex min-w-0 flex-1 items-center gap-2 text-left", children: [!selectedItem && !hideTriggerSearchIcon && (jsx(Search, { "aria-hidden": "true", className: "h-4 w-4 shrink-0 text-muted-foreground" })), triggerNode] }), jsx(ChevronDown, { "aria-hidden": "true", className: "h-4 w-4 shrink-0 text-muted-foreground opacity-60" })] }) }), jsx(PopoverContent, { style: { width: 'var(--radix-popover-trigger-width)' }, className: cn('p-0', popoverClassName), align: align, children: jsxs(Command, { shouldFilter: false, children: [jsx(CommandInput, { placeholder: searchPlaceholder, value: search, onValueChange: setSearch }), headerSlot, jsx(CommandList, { ref: listRef, onScroll: handleScroll, className: LIST_MAX_HEIGHT_CLASS, children: loading ? (jsxs("div", { className: "flex items-center justify-center gap-2 p-3 text-sm text-muted-foreground", children: [jsx(LoaderCircle, { className: "h-4 w-4 animate-spin" }), "Loading\u2026"] })) : visibleItems.length === 0 ? (jsx(CommandEmpty, { children: emptyMessage })) : (jsxs(CommandGroup, { className: "p-1", children: [visibleItems.map((item) => {
15117
+ 'disabled:bg-muted/50 disabled:text-label disabled:opacity-100', className), children: [jsxs("div", { className: "flex min-w-0 flex-1 items-center gap-2 text-left", children: [!selectedItem && !hideTriggerSearchIcon && (jsx(Search, { "aria-hidden": "true", className: "h-4 w-4 shrink-0 text-muted-foreground" })), triggerNode] }), jsx(ChevronDown, { "aria-hidden": "true", className: "h-4 w-4 shrink-0 text-muted-foreground opacity-60" })] }) }), jsx(PopoverContent, { style: { width: 'var(--radix-popover-trigger-width)' }, className: cn('p-0', popoverClassName), align: align, children: jsxs(Command, { shouldFilter: false, value: activeValue, onValueChange: setActiveValue, children: [jsx(CommandInput, { placeholder: searchPlaceholder, value: search, onValueChange: setSearch }), headerSlot, jsx(CommandList, { ref: listRef, onScroll: handleScroll,
15118
+ // cmdk keeps the last hovered row active for keyboard navigation;
15119
+ // clear it so the highlight doesn't linger once the pointer leaves.
15120
+ onMouseLeave: () => setActiveValue(''), className: LIST_MAX_HEIGHT_CLASS, children: loading ? (jsxs("div", { className: "flex items-center justify-center gap-2 p-3 text-sm text-muted-foreground", children: [jsx(LoaderCircle, { className: "h-4 w-4 animate-spin" }), "Loading\u2026"] })) : visibleItems.length === 0 ? (jsx(CommandEmpty, { children: emptyMessage })) : (jsxs(CommandGroup, { className: "p-1", children: [visibleItems.map((item) => {
15105
15121
  const key = getOptionKey(item);
15106
15122
  const isSelected = selectedKey === key;
15107
15123
  if (renderOption) {
@@ -15202,10 +15218,12 @@ function ApiComboBox({ contactType, selectedItem, onSelect, fetchBusinessContact
15202
15218
  const selected = selectedItem?.value ? selectedItem : null;
15203
15219
  return (jsx(SearchableSelect, { selectedItem: selected, onChange: onSelect, getOptionKey: (i) => i.value, getOptionLabel: (i) => {
15204
15220
  const c = i.contactData;
15221
+ // A pre-selected item carries only { value, label }; its label is already formatted.
15222
+ if (!c)
15223
+ return i.label;
15205
15224
  const cid = i.contactId || i.code || '';
15206
- const contactName = c?.contactName || i.label || '';
15207
- const company = c?.companyName || '';
15208
- return (`${company}${company ? ' ' : ''}${cid ? `(${cid})` : ''}${contactName ? ` · ${contactName}` : ''}`.trim() || i.label);
15225
+ const companyPart = [c.companyName, cid && `(${cid})`].filter(Boolean).join(' ');
15226
+ return [companyPart, c.contactName].filter(Boolean).join(' · ') || i.label;
15209
15227
  }, getOptionSubLabel: (i) => {
15210
15228
  const c = i.contactData;
15211
15229
  const email = c?.email?.primary || '';