@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.d.cts CHANGED
@@ -365,34 +365,24 @@ declare const TextInput: React$1.ForwardRefExoticComponent<TextInputProps & Reac
365
365
  interface AutocompleteProps extends Omit<React$1.InputHTMLAttributes<HTMLInputElement>, "size" | "disabled" | "value" | "defaultValue" | "onChange"> {
366
366
  label?: React$1.ReactNode;
367
367
  hint?: React$1.ReactNode;
368
- /** If true, the hint will not be rendered even if provided. */
369
368
  hideHint?: boolean;
370
369
  status?: TextInputStatus;
371
370
  size?: TextInputSize;
372
371
  disabled?: boolean;
373
372
  leadingIcon?: React$1.ReactNode | null;
374
373
  trailingIcon?: React$1.ReactNode | null;
375
- /** Options to render (assumed already server-filtered). */
376
374
  options: string[];
377
375
  loading?: boolean;
378
376
  loadingText?: React$1.ReactNode;
379
377
  noOptionsText?: React$1.ReactNode;
380
- /** Committed value (e.g. selected option). */
381
378
  value?: string;
382
379
  defaultValue?: string;
383
380
  onChange?: (value: string) => void;
384
- /** Current input text (used as query). */
385
381
  inputValue?: string;
386
382
  defaultInputValue?: string;
387
383
  onInputChange?: (value: string) => void;
388
- /**
389
- * If true, free-typed input is also treated as the committed value.
390
- * Selecting an option from the dropdown still overrides the value.
391
- */
392
384
  freeSolo?: boolean;
393
- /** Optional className for the dropdown (options container). */
394
385
  dropdownClassName?: string;
395
- /** Optional className for the listbox (<ul>). */
396
386
  listboxClassName?: string;
397
387
  }
398
388
  declare const Autocomplete: React$1.ForwardRefExoticComponent<AutocompleteProps & React$1.RefAttributes<HTMLInputElement>>;
package/dist/index.d.ts CHANGED
@@ -365,34 +365,24 @@ declare const TextInput: React$1.ForwardRefExoticComponent<TextInputProps & Reac
365
365
  interface AutocompleteProps extends Omit<React$1.InputHTMLAttributes<HTMLInputElement>, "size" | "disabled" | "value" | "defaultValue" | "onChange"> {
366
366
  label?: React$1.ReactNode;
367
367
  hint?: React$1.ReactNode;
368
- /** If true, the hint will not be rendered even if provided. */
369
368
  hideHint?: boolean;
370
369
  status?: TextInputStatus;
371
370
  size?: TextInputSize;
372
371
  disabled?: boolean;
373
372
  leadingIcon?: React$1.ReactNode | null;
374
373
  trailingIcon?: React$1.ReactNode | null;
375
- /** Options to render (assumed already server-filtered). */
376
374
  options: string[];
377
375
  loading?: boolean;
378
376
  loadingText?: React$1.ReactNode;
379
377
  noOptionsText?: React$1.ReactNode;
380
- /** Committed value (e.g. selected option). */
381
378
  value?: string;
382
379
  defaultValue?: string;
383
380
  onChange?: (value: string) => void;
384
- /** Current input text (used as query). */
385
381
  inputValue?: string;
386
382
  defaultInputValue?: string;
387
383
  onInputChange?: (value: string) => void;
388
- /**
389
- * If true, free-typed input is also treated as the committed value.
390
- * Selecting an option from the dropdown still overrides the value.
391
- */
392
384
  freeSolo?: boolean;
393
- /** Optional className for the dropdown (options container). */
394
385
  dropdownClassName?: string;
395
- /** Optional className for the listbox (<ul>). */
396
386
  listboxClassName?: string;
397
387
  }
398
388
  declare const Autocomplete: React$1.ForwardRefExoticComponent<AutocompleteProps & React$1.RefAttributes<HTMLInputElement>>;
package/dist/index.js CHANGED
@@ -1653,6 +1653,7 @@ Checkbox.displayName = "Checkbox";
1653
1653
 
1654
1654
  // src/components/Inputs/Autocomplete.tsx
1655
1655
  import * as React24 from "react";
1656
+ import * as ReactDOM from "react-dom";
1656
1657
  import { cva as cva15 } from "class-variance-authority";
1657
1658
 
1658
1659
  // src/components/Inputs/InputShell.tsx
@@ -1739,9 +1740,7 @@ var inputTextVariants = cva15("truncate", {
1739
1740
  xl: "h6-title"
1740
1741
  }
1741
1742
  },
1742
- defaultVariants: {
1743
- size: "lg"
1744
- }
1743
+ defaultVariants: { size: "lg" }
1745
1744
  });
1746
1745
  var optionVariants = cva15(
1747
1746
  "w-full text-left hover:bg-(--background-secondary)",
@@ -1753,14 +1752,9 @@ var optionVariants = cva15(
1753
1752
  lg: "paragraph-lg py-(--space-8) ",
1754
1753
  xl: "subtitle py-(--space-10) "
1755
1754
  },
1756
- active: {
1757
- true: "bg-(--background-secondary)"
1758
- }
1755
+ active: { true: "bg-(--background-secondary)" }
1759
1756
  },
1760
- defaultVariants: {
1761
- size: "lg",
1762
- active: false
1763
- }
1757
+ defaultVariants: { size: "lg", active: false }
1764
1758
  }
1765
1759
  );
1766
1760
  var iconWrapperVariants = cva15(
@@ -1773,13 +1767,9 @@ var iconWrapperVariants = cva15(
1773
1767
  lg: "size-5 [&>svg]:size-5",
1774
1768
  xl: "size-6 [&>svg]:size-6"
1775
1769
  },
1776
- disabled: {
1777
- true: "text-(--icon-primary-disabled)"
1778
- }
1770
+ disabled: { true: "text-(--icon-primary-disabled)" }
1779
1771
  },
1780
- defaultVariants: {
1781
- size: "lg"
1782
- }
1772
+ defaultVariants: { size: "lg" }
1783
1773
  }
1784
1774
  );
1785
1775
  var Autocomplete = React24.forwardRef((props, forwardedRef) => {
@@ -1814,84 +1804,68 @@ var Autocomplete = React24.forwardRef((props, forwardedRef) => {
1814
1804
  ...inputProps
1815
1805
  } = props;
1816
1806
  const isValueControlled = value !== void 0;
1817
- const [internalValue, setInternalValue] = React24.useState(
1818
- defaultValue ?? ""
1819
- );
1807
+ const [internalValue, setInternalValue] = React24.useState(defaultValue ?? "");
1820
1808
  const isInputControlled = inputValue !== void 0;
1821
- const [internalInputValue, setInternalInputValue] = React24.useState(
1822
- defaultInputValue ?? ""
1823
- );
1809
+ const [internalInputValue, setInternalInputValue] = React24.useState(defaultInputValue ?? "");
1824
1810
  const [isFocused, setIsFocused] = React24.useState(false);
1825
1811
  const [activeIndex, setActiveIndex] = React24.useState(-1);
1812
+ const [dropdownStyle, setDropdownStyle] = React24.useState({});
1826
1813
  const inputRef = React24.useRef(null);
1827
- const setInputRef = React24.useCallback(
1828
- (node) => {
1829
- inputRef.current = node;
1830
- if (!forwardedRef) return;
1831
- if (typeof forwardedRef === "function") {
1832
- forwardedRef(node);
1833
- } else {
1834
- forwardedRef.current = node;
1835
- }
1836
- },
1837
- [forwardedRef]
1838
- );
1814
+ const anchorRef = React24.useRef(null);
1815
+ const rafIdRef = React24.useRef(null);
1839
1816
  const baseId = React24.useId();
1840
1817
  const inputId = id ?? baseId;
1841
1818
  const listboxId = `${inputId}-listbox`;
1842
1819
  const currentValue = (isValueControlled ? value : internalValue) ?? "";
1843
1820
  const currentInput = (isInputControlled ? inputValue : internalInputValue) ?? "";
1844
- React24.useEffect(() => {
1845
- if (isFocused) return;
1846
- if (isInputControlled) return;
1847
- if (!isValueControlled) return;
1848
- setInternalInputValue(currentValue);
1849
- }, [currentValue, isFocused, isInputControlled, isValueControlled]);
1850
- const showDropdown = isFocused && (loading || options.length > 0 || currentInput.trim().length > 0);
1821
+ const trimmedInput = currentInput.trim();
1822
+ const showDropdown = isFocused && (loading || options.length > 0 || trimmedInput.length > 0);
1851
1823
  const setInputText = (next) => {
1852
- if (!isInputControlled) {
1853
- setInternalInputValue(next);
1854
- }
1824
+ if (!isInputControlled) setInternalInputValue(next);
1855
1825
  onInputChange?.(next);
1856
1826
  };
1857
1827
  const commitTypedValue = (next) => {
1858
- if (!isValueControlled) {
1859
- setInternalValue(next);
1860
- }
1828
+ if (!isValueControlled) setInternalValue(next);
1861
1829
  onChange?.(next);
1862
1830
  };
1863
1831
  const commitValue = (next) => {
1864
- if (!isValueControlled) {
1865
- setInternalValue(next);
1866
- }
1832
+ if (!isValueControlled) setInternalValue(next);
1867
1833
  onChange?.(next);
1868
1834
  setInputText(next);
1869
1835
  setActiveIndex(-1);
1870
1836
  };
1871
- const handleContainerClick = () => {
1872
- if (disabled) return;
1873
- inputRef.current?.focus();
1874
- };
1837
+ const setInputRef = React24.useCallback((node) => {
1838
+ inputRef.current = node;
1839
+ if (!forwardedRef) return;
1840
+ if (typeof forwardedRef === "function") forwardedRef(node);
1841
+ else forwardedRef.current = node;
1842
+ }, [forwardedRef]);
1843
+ const updateDropdownPosition = React24.useCallback(() => {
1844
+ if (rafIdRef.current !== null) return;
1845
+ rafIdRef.current = requestAnimationFrame(() => {
1846
+ rafIdRef.current = null;
1847
+ if (!anchorRef.current) return;
1848
+ const rect = anchorRef.current.getBoundingClientRect();
1849
+ setDropdownStyle({
1850
+ position: "fixed",
1851
+ top: rect.bottom,
1852
+ left: rect.left,
1853
+ width: rect.width,
1854
+ zIndex: 9999
1855
+ });
1856
+ });
1857
+ }, []);
1875
1858
  const handleInputChange = (event) => {
1876
1859
  const next = event.target.value;
1877
1860
  setInputText(next);
1878
1861
  setActiveIndex(-1);
1879
- if (freeSolo) {
1880
- commitTypedValue(next);
1881
- }
1882
- };
1883
- const handleFocus = (event) => {
1884
- setIsFocused(true);
1885
- onFocus?.(event);
1862
+ if (freeSolo) commitTypedValue(next);
1886
1863
  };
1887
1864
  const handleBlur = (event) => {
1888
1865
  setIsFocused(false);
1889
1866
  setActiveIndex(-1);
1890
- if (freeSolo) {
1891
- const trimmed = currentInput.trim();
1892
- if (trimmed.length > 0 && currentInput !== currentValue) {
1893
- commitTypedValue(currentInput);
1894
- }
1867
+ if (freeSolo && trimmedInput.length > 0 && currentInput !== currentValue) {
1868
+ commitTypedValue(currentInput);
1895
1869
  }
1896
1870
  onBlur?.(event);
1897
1871
  };
@@ -1903,64 +1877,64 @@ var Autocomplete = React24.forwardRef((props, forwardedRef) => {
1903
1877
  return;
1904
1878
  }
1905
1879
  switch (event.key) {
1906
- case "ArrowDown": {
1880
+ case "ArrowDown":
1907
1881
  event.preventDefault();
1908
- setActiveIndex((prev) => {
1909
- if (options.length === 0) return -1;
1910
- const next = prev < 0 ? 0 : Math.min(prev + 1, options.length - 1);
1911
- return next;
1912
- });
1882
+ setActiveIndex((prev) => options.length === 0 ? -1 : prev < 0 ? 0 : Math.min(prev + 1, options.length - 1));
1913
1883
  break;
1914
- }
1915
- case "ArrowUp": {
1884
+ case "ArrowUp":
1916
1885
  event.preventDefault();
1917
- setActiveIndex((prev) => {
1918
- if (options.length === 0) return -1;
1919
- const next = prev <= 0 ? 0 : prev - 1;
1920
- return next;
1921
- });
1886
+ setActiveIndex((prev) => options.length === 0 ? -1 : prev <= 0 ? 0 : prev - 1);
1922
1887
  break;
1923
- }
1924
- case "Enter": {
1888
+ case "Enter":
1925
1889
  if (activeIndex >= 0 && activeIndex < options.length) {
1926
1890
  event.preventDefault();
1927
1891
  commitValue(options[activeIndex]);
1928
1892
  setIsFocused(false);
1929
- break;
1930
- }
1931
- if (freeSolo) {
1932
- const trimmed = currentInput.trim();
1933
- if (trimmed.length > 0) {
1934
- event.preventDefault();
1935
- if (currentInput !== currentValue) {
1936
- commitTypedValue(currentInput);
1937
- }
1938
- setIsFocused(false);
1939
- }
1893
+ } else if (freeSolo && trimmedInput.length > 0) {
1894
+ event.preventDefault();
1895
+ if (currentInput !== currentValue) commitTypedValue(currentInput);
1896
+ setIsFocused(false);
1940
1897
  }
1941
1898
  break;
1942
- }
1943
- case "Escape": {
1899
+ case "Escape":
1944
1900
  event.preventDefault();
1945
1901
  setIsFocused(false);
1946
1902
  setActiveIndex(-1);
1947
1903
  break;
1948
- }
1949
- default:
1950
- break;
1951
1904
  }
1952
1905
  };
1953
- const handleOptionMouseDown = (event) => {
1954
- event.preventDefault();
1906
+ const handleOptionMouseEnter = (event) => {
1907
+ const index = Number(event.currentTarget.dataset.index);
1908
+ if (!isNaN(index)) setActiveIndex(index);
1955
1909
  };
1956
- const handleOptionClick = (option) => {
1957
- commitValue(option);
1958
- setIsFocused(false);
1910
+ const handleOptionClick = (event) => {
1911
+ const index = Number(event.currentTarget.dataset.index);
1912
+ if (index >= 0 && index < options.length) {
1913
+ commitValue(options[index]);
1914
+ setIsFocused(false);
1915
+ }
1959
1916
  };
1960
- const activeDescendantId = activeIndex >= 0 ? `${inputId}-option-${activeIndex}` : void 0;
1961
- const showLeadingIcon = !!leadingIcon;
1962
- const showTrailingIcon = !!trailingIcon;
1963
- return /* @__PURE__ */ jsx26(Field, { label, hint, hideHint, status, disabled, children: /* @__PURE__ */ jsxs14("div", { className: "relative w-full", children: [
1917
+ const handleContainerClick = () => {
1918
+ if (disabled) return;
1919
+ inputRef.current?.focus();
1920
+ };
1921
+ React24.useLayoutEffect(() => {
1922
+ if (!showDropdown) return;
1923
+ updateDropdownPosition();
1924
+ window.addEventListener("scroll", updateDropdownPosition, { capture: true, passive: true });
1925
+ window.addEventListener("resize", updateDropdownPosition);
1926
+ return () => {
1927
+ window.removeEventListener("scroll", updateDropdownPosition, { capture: true });
1928
+ window.removeEventListener("resize", updateDropdownPosition);
1929
+ if (rafIdRef.current !== null) cancelAnimationFrame(rafIdRef.current);
1930
+ };
1931
+ }, [showDropdown, updateDropdownPosition]);
1932
+ React24.useEffect(() => {
1933
+ if (!isFocused && !isInputControlled && isValueControlled) {
1934
+ setInternalInputValue(currentValue);
1935
+ }
1936
+ }, [currentValue, isFocused, isInputControlled, isValueControlled]);
1937
+ return /* @__PURE__ */ jsx26(Field, { label, hint, hideHint, status, disabled, children: /* @__PURE__ */ jsxs14("div", { ref: anchorRef, className: "relative w-full", children: [
1964
1938
  /* @__PURE__ */ jsxs14(
1965
1939
  InputShell,
1966
1940
  {
@@ -1970,7 +1944,7 @@ var Autocomplete = React24.forwardRef((props, forwardedRef) => {
1970
1944
  className,
1971
1945
  onClick: handleContainerClick,
1972
1946
  children: [
1973
- showLeadingIcon && /* @__PURE__ */ jsx26("span", { className: cn(iconWrapperVariants({ size, disabled: !!disabled })), children: leadingIcon }),
1947
+ leadingIcon && /* @__PURE__ */ jsx26("span", { className: cn(iconWrapperVariants({ size, disabled: !!disabled })), children: leadingIcon }),
1974
1948
  /* @__PURE__ */ jsx26(
1975
1949
  Input,
1976
1950
  {
@@ -1981,72 +1955,53 @@ var Autocomplete = React24.forwardRef((props, forwardedRef) => {
1981
1955
  placeholder,
1982
1956
  value: currentInput,
1983
1957
  onChange: handleInputChange,
1984
- onFocus: handleFocus,
1958
+ onFocus: (e) => {
1959
+ setIsFocused(true);
1960
+ onFocus?.(e);
1961
+ },
1985
1962
  onBlur: handleBlur,
1986
1963
  onKeyDown: handleKeyDown,
1987
1964
  role: "combobox",
1988
1965
  "aria-autocomplete": "list",
1989
1966
  "aria-controls": listboxId,
1990
1967
  "aria-expanded": showDropdown,
1991
- "aria-activedescendant": activeDescendantId,
1968
+ "aria-activedescendant": activeIndex >= 0 ? `${inputId}-option-${activeIndex}` : void 0,
1992
1969
  variant: "bare",
1993
1970
  className: cn(inputTextVariants({ size }), "bg-transparent outline-none w-full"),
1994
1971
  ...inputProps
1995
1972
  }
1996
1973
  ),
1997
- showTrailingIcon && /* @__PURE__ */ jsx26("span", { className: cn(iconWrapperVariants({ size, disabled: !!disabled })), children: trailingIcon })
1974
+ trailingIcon && /* @__PURE__ */ jsx26("span", { className: cn(iconWrapperVariants({ size, disabled: !!disabled })), children: trailingIcon })
1998
1975
  ]
1999
1976
  }
2000
1977
  ),
2001
- showDropdown && /* @__PURE__ */ jsx26(
2002
- "div",
2003
- {
2004
- className: cn(
2005
- "absolute left-0 right-0 mt-1",
2006
- dropdownSurfaceClass,
2007
- dropdownScrollClass,
2008
- dropdownClassName
2009
- ),
2010
- children: loading ? /* @__PURE__ */ jsx26(
2011
- "div",
2012
- {
2013
- className: cn(optionVariants({ size }), "px-(--space-8) pr-(--space-16) text-secondary"),
2014
- "aria-live": "polite",
2015
- children: loadingText
2016
- }
2017
- ) : options.length === 0 ? /* @__PURE__ */ jsx26(
2018
- "div",
2019
- {
2020
- className: cn(optionVariants({ size }), "px-(--space-8) pr-(--space-16) text-secondary"),
2021
- "aria-live": "polite",
2022
- children: noOptionsText
2023
- }
2024
- ) : /* @__PURE__ */ jsx26(
2025
- "ul",
2026
- {
2027
- id: listboxId,
2028
- role: "listbox",
2029
- className: cn("flex flex-col", listboxClassName),
2030
- children: options.map((option, index) => /* @__PURE__ */ jsx26(
2031
- "li",
2032
- {
2033
- id: `${inputId}-option-${index}`,
2034
- role: "option",
2035
- "aria-selected": index === activeIndex,
2036
- className: cn(
2037
- optionVariants({ size, active: index === activeIndex }),
2038
- "px-(--space-8) pr-(--space-16) text-primary cursor-pointer"
2039
- ),
2040
- onMouseDown: handleOptionMouseDown,
2041
- onMouseEnter: () => setActiveIndex(index),
2042
- onClick: () => handleOptionClick(option),
2043
- children: option
2044
- },
2045
- `${option}-${index}`
2046
- ))
2047
- }
2048
- )
2049
- }
1978
+ showDropdown && ReactDOM.createPortal(
1979
+ /* @__PURE__ */ jsx26(
1980
+ "div",
1981
+ {
1982
+ style: dropdownStyle,
1983
+ className: cn(dropdownSurfaceClass, dropdownScrollClass, dropdownClassName),
1984
+ children: loading ? /* @__PURE__ */ jsx26("div", { className: cn(optionVariants({ size }), "px-(--space-8) pr-(--space-16) text-secondary"), "aria-live": "polite", children: loadingText }) : options.length === 0 ? /* @__PURE__ */ jsx26("div", { className: cn(optionVariants({ size }), "px-(--space-8) pr-(--space-16) text-secondary"), "aria-live": "polite", children: noOptionsText }) : /* @__PURE__ */ jsx26("ul", { id: listboxId, role: "listbox", className: cn("flex flex-col", listboxClassName), children: options.map((option, index) => /* @__PURE__ */ jsx26(
1985
+ "li",
1986
+ {
1987
+ id: `${inputId}-option-${index}`,
1988
+ role: "option",
1989
+ "aria-selected": index === activeIndex,
1990
+ "data-index": index,
1991
+ className: cn(
1992
+ optionVariants({ size, active: index === activeIndex }),
1993
+ "px-(--space-8) pr-(--space-16) text-primary cursor-pointer"
1994
+ ),
1995
+ onMouseDown: (e) => e.preventDefault(),
1996
+ onMouseEnter: handleOptionMouseEnter,
1997
+ onClick: handleOptionClick,
1998
+ children: option
1999
+ },
2000
+ `${option}-${index}`
2001
+ )) })
2002
+ }
2003
+ ),
2004
+ document.body
2050
2005
  )
2051
2006
  ] }) });
2052
2007
  });