@rufous/ui 0.3.39 → 0.3.40

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/main.cjs CHANGED
@@ -10669,6 +10669,8 @@ function SmartSelect({
10669
10669
  options,
10670
10670
  value,
10671
10671
  onChange,
10672
+ inputValue: inputValueProp,
10673
+ onInputChange,
10672
10674
  onSearchChange,
10673
10675
  searchResults = [],
10674
10676
  debounceMs = 300,
@@ -10698,17 +10700,19 @@ function SmartSelect({
10698
10700
  sx
10699
10701
  }) {
10700
10702
  const debounceTimer = (0, import_react54.useRef)(null);
10703
+ const isControlled = inputValueProp !== void 0;
10701
10704
  (0, import_react54.useEffect)(() => () => {
10702
10705
  if (debounceTimer.current) clearTimeout(debounceTimer.current);
10703
10706
  }, []);
10704
- const [inputValue, setInputValue] = (0, import_react54.useState)(
10707
+ const [internalInput, setInternalInput] = (0, import_react54.useState)(
10705
10708
  () => !multiple && value != null ? getOptionLabel(value) : ""
10706
10709
  );
10707
10710
  (0, import_react54.useEffect)(() => {
10708
- if (!multiple) {
10709
- setInputValue(value != null ? getOptionLabel(value) : "");
10711
+ if (!isControlled && !multiple) {
10712
+ setInternalInput(value != null ? getOptionLabel(value) : "");
10710
10713
  }
10711
- }, [value, multiple, getOptionLabel]);
10714
+ }, [value, multiple, getOptionLabel, isControlled]);
10715
+ const activeInput = isControlled ? inputValueProp : internalInput;
10712
10716
  const getValue = (0, import_react54.useCallback)(
10713
10717
  (o) => getOptionValue ? getOptionValue(o) : String(getOptionLabel(o)),
10714
10718
  [getOptionValue, getOptionLabel]
@@ -10751,16 +10755,20 @@ function SmartSelect({
10751
10755
  }
10752
10756
  return value != null ? /* @__PURE__ */ new Set([getValue(value)]) : /* @__PURE__ */ new Set();
10753
10757
  }, [multiple, value, getValue]);
10754
- const handleInputChange = (0, import_react54.useCallback)((_, inputValue2, reason) => {
10755
- setInputValue(inputValue2);
10758
+ const handleInputChange = (0, import_react54.useCallback)((_, val, reason) => {
10759
+ const resolvedReason = reason ?? "input";
10760
+ if (!isControlled) {
10761
+ setInternalInput(val);
10762
+ }
10763
+ onInputChange?.(val, resolvedReason);
10756
10764
  if (!onSearchChange) return;
10757
10765
  if (debounceTimer.current) clearTimeout(debounceTimer.current);
10758
- if (reason !== "input") {
10759
- if (reason === "clear") onSearchChange("", 0);
10766
+ if (resolvedReason !== "input") {
10767
+ if (resolvedReason === "clear") onSearchChange("", 0);
10760
10768
  return;
10761
10769
  }
10762
- if (!inputValue2) return;
10763
- const q = inputValue2.toLowerCase();
10770
+ if (!val) return;
10771
+ const q = val.toLowerCase();
10764
10772
  let localCount = 0;
10765
10773
  for (const opt of flatOptionsList) {
10766
10774
  if (getOptionLabel(opt).toLowerCase().includes(q) || (getOptionSubLabel?.(opt) ?? "").toLowerCase().includes(q)) {
@@ -10771,13 +10779,13 @@ function SmartSelect({
10771
10779
  if (localCount >= searchThreshold) return;
10772
10780
  const needed = searchThreshold - localCount;
10773
10781
  if (debounceMs <= 0) {
10774
- onSearchChange(inputValue2, needed);
10782
+ onSearchChange(val, needed);
10775
10783
  } else {
10776
10784
  debounceTimer.current = setTimeout(() => {
10777
- onSearchChange(inputValue2, needed);
10785
+ onSearchChange(val, needed);
10778
10786
  }, debounceMs);
10779
10787
  }
10780
- }, [onSearchChange, debounceMs, searchThreshold, flatOptionsList, getOptionLabel, getOptionSubLabel]);
10788
+ }, [isControlled, onInputChange, onSearchChange, debounceMs, searchThreshold, flatOptionsList, getOptionLabel, getOptionSubLabel]);
10781
10789
  const handleChange = (0, import_react54.useCallback)((_, newValue) => {
10782
10790
  if (!multiple || !allowChildNodesSelection || !getOptionChildren) {
10783
10791
  onChange?.(newValue);
@@ -10853,13 +10861,13 @@ function SmartSelect({
10853
10861
  /* @__PURE__ */ import_react54.default.createElement("span", { className: "rf-select__option-check", "aria-hidden": "true" }, /* @__PURE__ */ import_react54.default.createElement(CheckIcon3, null))
10854
10862
  );
10855
10863
  }, [depthMap, getValue, getOptionLabel, getOptionSubLabel, selectedKeys]);
10856
- const computedFilterOptions = (0, import_react54.useCallback)((opts, inputValue2) => {
10857
- if (filterOptionsProp) return filterOptionsProp(opts, inputValue2);
10864
+ const computedFilterOptions = (0, import_react54.useCallback)((opts, inputVal) => {
10865
+ if (filterOptionsProp) return filterOptionsProp(opts, inputVal);
10858
10866
  if (multiple) {
10859
10867
  const selected = opts.filter((o) => selectedKeys.has(getValue(o)));
10860
10868
  const unselected = opts.filter((o) => !selectedKeys.has(getValue(o)));
10861
- if (!inputValue2) return [...selected, ...unselected];
10862
- const q2 = inputValue2.toLowerCase();
10869
+ if (!inputVal) return [...selected, ...unselected];
10870
+ const q2 = inputVal.toLowerCase();
10863
10871
  const filteredUnselected = unselected.filter(
10864
10872
  (opt) => getOptionLabel(opt).toLowerCase().includes(q2) || (getOptionSubLabel?.(opt) ?? "").toLowerCase().includes(q2)
10865
10873
  ).slice(0, searchThreshold);
@@ -10870,7 +10878,7 @@ function SmartSelect({
10870
10878
  const selectedLabel = getOptionLabel(value);
10871
10879
  const inOpts = opts.some((o) => getValue(o) === selectedKey);
10872
10880
  const selectedFallback = inOpts ? [] : [value];
10873
- if (!inputValue2 || inputValue2 === selectedLabel) {
10881
+ if (!inputVal || inputVal === selectedLabel) {
10874
10882
  return [
10875
10883
  ...selectedFallback,
10876
10884
  ...opts.filter((o) => getValue(o) === selectedKey),
@@ -10878,8 +10886,8 @@ function SmartSelect({
10878
10886
  ];
10879
10887
  }
10880
10888
  }
10881
- if (!inputValue2) return opts;
10882
- const q = inputValue2.toLowerCase();
10889
+ if (!inputVal) return opts;
10890
+ const q = inputVal.toLowerCase();
10883
10891
  return opts.filter(
10884
10892
  (opt) => getOptionLabel(opt).toLowerCase().includes(q) || (getOptionSubLabel?.(opt) ?? "").toLowerCase().includes(q)
10885
10893
  ).slice(0, searchThreshold);
@@ -10890,7 +10898,7 @@ function SmartSelect({
10890
10898
  options: displayOptions,
10891
10899
  value: value ?? (multiple ? [] : null),
10892
10900
  onChange: handleChange,
10893
- inputValue: multiple ? void 0 : inputValue,
10901
+ inputValue: multiple ? isControlled ? inputValueProp : void 0 : activeInput,
10894
10902
  onInputChange: handleInputChange,
10895
10903
  multiple,
10896
10904
  limitTags,
package/dist/main.d.cts CHANGED
@@ -2240,6 +2240,28 @@ interface SmartSelectProps<T = any> {
2240
2240
  value?: T | T[] | null;
2241
2241
  /** Called when selection changes */
2242
2242
  onChange?: (value: T | T[] | null) => void;
2243
+ /**
2244
+ * Controlled text shown in the search input.
2245
+ * When provided the input is fully controlled — update it via `onInputChange`.
2246
+ * When omitted SmartSelect manages the input text internally.
2247
+ */
2248
+ inputValue?: string;
2249
+ /**
2250
+ * Called whenever the input text changes.
2251
+ * - `reason: 'input'` — user typed
2252
+ * - `reason: 'reset'` — option selected or dropdown closed
2253
+ * - `reason: 'clear'` — field was cleared
2254
+ *
2255
+ * Pair with `inputValue` for full controlled mode:
2256
+ * ```
2257
+ * const [search, setSearch] = useState('');
2258
+ * <SmartSelect
2259
+ * inputValue={search}
2260
+ * onInputChange={(val, reason) => { if (reason !== 'reset') setSearch(val); }}
2261
+ * />
2262
+ * ```
2263
+ */
2264
+ onInputChange?: (value: string, reason: 'input' | 'reset' | 'clear') => void;
2243
2265
  /**
2244
2266
  * Called when local matches fall below `searchThreshold`.
2245
2267
  * Receives the current query and how many more records are needed to fill the threshold.
@@ -2316,7 +2338,7 @@ interface SmartSelectProps<T = any> {
2316
2338
  style?: CSSProperties;
2317
2339
  sx?: SxProp;
2318
2340
  }
2319
- declare function SmartSelect<T = any>({ options, value, onChange, onSearchChange, searchResults, debounceMs, searchThreshold, getOptionLabel, getOptionValue, getOptionSubLabel, getOptionChildren, multiple, allowChildNodesSelection, loading, loadingText, filterOptions: filterOptionsProp, renderOption: renderOptionProp, limitTags, label, placeholder, variant, size, disabled, error, helperText, fullWidth, required, className, style, sx, }: SmartSelectProps<T>): React__default.JSX.Element;
2341
+ declare function SmartSelect<T = any>({ options, value, onChange, inputValue: inputValueProp, onInputChange, onSearchChange, searchResults, debounceMs, searchThreshold, getOptionLabel, getOptionValue, getOptionSubLabel, getOptionChildren, multiple, allowChildNodesSelection, loading, loadingText, filterOptions: filterOptionsProp, renderOption: renderOptionProp, limitTags, label, placeholder, variant, size, disabled, error, helperText, fullWidth, required, className, style, sx, }: SmartSelectProps<T>): React__default.JSX.Element;
2320
2342
 
2321
2343
  type ToolbarButton = 'undo' | 'redo' | 'ai' | 'paragraph' | 'fontsize' | 'font' | 'color' | 'bold' | 'italic' | 'strike' | 'link' | 'lineheight' | 'ul' | 'ol' | 'align' | 'indent' | 'outdent' | 'table' | 'image' | 'video' | 'cut' | 'copy' | 'paste' | 'specialchars' | 'code' | 'fullscreen' | 'tts' | 'stt' | 'translate' | 'todo' | '|';
2322
2344
  type EditorVariant = 'default' | 'basic';
package/dist/main.d.ts CHANGED
@@ -2240,6 +2240,28 @@ interface SmartSelectProps<T = any> {
2240
2240
  value?: T | T[] | null;
2241
2241
  /** Called when selection changes */
2242
2242
  onChange?: (value: T | T[] | null) => void;
2243
+ /**
2244
+ * Controlled text shown in the search input.
2245
+ * When provided the input is fully controlled — update it via `onInputChange`.
2246
+ * When omitted SmartSelect manages the input text internally.
2247
+ */
2248
+ inputValue?: string;
2249
+ /**
2250
+ * Called whenever the input text changes.
2251
+ * - `reason: 'input'` — user typed
2252
+ * - `reason: 'reset'` — option selected or dropdown closed
2253
+ * - `reason: 'clear'` — field was cleared
2254
+ *
2255
+ * Pair with `inputValue` for full controlled mode:
2256
+ * ```
2257
+ * const [search, setSearch] = useState('');
2258
+ * <SmartSelect
2259
+ * inputValue={search}
2260
+ * onInputChange={(val, reason) => { if (reason !== 'reset') setSearch(val); }}
2261
+ * />
2262
+ * ```
2263
+ */
2264
+ onInputChange?: (value: string, reason: 'input' | 'reset' | 'clear') => void;
2243
2265
  /**
2244
2266
  * Called when local matches fall below `searchThreshold`.
2245
2267
  * Receives the current query and how many more records are needed to fill the threshold.
@@ -2316,7 +2338,7 @@ interface SmartSelectProps<T = any> {
2316
2338
  style?: CSSProperties;
2317
2339
  sx?: SxProp;
2318
2340
  }
2319
- declare function SmartSelect<T = any>({ options, value, onChange, onSearchChange, searchResults, debounceMs, searchThreshold, getOptionLabel, getOptionValue, getOptionSubLabel, getOptionChildren, multiple, allowChildNodesSelection, loading, loadingText, filterOptions: filterOptionsProp, renderOption: renderOptionProp, limitTags, label, placeholder, variant, size, disabled, error, helperText, fullWidth, required, className, style, sx, }: SmartSelectProps<T>): React__default.JSX.Element;
2341
+ declare function SmartSelect<T = any>({ options, value, onChange, inputValue: inputValueProp, onInputChange, onSearchChange, searchResults, debounceMs, searchThreshold, getOptionLabel, getOptionValue, getOptionSubLabel, getOptionChildren, multiple, allowChildNodesSelection, loading, loadingText, filterOptions: filterOptionsProp, renderOption: renderOptionProp, limitTags, label, placeholder, variant, size, disabled, error, helperText, fullWidth, required, className, style, sx, }: SmartSelectProps<T>): React__default.JSX.Element;
2320
2342
 
2321
2343
  type ToolbarButton = 'undo' | 'redo' | 'ai' | 'paragraph' | 'fontsize' | 'font' | 'color' | 'bold' | 'italic' | 'strike' | 'link' | 'lineheight' | 'ul' | 'ol' | 'align' | 'indent' | 'outdent' | 'table' | 'image' | 'video' | 'cut' | 'copy' | 'paste' | 'specialchars' | 'code' | 'fullscreen' | 'tts' | 'stt' | 'translate' | 'todo' | '|';
2322
2344
  type EditorVariant = 'default' | 'basic';
package/dist/main.js CHANGED
@@ -10552,6 +10552,8 @@ function SmartSelect({
10552
10552
  options,
10553
10553
  value,
10554
10554
  onChange,
10555
+ inputValue: inputValueProp,
10556
+ onInputChange,
10555
10557
  onSearchChange,
10556
10558
  searchResults = [],
10557
10559
  debounceMs = 300,
@@ -10581,17 +10583,19 @@ function SmartSelect({
10581
10583
  sx
10582
10584
  }) {
10583
10585
  const debounceTimer = useRef25(null);
10586
+ const isControlled = inputValueProp !== void 0;
10584
10587
  useEffect21(() => () => {
10585
10588
  if (debounceTimer.current) clearTimeout(debounceTimer.current);
10586
10589
  }, []);
10587
- const [inputValue, setInputValue] = useState27(
10590
+ const [internalInput, setInternalInput] = useState27(
10588
10591
  () => !multiple && value != null ? getOptionLabel(value) : ""
10589
10592
  );
10590
10593
  useEffect21(() => {
10591
- if (!multiple) {
10592
- setInputValue(value != null ? getOptionLabel(value) : "");
10594
+ if (!isControlled && !multiple) {
10595
+ setInternalInput(value != null ? getOptionLabel(value) : "");
10593
10596
  }
10594
- }, [value, multiple, getOptionLabel]);
10597
+ }, [value, multiple, getOptionLabel, isControlled]);
10598
+ const activeInput = isControlled ? inputValueProp : internalInput;
10595
10599
  const getValue = useCallback12(
10596
10600
  (o) => getOptionValue ? getOptionValue(o) : String(getOptionLabel(o)),
10597
10601
  [getOptionValue, getOptionLabel]
@@ -10634,16 +10638,20 @@ function SmartSelect({
10634
10638
  }
10635
10639
  return value != null ? /* @__PURE__ */ new Set([getValue(value)]) : /* @__PURE__ */ new Set();
10636
10640
  }, [multiple, value, getValue]);
10637
- const handleInputChange = useCallback12((_, inputValue2, reason) => {
10638
- setInputValue(inputValue2);
10641
+ const handleInputChange = useCallback12((_, val, reason) => {
10642
+ const resolvedReason = reason ?? "input";
10643
+ if (!isControlled) {
10644
+ setInternalInput(val);
10645
+ }
10646
+ onInputChange?.(val, resolvedReason);
10639
10647
  if (!onSearchChange) return;
10640
10648
  if (debounceTimer.current) clearTimeout(debounceTimer.current);
10641
- if (reason !== "input") {
10642
- if (reason === "clear") onSearchChange("", 0);
10649
+ if (resolvedReason !== "input") {
10650
+ if (resolvedReason === "clear") onSearchChange("", 0);
10643
10651
  return;
10644
10652
  }
10645
- if (!inputValue2) return;
10646
- const q = inputValue2.toLowerCase();
10653
+ if (!val) return;
10654
+ const q = val.toLowerCase();
10647
10655
  let localCount = 0;
10648
10656
  for (const opt of flatOptionsList) {
10649
10657
  if (getOptionLabel(opt).toLowerCase().includes(q) || (getOptionSubLabel?.(opt) ?? "").toLowerCase().includes(q)) {
@@ -10654,13 +10662,13 @@ function SmartSelect({
10654
10662
  if (localCount >= searchThreshold) return;
10655
10663
  const needed = searchThreshold - localCount;
10656
10664
  if (debounceMs <= 0) {
10657
- onSearchChange(inputValue2, needed);
10665
+ onSearchChange(val, needed);
10658
10666
  } else {
10659
10667
  debounceTimer.current = setTimeout(() => {
10660
- onSearchChange(inputValue2, needed);
10668
+ onSearchChange(val, needed);
10661
10669
  }, debounceMs);
10662
10670
  }
10663
- }, [onSearchChange, debounceMs, searchThreshold, flatOptionsList, getOptionLabel, getOptionSubLabel]);
10671
+ }, [isControlled, onInputChange, onSearchChange, debounceMs, searchThreshold, flatOptionsList, getOptionLabel, getOptionSubLabel]);
10664
10672
  const handleChange = useCallback12((_, newValue) => {
10665
10673
  if (!multiple || !allowChildNodesSelection || !getOptionChildren) {
10666
10674
  onChange?.(newValue);
@@ -10736,13 +10744,13 @@ function SmartSelect({
10736
10744
  /* @__PURE__ */ React109.createElement("span", { className: "rf-select__option-check", "aria-hidden": "true" }, /* @__PURE__ */ React109.createElement(CheckIcon3, null))
10737
10745
  );
10738
10746
  }, [depthMap, getValue, getOptionLabel, getOptionSubLabel, selectedKeys]);
10739
- const computedFilterOptions = useCallback12((opts, inputValue2) => {
10740
- if (filterOptionsProp) return filterOptionsProp(opts, inputValue2);
10747
+ const computedFilterOptions = useCallback12((opts, inputVal) => {
10748
+ if (filterOptionsProp) return filterOptionsProp(opts, inputVal);
10741
10749
  if (multiple) {
10742
10750
  const selected = opts.filter((o) => selectedKeys.has(getValue(o)));
10743
10751
  const unselected = opts.filter((o) => !selectedKeys.has(getValue(o)));
10744
- if (!inputValue2) return [...selected, ...unselected];
10745
- const q2 = inputValue2.toLowerCase();
10752
+ if (!inputVal) return [...selected, ...unselected];
10753
+ const q2 = inputVal.toLowerCase();
10746
10754
  const filteredUnselected = unselected.filter(
10747
10755
  (opt) => getOptionLabel(opt).toLowerCase().includes(q2) || (getOptionSubLabel?.(opt) ?? "").toLowerCase().includes(q2)
10748
10756
  ).slice(0, searchThreshold);
@@ -10753,7 +10761,7 @@ function SmartSelect({
10753
10761
  const selectedLabel = getOptionLabel(value);
10754
10762
  const inOpts = opts.some((o) => getValue(o) === selectedKey);
10755
10763
  const selectedFallback = inOpts ? [] : [value];
10756
- if (!inputValue2 || inputValue2 === selectedLabel) {
10764
+ if (!inputVal || inputVal === selectedLabel) {
10757
10765
  return [
10758
10766
  ...selectedFallback,
10759
10767
  ...opts.filter((o) => getValue(o) === selectedKey),
@@ -10761,8 +10769,8 @@ function SmartSelect({
10761
10769
  ];
10762
10770
  }
10763
10771
  }
10764
- if (!inputValue2) return opts;
10765
- const q = inputValue2.toLowerCase();
10772
+ if (!inputVal) return opts;
10773
+ const q = inputVal.toLowerCase();
10766
10774
  return opts.filter(
10767
10775
  (opt) => getOptionLabel(opt).toLowerCase().includes(q) || (getOptionSubLabel?.(opt) ?? "").toLowerCase().includes(q)
10768
10776
  ).slice(0, searchThreshold);
@@ -10773,7 +10781,7 @@ function SmartSelect({
10773
10781
  options: displayOptions,
10774
10782
  value: value ?? (multiple ? [] : null),
10775
10783
  onChange: handleChange,
10776
- inputValue: multiple ? void 0 : inputValue,
10784
+ inputValue: multiple ? isControlled ? inputValueProp : void 0 : activeInput,
10777
10785
  onInputChange: handleInputChange,
10778
10786
  multiple,
10779
10787
  limitTags,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@rufous/ui",
3
3
  "private": false,
4
- "version": "0.3.39",
4
+ "version": "0.3.40",
5
5
  "type": "module",
6
6
  "description": "Experimental: A lightweight React UI component library (Beta)",
7
7
  "style": "./dist/main.css",
@@ -68,6 +68,8 @@
68
68
  "tippy.js": "^6.3.7"
69
69
  },
70
70
  "devDependencies": {
71
+ "react": "^18.3.1",
72
+ "react-dom": "^18.3.1",
71
73
  "react-router-dom": "^7.13.1",
72
74
  "@eslint/js": "^9.30.1",
73
75
  "@types/node": "^24.1.0",