@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/web/index.mjs CHANGED
@@ -1,5 +1,11 @@
1
1
  // src/MultiSelect.tsx
2
- import { forwardRef as forwardRef4, useRef as useRef4, useEffect as useEffect4, useState as useState4, useCallback as useCallback2 } from "react";
2
+ import {
3
+ forwardRef as forwardRef4,
4
+ useRef as useRef4,
5
+ useEffect as useEffect4,
6
+ useState as useState4,
7
+ useCallback as useCallback2
8
+ } from "react";
3
9
 
4
10
  // ../../foundation/primitives-web/src/Box.tsx
5
11
  import React2 from "react";
@@ -323,10 +329,6 @@ var isWeb = true;
323
329
  // src/MultiSelect.tsx
324
330
  import { useModalId, useResolvedTheme as useResolvedTheme4 } from "@xsolla/xui-core";
325
331
 
326
- // src/MultiSelectControl.tsx
327
- import { forwardRef as forwardRef3, useRef as useRef2 } from "react";
328
- import { useResolvedTheme as useResolvedTheme3 } from "@xsolla/xui-core";
329
-
330
332
  // ../../foundation/icons-base/dist/web/index.mjs
331
333
  import styled4 from "styled-components";
332
334
  import { jsx as jsx4 } from "react/jsx-runtime";
@@ -1105,6 +1107,13 @@ var ExclamationMarkCr = (props) => /* @__PURE__ */ jsx149(BaseIcon, { ...props,
1105
1107
  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>`;
1106
1108
  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>`;
1107
1109
  var Remove = (props) => /* @__PURE__ */ jsx173(BaseIcon, { ...props, solidContent: solidContent172, lineContent: lineContent172 });
1110
+ 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>`;
1111
+ 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>`;
1112
+ var Search = (props) => /* @__PURE__ */ jsx707(BaseIcon, { ...props, solidContent: solidContent706, lineContent: lineContent706 });
1113
+
1114
+ // src/MultiSelectControl.tsx
1115
+ import { forwardRef as forwardRef3, useRef as useRef2 } from "react";
1116
+ import { useResolvedTheme as useResolvedTheme3 } from "@xsolla/xui-core";
1108
1117
 
1109
1118
  // src/useMultiSelectControl.tsx
1110
1119
  import {
@@ -2208,10 +2217,14 @@ var MultiSelect = forwardRef4(
2208
2217
  iconLeft,
2209
2218
  iconRight,
2210
2219
  maxHeight = 300,
2220
+ optionOverflow = "truncate",
2211
2221
  dropdownMenu = true,
2212
2222
  onTriggerPress,
2213
2223
  menuOpen = false,
2214
2224
  menuMinWidth,
2225
+ searchable = false,
2226
+ searchPlaceholder = "Search",
2227
+ noOptionsMessage = "No results",
2215
2228
  testID,
2216
2229
  themeMode,
2217
2230
  themeProductContext
@@ -2220,8 +2233,12 @@ var MultiSelect = forwardRef4(
2220
2233
  const modalId = useModalId();
2221
2234
  const controlRef = useRef4(null);
2222
2235
  const menuRef = useRef4(null);
2236
+ const containerRef = useRef4(null);
2237
+ const searchInputRef = useRef4(null);
2223
2238
  const [menuPosition, setMenuPosition] = useState4(null);
2239
+ const [searchValue, setSearchValue] = useState4("");
2224
2240
  const sizeStyles = theme.sizing.input(size);
2241
+ const inputColors = theme.colors.control.input;
2225
2242
  const state = externalState || (disabled ? "disable" : "default");
2226
2243
  const isDisable = state === "disable" || disabled;
2227
2244
  const isError = state === "error" || !!errorMessage;
@@ -2246,7 +2263,19 @@ var MultiSelect = forwardRef4(
2246
2263
  onClose();
2247
2264
  }
2248
2265
  }, [isDisable, dropdownMenu, onClose]);
2249
- const menuItems = options.map((opt) => {
2266
+ useEffect4(() => {
2267
+ if (isOpen && searchable && isWeb) {
2268
+ searchInputRef.current?.focus();
2269
+ }
2270
+ }, [isOpen, searchable]);
2271
+ useEffect4(() => {
2272
+ if (!isOpen) setSearchValue("");
2273
+ }, [isOpen]);
2274
+ const getOptionText = (opt) => typeof opt.label === "string" ? opt.label : String(opt.value);
2275
+ const filteredOptions = searchable && searchValue ? options.filter(
2276
+ (opt) => getOptionText(opt).toLowerCase().includes(searchValue.toLowerCase())
2277
+ ) : options;
2278
+ const menuItems = filteredOptions.map((opt) => {
2250
2279
  const id = String(opt.value);
2251
2280
  const checked = values.map(String).includes(id);
2252
2281
  return {
@@ -2264,6 +2293,22 @@ var MultiSelect = forwardRef4(
2264
2293
  };
2265
2294
  const controlMenuOpen = dropdownMenu ? isOpen : Boolean(menuOpen);
2266
2295
  const controlOnClick = dropdownMenu ? onSelectClick : onTriggerPress;
2296
+ const isScrollOverflow = optionOverflow === "scroll";
2297
+ const isWrapOverflow = optionOverflow === "wrap";
2298
+ const optionListOverflowX = isScrollOverflow ? "auto" : "hidden";
2299
+ const optionRowMinWidth = isScrollOverflow ? void 0 : 0;
2300
+ const optionLabelNumberOfLines = optionOverflow === "truncate" ? 1 : void 0;
2301
+ const optionLabelStyle = isScrollOverflow ? { whiteSpace: "nowrap" } : isWrapOverflow ? {
2302
+ minWidth: 0,
2303
+ whiteSpace: "normal",
2304
+ overflowWrap: "break-word",
2305
+ wordBreak: "break-word"
2306
+ } : {
2307
+ minWidth: 0,
2308
+ whiteSpace: "nowrap",
2309
+ overflow: "hidden",
2310
+ textOverflow: "ellipsis"
2311
+ };
2267
2312
  const updateMenuPosition = useCallback2(() => {
2268
2313
  if (!isWeb || !controlRef.current) return;
2269
2314
  const rect = controlRef.current.getBoundingClientRect();
@@ -2283,13 +2328,25 @@ var MultiSelect = forwardRef4(
2283
2328
  window.removeEventListener("scroll", updateMenuPosition, true);
2284
2329
  };
2285
2330
  }, [controlMenuOpen, dropdownMenu, isDisable, updateMenuPosition]);
2331
+ useEffect4(() => {
2332
+ if (!isWeb || !dropdownMenu || !isOpen || isDisable) return;
2333
+ const handleOutsideMouseDown = (event) => {
2334
+ const path = typeof event.composedPath === "function" ? event.composedPath() : [];
2335
+ const targetNode = event.target;
2336
+ const isInside = (el) => !!el && (path.length ? path.includes(el) : !!targetNode && el.contains(targetNode));
2337
+ if (isInside(containerRef.current) || isInside(menuRef.current)) return;
2338
+ onClose();
2339
+ };
2340
+ document.addEventListener("mousedown", handleOutsideMouseDown);
2341
+ return () => document.removeEventListener("mousedown", handleOutsideMouseDown);
2342
+ }, [dropdownMenu, isOpen, isDisable, onClose]);
2286
2343
  const externalFieldLayout = !dropdownMenu ? {
2287
2344
  width: "100%",
2288
2345
  minWidth: menuMinWidth ?? EXTERNAL_MENU_MIN_WIDTH_DEFAULT,
2289
2346
  boxSizing: "border-box"
2290
2347
  } : void 0;
2291
2348
  const dropdown = dropdownMenu && isOpen && !isDisable && (!isWeb || menuPosition) && /* @__PURE__ */ jsxs4(Fragment, { children: [
2292
- /* @__PURE__ */ jsx726(
2349
+ !isWeb && /* @__PURE__ */ jsx726(
2293
2350
  Box,
2294
2351
  {
2295
2352
  "data-modal-id": modalId,
@@ -2305,7 +2362,7 @@ var MultiSelect = forwardRef4(
2305
2362
  onPress: onClose
2306
2363
  }
2307
2364
  ),
2308
- /* @__PURE__ */ jsx726(
2365
+ /* @__PURE__ */ jsxs4(
2309
2366
  Box,
2310
2367
  {
2311
2368
  ref: menuRef,
@@ -2314,7 +2371,6 @@ var MultiSelect = forwardRef4(
2314
2371
  borderColor: theme.colors.border.secondary,
2315
2372
  borderWidth: 1,
2316
2373
  borderRadius: sizeStyles.radius,
2317
- paddingVertical: 4,
2318
2374
  style: {
2319
2375
  position: isWeb ? "fixed" : "absolute",
2320
2376
  top: isWeb ? menuPosition?.top : "100%",
@@ -2324,54 +2380,134 @@ var MultiSelect = forwardRef4(
2324
2380
  marginTop: isWeb ? void 0 : 4,
2325
2381
  zIndex: 1001,
2326
2382
  // Above control (1000) and backdrop (999)
2327
- boxShadow: theme.shadow.popover,
2328
- maxHeight,
2329
- overflowY: "auto"
2383
+ boxShadow: theme.shadow.popover
2330
2384
  },
2331
- children: menuItems.map((item, _index) => {
2332
- const brandColors = theme.colors.control.brand.primary;
2333
- const contentColors = theme.colors.content;
2334
- return /* @__PURE__ */ jsx726(
2385
+ children: [
2386
+ searchable && isWeb && /* @__PURE__ */ jsx726(
2335
2387
  Box,
2336
2388
  {
2337
- testID,
2338
2389
  paddingHorizontal: sizeStyles.paddingHorizontal,
2339
- paddingVertical: 8,
2340
- onPress: () => {
2341
- if (!item.disabled) {
2342
- handleItemToggle(item.id, !item.checked);
2390
+ paddingVertical: sizeStyles.paddingVertical,
2391
+ borderBottomWidth: 1,
2392
+ borderColor: theme.colors.border.secondary,
2393
+ children: /* @__PURE__ */ jsxs4(
2394
+ Box,
2395
+ {
2396
+ flexDirection: "row",
2397
+ alignItems: "center",
2398
+ gap: sizeStyles.paddingHorizontal / 2,
2399
+ paddingHorizontal: 4,
2400
+ children: [
2401
+ /* @__PURE__ */ jsx726(
2402
+ Icon,
2403
+ {
2404
+ size: sizeStyles.iconSize - 2,
2405
+ color: inputColors.placeholder,
2406
+ children: /* @__PURE__ */ jsx726(Search, {})
2407
+ }
2408
+ ),
2409
+ /* @__PURE__ */ jsx726(
2410
+ Box,
2411
+ {
2412
+ as: "input",
2413
+ ref: searchInputRef,
2414
+ testID: testID ? `${testID}-search-input` : void 0,
2415
+ flex: 1,
2416
+ type: "text",
2417
+ "aria-label": searchPlaceholder,
2418
+ value: searchValue,
2419
+ onChange: (e) => setSearchValue(e.target.value),
2420
+ placeholder: searchPlaceholder,
2421
+ style: {
2422
+ border: "none",
2423
+ outline: "none",
2424
+ background: "transparent",
2425
+ color: inputColors.text,
2426
+ fontSize: sizeStyles.fontSize,
2427
+ width: "100%"
2428
+ }
2429
+ }
2430
+ )
2431
+ ]
2343
2432
  }
2344
- },
2345
- flexDirection: "row",
2346
- alignItems: "center",
2347
- justifyContent: "space-between",
2348
- backgroundColor: item.checked ? brandColors.bg : "transparent",
2349
- hoverStyle: !item.disabled && !item.checked ? {
2350
- backgroundColor: theme.colors.control.input.bgHover
2351
- } : void 0,
2433
+ )
2434
+ }
2435
+ ),
2436
+ /* @__PURE__ */ jsx726(
2437
+ Box,
2438
+ {
2439
+ paddingVertical: 4,
2352
2440
  style: {
2353
- cursor: item.disabled ? "not-allowed" : "pointer",
2354
- opacity: item.disabled ? 0.5 : 1
2441
+ maxHeight: searchable ? maxHeight - 60 : maxHeight,
2442
+ overflowY: "auto",
2443
+ overflowX: optionListOverflowX
2355
2444
  },
2356
- children: /* @__PURE__ */ jsx726(
2357
- Text,
2445
+ children: menuItems.length === 0 ? /* @__PURE__ */ jsx726(
2446
+ Box,
2358
2447
  {
2359
- color: item.checked ? contentColors.on.brand : theme.colors.content.secondary,
2360
- fontSize: sizeStyles.fontSize,
2361
- fontWeight: "400",
2362
- children: item.children
2448
+ paddingVertical: sizeStyles.paddingVertical * 2,
2449
+ paddingHorizontal: sizeStyles.paddingHorizontal,
2450
+ alignItems: "center",
2451
+ children: /* @__PURE__ */ jsx726(
2452
+ Text,
2453
+ {
2454
+ color: theme.colors.content.tertiary,
2455
+ fontSize: sizeStyles.fontSize,
2456
+ children: noOptionsMessage
2457
+ }
2458
+ )
2363
2459
  }
2364
- )
2365
- },
2366
- item.id
2367
- );
2368
- })
2460
+ ) : menuItems.map((item, _index) => {
2461
+ const brandColors = theme.colors.control.brand.primary;
2462
+ const contentColors = theme.colors.content;
2463
+ return /* @__PURE__ */ jsx726(
2464
+ Box,
2465
+ {
2466
+ testID,
2467
+ paddingHorizontal: sizeStyles.paddingHorizontal,
2468
+ paddingVertical: 8,
2469
+ onPress: () => {
2470
+ if (!item.disabled) {
2471
+ handleItemToggle(item.id, !item.checked);
2472
+ }
2473
+ },
2474
+ flexDirection: "row",
2475
+ alignItems: "center",
2476
+ justifyContent: "space-between",
2477
+ backgroundColor: item.checked ? brandColors.bg : "transparent",
2478
+ hoverStyle: !item.disabled && !item.checked ? {
2479
+ backgroundColor: theme.colors.control.input.bgHover
2480
+ } : void 0,
2481
+ style: {
2482
+ cursor: item.disabled ? "not-allowed" : "pointer",
2483
+ opacity: item.disabled ? 0.5 : 1,
2484
+ minWidth: optionRowMinWidth
2485
+ },
2486
+ children: /* @__PURE__ */ jsx726(
2487
+ Text,
2488
+ {
2489
+ color: item.checked ? contentColors.on.brand : theme.colors.content.secondary,
2490
+ fontSize: sizeStyles.fontSize,
2491
+ fontWeight: "400",
2492
+ numberOfLines: optionLabelNumberOfLines,
2493
+ style: optionLabelStyle,
2494
+ children: item.children
2495
+ }
2496
+ )
2497
+ },
2498
+ item.id
2499
+ );
2500
+ })
2501
+ }
2502
+ )
2503
+ ]
2369
2504
  }
2370
2505
  )
2371
2506
  ] });
2372
2507
  return /* @__PURE__ */ jsxs4(
2373
2508
  Box,
2374
2509
  {
2510
+ ref: containerRef,
2375
2511
  testID,
2376
2512
  flexDirection: "column",
2377
2513
  gap: sizeStyles.fieldGap,