@bubo-squared/ui-framework 0.2.35 → 0.2.36

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.cjs CHANGED
@@ -1730,6 +1730,7 @@ Checkbox.displayName = "Checkbox";
1730
1730
 
1731
1731
  // src/components/Inputs/Autocomplete.tsx
1732
1732
  var React24 = __toESM(require("react"), 1);
1733
+ var ReactDOM = __toESM(require("react-dom"), 1);
1733
1734
  var import_class_variance_authority15 = require("class-variance-authority");
1734
1735
 
1735
1736
  // src/components/Inputs/InputShell.tsx
@@ -1816,9 +1817,7 @@ var inputTextVariants = (0, import_class_variance_authority15.cva)("truncate", {
1816
1817
  xl: "h6-title"
1817
1818
  }
1818
1819
  },
1819
- defaultVariants: {
1820
- size: "lg"
1821
- }
1820
+ defaultVariants: { size: "lg" }
1822
1821
  });
1823
1822
  var optionVariants = (0, import_class_variance_authority15.cva)(
1824
1823
  "w-full text-left hover:bg-(--background-secondary)",
@@ -1830,14 +1829,9 @@ var optionVariants = (0, import_class_variance_authority15.cva)(
1830
1829
  lg: "paragraph-lg py-(--space-8) ",
1831
1830
  xl: "subtitle py-(--space-10) "
1832
1831
  },
1833
- active: {
1834
- true: "bg-(--background-secondary)"
1835
- }
1832
+ active: { true: "bg-(--background-secondary)" }
1836
1833
  },
1837
- defaultVariants: {
1838
- size: "lg",
1839
- active: false
1840
- }
1834
+ defaultVariants: { size: "lg", active: false }
1841
1835
  }
1842
1836
  );
1843
1837
  var iconWrapperVariants = (0, import_class_variance_authority15.cva)(
@@ -1850,13 +1844,9 @@ var iconWrapperVariants = (0, import_class_variance_authority15.cva)(
1850
1844
  lg: "size-5 [&>svg]:size-5",
1851
1845
  xl: "size-6 [&>svg]:size-6"
1852
1846
  },
1853
- disabled: {
1854
- true: "text-(--icon-primary-disabled)"
1855
- }
1847
+ disabled: { true: "text-(--icon-primary-disabled)" }
1856
1848
  },
1857
- defaultVariants: {
1858
- size: "lg"
1859
- }
1849
+ defaultVariants: { size: "lg" }
1860
1850
  }
1861
1851
  );
1862
1852
  var Autocomplete = React24.forwardRef((props, forwardedRef) => {
@@ -1891,84 +1881,68 @@ var Autocomplete = React24.forwardRef((props, forwardedRef) => {
1891
1881
  ...inputProps
1892
1882
  } = props;
1893
1883
  const isValueControlled = value !== void 0;
1894
- const [internalValue, setInternalValue] = React24.useState(
1895
- defaultValue ?? ""
1896
- );
1884
+ const [internalValue, setInternalValue] = React24.useState(defaultValue ?? "");
1897
1885
  const isInputControlled = inputValue !== void 0;
1898
- const [internalInputValue, setInternalInputValue] = React24.useState(
1899
- defaultInputValue ?? ""
1900
- );
1886
+ const [internalInputValue, setInternalInputValue] = React24.useState(defaultInputValue ?? "");
1901
1887
  const [isFocused, setIsFocused] = React24.useState(false);
1902
1888
  const [activeIndex, setActiveIndex] = React24.useState(-1);
1889
+ const [dropdownStyle, setDropdownStyle] = React24.useState({});
1903
1890
  const inputRef = React24.useRef(null);
1904
- const setInputRef = React24.useCallback(
1905
- (node) => {
1906
- inputRef.current = node;
1907
- if (!forwardedRef) return;
1908
- if (typeof forwardedRef === "function") {
1909
- forwardedRef(node);
1910
- } else {
1911
- forwardedRef.current = node;
1912
- }
1913
- },
1914
- [forwardedRef]
1915
- );
1891
+ const anchorRef = React24.useRef(null);
1892
+ const rafIdRef = React24.useRef(null);
1916
1893
  const baseId = React24.useId();
1917
1894
  const inputId = id ?? baseId;
1918
1895
  const listboxId = `${inputId}-listbox`;
1919
1896
  const currentValue = (isValueControlled ? value : internalValue) ?? "";
1920
1897
  const currentInput = (isInputControlled ? inputValue : internalInputValue) ?? "";
1921
- React24.useEffect(() => {
1922
- if (isFocused) return;
1923
- if (isInputControlled) return;
1924
- if (!isValueControlled) return;
1925
- setInternalInputValue(currentValue);
1926
- }, [currentValue, isFocused, isInputControlled, isValueControlled]);
1927
- const showDropdown = isFocused && (loading || options.length > 0 || currentInput.trim().length > 0);
1898
+ const trimmedInput = currentInput.trim();
1899
+ const showDropdown = isFocused && (loading || options.length > 0 || trimmedInput.length > 0);
1928
1900
  const setInputText = (next) => {
1929
- if (!isInputControlled) {
1930
- setInternalInputValue(next);
1931
- }
1901
+ if (!isInputControlled) setInternalInputValue(next);
1932
1902
  onInputChange?.(next);
1933
1903
  };
1934
1904
  const commitTypedValue = (next) => {
1935
- if (!isValueControlled) {
1936
- setInternalValue(next);
1937
- }
1905
+ if (!isValueControlled) setInternalValue(next);
1938
1906
  onChange?.(next);
1939
1907
  };
1940
1908
  const commitValue = (next) => {
1941
- if (!isValueControlled) {
1942
- setInternalValue(next);
1943
- }
1909
+ if (!isValueControlled) setInternalValue(next);
1944
1910
  onChange?.(next);
1945
1911
  setInputText(next);
1946
1912
  setActiveIndex(-1);
1947
1913
  };
1948
- const handleContainerClick = () => {
1949
- if (disabled) return;
1950
- inputRef.current?.focus();
1951
- };
1914
+ const setInputRef = React24.useCallback((node) => {
1915
+ inputRef.current = node;
1916
+ if (!forwardedRef) return;
1917
+ if (typeof forwardedRef === "function") forwardedRef(node);
1918
+ else forwardedRef.current = node;
1919
+ }, [forwardedRef]);
1920
+ const updateDropdownPosition = React24.useCallback(() => {
1921
+ if (rafIdRef.current !== null) return;
1922
+ rafIdRef.current = requestAnimationFrame(() => {
1923
+ rafIdRef.current = null;
1924
+ if (!anchorRef.current) return;
1925
+ const rect = anchorRef.current.getBoundingClientRect();
1926
+ setDropdownStyle({
1927
+ position: "fixed",
1928
+ top: rect.bottom,
1929
+ left: rect.left,
1930
+ width: rect.width,
1931
+ zIndex: 9999
1932
+ });
1933
+ });
1934
+ }, []);
1952
1935
  const handleInputChange = (event) => {
1953
1936
  const next = event.target.value;
1954
1937
  setInputText(next);
1955
1938
  setActiveIndex(-1);
1956
- if (freeSolo) {
1957
- commitTypedValue(next);
1958
- }
1959
- };
1960
- const handleFocus = (event) => {
1961
- setIsFocused(true);
1962
- onFocus?.(event);
1939
+ if (freeSolo) commitTypedValue(next);
1963
1940
  };
1964
1941
  const handleBlur = (event) => {
1965
1942
  setIsFocused(false);
1966
1943
  setActiveIndex(-1);
1967
- if (freeSolo) {
1968
- const trimmed = currentInput.trim();
1969
- if (trimmed.length > 0 && currentInput !== currentValue) {
1970
- commitTypedValue(currentInput);
1971
- }
1944
+ if (freeSolo && trimmedInput.length > 0 && currentInput !== currentValue) {
1945
+ commitTypedValue(currentInput);
1972
1946
  }
1973
1947
  onBlur?.(event);
1974
1948
  };
@@ -1980,64 +1954,64 @@ var Autocomplete = React24.forwardRef((props, forwardedRef) => {
1980
1954
  return;
1981
1955
  }
1982
1956
  switch (event.key) {
1983
- case "ArrowDown": {
1957
+ case "ArrowDown":
1984
1958
  event.preventDefault();
1985
- setActiveIndex((prev) => {
1986
- if (options.length === 0) return -1;
1987
- const next = prev < 0 ? 0 : Math.min(prev + 1, options.length - 1);
1988
- return next;
1989
- });
1959
+ setActiveIndex((prev) => options.length === 0 ? -1 : prev < 0 ? 0 : Math.min(prev + 1, options.length - 1));
1990
1960
  break;
1991
- }
1992
- case "ArrowUp": {
1961
+ case "ArrowUp":
1993
1962
  event.preventDefault();
1994
- setActiveIndex((prev) => {
1995
- if (options.length === 0) return -1;
1996
- const next = prev <= 0 ? 0 : prev - 1;
1997
- return next;
1998
- });
1963
+ setActiveIndex((prev) => options.length === 0 ? -1 : prev <= 0 ? 0 : prev - 1);
1999
1964
  break;
2000
- }
2001
- case "Enter": {
1965
+ case "Enter":
2002
1966
  if (activeIndex >= 0 && activeIndex < options.length) {
2003
1967
  event.preventDefault();
2004
1968
  commitValue(options[activeIndex]);
2005
1969
  setIsFocused(false);
2006
- break;
2007
- }
2008
- if (freeSolo) {
2009
- const trimmed = currentInput.trim();
2010
- if (trimmed.length > 0) {
2011
- event.preventDefault();
2012
- if (currentInput !== currentValue) {
2013
- commitTypedValue(currentInput);
2014
- }
2015
- setIsFocused(false);
2016
- }
1970
+ } else if (freeSolo && trimmedInput.length > 0) {
1971
+ event.preventDefault();
1972
+ if (currentInput !== currentValue) commitTypedValue(currentInput);
1973
+ setIsFocused(false);
2017
1974
  }
2018
1975
  break;
2019
- }
2020
- case "Escape": {
1976
+ case "Escape":
2021
1977
  event.preventDefault();
2022
1978
  setIsFocused(false);
2023
1979
  setActiveIndex(-1);
2024
1980
  break;
2025
- }
2026
- default:
2027
- break;
2028
1981
  }
2029
1982
  };
2030
- const handleOptionMouseDown = (event) => {
2031
- event.preventDefault();
1983
+ const handleOptionMouseEnter = (event) => {
1984
+ const index = Number(event.currentTarget.dataset.index);
1985
+ if (!isNaN(index)) setActiveIndex(index);
2032
1986
  };
2033
- const handleOptionClick = (option) => {
2034
- commitValue(option);
2035
- setIsFocused(false);
1987
+ const handleOptionClick = (event) => {
1988
+ const index = Number(event.currentTarget.dataset.index);
1989
+ if (index >= 0 && index < options.length) {
1990
+ commitValue(options[index]);
1991
+ setIsFocused(false);
1992
+ }
2036
1993
  };
2037
- const activeDescendantId = activeIndex >= 0 ? `${inputId}-option-${activeIndex}` : void 0;
2038
- const showLeadingIcon = !!leadingIcon;
2039
- const showTrailingIcon = !!trailingIcon;
2040
- return /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(Field, { label, hint, hideHint, status, disabled, children: /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("div", { className: "relative w-full", children: [
1994
+ const handleContainerClick = () => {
1995
+ if (disabled) return;
1996
+ inputRef.current?.focus();
1997
+ };
1998
+ React24.useLayoutEffect(() => {
1999
+ if (!showDropdown) return;
2000
+ updateDropdownPosition();
2001
+ window.addEventListener("scroll", updateDropdownPosition, { capture: true, passive: true });
2002
+ window.addEventListener("resize", updateDropdownPosition);
2003
+ return () => {
2004
+ window.removeEventListener("scroll", updateDropdownPosition, { capture: true });
2005
+ window.removeEventListener("resize", updateDropdownPosition);
2006
+ if (rafIdRef.current !== null) cancelAnimationFrame(rafIdRef.current);
2007
+ };
2008
+ }, [showDropdown, updateDropdownPosition]);
2009
+ React24.useEffect(() => {
2010
+ if (!isFocused && !isInputControlled && isValueControlled) {
2011
+ setInternalInputValue(currentValue);
2012
+ }
2013
+ }, [currentValue, isFocused, isInputControlled, isValueControlled]);
2014
+ return /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(Field, { label, hint, hideHint, status, disabled, children: /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("div", { ref: anchorRef, className: "relative w-full", children: [
2041
2015
  /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)(
2042
2016
  InputShell,
2043
2017
  {
@@ -2047,7 +2021,7 @@ var Autocomplete = React24.forwardRef((props, forwardedRef) => {
2047
2021
  className,
2048
2022
  onClick: handleContainerClick,
2049
2023
  children: [
2050
- showLeadingIcon && /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("span", { className: cn(iconWrapperVariants({ size, disabled: !!disabled })), children: leadingIcon }),
2024
+ leadingIcon && /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("span", { className: cn(iconWrapperVariants({ size, disabled: !!disabled })), children: leadingIcon }),
2051
2025
  /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
2052
2026
  Input,
2053
2027
  {
@@ -2058,72 +2032,53 @@ var Autocomplete = React24.forwardRef((props, forwardedRef) => {
2058
2032
  placeholder,
2059
2033
  value: currentInput,
2060
2034
  onChange: handleInputChange,
2061
- onFocus: handleFocus,
2035
+ onFocus: (e) => {
2036
+ setIsFocused(true);
2037
+ onFocus?.(e);
2038
+ },
2062
2039
  onBlur: handleBlur,
2063
2040
  onKeyDown: handleKeyDown,
2064
2041
  role: "combobox",
2065
2042
  "aria-autocomplete": "list",
2066
2043
  "aria-controls": listboxId,
2067
2044
  "aria-expanded": showDropdown,
2068
- "aria-activedescendant": activeDescendantId,
2045
+ "aria-activedescendant": activeIndex >= 0 ? `${inputId}-option-${activeIndex}` : void 0,
2069
2046
  variant: "bare",
2070
2047
  className: cn(inputTextVariants({ size }), "bg-transparent outline-none w-full"),
2071
2048
  ...inputProps
2072
2049
  }
2073
2050
  ),
2074
- showTrailingIcon && /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("span", { className: cn(iconWrapperVariants({ size, disabled: !!disabled })), children: trailingIcon })
2051
+ trailingIcon && /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("span", { className: cn(iconWrapperVariants({ size, disabled: !!disabled })), children: trailingIcon })
2075
2052
  ]
2076
2053
  }
2077
2054
  ),
2078
- showDropdown && /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
2079
- "div",
2080
- {
2081
- className: cn(
2082
- "absolute left-0 right-0 mt-1",
2083
- dropdownSurfaceClass,
2084
- dropdownScrollClass,
2085
- dropdownClassName
2086
- ),
2087
- children: loading ? /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
2088
- "div",
2089
- {
2090
- className: cn(optionVariants({ size }), "px-(--space-8) pr-(--space-16) text-secondary"),
2091
- "aria-live": "polite",
2092
- children: loadingText
2093
- }
2094
- ) : options.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
2095
- "div",
2096
- {
2097
- className: cn(optionVariants({ size }), "px-(--space-8) pr-(--space-16) text-secondary"),
2098
- "aria-live": "polite",
2099
- children: noOptionsText
2100
- }
2101
- ) : /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
2102
- "ul",
2103
- {
2104
- id: listboxId,
2105
- role: "listbox",
2106
- className: cn("flex flex-col", listboxClassName),
2107
- children: options.map((option, index) => /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
2108
- "li",
2109
- {
2110
- id: `${inputId}-option-${index}`,
2111
- role: "option",
2112
- "aria-selected": index === activeIndex,
2113
- className: cn(
2114
- optionVariants({ size, active: index === activeIndex }),
2115
- "px-(--space-8) pr-(--space-16) text-primary cursor-pointer"
2116
- ),
2117
- onMouseDown: handleOptionMouseDown,
2118
- onMouseEnter: () => setActiveIndex(index),
2119
- onClick: () => handleOptionClick(option),
2120
- children: option
2121
- },
2122
- `${option}-${index}`
2123
- ))
2124
- }
2125
- )
2126
- }
2055
+ showDropdown && ReactDOM.createPortal(
2056
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
2057
+ "div",
2058
+ {
2059
+ style: dropdownStyle,
2060
+ className: cn(dropdownSurfaceClass, dropdownScrollClass, dropdownClassName),
2061
+ children: loading ? /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("div", { className: cn(optionVariants({ size }), "px-(--space-8) pr-(--space-16) text-secondary"), "aria-live": "polite", children: loadingText }) : options.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("div", { className: cn(optionVariants({ size }), "px-(--space-8) pr-(--space-16) text-secondary"), "aria-live": "polite", children: noOptionsText }) : /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("ul", { id: listboxId, role: "listbox", className: cn("flex flex-col", listboxClassName), children: options.map((option, index) => /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
2062
+ "li",
2063
+ {
2064
+ id: `${inputId}-option-${index}`,
2065
+ role: "option",
2066
+ "aria-selected": index === activeIndex,
2067
+ "data-index": index,
2068
+ className: cn(
2069
+ optionVariants({ size, active: index === activeIndex }),
2070
+ "px-(--space-8) pr-(--space-16) text-primary cursor-pointer"
2071
+ ),
2072
+ onMouseDown: (e) => e.preventDefault(),
2073
+ onMouseEnter: handleOptionMouseEnter,
2074
+ onClick: handleOptionClick,
2075
+ children: option
2076
+ },
2077
+ `${option}-${index}`
2078
+ )) })
2079
+ }
2080
+ ),
2081
+ document.body
2127
2082
  )
2128
2083
  ] }) });
2129
2084
  });