@xsolla/xui-multi-select 0.184.0 → 0.185.1

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/native/index.mjs CHANGED
@@ -1,5 +1,11 @@
1
1
  // src/MultiSelect.tsx
2
- import { forwardRef as forwardRef4, useRef as useRef4, useEffect as useEffect3, useState as useState3, useCallback as useCallback2 } from "react";
2
+ import {
3
+ forwardRef as forwardRef4,
4
+ useRef as useRef4,
5
+ useEffect as useEffect3,
6
+ useState as useState3,
7
+ useCallback as useCallback2
8
+ } from "react";
3
9
 
4
10
  // ../../foundation/primitives-native/src/Box.tsx
5
11
  import {
@@ -283,10 +289,6 @@ var isWeb = false;
283
289
  // src/MultiSelect.tsx
284
290
  import { useModalId, useResolvedTheme as useResolvedTheme4 } from "@xsolla/xui-core";
285
291
 
286
- // src/MultiSelectControl.tsx
287
- import { forwardRef as forwardRef3, useRef as useRef2 } from "react";
288
- import { useResolvedTheme as useResolvedTheme3 } from "@xsolla/xui-core";
289
-
290
292
  // ../../foundation/icons-base/dist/web/index.mjs
291
293
  import styled from "styled-components";
292
294
  import { jsx as jsx4 } from "react/jsx-runtime";
@@ -1065,6 +1067,13 @@ var ExclamationMarkCr = (props) => /* @__PURE__ */ jsx149(BaseIcon, { ...props,
1065
1067
  var solidContent172 = `<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" width="100%" height="100%"><g id="icon_remove--solid"><path id="Union" d="M20.5 3C20.7761 3 21 3.22386 21 3.5V20.5C21 20.7761 20.7761 21 20.5 21H3.5C3.22386 21 3 20.7761 3 20.5V3.5C3 3.22386 3.22386 3 3.5 3H20.5ZM12 10.5859L7.20703 5.79297L5.79297 7.20703L10.5859 12L5.79297 16.793L7.20703 18.207L12 13.4141L16.793 18.207L18.207 16.793L13.4141 12L18.207 7.20703L16.793 5.79297L12 10.5859Z" style="fill: currentColor"/></g></svg>`;
1066
1068
  var lineContent172 = `<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" width="100%" height="100%"><g id="icon_remove--line"><path id="Union" d="M20.0303 5.41455L13.4199 12.0249L20.0195 18.6245L18.6055 20.0386L12.0059 13.439L5.41406 20.0308L4 18.6167L10.5918 12.0249L4.00879 5.44189L5.42285 4.02783L12.0059 10.6108L18.6162 4.00049L20.0303 5.41455Z" style="fill: currentColor"/></g></svg>`;
1067
1069
  var Remove = (props) => /* @__PURE__ */ jsx173(BaseIcon, { ...props, solidContent: solidContent172, lineContent: lineContent172 });
1070
+ var solidContent706 = `<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" width="100%" height="100%"><g id="icon_search--solid"><path id="Union" d="M10.0215 2.02148C14.4398 2.02148 18.0215 5.60321 18.0215 10.0215C18.0215 11.8699 17.3926 13.5702 16.3398 14.9248L21.9785 20.5645L20.5645 21.9785L14.9248 16.3389C13.5702 17.3918 11.8701 18.0215 10.0215 18.0215C5.60321 18.0215 2.02148 14.4398 2.02148 10.0215C2.02148 5.60321 5.60321 2.02148 10.0215 2.02148ZM10.0215 4.02148C6.70778 4.02148 4.02148 6.70778 4.02148 10.0215C4.02148 13.3352 6.70778 16.0215 10.0215 16.0215C13.3352 16.0215 16.0215 13.3352 16.0215 10.0215C16.0215 6.70778 13.3352 4.02148 10.0215 4.02148ZM7.19336 7.19336C8.75546 5.63126 11.2875 5.63126 12.8496 7.19336C14.4117 8.75546 14.4117 11.2875 12.8496 12.8496C11.2875 14.4117 8.75546 14.4117 7.19336 12.8496C5.63126 11.2875 5.63127 8.75546 7.19336 7.19336Z" style="fill: currentColor"/></g></svg>`;
1071
+ var lineContent706 = `<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" width="100%" height="100%"><g id="icon_search--line"><path id="Union" fill-rule="evenodd" clip-rule="evenodd" d="M10 2C14.4183 2 18 5.58172 18 10C18 11.8484 17.3711 13.5488 16.3184 14.9033L21.957 20.543L20.543 21.957L14.9033 16.3174C13.5487 17.3703 11.8486 18 10 18C5.58172 18 2 14.4183 2 10C2 5.58172 5.58172 2 10 2ZM10 4C6.68629 4 4 6.68629 4 10C4 13.3137 6.68629 16 10 16C13.3137 16 16 13.3137 16 10C16 6.68629 13.3137 4 10 4Z" style="fill: currentColor"/></g></svg>`;
1072
+ var Search = (props) => /* @__PURE__ */ jsx707(BaseIcon, { ...props, solidContent: solidContent706, lineContent: lineContent706 });
1073
+
1074
+ // src/MultiSelectControl.tsx
1075
+ import { forwardRef as forwardRef3, useRef as useRef2 } from "react";
1076
+ import { useResolvedTheme as useResolvedTheme3 } from "@xsolla/xui-core";
1068
1077
 
1069
1078
  // src/useMultiSelectControl.tsx
1070
1079
  import {
@@ -2162,10 +2171,14 @@ var MultiSelect = forwardRef4(
2162
2171
  iconLeft,
2163
2172
  iconRight,
2164
2173
  maxHeight = 300,
2174
+ optionOverflow = "truncate",
2165
2175
  dropdownMenu = true,
2166
2176
  onTriggerPress,
2167
2177
  menuOpen = false,
2168
2178
  menuMinWidth,
2179
+ searchable = false,
2180
+ searchPlaceholder = "Search",
2181
+ noOptionsMessage = "No results",
2169
2182
  testID,
2170
2183
  themeMode,
2171
2184
  themeProductContext
@@ -2174,8 +2187,12 @@ var MultiSelect = forwardRef4(
2174
2187
  const modalId = useModalId();
2175
2188
  const controlRef = useRef4(null);
2176
2189
  const menuRef = useRef4(null);
2190
+ const containerRef = useRef4(null);
2191
+ const searchInputRef = useRef4(null);
2177
2192
  const [menuPosition, setMenuPosition] = useState3(null);
2193
+ const [searchValue, setSearchValue] = useState3("");
2178
2194
  const sizeStyles = theme.sizing.input(size);
2195
+ const inputColors = theme.colors.control.input;
2179
2196
  const state = externalState || (disabled ? "disable" : "default");
2180
2197
  const isDisable = state === "disable" || disabled;
2181
2198
  const isError = state === "error" || !!errorMessage;
@@ -2200,7 +2217,19 @@ var MultiSelect = forwardRef4(
2200
2217
  onClose();
2201
2218
  }
2202
2219
  }, [isDisable, dropdownMenu, onClose]);
2203
- const menuItems = options.map((opt) => {
2220
+ useEffect3(() => {
2221
+ if (isOpen && searchable && isWeb) {
2222
+ searchInputRef.current?.focus();
2223
+ }
2224
+ }, [isOpen, searchable]);
2225
+ useEffect3(() => {
2226
+ if (!isOpen) setSearchValue("");
2227
+ }, [isOpen]);
2228
+ const getOptionText = (opt) => typeof opt.label === "string" ? opt.label : String(opt.value);
2229
+ const filteredOptions = searchable && searchValue ? options.filter(
2230
+ (opt) => getOptionText(opt).toLowerCase().includes(searchValue.toLowerCase())
2231
+ ) : options;
2232
+ const menuItems = filteredOptions.map((opt) => {
2204
2233
  const id = String(opt.value);
2205
2234
  const checked = values.map(String).includes(id);
2206
2235
  return {
@@ -2218,6 +2247,22 @@ var MultiSelect = forwardRef4(
2218
2247
  };
2219
2248
  const controlMenuOpen = dropdownMenu ? isOpen : Boolean(menuOpen);
2220
2249
  const controlOnClick = dropdownMenu ? onSelectClick : onTriggerPress;
2250
+ const isScrollOverflow = optionOverflow === "scroll";
2251
+ const isWrapOverflow = optionOverflow === "wrap";
2252
+ const optionListOverflowX = isScrollOverflow ? "auto" : "hidden";
2253
+ const optionRowMinWidth = isScrollOverflow ? void 0 : 0;
2254
+ const optionLabelNumberOfLines = optionOverflow === "truncate" ? 1 : void 0;
2255
+ const optionLabelStyle = isScrollOverflow ? { whiteSpace: "nowrap" } : isWrapOverflow ? {
2256
+ minWidth: 0,
2257
+ whiteSpace: "normal",
2258
+ overflowWrap: "break-word",
2259
+ wordBreak: "break-word"
2260
+ } : {
2261
+ minWidth: 0,
2262
+ whiteSpace: "nowrap",
2263
+ overflow: "hidden",
2264
+ textOverflow: "ellipsis"
2265
+ };
2221
2266
  const updateMenuPosition = useCallback2(() => {
2222
2267
  if (!isWeb || !controlRef.current) return;
2223
2268
  const rect = controlRef.current.getBoundingClientRect();
@@ -2237,13 +2282,25 @@ var MultiSelect = forwardRef4(
2237
2282
  window.removeEventListener("scroll", updateMenuPosition, true);
2238
2283
  };
2239
2284
  }, [controlMenuOpen, dropdownMenu, isDisable, updateMenuPosition]);
2285
+ useEffect3(() => {
2286
+ if (!isWeb || !dropdownMenu || !isOpen || isDisable) return;
2287
+ const handleOutsideMouseDown = (event) => {
2288
+ const path = typeof event.composedPath === "function" ? event.composedPath() : [];
2289
+ const targetNode = event.target;
2290
+ const isInside = (el) => !!el && (path.length ? path.includes(el) : !!targetNode && el.contains(targetNode));
2291
+ if (isInside(containerRef.current) || isInside(menuRef.current)) return;
2292
+ onClose();
2293
+ };
2294
+ document.addEventListener("mousedown", handleOutsideMouseDown);
2295
+ return () => document.removeEventListener("mousedown", handleOutsideMouseDown);
2296
+ }, [dropdownMenu, isOpen, isDisable, onClose]);
2240
2297
  const externalFieldLayout = !dropdownMenu ? {
2241
2298
  width: "100%",
2242
2299
  minWidth: menuMinWidth ?? EXTERNAL_MENU_MIN_WIDTH_DEFAULT,
2243
2300
  boxSizing: "border-box"
2244
2301
  } : void 0;
2245
2302
  const dropdown = dropdownMenu && isOpen && !isDisable && (!isWeb || menuPosition) && /* @__PURE__ */ jsxs4(Fragment2, { children: [
2246
- /* @__PURE__ */ jsx727(
2303
+ !isWeb && /* @__PURE__ */ jsx727(
2247
2304
  Box,
2248
2305
  {
2249
2306
  "data-modal-id": modalId,
@@ -2259,7 +2316,7 @@ var MultiSelect = forwardRef4(
2259
2316
  onPress: onClose
2260
2317
  }
2261
2318
  ),
2262
- /* @__PURE__ */ jsx727(
2319
+ /* @__PURE__ */ jsxs4(
2263
2320
  Box,
2264
2321
  {
2265
2322
  ref: menuRef,
@@ -2268,7 +2325,6 @@ var MultiSelect = forwardRef4(
2268
2325
  borderColor: theme.colors.border.secondary,
2269
2326
  borderWidth: 1,
2270
2327
  borderRadius: sizeStyles.radius,
2271
- paddingVertical: 4,
2272
2328
  style: {
2273
2329
  position: isWeb ? "fixed" : "absolute",
2274
2330
  top: isWeb ? menuPosition?.top : "100%",
@@ -2278,54 +2334,134 @@ var MultiSelect = forwardRef4(
2278
2334
  marginTop: isWeb ? void 0 : 4,
2279
2335
  zIndex: 1001,
2280
2336
  // Above control (1000) and backdrop (999)
2281
- boxShadow: theme.shadow.popover,
2282
- maxHeight,
2283
- overflowY: "auto"
2337
+ boxShadow: theme.shadow.popover
2284
2338
  },
2285
- children: menuItems.map((item, _index) => {
2286
- const brandColors = theme.colors.control.brand.primary;
2287
- const contentColors = theme.colors.content;
2288
- return /* @__PURE__ */ jsx727(
2339
+ children: [
2340
+ searchable && isWeb && /* @__PURE__ */ jsx727(
2289
2341
  Box,
2290
2342
  {
2291
- testID,
2292
2343
  paddingHorizontal: sizeStyles.paddingHorizontal,
2293
- paddingVertical: 8,
2294
- onPress: () => {
2295
- if (!item.disabled) {
2296
- handleItemToggle(item.id, !item.checked);
2344
+ paddingVertical: sizeStyles.paddingVertical,
2345
+ borderBottomWidth: 1,
2346
+ borderColor: theme.colors.border.secondary,
2347
+ children: /* @__PURE__ */ jsxs4(
2348
+ Box,
2349
+ {
2350
+ flexDirection: "row",
2351
+ alignItems: "center",
2352
+ gap: sizeStyles.paddingHorizontal / 2,
2353
+ paddingHorizontal: 4,
2354
+ children: [
2355
+ /* @__PURE__ */ jsx727(
2356
+ Icon,
2357
+ {
2358
+ size: sizeStyles.iconSize - 2,
2359
+ color: inputColors.placeholder,
2360
+ children: /* @__PURE__ */ jsx727(Search, {})
2361
+ }
2362
+ ),
2363
+ /* @__PURE__ */ jsx727(
2364
+ Box,
2365
+ {
2366
+ as: "input",
2367
+ ref: searchInputRef,
2368
+ testID: testID ? `${testID}-search-input` : void 0,
2369
+ flex: 1,
2370
+ type: "text",
2371
+ "aria-label": searchPlaceholder,
2372
+ value: searchValue,
2373
+ onChange: (e) => setSearchValue(e.target.value),
2374
+ placeholder: searchPlaceholder,
2375
+ style: {
2376
+ border: "none",
2377
+ outline: "none",
2378
+ background: "transparent",
2379
+ color: inputColors.text,
2380
+ fontSize: sizeStyles.fontSize,
2381
+ width: "100%"
2382
+ }
2383
+ }
2384
+ )
2385
+ ]
2297
2386
  }
2298
- },
2299
- flexDirection: "row",
2300
- alignItems: "center",
2301
- justifyContent: "space-between",
2302
- backgroundColor: item.checked ? brandColors.bg : "transparent",
2303
- hoverStyle: !item.disabled && !item.checked ? {
2304
- backgroundColor: theme.colors.control.input.bgHover
2305
- } : void 0,
2387
+ )
2388
+ }
2389
+ ),
2390
+ /* @__PURE__ */ jsx727(
2391
+ Box,
2392
+ {
2393
+ paddingVertical: 4,
2306
2394
  style: {
2307
- cursor: item.disabled ? "not-allowed" : "pointer",
2308
- opacity: item.disabled ? 0.5 : 1
2395
+ maxHeight: searchable ? maxHeight - 60 : maxHeight,
2396
+ overflowY: "auto",
2397
+ overflowX: optionListOverflowX
2309
2398
  },
2310
- children: /* @__PURE__ */ jsx727(
2311
- Text,
2399
+ children: menuItems.length === 0 ? /* @__PURE__ */ jsx727(
2400
+ Box,
2312
2401
  {
2313
- color: item.checked ? contentColors.on.brand : theme.colors.content.secondary,
2314
- fontSize: sizeStyles.fontSize,
2315
- fontWeight: "400",
2316
- children: item.children
2402
+ paddingVertical: sizeStyles.paddingVertical * 2,
2403
+ paddingHorizontal: sizeStyles.paddingHorizontal,
2404
+ alignItems: "center",
2405
+ children: /* @__PURE__ */ jsx727(
2406
+ Text,
2407
+ {
2408
+ color: theme.colors.content.tertiary,
2409
+ fontSize: sizeStyles.fontSize,
2410
+ children: noOptionsMessage
2411
+ }
2412
+ )
2317
2413
  }
2318
- )
2319
- },
2320
- item.id
2321
- );
2322
- })
2414
+ ) : menuItems.map((item, _index) => {
2415
+ const brandColors = theme.colors.control.brand.primary;
2416
+ const contentColors = theme.colors.content;
2417
+ return /* @__PURE__ */ jsx727(
2418
+ Box,
2419
+ {
2420
+ testID,
2421
+ paddingHorizontal: sizeStyles.paddingHorizontal,
2422
+ paddingVertical: 8,
2423
+ onPress: () => {
2424
+ if (!item.disabled) {
2425
+ handleItemToggle(item.id, !item.checked);
2426
+ }
2427
+ },
2428
+ flexDirection: "row",
2429
+ alignItems: "center",
2430
+ justifyContent: "space-between",
2431
+ backgroundColor: item.checked ? brandColors.bg : "transparent",
2432
+ hoverStyle: !item.disabled && !item.checked ? {
2433
+ backgroundColor: theme.colors.control.input.bgHover
2434
+ } : void 0,
2435
+ style: {
2436
+ cursor: item.disabled ? "not-allowed" : "pointer",
2437
+ opacity: item.disabled ? 0.5 : 1,
2438
+ minWidth: optionRowMinWidth
2439
+ },
2440
+ children: /* @__PURE__ */ jsx727(
2441
+ Text,
2442
+ {
2443
+ color: item.checked ? contentColors.on.brand : theme.colors.content.secondary,
2444
+ fontSize: sizeStyles.fontSize,
2445
+ fontWeight: "400",
2446
+ numberOfLines: optionLabelNumberOfLines,
2447
+ style: optionLabelStyle,
2448
+ children: item.children
2449
+ }
2450
+ )
2451
+ },
2452
+ item.id
2453
+ );
2454
+ })
2455
+ }
2456
+ )
2457
+ ]
2323
2458
  }
2324
2459
  )
2325
2460
  ] });
2326
2461
  return /* @__PURE__ */ jsxs4(
2327
2462
  Box,
2328
2463
  {
2464
+ ref: containerRef,
2329
2465
  testID,
2330
2466
  flexDirection: "column",
2331
2467
  gap: sizeStyles.fieldGap,