@seedgrid/fe-components 2026.3.31-4 → 2026.3.31-5

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.
@@ -36,6 +36,7 @@ export type SgAutocompleteProps<T = SgAutocompleteItem> = Omit<SgInputTextProps,
36
36
  renderFooter?: (query: string, hasResults: boolean) => React.ReactNode;
37
37
  renderEmpty?: (query: string) => React.ReactNode;
38
38
  formatSelection?: (item: SgAutocompleteItem) => string;
39
+ renderValue?: (item: SgAutocompleteItem | null) => React.ReactNode;
39
40
  itemTooltip?: (item: SgAutocompleteItem) => React.ReactNode;
40
41
  } & RhfFieldProps;
41
42
  export declare function SgAutocomplete<T = SgAutocompleteItem>(props: SgAutocompleteProps<T>): import("react/jsx-runtime").JSX.Element;
@@ -1 +1 @@
1
- {"version":3,"file":"SgAutocomplete.d.ts","sourceRoot":"","sources":["../../src/inputs/SgAutocomplete.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,MAAM,OAAO,CAAC;AAI1B,OAAO,EAAwC,KAAK,aAAa,EAAE,MAAM,QAAQ,CAAC;AAClF,OAAO,EAAe,KAAK,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAGnE,MAAM,MAAM,kBAAkB,GAAG;IAC/B,EAAE,EAAE,MAAM,GAAG,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,oBAAoB,CAAC,CAAC,GAAG,kBAAkB,IAAI,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC;AAEjG,MAAM,MAAM,mBAAmB,CAAC,CAAC,GAAG,kBAAkB,IAAI,IAAI,CAAC,gBAAgB,EAAE,UAAU,GAAG,OAAO,CAAC,GAAG;IACvG,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACnC,MAAM,EAAE,oBAAoB,CAAC,CAAC,CAAC,CAAC;IAChC,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,CAAC,KAAK,kBAAkB,CAAC;IACzC,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,kBAAkB,KAAK,IAAI,CAAC;IAC9C,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACnC,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;IACvC,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,kBAAkB,EAAE,QAAQ,EAAE,OAAO,KAAK,KAAK,CAAC,SAAS,CAAC;IAC9E,iBAAiB,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,KAAK,CAAC,SAAS,CAAC;IACvD,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,OAAO,KAAK,KAAK,CAAC,SAAS,CAAC;IACvE,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,KAAK,CAAC,SAAS,CAAC;IACjD,eAAe,CAAC,EAAE,CAAC,IAAI,EAAE,kBAAkB,KAAK,MAAM,CAAC;IACvD,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,kBAAkB,KAAK,KAAK,CAAC,SAAS,CAAC;CAC7D,GAAG,aAAa,CAAC;AAQlB,wBAAgB,cAAc,CAAC,CAAC,GAAG,kBAAkB,EAAE,KAAK,EAAE,mBAAmB,CAAC,CAAC,CAAC,2CAyCnF"}
1
+ {"version":3,"file":"SgAutocomplete.d.ts","sourceRoot":"","sources":["../../src/inputs/SgAutocomplete.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,MAAM,OAAO,CAAC;AAK1B,OAAO,EAAwC,KAAK,aAAa,EAAE,MAAM,QAAQ,CAAC;AAClF,OAAO,EAAe,KAAK,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAGnE,MAAM,MAAM,kBAAkB,GAAG;IAC/B,EAAE,EAAE,MAAM,GAAG,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,oBAAoB,CAAC,CAAC,GAAG,kBAAkB,IAAI,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC;AAEjG,MAAM,MAAM,mBAAmB,CAAC,CAAC,GAAG,kBAAkB,IAAI,IAAI,CAAC,gBAAgB,EAAE,UAAU,GAAG,OAAO,CAAC,GAAG;IACvG,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACnC,MAAM,EAAE,oBAAoB,CAAC,CAAC,CAAC,CAAC;IAChC,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,CAAC,KAAK,kBAAkB,CAAC;IACzC,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,kBAAkB,KAAK,IAAI,CAAC;IAC9C,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACnC,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;IACvC,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,kBAAkB,EAAE,QAAQ,EAAE,OAAO,KAAK,KAAK,CAAC,SAAS,CAAC;IAC9E,iBAAiB,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,KAAK,CAAC,SAAS,CAAC;IACvD,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,OAAO,KAAK,KAAK,CAAC,SAAS,CAAC;IACvE,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,KAAK,CAAC,SAAS,CAAC;IACjD,eAAe,CAAC,EAAE,CAAC,IAAI,EAAE,kBAAkB,KAAK,MAAM,CAAC;IACvD,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,kBAAkB,GAAG,IAAI,KAAK,KAAK,CAAC,SAAS,CAAC;IACnE,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,kBAAkB,KAAK,KAAK,CAAC,SAAS,CAAC;CAC7D,GAAG,aAAa,CAAC;AAQlB,wBAAgB,cAAc,CAAC,CAAC,GAAG,kBAAkB,EAAE,KAAK,EAAE,mBAAmB,CAAC,CAAC,CAAC,2CAyCnF"}
@@ -1,6 +1,7 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import React from "react";
4
+ import { createPortal } from "react-dom";
4
5
  import { ChevronDown } from "lucide-react";
5
6
  import { Controller } from "react-hook-form";
6
7
  import { mergeRequiredRule, resolveFieldError } from "../rhf";
@@ -19,7 +20,7 @@ export function SgAutocomplete(props) {
19
20
  return (_jsx(SgAutocompleteBase, { ...rest, name: name, register: register, rules: resolvedRules }));
20
21
  }
21
22
  function SgAutocompleteBase(props) {
22
- const { value, onChange, source, mapItem, minLengthForSearch = 2, delay = 300, maxResult = 50, cacheEnabled = true, cacheTTL = 5 * 60 * 1000, showDropDownButton = false, openOnFocus = false, clearOnSelect = false, allowCustomValue = false, grouped, groupped, placeholderEmpty: placeholderEmptyProp, loadingText: loadingTextProp, onSelect, onSearch, onOpenChange, renderItem, renderGroupHeader, renderFooter, renderEmpty, formatSelection, itemTooltip, inputProps, iconButtons, enabled, readOnly, borderRadius, ...rest } = props;
23
+ const { value, onChange, source, mapItem, minLengthForSearch = 2, delay = 300, maxResult = 50, cacheEnabled = true, cacheTTL = 5 * 60 * 1000, showDropDownButton = false, openOnFocus = false, clearOnSelect = false, allowCustomValue = false, grouped, groupped, placeholderEmpty: placeholderEmptyProp, loadingText: loadingTextProp, onSelect, onSearch, onOpenChange, renderItem, renderGroupHeader, renderFooter, renderEmpty, formatSelection, renderValue, itemTooltip, inputProps, iconButtons, enabled, readOnly, borderRadius, ...rest } = props;
23
24
  const i18n = useComponentsI18n();
24
25
  const placeholderEmpty = placeholderEmptyProp ?? t(i18n, "components.autocomplete.empty");
25
26
  const loadingText = loadingTextProp ?? t(i18n, "components.autocomplete.loading");
@@ -29,9 +30,13 @@ function SgAutocompleteBase(props) {
29
30
  const [loading, setLoading] = React.useState(false);
30
31
  const [open, setOpen] = React.useState(false);
31
32
  const wrapperRef = React.useRef(null);
33
+ const inputRef = React.useRef(null);
34
+ const dropdownRef = React.useRef(null);
32
35
  const ignoreBlurRef = React.useRef(false);
33
36
  const [activeIndex, setActiveIndex] = React.useState(-1);
34
37
  const [lastSelected, setLastSelected] = React.useState("");
38
+ const [selectedItem, setSelectedItem] = React.useState(null);
39
+ const [dropdownStyle, setDropdownStyle] = React.useState({});
35
40
  const cacheRef = React.useRef(new Map());
36
41
  const requestIdRef = React.useRef(0);
37
42
  const openRef = React.useRef(false);
@@ -47,6 +52,18 @@ function SgAutocompleteBase(props) {
47
52
  setInputValue(value);
48
53
  setLastSelected(value);
49
54
  }, [value]);
55
+ React.useEffect(() => {
56
+ if (!value) {
57
+ setSelectedItem(null);
58
+ return;
59
+ }
60
+ setSelectedItem((current) => {
61
+ if (current && (current.value === value || current.label === value)) {
62
+ return current;
63
+ }
64
+ return current;
65
+ });
66
+ }, [value]);
50
67
  const toItem = React.useCallback((raw) => {
51
68
  if (mapItem)
52
69
  return mapItem(raw);
@@ -112,6 +129,7 @@ function SgAutocompleteBase(props) {
112
129
  if (item.disabled)
113
130
  return;
114
131
  const selection = formatSelection ? formatSelection(item) : item.label;
132
+ setSelectedItem(item);
115
133
  setInputValue(selection);
116
134
  onChange?.(selection);
117
135
  setLastSelected(selection);
@@ -124,6 +142,9 @@ function SgAutocompleteBase(props) {
124
142
  }
125
143
  };
126
144
  const handleInputChange = (next) => {
145
+ if (selectedItem && next !== (formatSelection ? formatSelection(selectedItem) : selectedItem.label)) {
146
+ setSelectedItem(null);
147
+ }
127
148
  setInputValue(next);
128
149
  onChange?.(next);
129
150
  if (next.length === 0) {
@@ -157,6 +178,10 @@ function SgAutocompleteBase(props) {
157
178
  onOpenChange?.(false);
158
179
  };
159
180
  const handleFocus = () => {
181
+ if (renderValue && selectedItem) {
182
+ const selection = formatSelection ? formatSelection(selectedItem) : selectedItem.label;
183
+ setInputValue(selection);
184
+ }
160
185
  if (openOnFocus) {
161
186
  setOpen(true);
162
187
  onOpenChange?.(true);
@@ -185,6 +210,21 @@ function SgAutocompleteBase(props) {
185
210
  onOpenChange?.(false);
186
211
  }
187
212
  };
213
+ const syncDropdownPosition = React.useCallback(() => {
214
+ const anchor = wrapperRef.current?.querySelector("input") ?? inputRef.current ?? wrapperRef.current;
215
+ if (!anchor)
216
+ return;
217
+ const rect = anchor.getBoundingClientRect();
218
+ setDropdownStyle({
219
+ position: "fixed",
220
+ top: rect.bottom + 4,
221
+ left: rect.left,
222
+ minWidth: rect.width,
223
+ width: "max-content",
224
+ maxWidth: "min(32rem, calc(100vw - 24px))",
225
+ borderRadius: resolvedBorderRadius
226
+ });
227
+ }, [resolvedBorderRadius]);
188
228
  const dropdownButton = showDropDownButton ? (_jsx("button", { type: "button", className: "text-foreground/60 hover:text-foreground", onMouseDown: (event) => {
189
229
  event.preventDefault();
190
230
  ignoreBlurRef.current = true;
@@ -236,13 +276,53 @@ function SgAutocompleteBase(props) {
236
276
  }, onClick: () => selectItem(item), children: [renderItem ? renderItem(item, isActive) : item.label, itemTooltip ? (_jsx("div", { className: "pointer-events-none absolute left-full top-1/2 z-20 ml-2 -translate-y-1/2 rounded border border-border bg-[rgb(var(--sg-surface,var(--sg-bg)))] px-2 py-1 text-xs text-[rgb(var(--sg-text,var(--sg-fg)))] shadow-md opacity-0 transition-opacity group-hover:opacity-100", children: itemTooltip(item) })) : null] }, item.id));
237
277
  });
238
278
  };
279
+ React.useEffect(() => {
280
+ if (!open)
281
+ return;
282
+ const handleOutside = (event) => {
283
+ if (wrapperRef.current?.contains(event.target))
284
+ return;
285
+ if (dropdownRef.current?.contains(event.target))
286
+ return;
287
+ if (!allowCustomValue && lastSelected && inputValue !== lastSelected) {
288
+ setInputValue(lastSelected);
289
+ onChange?.(lastSelected);
290
+ }
291
+ setOpen(false);
292
+ onOpenChange?.(false);
293
+ };
294
+ document.addEventListener("mousedown", handleOutside);
295
+ return () => document.removeEventListener("mousedown", handleOutside);
296
+ }, [allowCustomValue, inputValue, lastSelected, onChange, onOpenChange, open]);
297
+ React.useEffect(() => {
298
+ if (!open)
299
+ return;
300
+ syncDropdownPosition();
301
+ const handleLayoutChange = () => {
302
+ syncDropdownPosition();
303
+ };
304
+ window.addEventListener("resize", handleLayoutChange);
305
+ window.addEventListener("scroll", handleLayoutChange, true);
306
+ return () => {
307
+ window.removeEventListener("resize", handleLayoutChange);
308
+ window.removeEventListener("scroll", handleLayoutChange, true);
309
+ };
310
+ }, [open, syncDropdownPosition]);
239
311
  return (_jsxs("div", { className: "relative", ref: wrapperRef, children: [_jsx(SgInputText, { ...rest, enabled: enabled, readOnly: readOnly, borderRadius: borderRadius, iconButtons: mergedIconButtons, inputProps: {
240
312
  ...inputProps,
313
+ ref: inputRef,
241
314
  autoComplete: "off",
242
315
  autoCorrect: "off",
243
316
  autoCapitalize: "off",
244
317
  spellCheck: false,
245
318
  value: inputValue,
319
+ style: renderValue && selectedItem && !open
320
+ ? {
321
+ ...(inputProps?.style ?? {}),
322
+ color: "transparent",
323
+ textShadow: "none"
324
+ }
325
+ : inputProps?.style,
246
326
  onChange: (event) => handleInputChange(event.currentTarget.value),
247
327
  onBlur: (event) => {
248
328
  inputProps?.onBlur?.(event);
@@ -256,5 +336,7 @@ function SgAutocompleteBase(props) {
256
336
  inputProps?.onKeyDown?.(event);
257
337
  handleKeyDown(event);
258
338
  }
259
- } }), open && !(enabled === false || readOnly) ? (_jsxs("div", { className: "absolute left-0 right-0 z-30 mt-1 overflow-hidden rounded-md border border-border bg-[rgb(var(--sg-surface,var(--sg-bg)))] text-[rgb(var(--sg-text,var(--sg-fg)))] shadow-lg", style: resolvedBorderRadius ? { borderRadius: resolvedBorderRadius } : undefined, children: [_jsx("div", { className: "max-h-64 overflow-auto", children: listContent() }), renderFooter ? (_jsx("div", { className: "border-t border-border bg-[rgb(var(--sg-surface,var(--sg-bg)))] px-3 py-2", children: renderFooter(inputValue, items.length > 0) })) : null] })) : null] }));
339
+ } }), renderValue && selectedItem && !open ? (_jsx("div", { className: "pointer-events-none absolute inset-y-0 left-3 right-10 z-10 flex items-center overflow-hidden", children: renderValue(selectedItem) })) : null, open && !(enabled === false || readOnly) && typeof document !== "undefined"
340
+ ? createPortal(_jsxs("div", { ref: dropdownRef, className: "z-[1100] overflow-hidden rounded-md border border-border bg-[rgb(var(--sg-surface,var(--sg-bg)))] text-[rgb(var(--sg-text,var(--sg-fg)))] shadow-lg", style: dropdownStyle, children: [_jsx("div", { className: "max-h-64 overflow-auto", children: listContent() }), renderFooter ? (_jsx("div", { className: "border-t border-border bg-[rgb(var(--sg-surface,var(--sg-bg)))] px-3 py-2", children: renderFooter(inputValue, items.length > 0) })) : null] }), document.body)
341
+ : null] }));
260
342
  }
@@ -16,6 +16,7 @@ export type SgComboboxProps<T = SgAutocompleteItem> = SgComboboxBaseProps & {
16
16
  openOnFocus?: boolean;
17
17
  onSelect?: (value: T) => void;
18
18
  renderItem?: (item: SgAutocompleteItem, isActive: boolean) => React.ReactNode;
19
+ renderValue?: (item: SgAutocompleteItem | null) => React.ReactNode;
19
20
  renderGroupHeader?: (group: string) => React.ReactNode;
20
21
  renderFooter?: (query: string, hasResults: boolean) => React.ReactNode;
21
22
  itemTooltip?: (item: SgAutocompleteItem) => React.ReactNode;
@@ -1 +1 @@
1
- {"version":3,"file":"SgCombobox.d.ts","sourceRoot":"","sources":["../../src/inputs/SgCombobox.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,MAAM,OAAO,CAAC;AAK1B,OAAO,EAAe,KAAK,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACnE,OAAO,EAAE,KAAK,kBAAkB,EAAE,KAAK,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAGtF,KAAK,aAAa,GAAG,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;AAE5C,KAAK,mBAAmB,GAAG,IAAI,CAC7B,gBAAgB,EAChB,UAAU,GAAG,aAAa,GAAG,aAAa,GAAG,YAAY,GAAG,UAAU,GAAG,OAAO,GAAG,MAAM,CAC1F,CAAC;AAEF,MAAM,MAAM,gBAAgB,CAAC,CAAC,GAAG,kBAAkB,IAAI,CAAC,EAAE,GAAG,oBAAoB,CAAC,CAAC,CAAC,CAAC;AAErF,MAAM,MAAM,eAAe,CAAC,CAAC,GAAG,kBAAkB,IAAI,mBAAmB,GAAG;IAC1E,MAAM,EAAE,gBAAgB,CAAC,CAAC,CAAC,CAAC;IAC5B,KAAK,CAAC,EAAE,aAAa,CAAC;IACtB,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC;IAC/C,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,CAAC,KAAK,kBAAkB,CAAC;IACzC,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,IAAI,CAAC;IAC9B,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,kBAAkB,EAAE,QAAQ,EAAE,OAAO,KAAK,KAAK,CAAC,SAAS,CAAC;IAC9E,iBAAiB,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,KAAK,CAAC,SAAS,CAAC;IACvD,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,OAAO,KAAK,KAAK,CAAC,SAAS,CAAC;IACvE,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,kBAAkB,KAAK,KAAK,CAAC,SAAS,CAAC;IAC5D,UAAU,CAAC,EAAE,gBAAgB,CAAC,YAAY,CAAC,CAAC;CAC7C,CAAC;AA4fF,wBAAgB,UAAU,CAAC,CAAC,GAAG,kBAAkB,EAAE,KAAK,EAAE,QAAQ,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC,2CAsBrF"}
1
+ {"version":3,"file":"SgCombobox.d.ts","sourceRoot":"","sources":["../../src/inputs/SgCombobox.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,MAAM,OAAO,CAAC;AAM1B,OAAO,EAAe,KAAK,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACnE,OAAO,EAAE,KAAK,kBAAkB,EAAE,KAAK,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAGtF,KAAK,aAAa,GAAG,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;AAE5C,KAAK,mBAAmB,GAAG,IAAI,CAC7B,gBAAgB,EAChB,UAAU,GAAG,aAAa,GAAG,aAAa,GAAG,YAAY,GAAG,UAAU,GAAG,OAAO,GAAG,MAAM,CAC1F,CAAC;AAEF,MAAM,MAAM,gBAAgB,CAAC,CAAC,GAAG,kBAAkB,IAAI,CAAC,EAAE,GAAG,oBAAoB,CAAC,CAAC,CAAC,CAAC;AAErF,MAAM,MAAM,eAAe,CAAC,CAAC,GAAG,kBAAkB,IAAI,mBAAmB,GAAG;IAC1E,MAAM,EAAE,gBAAgB,CAAC,CAAC,CAAC,CAAC;IAC5B,KAAK,CAAC,EAAE,aAAa,CAAC;IACtB,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC;IAC/C,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,CAAC,KAAK,kBAAkB,CAAC;IACzC,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,IAAI,CAAC;IAC9B,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,kBAAkB,EAAE,QAAQ,EAAE,OAAO,KAAK,KAAK,CAAC,SAAS,CAAC;IAC9E,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,kBAAkB,GAAG,IAAI,KAAK,KAAK,CAAC,SAAS,CAAC;IACnE,iBAAiB,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,KAAK,CAAC,SAAS,CAAC;IACvD,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,OAAO,KAAK,KAAK,CAAC,SAAS,CAAC;IACvE,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,kBAAkB,KAAK,KAAK,CAAC,SAAS,CAAC;IAC5D,UAAU,CAAC,EAAE,gBAAgB,CAAC,YAAY,CAAC,CAAC;CAC7C,CAAC;AA8jBF,wBAAgB,UAAU,CAAC,CAAC,GAAG,kBAAkB,EAAE,KAAK,EAAE,QAAQ,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC,2CAsBrF"}
@@ -1,6 +1,7 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import React from "react";
4
+ import { createPortal } from "react-dom";
4
5
  import { ChevronDown } from "lucide-react";
5
6
  import { Controller } from "react-hook-form";
6
7
  import { resolveFieldError } from "../rhf";
@@ -51,13 +52,15 @@ function findTypeAheadMatchIndex(entries, query, startIndex) {
51
52
  return -1;
52
53
  }
53
54
  function SgComboboxBase(props) {
54
- const { source, mapItem: mapItemProp, value, onValueChange, grouped, groupped, loadingText: loadingTextProp, emptyText: emptyTextProp, openOnFocus = false, onSelect, renderItem, renderGroupHeader, renderFooter, itemTooltip, inputProps, enabled, borderRadius, ...rest } = props;
55
+ const { source, mapItem: mapItemProp, value, onValueChange, grouped, groupped, loadingText: loadingTextProp, emptyText: emptyTextProp, openOnFocus = false, onSelect, renderItem, renderValue, renderGroupHeader, renderFooter, itemTooltip, inputProps, enabled, borderRadius, ...rest } = props;
55
56
  const i18n = useComponentsI18n();
56
57
  const loadingText = loadingTextProp ?? t(i18n, "components.autocomplete.loading");
57
58
  const emptyText = emptyTextProp ?? t(i18n, "components.autocomplete.empty");
58
59
  const effectiveGrouped = grouped ?? groupped ?? false;
59
60
  const isControlled = value !== undefined;
60
61
  const wrapperRef = React.useRef(null);
62
+ const inputRef = React.useRef(null);
63
+ const dropdownRef = React.useRef(null);
61
64
  const ignoreBlurRef = React.useRef(false);
62
65
  const requestIdRef = React.useRef(0);
63
66
  const typeAheadRef = React.useRef({
@@ -70,6 +73,7 @@ function SgComboboxBase(props) {
70
73
  const [activeIndex, setActiveIndex] = React.useState(-1);
71
74
  const [internalValue, setInternalValue] = React.useState(null);
72
75
  const [lastSelectedLabel, setLastSelectedLabel] = React.useState("");
76
+ const [dropdownStyle, setDropdownStyle] = React.useState({});
73
77
  const resolvedBorderRadius = React.useMemo(() => {
74
78
  if (borderRadius === undefined)
75
79
  return undefined;
@@ -149,6 +153,18 @@ function SgComboboxBase(props) {
149
153
  const displayedValue = resolvedValue == null || resolvedValue === ""
150
154
  ? ""
151
155
  : selectedEntry?.item.label ?? lastSelectedLabel;
156
+ const selectedVisualItem = React.useMemo(() => {
157
+ if (selectedEntry?.item)
158
+ return selectedEntry.item;
159
+ if (resolvedValue == null || resolvedValue === "")
160
+ return null;
161
+ if (!lastSelectedLabel)
162
+ return null;
163
+ return {
164
+ id: resolvedValue,
165
+ label: lastSelectedLabel
166
+ };
167
+ }, [lastSelectedLabel, resolvedValue, selectedEntry]);
152
168
  const setSelectedValue = React.useCallback((nextValue) => {
153
169
  if (!isControlled) {
154
170
  setInternalValue(nextValue);
@@ -166,6 +182,21 @@ function SgComboboxBase(props) {
166
182
  setOpen(true);
167
183
  void refreshFromSource();
168
184
  }, [isDisabled, refreshFromSource]);
185
+ const syncDropdownPosition = React.useCallback(() => {
186
+ const anchor = wrapperRef.current?.querySelector("input") ?? inputRef.current ?? wrapperRef.current;
187
+ if (!anchor)
188
+ return;
189
+ const rect = anchor.getBoundingClientRect();
190
+ setDropdownStyle({
191
+ position: "fixed",
192
+ top: rect.bottom + 4,
193
+ left: rect.left,
194
+ minWidth: rect.width,
195
+ width: "max-content",
196
+ maxWidth: "min(32rem, calc(100vw - 24px))",
197
+ borderRadius: resolvedBorderRadius
198
+ });
199
+ }, [resolvedBorderRadius]);
169
200
  const selectIndex = React.useCallback((index) => {
170
201
  const entry = entries[index];
171
202
  if (!entry || entry.item.disabled)
@@ -217,11 +248,27 @@ function SgComboboxBase(props) {
217
248
  const handleOutside = (event) => {
218
249
  if (wrapperRef.current?.contains(event.target))
219
250
  return;
251
+ if (dropdownRef.current?.contains(event.target))
252
+ return;
220
253
  closeDropdown();
221
254
  };
222
255
  document.addEventListener("mousedown", handleOutside);
223
256
  return () => document.removeEventListener("mousedown", handleOutside);
224
257
  }, [closeDropdown, open]);
258
+ React.useEffect(() => {
259
+ if (!open)
260
+ return;
261
+ syncDropdownPosition();
262
+ const handleLayoutChange = () => {
263
+ syncDropdownPosition();
264
+ };
265
+ window.addEventListener("resize", handleLayoutChange);
266
+ window.addEventListener("scroll", handleLayoutChange, true);
267
+ return () => {
268
+ window.removeEventListener("resize", handleLayoutChange);
269
+ window.removeEventListener("scroll", handleLayoutChange, true);
270
+ };
271
+ }, [open, syncDropdownPosition]);
225
272
  React.useEffect(() => {
226
273
  if (!open)
227
274
  return;
@@ -263,7 +310,15 @@ function SgComboboxBase(props) {
263
310
  }, "aria-label": t(i18n, "components.actions.openList"), children: _jsx(ChevronDown, { size: 16 }) }));
264
311
  return (_jsxs("div", { className: open ? "relative z-[1100]" : "relative", ref: wrapperRef, children: [_jsx(SgInputText, { ...rest, enabled: enabled, borderRadius: borderRadius, clearButton: false, readOnly: true, iconButtons: [dropdownButton], inputProps: {
265
312
  ...inputProps,
313
+ ref: inputRef,
266
314
  value: displayedValue,
315
+ style: renderValue && displayedValue
316
+ ? {
317
+ ...(inputProps?.style ?? {}),
318
+ color: "transparent",
319
+ textShadow: "none"
320
+ }
321
+ : inputProps?.style,
267
322
  onMouseDown: (event) => {
268
323
  inputProps?.onMouseDown?.(event);
269
324
  if (isDisabled)
@@ -340,19 +395,21 @@ function SgComboboxBase(props) {
340
395
  }
341
396
  }
342
397
  }
343
- } }), open && !isDisabled ? (_jsxs("div", { className: "absolute left-0 z-[1100] mt-1 min-w-full max-w-[min(32rem,calc(100vw-24px))] overflow-hidden rounded-md border border-border bg-[rgb(var(--sg-surface,var(--sg-bg)))] text-[rgb(var(--sg-text,var(--sg-fg)))] shadow-lg", style: resolvedBorderRadius ? { borderRadius: resolvedBorderRadius, width: "max-content" } : { width: "max-content" }, children: [_jsx("div", { className: "max-h-64 overflow-auto", children: loading ? (_jsx("div", { className: "px-3 py-2 text-sm text-muted-foreground", children: loadingText })) : entries.length === 0 ? (_jsx("div", { className: "px-3 py-2 text-sm text-muted-foreground", children: emptyText })) : groupedEntries ? (groupedEntries.map(({ group, list }) => (_jsxs("div", { className: "border-b border-border last:border-b-0", children: [_jsx("div", { className: "px-3 py-1 text-xs font-semibold text-muted-foreground", children: renderGroupHeader ? renderGroupHeader(group) : group || " " }), list.map(({ entry, index }) => {
344
- const isActive = activeIndex === index;
345
- return (_jsxs("div", { className: `group relative cursor-pointer px-3 py-2 text-sm ${isActive ? "bg-muted/60" : ""} ${entry.item.disabled ? "cursor-not-allowed opacity-50" : "hover:bg-muted/40"}`, onMouseEnter: () => setActiveIndex(index), onMouseDown: (event) => {
346
- event.preventDefault();
347
- ignoreBlurRef.current = true;
348
- }, onClick: () => selectIndex(index), children: [renderItem ? renderItem(entry.item, isActive) : entry.item.label, itemTooltip ? (_jsx("div", { className: "pointer-events-none absolute left-full top-1/2 z-20 ml-2 -translate-y-1/2 rounded border border-border bg-[rgb(var(--sg-surface,var(--sg-bg)))] px-2 py-1 text-xs text-[rgb(var(--sg-text,var(--sg-fg)))] shadow-md opacity-0 transition-opacity group-hover:opacity-100", children: itemTooltip(entry.item) })) : null] }, entry.item.id));
349
- })] }, group || "default")))) : (entries.map((entry, index) => {
350
- const isActive = activeIndex === index;
351
- return (_jsxs("div", { className: `group relative cursor-pointer px-3 py-2 text-sm ${isActive ? "bg-muted/60" : ""} ${entry.item.disabled ? "cursor-not-allowed opacity-50" : "hover:bg-muted/40"}`, onMouseEnter: () => setActiveIndex(index), onMouseDown: (event) => {
352
- event.preventDefault();
353
- ignoreBlurRef.current = true;
354
- }, onClick: () => selectIndex(index), children: [renderItem ? renderItem(entry.item, isActive) : entry.item.label, itemTooltip ? (_jsx("div", { className: "pointer-events-none absolute left-full top-1/2 z-20 ml-2 -translate-y-1/2 rounded border border-border bg-[rgb(var(--sg-surface,var(--sg-bg)))] px-2 py-1 text-xs text-[rgb(var(--sg-text,var(--sg-fg)))] shadow-md opacity-0 transition-opacity group-hover:opacity-100", children: itemTooltip(entry.item) })) : null] }, entry.item.id));
355
- })) }), renderFooter ? (_jsx("div", { className: "border-t border-border bg-[rgb(var(--sg-surface,var(--sg-bg)))] px-3 py-2", children: renderFooter("", entries.length > 0) })) : null] })) : null] }));
398
+ } }), renderValue && displayedValue ? (_jsx("div", { className: "pointer-events-none absolute inset-y-0 left-3 right-10 z-10 flex items-center overflow-hidden", children: renderValue(selectedVisualItem) })) : null, open && !isDisabled && typeof document !== "undefined"
399
+ ? createPortal(_jsxs("div", { ref: dropdownRef, className: "z-[1100] overflow-hidden rounded-md border border-border bg-[rgb(var(--sg-surface,var(--sg-bg)))] text-[rgb(var(--sg-text,var(--sg-fg)))] shadow-lg", style: dropdownStyle, children: [_jsx("div", { className: "max-h-64 overflow-auto", children: loading ? (_jsx("div", { className: "px-3 py-2 text-sm text-muted-foreground", children: loadingText })) : entries.length === 0 ? (_jsx("div", { className: "px-3 py-2 text-sm text-muted-foreground", children: emptyText })) : groupedEntries ? (groupedEntries.map(({ group, list }) => (_jsxs("div", { className: "border-b border-border last:border-b-0", children: [_jsx("div", { className: "px-3 py-1 text-xs font-semibold text-muted-foreground", children: renderGroupHeader ? renderGroupHeader(group) : group || " " }), list.map(({ entry, index }) => {
400
+ const isActive = activeIndex === index;
401
+ return (_jsxs("div", { className: `group relative cursor-pointer px-3 py-2 text-sm ${isActive ? "bg-muted/60" : ""} ${entry.item.disabled ? "cursor-not-allowed opacity-50" : "hover:bg-muted/40"}`, onMouseEnter: () => setActiveIndex(index), onMouseDown: (event) => {
402
+ event.preventDefault();
403
+ ignoreBlurRef.current = true;
404
+ }, onClick: () => selectIndex(index), children: [renderItem ? renderItem(entry.item, isActive) : entry.item.label, itemTooltip ? (_jsx("div", { className: "pointer-events-none absolute left-full top-1/2 z-20 ml-2 -translate-y-1/2 rounded border border-border bg-[rgb(var(--sg-surface,var(--sg-bg)))] px-2 py-1 text-xs text-[rgb(var(--sg-text,var(--sg-fg)))] shadow-md opacity-0 transition-opacity group-hover:opacity-100", children: itemTooltip(entry.item) })) : null] }, entry.item.id));
405
+ })] }, group || "default")))) : (entries.map((entry, index) => {
406
+ const isActive = activeIndex === index;
407
+ return (_jsxs("div", { className: `group relative cursor-pointer px-3 py-2 text-sm ${isActive ? "bg-muted/60" : ""} ${entry.item.disabled ? "cursor-not-allowed opacity-50" : "hover:bg-muted/40"}`, onMouseEnter: () => setActiveIndex(index), onMouseDown: (event) => {
408
+ event.preventDefault();
409
+ ignoreBlurRef.current = true;
410
+ }, onClick: () => selectIndex(index), children: [renderItem ? renderItem(entry.item, isActive) : entry.item.label, itemTooltip ? (_jsx("div", { className: "pointer-events-none absolute left-full top-1/2 z-20 ml-2 -translate-y-1/2 rounded border border-border bg-[rgb(var(--sg-surface,var(--sg-bg)))] px-2 py-1 text-xs text-[rgb(var(--sg-text,var(--sg-fg)))] shadow-md opacity-0 transition-opacity group-hover:opacity-100", children: itemTooltip(entry.item) })) : null] }, entry.item.id));
411
+ })) }), renderFooter ? (_jsx("div", { className: "border-t border-border bg-[rgb(var(--sg-surface,var(--sg-bg)))] px-3 py-2", children: renderFooter("", entries.length > 0) })) : null] }), document.body)
412
+ : null] }));
356
413
  }
357
414
  export function SgCombobox(props) {
358
415
  const { control, name, rules, ...rest } = props;