@rufous/ui 0.3.39 → 0.3.41
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 +141 -44
- package/dist/main.d.cts +44 -7
- package/dist/main.d.ts +44 -7
- package/dist/main.js +141 -44
- package/package.json +3 -1
package/dist/main.cjs
CHANGED
|
@@ -1422,14 +1422,14 @@ var SubmitButton = ({
|
|
|
1422
1422
|
setLoading(false);
|
|
1423
1423
|
}
|
|
1424
1424
|
};
|
|
1425
|
-
const handleClick = (e) => runProtected(e, onClick);
|
|
1426
|
-
const handleDoubleClick = (e) => runProtected(e, onDoubleClick);
|
|
1425
|
+
const handleClick = onClick ? (e) => runProtected(e, onClick) : void 0;
|
|
1426
|
+
const handleDoubleClick = onDoubleClick ? (e) => runProtected(e, onDoubleClick) : void 0;
|
|
1427
1427
|
const showLoader = loading || isLoading;
|
|
1428
1428
|
return /* @__PURE__ */ React60.createElement(
|
|
1429
1429
|
"button",
|
|
1430
1430
|
{
|
|
1431
1431
|
...props,
|
|
1432
|
-
type
|
|
1432
|
+
type,
|
|
1433
1433
|
className: ["btn submit-btn", bgGradiant ? "submit-btn-gradiant" : "", sxClass, className].filter(Boolean).join(" "),
|
|
1434
1434
|
disabled: props.disabled || showLoader,
|
|
1435
1435
|
onClick: handleClick,
|
|
@@ -10665,10 +10665,20 @@ function buildLookup(options, getChildren, getValue, map = /* @__PURE__ */ new M
|
|
|
10665
10665
|
}
|
|
10666
10666
|
return map;
|
|
10667
10667
|
}
|
|
10668
|
+
function safeLabel(getOptionLabel, option) {
|
|
10669
|
+
try {
|
|
10670
|
+
const l = getOptionLabel(option);
|
|
10671
|
+
return typeof l === "string" ? l : "";
|
|
10672
|
+
} catch {
|
|
10673
|
+
return "";
|
|
10674
|
+
}
|
|
10675
|
+
}
|
|
10668
10676
|
function SmartSelect({
|
|
10669
10677
|
options,
|
|
10670
10678
|
value,
|
|
10671
10679
|
onChange,
|
|
10680
|
+
inputValue: inputValueProp,
|
|
10681
|
+
onInputChange,
|
|
10672
10682
|
onSearchChange,
|
|
10673
10683
|
searchResults = [],
|
|
10674
10684
|
debounceMs = 300,
|
|
@@ -10679,6 +10689,7 @@ function SmartSelect({
|
|
|
10679
10689
|
getOptionChildren,
|
|
10680
10690
|
multiple = false,
|
|
10681
10691
|
allowChildNodesSelection = true,
|
|
10692
|
+
strictSelection = false,
|
|
10682
10693
|
loading = false,
|
|
10683
10694
|
loadingText,
|
|
10684
10695
|
filterOptions: filterOptionsProp,
|
|
@@ -10698,17 +10709,10 @@ function SmartSelect({
|
|
|
10698
10709
|
sx
|
|
10699
10710
|
}) {
|
|
10700
10711
|
const debounceTimer = (0, import_react54.useRef)(null);
|
|
10712
|
+
const isControlled = inputValueProp !== void 0;
|
|
10701
10713
|
(0, import_react54.useEffect)(() => () => {
|
|
10702
10714
|
if (debounceTimer.current) clearTimeout(debounceTimer.current);
|
|
10703
10715
|
}, []);
|
|
10704
|
-
const [inputValue, setInputValue] = (0, import_react54.useState)(
|
|
10705
|
-
() => !multiple && value != null ? getOptionLabel(value) : ""
|
|
10706
|
-
);
|
|
10707
|
-
(0, import_react54.useEffect)(() => {
|
|
10708
|
-
if (!multiple) {
|
|
10709
|
-
setInputValue(value != null ? getOptionLabel(value) : "");
|
|
10710
|
-
}
|
|
10711
|
-
}, [value, multiple, getOptionLabel]);
|
|
10712
10716
|
const getValue = (0, import_react54.useCallback)(
|
|
10713
10717
|
(o) => getOptionValue ? getOptionValue(o) : String(getOptionLabel(o)),
|
|
10714
10718
|
[getOptionValue, getOptionLabel]
|
|
@@ -10718,6 +10722,55 @@ function SmartSelect({
|
|
|
10718
10722
|
return flattenTree(options, getOptionChildren);
|
|
10719
10723
|
}, [options, getOptionChildren]);
|
|
10720
10724
|
const flatOptionsList = (0, import_react54.useMemo)(() => flatItems.map((f) => f.option), [flatItems]);
|
|
10725
|
+
const strictValidation = (0, import_react54.useMemo)(() => {
|
|
10726
|
+
if (!strictSelection || multiple) return { valid: true, canonicalValue: value };
|
|
10727
|
+
if (value == null || value === "") {
|
|
10728
|
+
return { valid: false, displayText: "" };
|
|
10729
|
+
}
|
|
10730
|
+
const invalidDisplayText = (v) => {
|
|
10731
|
+
const fromLabel = safeLabel(getOptionLabel, v).trim();
|
|
10732
|
+
if (fromLabel) return fromLabel;
|
|
10733
|
+
if (typeof v === "string" || typeof v === "number") return String(v);
|
|
10734
|
+
return "";
|
|
10735
|
+
};
|
|
10736
|
+
try {
|
|
10737
|
+
const key = getValue(value);
|
|
10738
|
+
const canonicalOpt = flatOptionsList.find((o) => getValue(o) === key) ?? searchResults.find((o) => getValue(o) === key);
|
|
10739
|
+
if (!canonicalOpt) {
|
|
10740
|
+
return { valid: false, displayText: invalidDisplayText(value) };
|
|
10741
|
+
}
|
|
10742
|
+
if (typeof canonicalOpt === "object" && canonicalOpt !== null && typeof value === "object" && value !== null) {
|
|
10743
|
+
const src = canonicalOpt;
|
|
10744
|
+
const inc = value;
|
|
10745
|
+
const fullMatch = Object.keys(src).every((k) => k in inc && inc[k] === src[k]);
|
|
10746
|
+
if (!fullMatch) {
|
|
10747
|
+
return { valid: false, displayText: invalidDisplayText(value) };
|
|
10748
|
+
}
|
|
10749
|
+
}
|
|
10750
|
+
return { valid: true, canonicalValue: canonicalOpt };
|
|
10751
|
+
} catch {
|
|
10752
|
+
return { valid: false, displayText: invalidDisplayText(value) };
|
|
10753
|
+
}
|
|
10754
|
+
}, [strictSelection, multiple, value, getValue, flatOptionsList, searchResults, getOptionLabel]);
|
|
10755
|
+
const [internalInput, setInternalInput] = (0, import_react54.useState)(
|
|
10756
|
+
() => !multiple && value != null ? getOptionLabel(value) : ""
|
|
10757
|
+
);
|
|
10758
|
+
const strictValidationRef = (0, import_react54.useRef)(strictValidation);
|
|
10759
|
+
strictValidationRef.current = strictValidation;
|
|
10760
|
+
const lastSeededValue = (0, import_react54.useRef)(/* @__PURE__ */ Symbol("init"));
|
|
10761
|
+
(0, import_react54.useEffect)(() => {
|
|
10762
|
+
if (isControlled || multiple) return;
|
|
10763
|
+
if (strictSelection && !strictValidationRef.current.valid) {
|
|
10764
|
+
if (Object.is(value, lastSeededValue.current)) return;
|
|
10765
|
+
lastSeededValue.current = value;
|
|
10766
|
+
const sv = strictValidationRef.current;
|
|
10767
|
+
setInternalInput(sv.displayText);
|
|
10768
|
+
return;
|
|
10769
|
+
}
|
|
10770
|
+
lastSeededValue.current = value;
|
|
10771
|
+
setInternalInput(value != null ? getOptionLabel(value) : "");
|
|
10772
|
+
}, [value, multiple, getOptionLabel, isControlled, strictSelection]);
|
|
10773
|
+
const activeInput = isControlled ? inputValueProp : internalInput;
|
|
10721
10774
|
const displayOptions = (0, import_react54.useMemo)(() => {
|
|
10722
10775
|
let base = flatOptionsList;
|
|
10723
10776
|
if (searchResults.length) {
|
|
@@ -10725,17 +10778,26 @@ function SmartSelect({
|
|
|
10725
10778
|
const serverOnly = searchResults.filter((o) => !localKeys.has(getValue(o)));
|
|
10726
10779
|
base = [...flatOptionsList, ...serverOnly];
|
|
10727
10780
|
}
|
|
10728
|
-
if (!multiple
|
|
10729
|
-
|
|
10730
|
-
|
|
10731
|
-
|
|
10732
|
-
|
|
10781
|
+
if (!multiple) {
|
|
10782
|
+
if (strictSelection) {
|
|
10783
|
+
const injectValue = strictValidation.valid ? strictValidation.canonicalValue : null;
|
|
10784
|
+
if (injectValue != null) {
|
|
10785
|
+
const key = getValue(injectValue);
|
|
10786
|
+
if (!base.some((o) => getValue(o) === key)) base = [injectValue, ...base];
|
|
10787
|
+
}
|
|
10788
|
+
} else {
|
|
10789
|
+
if (value != null) {
|
|
10790
|
+
const key = getValue(value);
|
|
10791
|
+
if (!base.some((o) => getValue(o) === key)) base = [value, ...base];
|
|
10792
|
+
}
|
|
10793
|
+
}
|
|
10794
|
+
} else if (Array.isArray(value) && value.length > 0) {
|
|
10733
10795
|
const baseKeys = new Set(base.map((o) => getValue(o)));
|
|
10734
10796
|
const missing = value.filter((v) => !baseKeys.has(getValue(v)));
|
|
10735
10797
|
if (missing.length > 0) base = [...base, ...missing];
|
|
10736
10798
|
}
|
|
10737
10799
|
return base;
|
|
10738
|
-
}, [flatOptionsList, searchResults, getValue, value, multiple]);
|
|
10800
|
+
}, [flatOptionsList, searchResults, getValue, value, multiple, strictSelection, strictValidation]);
|
|
10739
10801
|
const depthMap = (0, import_react54.useMemo)(() => {
|
|
10740
10802
|
const map = /* @__PURE__ */ new Map();
|
|
10741
10803
|
flatItems.forEach(({ option, depth }) => map.set(getValue(option), depth));
|
|
@@ -10749,18 +10811,40 @@ function SmartSelect({
|
|
|
10749
10811
|
if (multiple) {
|
|
10750
10812
|
return new Set((Array.isArray(value) ? value : []).map((v) => getValue(v)));
|
|
10751
10813
|
}
|
|
10814
|
+
if (strictSelection) {
|
|
10815
|
+
const sv = strictValidation;
|
|
10816
|
+
if (!strictValidation.valid || sv.canonicalValue == null) return /* @__PURE__ */ new Set();
|
|
10817
|
+
return /* @__PURE__ */ new Set([getValue(sv.canonicalValue)]);
|
|
10818
|
+
}
|
|
10752
10819
|
return value != null ? /* @__PURE__ */ new Set([getValue(value)]) : /* @__PURE__ */ new Set();
|
|
10753
|
-
}, [multiple, value, getValue]);
|
|
10754
|
-
|
|
10755
|
-
|
|
10820
|
+
}, [multiple, value, getValue, strictSelection, strictValidation]);
|
|
10821
|
+
let autocompleteValue;
|
|
10822
|
+
let autocompleteInputValue;
|
|
10823
|
+
if (strictSelection && !multiple) {
|
|
10824
|
+
const sv = strictValidation;
|
|
10825
|
+
if (!strictValidation.valid) {
|
|
10826
|
+
autocompleteValue = null;
|
|
10827
|
+
autocompleteInputValue = isControlled ? inputValueProp : internalInput;
|
|
10828
|
+
} else {
|
|
10829
|
+
autocompleteValue = sv.canonicalValue ?? null;
|
|
10830
|
+
autocompleteInputValue = isControlled ? inputValueProp : activeInput;
|
|
10831
|
+
}
|
|
10832
|
+
} else {
|
|
10833
|
+
autocompleteValue = value ?? (multiple ? [] : null);
|
|
10834
|
+
autocompleteInputValue = multiple ? isControlled ? inputValueProp : void 0 : activeInput;
|
|
10835
|
+
}
|
|
10836
|
+
const handleInputChange = (0, import_react54.useCallback)((_, val, reason) => {
|
|
10837
|
+
const resolvedReason = reason ?? "input";
|
|
10838
|
+
if (!isControlled) setInternalInput(val);
|
|
10839
|
+
onInputChange?.(val, resolvedReason);
|
|
10756
10840
|
if (!onSearchChange) return;
|
|
10757
10841
|
if (debounceTimer.current) clearTimeout(debounceTimer.current);
|
|
10758
|
-
if (
|
|
10759
|
-
if (
|
|
10842
|
+
if (resolvedReason !== "input") {
|
|
10843
|
+
if (resolvedReason === "clear") onSearchChange("", 0);
|
|
10760
10844
|
return;
|
|
10761
10845
|
}
|
|
10762
|
-
if (!
|
|
10763
|
-
const q =
|
|
10846
|
+
if (!val) return;
|
|
10847
|
+
const q = val.toLowerCase();
|
|
10764
10848
|
let localCount = 0;
|
|
10765
10849
|
for (const opt of flatOptionsList) {
|
|
10766
10850
|
if (getOptionLabel(opt).toLowerCase().includes(q) || (getOptionSubLabel?.(opt) ?? "").toLowerCase().includes(q)) {
|
|
@@ -10771,13 +10855,11 @@ function SmartSelect({
|
|
|
10771
10855
|
if (localCount >= searchThreshold) return;
|
|
10772
10856
|
const needed = searchThreshold - localCount;
|
|
10773
10857
|
if (debounceMs <= 0) {
|
|
10774
|
-
onSearchChange(
|
|
10858
|
+
onSearchChange(val, needed);
|
|
10775
10859
|
} else {
|
|
10776
|
-
debounceTimer.current = setTimeout(() =>
|
|
10777
|
-
onSearchChange(inputValue2, needed);
|
|
10778
|
-
}, debounceMs);
|
|
10860
|
+
debounceTimer.current = setTimeout(() => onSearchChange(val, needed), debounceMs);
|
|
10779
10861
|
}
|
|
10780
|
-
}, [onSearchChange, debounceMs, searchThreshold, flatOptionsList, getOptionLabel, getOptionSubLabel]);
|
|
10862
|
+
}, [isControlled, onInputChange, onSearchChange, debounceMs, searchThreshold, flatOptionsList, getOptionLabel, getOptionSubLabel]);
|
|
10781
10863
|
const handleChange = (0, import_react54.useCallback)((_, newValue) => {
|
|
10782
10864
|
if (!multiple || !allowChildNodesSelection || !getOptionChildren) {
|
|
10783
10865
|
onChange?.(newValue);
|
|
@@ -10853,24 +10935,33 @@ function SmartSelect({
|
|
|
10853
10935
|
/* @__PURE__ */ import_react54.default.createElement("span", { className: "rf-select__option-check", "aria-hidden": "true" }, /* @__PURE__ */ import_react54.default.createElement(CheckIcon3, null))
|
|
10854
10936
|
);
|
|
10855
10937
|
}, [depthMap, getValue, getOptionLabel, getOptionSubLabel, selectedKeys]);
|
|
10856
|
-
const computedFilterOptions = (0, import_react54.useCallback)((opts,
|
|
10857
|
-
if (filterOptionsProp) return filterOptionsProp(opts,
|
|
10938
|
+
const computedFilterOptions = (0, import_react54.useCallback)((opts, inputVal) => {
|
|
10939
|
+
if (filterOptionsProp) return filterOptionsProp(opts, inputVal);
|
|
10940
|
+
if (strictSelection && !strictValidation.valid) {
|
|
10941
|
+
const sv = strictValidation;
|
|
10942
|
+
if (!inputVal || inputVal === sv.displayText) return opts;
|
|
10943
|
+
const q2 = inputVal.toLowerCase();
|
|
10944
|
+
return opts.filter(
|
|
10945
|
+
(opt) => getOptionLabel(opt).toLowerCase().includes(q2) || (getOptionSubLabel?.(opt) ?? "").toLowerCase().includes(q2)
|
|
10946
|
+
).slice(0, searchThreshold);
|
|
10947
|
+
}
|
|
10858
10948
|
if (multiple) {
|
|
10859
10949
|
const selected = opts.filter((o) => selectedKeys.has(getValue(o)));
|
|
10860
10950
|
const unselected = opts.filter((o) => !selectedKeys.has(getValue(o)));
|
|
10861
|
-
if (!
|
|
10862
|
-
const q2 =
|
|
10951
|
+
if (!inputVal) return [...selected, ...unselected];
|
|
10952
|
+
const q2 = inputVal.toLowerCase();
|
|
10863
10953
|
const filteredUnselected = unselected.filter(
|
|
10864
10954
|
(opt) => getOptionLabel(opt).toLowerCase().includes(q2) || (getOptionSubLabel?.(opt) ?? "").toLowerCase().includes(q2)
|
|
10865
10955
|
).slice(0, searchThreshold);
|
|
10866
10956
|
return [...selected, ...filteredUnselected];
|
|
10867
10957
|
}
|
|
10868
|
-
|
|
10869
|
-
|
|
10870
|
-
const
|
|
10958
|
+
const effectiveVal = strictSelection ? strictValidation.canonicalValue : value;
|
|
10959
|
+
if (effectiveVal != null) {
|
|
10960
|
+
const selectedKey = getValue(effectiveVal);
|
|
10961
|
+
const selectedLabel = getOptionLabel(effectiveVal);
|
|
10871
10962
|
const inOpts = opts.some((o) => getValue(o) === selectedKey);
|
|
10872
|
-
const selectedFallback = inOpts ? [] : [
|
|
10873
|
-
if (!
|
|
10963
|
+
const selectedFallback = inOpts ? [] : [effectiveVal];
|
|
10964
|
+
if (!inputVal || inputVal === selectedLabel) {
|
|
10874
10965
|
return [
|
|
10875
10966
|
...selectedFallback,
|
|
10876
10967
|
...opts.filter((o) => getValue(o) === selectedKey),
|
|
@@ -10878,26 +10969,32 @@ function SmartSelect({
|
|
|
10878
10969
|
];
|
|
10879
10970
|
}
|
|
10880
10971
|
}
|
|
10881
|
-
if (!
|
|
10882
|
-
const q =
|
|
10972
|
+
if (!inputVal) return opts;
|
|
10973
|
+
const q = inputVal.toLowerCase();
|
|
10883
10974
|
return opts.filter(
|
|
10884
10975
|
(opt) => getOptionLabel(opt).toLowerCase().includes(q) || (getOptionSubLabel?.(opt) ?? "").toLowerCase().includes(q)
|
|
10885
10976
|
).slice(0, searchThreshold);
|
|
10886
|
-
}, [filterOptionsProp, multiple, selectedKeys, getValue, getOptionLabel, getOptionSubLabel, value, searchThreshold]);
|
|
10977
|
+
}, [filterOptionsProp, multiple, selectedKeys, getValue, getOptionLabel, getOptionSubLabel, value, searchThreshold, strictSelection, strictValidation]);
|
|
10887
10978
|
return /* @__PURE__ */ import_react54.default.createElement(
|
|
10888
10979
|
Autocomplete,
|
|
10889
10980
|
{
|
|
10890
10981
|
options: displayOptions,
|
|
10891
|
-
value:
|
|
10982
|
+
value: autocompleteValue,
|
|
10892
10983
|
onChange: handleChange,
|
|
10893
|
-
inputValue:
|
|
10984
|
+
inputValue: autocompleteInputValue,
|
|
10894
10985
|
onInputChange: handleInputChange,
|
|
10895
10986
|
multiple,
|
|
10896
10987
|
limitTags,
|
|
10897
10988
|
loading,
|
|
10898
10989
|
loadingText: loadingText ?? /* @__PURE__ */ import_react54.default.createElement("span", { style: { fontSize: "0.875rem", color: "var(--text-secondary)" } }, "Loading\u2026"),
|
|
10899
10990
|
getOptionLabel,
|
|
10900
|
-
isOptionEqualToValue: (opt, val) =>
|
|
10991
|
+
isOptionEqualToValue: (opt, val) => {
|
|
10992
|
+
try {
|
|
10993
|
+
return getValue(opt) === getValue(val);
|
|
10994
|
+
} catch {
|
|
10995
|
+
return false;
|
|
10996
|
+
}
|
|
10997
|
+
},
|
|
10901
10998
|
filterOptions: computedFilterOptions,
|
|
10902
10999
|
renderOption: renderOptionProp ?? defaultRenderOption,
|
|
10903
11000
|
label,
|
package/dist/main.d.cts
CHANGED
|
@@ -2240,10 +2240,31 @@ 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
|
-
* Receives the current query and how many more records are needed
|
|
2246
|
-
* Use this to trigger an API / server search and update `searchResults`.
|
|
2267
|
+
* Receives the current query and how many more records are needed.
|
|
2247
2268
|
*/
|
|
2248
2269
|
onSearchChange?: (query: string, needed: number) => void;
|
|
2249
2270
|
/**
|
|
@@ -2253,14 +2274,14 @@ interface SmartSelectProps<T = any> {
|
|
|
2253
2274
|
*/
|
|
2254
2275
|
searchResults?: T[];
|
|
2255
2276
|
/**
|
|
2256
|
-
* Debounce delay in ms before `onSearchChange` fires.
|
|
2257
|
-
*
|
|
2277
|
+
* Debounce delay in ms before `onSearchChange` fires. Default: 300.
|
|
2278
|
+
* Pass 0 to disable.
|
|
2258
2279
|
*/
|
|
2259
2280
|
debounceMs?: number;
|
|
2260
2281
|
/**
|
|
2261
2282
|
* Max results to show when a query is active.
|
|
2262
|
-
*
|
|
2263
|
-
*
|
|
2283
|
+
* `onSearchChange` fires only when local matches < this threshold.
|
|
2284
|
+
* Default: 10. Pass 0 to always call the API.
|
|
2264
2285
|
*/
|
|
2265
2286
|
searchThreshold?: number;
|
|
2266
2287
|
/** Primary display label for an option (required) */
|
|
@@ -2282,6 +2303,22 @@ interface SmartSelectProps<T = any> {
|
|
|
2282
2303
|
* Only relevant when getOptionChildren is provided and multiple is true.
|
|
2283
2304
|
*/
|
|
2284
2305
|
allowChildNodesSelection?: boolean;
|
|
2306
|
+
/**
|
|
2307
|
+
* When **true**, the incoming `value` is validated against the source options
|
|
2308
|
+
* before being used. Three cases are handled strictly:
|
|
2309
|
+
*
|
|
2310
|
+
* - **Case 1 — Empty**: `value` is `null`, `undefined`, or `""` → treated as no selection.
|
|
2311
|
+
* - **Case 2 — Missing**: value's key is not present in `options` or `searchResults`
|
|
2312
|
+
* → ignored; not injected into the dropdown list.
|
|
2313
|
+
* - **Case 3 — Incomplete**: value's key exists in the source but the canonical source
|
|
2314
|
+
* option returns an empty label from `getOptionLabel` → not treated as selected.
|
|
2315
|
+
*
|
|
2316
|
+
* When invalid, `null` is passed to Autocomplete (nothing selected) and the raw text
|
|
2317
|
+
* is shown in the input field. No `onChange` fires.
|
|
2318
|
+
*
|
|
2319
|
+
* Only applies to single-select mode. Default: **false**.
|
|
2320
|
+
*/
|
|
2321
|
+
strictSelection?: boolean;
|
|
2285
2322
|
/** Show a loading spinner in the dropdown */
|
|
2286
2323
|
loading?: boolean;
|
|
2287
2324
|
/** Content shown while loading */
|
|
@@ -2316,7 +2353,7 @@ interface SmartSelectProps<T = any> {
|
|
|
2316
2353
|
style?: CSSProperties;
|
|
2317
2354
|
sx?: SxProp;
|
|
2318
2355
|
}
|
|
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;
|
|
2356
|
+
declare function SmartSelect<T = any>({ options, value, onChange, inputValue: inputValueProp, onInputChange, onSearchChange, searchResults, debounceMs, searchThreshold, getOptionLabel, getOptionValue, getOptionSubLabel, getOptionChildren, multiple, allowChildNodesSelection, strictSelection, 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
2357
|
|
|
2321
2358
|
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
2359
|
type EditorVariant = 'default' | 'basic';
|
package/dist/main.d.ts
CHANGED
|
@@ -2240,10 +2240,31 @@ 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
|
-
* Receives the current query and how many more records are needed
|
|
2246
|
-
* Use this to trigger an API / server search and update `searchResults`.
|
|
2267
|
+
* Receives the current query and how many more records are needed.
|
|
2247
2268
|
*/
|
|
2248
2269
|
onSearchChange?: (query: string, needed: number) => void;
|
|
2249
2270
|
/**
|
|
@@ -2253,14 +2274,14 @@ interface SmartSelectProps<T = any> {
|
|
|
2253
2274
|
*/
|
|
2254
2275
|
searchResults?: T[];
|
|
2255
2276
|
/**
|
|
2256
|
-
* Debounce delay in ms before `onSearchChange` fires.
|
|
2257
|
-
*
|
|
2277
|
+
* Debounce delay in ms before `onSearchChange` fires. Default: 300.
|
|
2278
|
+
* Pass 0 to disable.
|
|
2258
2279
|
*/
|
|
2259
2280
|
debounceMs?: number;
|
|
2260
2281
|
/**
|
|
2261
2282
|
* Max results to show when a query is active.
|
|
2262
|
-
*
|
|
2263
|
-
*
|
|
2283
|
+
* `onSearchChange` fires only when local matches < this threshold.
|
|
2284
|
+
* Default: 10. Pass 0 to always call the API.
|
|
2264
2285
|
*/
|
|
2265
2286
|
searchThreshold?: number;
|
|
2266
2287
|
/** Primary display label for an option (required) */
|
|
@@ -2282,6 +2303,22 @@ interface SmartSelectProps<T = any> {
|
|
|
2282
2303
|
* Only relevant when getOptionChildren is provided and multiple is true.
|
|
2283
2304
|
*/
|
|
2284
2305
|
allowChildNodesSelection?: boolean;
|
|
2306
|
+
/**
|
|
2307
|
+
* When **true**, the incoming `value` is validated against the source options
|
|
2308
|
+
* before being used. Three cases are handled strictly:
|
|
2309
|
+
*
|
|
2310
|
+
* - **Case 1 — Empty**: `value` is `null`, `undefined`, or `""` → treated as no selection.
|
|
2311
|
+
* - **Case 2 — Missing**: value's key is not present in `options` or `searchResults`
|
|
2312
|
+
* → ignored; not injected into the dropdown list.
|
|
2313
|
+
* - **Case 3 — Incomplete**: value's key exists in the source but the canonical source
|
|
2314
|
+
* option returns an empty label from `getOptionLabel` → not treated as selected.
|
|
2315
|
+
*
|
|
2316
|
+
* When invalid, `null` is passed to Autocomplete (nothing selected) and the raw text
|
|
2317
|
+
* is shown in the input field. No `onChange` fires.
|
|
2318
|
+
*
|
|
2319
|
+
* Only applies to single-select mode. Default: **false**.
|
|
2320
|
+
*/
|
|
2321
|
+
strictSelection?: boolean;
|
|
2285
2322
|
/** Show a loading spinner in the dropdown */
|
|
2286
2323
|
loading?: boolean;
|
|
2287
2324
|
/** Content shown while loading */
|
|
@@ -2316,7 +2353,7 @@ interface SmartSelectProps<T = any> {
|
|
|
2316
2353
|
style?: CSSProperties;
|
|
2317
2354
|
sx?: SxProp;
|
|
2318
2355
|
}
|
|
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;
|
|
2356
|
+
declare function SmartSelect<T = any>({ options, value, onChange, inputValue: inputValueProp, onInputChange, onSearchChange, searchResults, debounceMs, searchThreshold, getOptionLabel, getOptionValue, getOptionSubLabel, getOptionChildren, multiple, allowChildNodesSelection, strictSelection, 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
2357
|
|
|
2321
2358
|
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
2359
|
type EditorVariant = 'default' | 'basic';
|
package/dist/main.js
CHANGED
|
@@ -1209,14 +1209,14 @@ var SubmitButton = ({
|
|
|
1209
1209
|
setLoading(false);
|
|
1210
1210
|
}
|
|
1211
1211
|
};
|
|
1212
|
-
const handleClick = (e) => runProtected(e, onClick);
|
|
1213
|
-
const handleDoubleClick = (e) => runProtected(e, onDoubleClick);
|
|
1212
|
+
const handleClick = onClick ? (e) => runProtected(e, onClick) : void 0;
|
|
1213
|
+
const handleDoubleClick = onDoubleClick ? (e) => runProtected(e, onDoubleClick) : void 0;
|
|
1214
1214
|
const showLoader = loading || isLoading;
|
|
1215
1215
|
return /* @__PURE__ */ React60.createElement(
|
|
1216
1216
|
"button",
|
|
1217
1217
|
{
|
|
1218
1218
|
...props,
|
|
1219
|
-
type
|
|
1219
|
+
type,
|
|
1220
1220
|
className: ["btn submit-btn", bgGradiant ? "submit-btn-gradiant" : "", sxClass, className].filter(Boolean).join(" "),
|
|
1221
1221
|
disabled: props.disabled || showLoader,
|
|
1222
1222
|
onClick: handleClick,
|
|
@@ -10548,10 +10548,20 @@ function buildLookup(options, getChildren, getValue, map = /* @__PURE__ */ new M
|
|
|
10548
10548
|
}
|
|
10549
10549
|
return map;
|
|
10550
10550
|
}
|
|
10551
|
+
function safeLabel(getOptionLabel, option) {
|
|
10552
|
+
try {
|
|
10553
|
+
const l = getOptionLabel(option);
|
|
10554
|
+
return typeof l === "string" ? l : "";
|
|
10555
|
+
} catch {
|
|
10556
|
+
return "";
|
|
10557
|
+
}
|
|
10558
|
+
}
|
|
10551
10559
|
function SmartSelect({
|
|
10552
10560
|
options,
|
|
10553
10561
|
value,
|
|
10554
10562
|
onChange,
|
|
10563
|
+
inputValue: inputValueProp,
|
|
10564
|
+
onInputChange,
|
|
10555
10565
|
onSearchChange,
|
|
10556
10566
|
searchResults = [],
|
|
10557
10567
|
debounceMs = 300,
|
|
@@ -10562,6 +10572,7 @@ function SmartSelect({
|
|
|
10562
10572
|
getOptionChildren,
|
|
10563
10573
|
multiple = false,
|
|
10564
10574
|
allowChildNodesSelection = true,
|
|
10575
|
+
strictSelection = false,
|
|
10565
10576
|
loading = false,
|
|
10566
10577
|
loadingText,
|
|
10567
10578
|
filterOptions: filterOptionsProp,
|
|
@@ -10581,17 +10592,10 @@ function SmartSelect({
|
|
|
10581
10592
|
sx
|
|
10582
10593
|
}) {
|
|
10583
10594
|
const debounceTimer = useRef25(null);
|
|
10595
|
+
const isControlled = inputValueProp !== void 0;
|
|
10584
10596
|
useEffect21(() => () => {
|
|
10585
10597
|
if (debounceTimer.current) clearTimeout(debounceTimer.current);
|
|
10586
10598
|
}, []);
|
|
10587
|
-
const [inputValue, setInputValue] = useState27(
|
|
10588
|
-
() => !multiple && value != null ? getOptionLabel(value) : ""
|
|
10589
|
-
);
|
|
10590
|
-
useEffect21(() => {
|
|
10591
|
-
if (!multiple) {
|
|
10592
|
-
setInputValue(value != null ? getOptionLabel(value) : "");
|
|
10593
|
-
}
|
|
10594
|
-
}, [value, multiple, getOptionLabel]);
|
|
10595
10599
|
const getValue = useCallback12(
|
|
10596
10600
|
(o) => getOptionValue ? getOptionValue(o) : String(getOptionLabel(o)),
|
|
10597
10601
|
[getOptionValue, getOptionLabel]
|
|
@@ -10601,6 +10605,55 @@ function SmartSelect({
|
|
|
10601
10605
|
return flattenTree(options, getOptionChildren);
|
|
10602
10606
|
}, [options, getOptionChildren]);
|
|
10603
10607
|
const flatOptionsList = useMemo3(() => flatItems.map((f) => f.option), [flatItems]);
|
|
10608
|
+
const strictValidation = useMemo3(() => {
|
|
10609
|
+
if (!strictSelection || multiple) return { valid: true, canonicalValue: value };
|
|
10610
|
+
if (value == null || value === "") {
|
|
10611
|
+
return { valid: false, displayText: "" };
|
|
10612
|
+
}
|
|
10613
|
+
const invalidDisplayText = (v) => {
|
|
10614
|
+
const fromLabel = safeLabel(getOptionLabel, v).trim();
|
|
10615
|
+
if (fromLabel) return fromLabel;
|
|
10616
|
+
if (typeof v === "string" || typeof v === "number") return String(v);
|
|
10617
|
+
return "";
|
|
10618
|
+
};
|
|
10619
|
+
try {
|
|
10620
|
+
const key = getValue(value);
|
|
10621
|
+
const canonicalOpt = flatOptionsList.find((o) => getValue(o) === key) ?? searchResults.find((o) => getValue(o) === key);
|
|
10622
|
+
if (!canonicalOpt) {
|
|
10623
|
+
return { valid: false, displayText: invalidDisplayText(value) };
|
|
10624
|
+
}
|
|
10625
|
+
if (typeof canonicalOpt === "object" && canonicalOpt !== null && typeof value === "object" && value !== null) {
|
|
10626
|
+
const src = canonicalOpt;
|
|
10627
|
+
const inc = value;
|
|
10628
|
+
const fullMatch = Object.keys(src).every((k) => k in inc && inc[k] === src[k]);
|
|
10629
|
+
if (!fullMatch) {
|
|
10630
|
+
return { valid: false, displayText: invalidDisplayText(value) };
|
|
10631
|
+
}
|
|
10632
|
+
}
|
|
10633
|
+
return { valid: true, canonicalValue: canonicalOpt };
|
|
10634
|
+
} catch {
|
|
10635
|
+
return { valid: false, displayText: invalidDisplayText(value) };
|
|
10636
|
+
}
|
|
10637
|
+
}, [strictSelection, multiple, value, getValue, flatOptionsList, searchResults, getOptionLabel]);
|
|
10638
|
+
const [internalInput, setInternalInput] = useState27(
|
|
10639
|
+
() => !multiple && value != null ? getOptionLabel(value) : ""
|
|
10640
|
+
);
|
|
10641
|
+
const strictValidationRef = useRef25(strictValidation);
|
|
10642
|
+
strictValidationRef.current = strictValidation;
|
|
10643
|
+
const lastSeededValue = useRef25(/* @__PURE__ */ Symbol("init"));
|
|
10644
|
+
useEffect21(() => {
|
|
10645
|
+
if (isControlled || multiple) return;
|
|
10646
|
+
if (strictSelection && !strictValidationRef.current.valid) {
|
|
10647
|
+
if (Object.is(value, lastSeededValue.current)) return;
|
|
10648
|
+
lastSeededValue.current = value;
|
|
10649
|
+
const sv = strictValidationRef.current;
|
|
10650
|
+
setInternalInput(sv.displayText);
|
|
10651
|
+
return;
|
|
10652
|
+
}
|
|
10653
|
+
lastSeededValue.current = value;
|
|
10654
|
+
setInternalInput(value != null ? getOptionLabel(value) : "");
|
|
10655
|
+
}, [value, multiple, getOptionLabel, isControlled, strictSelection]);
|
|
10656
|
+
const activeInput = isControlled ? inputValueProp : internalInput;
|
|
10604
10657
|
const displayOptions = useMemo3(() => {
|
|
10605
10658
|
let base = flatOptionsList;
|
|
10606
10659
|
if (searchResults.length) {
|
|
@@ -10608,17 +10661,26 @@ function SmartSelect({
|
|
|
10608
10661
|
const serverOnly = searchResults.filter((o) => !localKeys.has(getValue(o)));
|
|
10609
10662
|
base = [...flatOptionsList, ...serverOnly];
|
|
10610
10663
|
}
|
|
10611
|
-
if (!multiple
|
|
10612
|
-
|
|
10613
|
-
|
|
10614
|
-
|
|
10615
|
-
|
|
10664
|
+
if (!multiple) {
|
|
10665
|
+
if (strictSelection) {
|
|
10666
|
+
const injectValue = strictValidation.valid ? strictValidation.canonicalValue : null;
|
|
10667
|
+
if (injectValue != null) {
|
|
10668
|
+
const key = getValue(injectValue);
|
|
10669
|
+
if (!base.some((o) => getValue(o) === key)) base = [injectValue, ...base];
|
|
10670
|
+
}
|
|
10671
|
+
} else {
|
|
10672
|
+
if (value != null) {
|
|
10673
|
+
const key = getValue(value);
|
|
10674
|
+
if (!base.some((o) => getValue(o) === key)) base = [value, ...base];
|
|
10675
|
+
}
|
|
10676
|
+
}
|
|
10677
|
+
} else if (Array.isArray(value) && value.length > 0) {
|
|
10616
10678
|
const baseKeys = new Set(base.map((o) => getValue(o)));
|
|
10617
10679
|
const missing = value.filter((v) => !baseKeys.has(getValue(v)));
|
|
10618
10680
|
if (missing.length > 0) base = [...base, ...missing];
|
|
10619
10681
|
}
|
|
10620
10682
|
return base;
|
|
10621
|
-
}, [flatOptionsList, searchResults, getValue, value, multiple]);
|
|
10683
|
+
}, [flatOptionsList, searchResults, getValue, value, multiple, strictSelection, strictValidation]);
|
|
10622
10684
|
const depthMap = useMemo3(() => {
|
|
10623
10685
|
const map = /* @__PURE__ */ new Map();
|
|
10624
10686
|
flatItems.forEach(({ option, depth }) => map.set(getValue(option), depth));
|
|
@@ -10632,18 +10694,40 @@ function SmartSelect({
|
|
|
10632
10694
|
if (multiple) {
|
|
10633
10695
|
return new Set((Array.isArray(value) ? value : []).map((v) => getValue(v)));
|
|
10634
10696
|
}
|
|
10697
|
+
if (strictSelection) {
|
|
10698
|
+
const sv = strictValidation;
|
|
10699
|
+
if (!strictValidation.valid || sv.canonicalValue == null) return /* @__PURE__ */ new Set();
|
|
10700
|
+
return /* @__PURE__ */ new Set([getValue(sv.canonicalValue)]);
|
|
10701
|
+
}
|
|
10635
10702
|
return value != null ? /* @__PURE__ */ new Set([getValue(value)]) : /* @__PURE__ */ new Set();
|
|
10636
|
-
}, [multiple, value, getValue]);
|
|
10637
|
-
|
|
10638
|
-
|
|
10703
|
+
}, [multiple, value, getValue, strictSelection, strictValidation]);
|
|
10704
|
+
let autocompleteValue;
|
|
10705
|
+
let autocompleteInputValue;
|
|
10706
|
+
if (strictSelection && !multiple) {
|
|
10707
|
+
const sv = strictValidation;
|
|
10708
|
+
if (!strictValidation.valid) {
|
|
10709
|
+
autocompleteValue = null;
|
|
10710
|
+
autocompleteInputValue = isControlled ? inputValueProp : internalInput;
|
|
10711
|
+
} else {
|
|
10712
|
+
autocompleteValue = sv.canonicalValue ?? null;
|
|
10713
|
+
autocompleteInputValue = isControlled ? inputValueProp : activeInput;
|
|
10714
|
+
}
|
|
10715
|
+
} else {
|
|
10716
|
+
autocompleteValue = value ?? (multiple ? [] : null);
|
|
10717
|
+
autocompleteInputValue = multiple ? isControlled ? inputValueProp : void 0 : activeInput;
|
|
10718
|
+
}
|
|
10719
|
+
const handleInputChange = useCallback12((_, val, reason) => {
|
|
10720
|
+
const resolvedReason = reason ?? "input";
|
|
10721
|
+
if (!isControlled) setInternalInput(val);
|
|
10722
|
+
onInputChange?.(val, resolvedReason);
|
|
10639
10723
|
if (!onSearchChange) return;
|
|
10640
10724
|
if (debounceTimer.current) clearTimeout(debounceTimer.current);
|
|
10641
|
-
if (
|
|
10642
|
-
if (
|
|
10725
|
+
if (resolvedReason !== "input") {
|
|
10726
|
+
if (resolvedReason === "clear") onSearchChange("", 0);
|
|
10643
10727
|
return;
|
|
10644
10728
|
}
|
|
10645
|
-
if (!
|
|
10646
|
-
const q =
|
|
10729
|
+
if (!val) return;
|
|
10730
|
+
const q = val.toLowerCase();
|
|
10647
10731
|
let localCount = 0;
|
|
10648
10732
|
for (const opt of flatOptionsList) {
|
|
10649
10733
|
if (getOptionLabel(opt).toLowerCase().includes(q) || (getOptionSubLabel?.(opt) ?? "").toLowerCase().includes(q)) {
|
|
@@ -10654,13 +10738,11 @@ function SmartSelect({
|
|
|
10654
10738
|
if (localCount >= searchThreshold) return;
|
|
10655
10739
|
const needed = searchThreshold - localCount;
|
|
10656
10740
|
if (debounceMs <= 0) {
|
|
10657
|
-
onSearchChange(
|
|
10741
|
+
onSearchChange(val, needed);
|
|
10658
10742
|
} else {
|
|
10659
|
-
debounceTimer.current = setTimeout(() =>
|
|
10660
|
-
onSearchChange(inputValue2, needed);
|
|
10661
|
-
}, debounceMs);
|
|
10743
|
+
debounceTimer.current = setTimeout(() => onSearchChange(val, needed), debounceMs);
|
|
10662
10744
|
}
|
|
10663
|
-
}, [onSearchChange, debounceMs, searchThreshold, flatOptionsList, getOptionLabel, getOptionSubLabel]);
|
|
10745
|
+
}, [isControlled, onInputChange, onSearchChange, debounceMs, searchThreshold, flatOptionsList, getOptionLabel, getOptionSubLabel]);
|
|
10664
10746
|
const handleChange = useCallback12((_, newValue) => {
|
|
10665
10747
|
if (!multiple || !allowChildNodesSelection || !getOptionChildren) {
|
|
10666
10748
|
onChange?.(newValue);
|
|
@@ -10736,24 +10818,33 @@ function SmartSelect({
|
|
|
10736
10818
|
/* @__PURE__ */ React109.createElement("span", { className: "rf-select__option-check", "aria-hidden": "true" }, /* @__PURE__ */ React109.createElement(CheckIcon3, null))
|
|
10737
10819
|
);
|
|
10738
10820
|
}, [depthMap, getValue, getOptionLabel, getOptionSubLabel, selectedKeys]);
|
|
10739
|
-
const computedFilterOptions = useCallback12((opts,
|
|
10740
|
-
if (filterOptionsProp) return filterOptionsProp(opts,
|
|
10821
|
+
const computedFilterOptions = useCallback12((opts, inputVal) => {
|
|
10822
|
+
if (filterOptionsProp) return filterOptionsProp(opts, inputVal);
|
|
10823
|
+
if (strictSelection && !strictValidation.valid) {
|
|
10824
|
+
const sv = strictValidation;
|
|
10825
|
+
if (!inputVal || inputVal === sv.displayText) return opts;
|
|
10826
|
+
const q2 = inputVal.toLowerCase();
|
|
10827
|
+
return opts.filter(
|
|
10828
|
+
(opt) => getOptionLabel(opt).toLowerCase().includes(q2) || (getOptionSubLabel?.(opt) ?? "").toLowerCase().includes(q2)
|
|
10829
|
+
).slice(0, searchThreshold);
|
|
10830
|
+
}
|
|
10741
10831
|
if (multiple) {
|
|
10742
10832
|
const selected = opts.filter((o) => selectedKeys.has(getValue(o)));
|
|
10743
10833
|
const unselected = opts.filter((o) => !selectedKeys.has(getValue(o)));
|
|
10744
|
-
if (!
|
|
10745
|
-
const q2 =
|
|
10834
|
+
if (!inputVal) return [...selected, ...unselected];
|
|
10835
|
+
const q2 = inputVal.toLowerCase();
|
|
10746
10836
|
const filteredUnselected = unselected.filter(
|
|
10747
10837
|
(opt) => getOptionLabel(opt).toLowerCase().includes(q2) || (getOptionSubLabel?.(opt) ?? "").toLowerCase().includes(q2)
|
|
10748
10838
|
).slice(0, searchThreshold);
|
|
10749
10839
|
return [...selected, ...filteredUnselected];
|
|
10750
10840
|
}
|
|
10751
|
-
|
|
10752
|
-
|
|
10753
|
-
const
|
|
10841
|
+
const effectiveVal = strictSelection ? strictValidation.canonicalValue : value;
|
|
10842
|
+
if (effectiveVal != null) {
|
|
10843
|
+
const selectedKey = getValue(effectiveVal);
|
|
10844
|
+
const selectedLabel = getOptionLabel(effectiveVal);
|
|
10754
10845
|
const inOpts = opts.some((o) => getValue(o) === selectedKey);
|
|
10755
|
-
const selectedFallback = inOpts ? [] : [
|
|
10756
|
-
if (!
|
|
10846
|
+
const selectedFallback = inOpts ? [] : [effectiveVal];
|
|
10847
|
+
if (!inputVal || inputVal === selectedLabel) {
|
|
10757
10848
|
return [
|
|
10758
10849
|
...selectedFallback,
|
|
10759
10850
|
...opts.filter((o) => getValue(o) === selectedKey),
|
|
@@ -10761,26 +10852,32 @@ function SmartSelect({
|
|
|
10761
10852
|
];
|
|
10762
10853
|
}
|
|
10763
10854
|
}
|
|
10764
|
-
if (!
|
|
10765
|
-
const q =
|
|
10855
|
+
if (!inputVal) return opts;
|
|
10856
|
+
const q = inputVal.toLowerCase();
|
|
10766
10857
|
return opts.filter(
|
|
10767
10858
|
(opt) => getOptionLabel(opt).toLowerCase().includes(q) || (getOptionSubLabel?.(opt) ?? "").toLowerCase().includes(q)
|
|
10768
10859
|
).slice(0, searchThreshold);
|
|
10769
|
-
}, [filterOptionsProp, multiple, selectedKeys, getValue, getOptionLabel, getOptionSubLabel, value, searchThreshold]);
|
|
10860
|
+
}, [filterOptionsProp, multiple, selectedKeys, getValue, getOptionLabel, getOptionSubLabel, value, searchThreshold, strictSelection, strictValidation]);
|
|
10770
10861
|
return /* @__PURE__ */ React109.createElement(
|
|
10771
10862
|
Autocomplete,
|
|
10772
10863
|
{
|
|
10773
10864
|
options: displayOptions,
|
|
10774
|
-
value:
|
|
10865
|
+
value: autocompleteValue,
|
|
10775
10866
|
onChange: handleChange,
|
|
10776
|
-
inputValue:
|
|
10867
|
+
inputValue: autocompleteInputValue,
|
|
10777
10868
|
onInputChange: handleInputChange,
|
|
10778
10869
|
multiple,
|
|
10779
10870
|
limitTags,
|
|
10780
10871
|
loading,
|
|
10781
10872
|
loadingText: loadingText ?? /* @__PURE__ */ React109.createElement("span", { style: { fontSize: "0.875rem", color: "var(--text-secondary)" } }, "Loading\u2026"),
|
|
10782
10873
|
getOptionLabel,
|
|
10783
|
-
isOptionEqualToValue: (opt, val) =>
|
|
10874
|
+
isOptionEqualToValue: (opt, val) => {
|
|
10875
|
+
try {
|
|
10876
|
+
return getValue(opt) === getValue(val);
|
|
10877
|
+
} catch {
|
|
10878
|
+
return false;
|
|
10879
|
+
}
|
|
10880
|
+
},
|
|
10784
10881
|
filterOptions: computedFilterOptions,
|
|
10785
10882
|
renderOption: renderOptionProp ?? defaultRenderOption,
|
|
10786
10883
|
label,
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rufous/ui",
|
|
3
3
|
"private": false,
|
|
4
|
-
"version": "0.3.
|
|
4
|
+
"version": "0.3.41",
|
|
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",
|