@underverse-ui/underverse 1.0.106 → 1.0.109

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -7158,6 +7158,7 @@ import { ChevronLeft, ChevronRight as ChevronRight2, ChevronsLeft, ChevronsRight
7158
7158
  // src/components/Combobox.tsx
7159
7159
  import * as React24 from "react";
7160
7160
  import { useId as useId6 } from "react";
7161
+ import { useVirtualizer } from "@tanstack/react-virtual";
7161
7162
  import { ChevronDown, Search as Search4, SearchX, Check as Check3, X as X9 } from "lucide-react";
7162
7163
  import { Fragment as Fragment5, jsx as jsx29, jsxs as jsxs22 } from "react/jsx-runtime";
7163
7164
  var getOptionLabel = (option) => {
@@ -7203,9 +7204,19 @@ var Combobox = ({
7203
7204
  groupBy,
7204
7205
  renderOption,
7205
7206
  renderValue,
7207
+ selectedOption: selectedOptionProp,
7206
7208
  error,
7207
7209
  helperText,
7208
- useOverlayScrollbar = false
7210
+ useOverlayScrollbar = false,
7211
+ virtualized = false,
7212
+ estimatedItemHeight = 44,
7213
+ overscan = 8,
7214
+ searchMode = "auto",
7215
+ onSearchChange,
7216
+ searchDebounceMs = 0,
7217
+ minSearchLength = 0,
7218
+ maxInitialOptions,
7219
+ showSearchPromptWhenEmptyQuery = false
7209
7220
  }) => {
7210
7221
  const tv = useSmartTranslations("ValidationInput");
7211
7222
  const [open, setOpen] = React24.useState(false);
@@ -7219,12 +7230,53 @@ var Combobox = ({
7219
7230
  const autoId = useId6();
7220
7231
  const resolvedId = id ? String(id) : `combobox-${autoId}`;
7221
7232
  const labelId = label ? `${resolvedId}-label` : void 0;
7222
- const enableSearch = options.length > 10;
7233
+ const enableSearch = options.length > 10 || searchMode === "manual" || minSearchLength > 0 || !!onSearchChange;
7234
+ const trimmedQuery = query.trim();
7235
+ const queryMeetsMinimum = trimmedQuery.length >= minSearchLength;
7236
+ const shouldPromptForSearch = minSearchLength > 0 && !queryMeetsMinimum && (searchMode === "manual" || showSearchPromptWhenEmptyQuery);
7223
7237
  const filteredOptions = React24.useMemo(
7224
- () => enableSearch ? options.filter((o) => getOptionLabel(o).toLowerCase().includes(query.trim().toLowerCase())) : options,
7225
- [options, query, enableSearch]
7238
+ () => {
7239
+ if (shouldPromptForSearch) return [];
7240
+ if (!enableSearch || searchMode === "manual") return options;
7241
+ const normalizedQuery = trimmedQuery.toLowerCase();
7242
+ if (!normalizedQuery) return options;
7243
+ return options.filter((o) => getOptionLabel(o).toLowerCase().includes(normalizedQuery));
7244
+ },
7245
+ [enableSearch, options, searchMode, shouldPromptForSearch, trimmedQuery]
7246
+ );
7247
+ const renderLimitedOptions = React24.useMemo(
7248
+ () => {
7249
+ if (trimmedQuery || maxInitialOptions === void 0 || maxInitialOptions < 1) {
7250
+ return filteredOptions;
7251
+ }
7252
+ return filteredOptions.slice(0, maxInitialOptions);
7253
+ },
7254
+ [filteredOptions, maxInitialOptions, trimmedQuery]
7226
7255
  );
7256
+ const canVirtualize = virtualized && !groupBy;
7257
+ const optionVirtualizer = useVirtualizer({
7258
+ count: canVirtualize ? renderLimitedOptions.length : 0,
7259
+ getScrollElement: () => optionsViewportRef.current,
7260
+ estimateSize: () => estimatedItemHeight,
7261
+ initialRect: { width: 0, height: maxHeight },
7262
+ overscan,
7263
+ enabled: canVirtualize
7264
+ });
7265
+ const virtualItems = canVirtualize ? optionVirtualizer.getVirtualItems() : [];
7227
7266
  const triggerRef = React24.useRef(null);
7267
+ const scrollVirtualListToIndex = React24.useCallback((index) => {
7268
+ if (!canVirtualize || renderLimitedOptions.length === 0) return;
7269
+ optionVirtualizer.scrollToIndex(index, { align: "auto" });
7270
+ }, [canVirtualize, optionVirtualizer, renderLimitedOptions.length]);
7271
+ const scrollVirtualListToStart = React24.useCallback(() => {
7272
+ scrollVirtualListToIndex(0);
7273
+ }, [scrollVirtualListToIndex]);
7274
+ const moveActiveIndex = React24.useCallback((direction) => {
7275
+ if (renderLimitedOptions.length === 0) return;
7276
+ const next = activeIndex === null ? direction === 1 ? 0 : renderLimitedOptions.length - 1 : (activeIndex + direction + renderLimitedOptions.length) % renderLimitedOptions.length;
7277
+ setActiveIndex(next);
7278
+ scrollVirtualListToIndex(next);
7279
+ }, [activeIndex, renderLimitedOptions.length, scrollVirtualListToIndex]);
7228
7280
  const handleSelect = (option) => {
7229
7281
  if (getOptionDisabled(option)) return;
7230
7282
  const val = getOptionValue(option);
@@ -7237,6 +7289,9 @@ var Combobox = ({
7237
7289
  };
7238
7290
  const handleClear = (e) => {
7239
7291
  e.stopPropagation();
7292
+ clearValue();
7293
+ };
7294
+ const clearValue = () => {
7240
7295
  onChange(null);
7241
7296
  setOpen(false);
7242
7297
  };
@@ -7244,13 +7299,26 @@ var Combobox = ({
7244
7299
  if (!open) {
7245
7300
  setQuery("");
7246
7301
  setActiveIndex(null);
7302
+ scrollVirtualListToStart();
7247
7303
  } else if (enableSearch) {
7248
7304
  setTimeout(() => {
7249
7305
  inputRef.current?.focus();
7250
7306
  }, 100);
7251
7307
  }
7252
- }, [open, enableSearch]);
7253
- const selectedOption = findOptionByValue(options, value);
7308
+ }, [enableSearch, open, scrollVirtualListToStart]);
7309
+ React24.useEffect(() => {
7310
+ if (!onSearchChange) return void 0;
7311
+ const timeoutId = window.setTimeout(() => onSearchChange(query), searchDebounceMs);
7312
+ return () => window.clearTimeout(timeoutId);
7313
+ }, [onSearchChange, query, searchDebounceMs]);
7314
+ React24.useEffect(() => {
7315
+ if (process.env.NODE_ENV !== "production" && options.length > 300 && !virtualized && searchMode !== "manual" && maxInitialOptions === void 0) {
7316
+ console.warn(
7317
+ '[Underverse UI] Combobox received more than 300 options without virtualization, manual search, or maxInitialOptions. Use virtualized, searchMode="manual", or maxInitialOptions to avoid rendering a large dropdown.'
7318
+ );
7319
+ }
7320
+ }, [maxInitialOptions, options.length, searchMode, virtualized]);
7321
+ const selectedOption = findOptionByValue(options, value) ?? (selectedOptionProp && getOptionValue(selectedOptionProp) === value ? selectedOptionProp : void 0);
7254
7322
  const displayValue = selectedOption ? getOptionLabel(selectedOption) : "";
7255
7323
  const selectedIcon = selectedOption ? getOptionIcon(selectedOption) : void 0;
7256
7324
  const hasValue = value !== void 0 && value !== null && value !== "";
@@ -7263,13 +7331,13 @@ var Combobox = ({
7263
7331
  const groupedOptions = React24.useMemo(() => {
7264
7332
  if (!groupBy) return null;
7265
7333
  const groups = {};
7266
- filteredOptions.forEach((opt) => {
7334
+ renderLimitedOptions.forEach((opt) => {
7267
7335
  const group = groupBy(opt);
7268
7336
  if (!groups[group]) groups[group] = [];
7269
7337
  groups[group].push(opt);
7270
7338
  });
7271
7339
  return groups;
7272
- }, [filteredOptions, groupBy]);
7340
+ }, [renderLimitedOptions, groupBy]);
7273
7341
  const itemSizeStyles = {
7274
7342
  sm: "px-2.5 py-1.5 text-xs gap-2",
7275
7343
  md: "px-3 py-2.5 text-sm gap-3",
@@ -7285,60 +7353,75 @@ var Combobox = ({
7285
7353
  md: "h-4 w-4",
7286
7354
  lg: "h-5 w-5"
7287
7355
  };
7288
- const renderOptionItem = (item, index) => {
7356
+ const renderOptionItem = (item, index, virtualItem) => {
7289
7357
  const itemValue = getOptionValue(item);
7290
7358
  const itemLabel = getOptionLabel(item);
7291
7359
  const itemIcon = getOptionIcon(item);
7292
7360
  const itemDescription = getOptionDescription(item);
7293
7361
  const itemDisabled = getOptionDisabled(item);
7294
7362
  const isSelected = itemValue === value;
7295
- return /* @__PURE__ */ jsx29("li", { className: "list-none", children: /* @__PURE__ */ jsxs22(
7296
- "button",
7363
+ return /* @__PURE__ */ jsx29(
7364
+ "li",
7297
7365
  {
7298
- id: `combobox-item-${index}`,
7299
- type: "button",
7300
- role: "option",
7301
- tabIndex: -1,
7302
- disabled: itemDisabled,
7303
- "aria-selected": isSelected,
7304
- onClick: () => handleSelect(item),
7305
- style: {
7306
- animationDelay: open ? `${Math.min(index * 15, 150)}ms` : "0ms"
7307
- },
7308
- className: cn(
7309
- "dropdown-item group flex w-full items-center rounded-full text-left",
7310
- itemSizeStyles[size],
7311
- "outline-none focus:outline-none focus-visible:outline-none",
7312
- "transition-all duration-150",
7313
- !itemDisabled && "cursor-pointer hover:bg-accent/70 hover:shadow-sm",
7314
- !itemDisabled && "focus:bg-accent/80 focus:text-accent-foreground",
7315
- index === activeIndex && !itemDisabled && "bg-accent/60",
7316
- isSelected && "bg-primary/10 text-primary font-medium",
7317
- itemDisabled && "opacity-50 cursor-not-allowed"
7318
- ),
7319
- children: [
7320
- itemIcon && /* @__PURE__ */ jsx29(
7321
- "span",
7322
- {
7323
- className: cn("shrink-0 flex items-center justify-center", iconSizeStyles[size], isSelected ? "text-primary" : "text-muted-foreground"),
7324
- children: itemIcon
7325
- }
7326
- ),
7327
- renderOption ? /* @__PURE__ */ jsx29("div", { className: "flex-1 min-w-0", children: renderOption(item, isSelected) }) : /* @__PURE__ */ jsxs22("div", { className: "flex-1 min-w-0", children: [
7328
- /* @__PURE__ */ jsx29("span", { className: "block truncate", children: itemLabel }),
7329
- itemDescription && /* @__PURE__ */ jsx29("span", { className: cn("block text-muted-foreground truncate mt-0.5", size === "sm" ? "text-[10px]" : "text-xs"), children: itemDescription })
7330
- ] }),
7331
- isSelected && showSelectedIcon && /* @__PURE__ */ jsx29("span", { className: "shrink-0 ml-auto", children: /* @__PURE__ */ jsx29(Check3, { className: cn(checkIconSizeStyles[size], "text-primary") }) })
7332
- ]
7333
- }
7334
- ) }, `${itemValue}-${index}`);
7366
+ ref: virtualItem ? optionVirtualizer.measureElement : void 0,
7367
+ "data-index": virtualItem?.index,
7368
+ className: "list-none",
7369
+ style: virtualItem ? {
7370
+ position: "absolute",
7371
+ top: 0,
7372
+ left: 0,
7373
+ width: "100%",
7374
+ transform: `translateY(${virtualItem.start}px)`
7375
+ } : void 0,
7376
+ children: /* @__PURE__ */ jsxs22(
7377
+ "button",
7378
+ {
7379
+ id: `${resolvedId}-item-${index}`,
7380
+ type: "button",
7381
+ role: "option",
7382
+ tabIndex: -1,
7383
+ disabled: itemDisabled,
7384
+ "aria-selected": isSelected,
7385
+ onClick: () => handleSelect(item),
7386
+ style: {
7387
+ animationDelay: open ? `${Math.min(index * 15, 150)}ms` : "0ms"
7388
+ },
7389
+ className: cn(
7390
+ "dropdown-item group flex w-full items-center rounded-full text-left",
7391
+ itemSizeStyles[size],
7392
+ "outline-none focus:outline-none focus-visible:outline-none",
7393
+ "transition-all duration-150",
7394
+ !itemDisabled && "cursor-pointer hover:bg-accent/70 hover:shadow-sm",
7395
+ !itemDisabled && "focus:bg-accent/80 focus:text-accent-foreground",
7396
+ index === activeIndex && !itemDisabled && "bg-accent/60",
7397
+ isSelected && "bg-primary/10 text-primary font-medium",
7398
+ itemDisabled && "opacity-50 cursor-not-allowed"
7399
+ ),
7400
+ children: [
7401
+ itemIcon && /* @__PURE__ */ jsx29(
7402
+ "span",
7403
+ {
7404
+ className: cn("shrink-0 flex items-center justify-center", iconSizeStyles[size], isSelected ? "text-primary" : "text-muted-foreground"),
7405
+ children: itemIcon
7406
+ }
7407
+ ),
7408
+ renderOption ? /* @__PURE__ */ jsx29("div", { className: "flex-1 min-w-0", children: renderOption(item, isSelected) }) : /* @__PURE__ */ jsxs22("div", { className: "flex-1 min-w-0", children: [
7409
+ /* @__PURE__ */ jsx29("span", { className: "block truncate", children: itemLabel }),
7410
+ itemDescription && /* @__PURE__ */ jsx29("span", { className: cn("block text-muted-foreground truncate mt-0.5", size === "sm" ? "text-[10px]" : "text-xs"), children: itemDescription })
7411
+ ] }),
7412
+ isSelected && showSelectedIcon && /* @__PURE__ */ jsx29("span", { className: "shrink-0 ml-auto", children: /* @__PURE__ */ jsx29(Check3, { className: cn(checkIconSizeStyles[size], "text-primary") }) })
7413
+ ]
7414
+ }
7415
+ )
7416
+ },
7417
+ `${itemValue}-${index}`
7418
+ );
7335
7419
  };
7336
7420
  const dropdownBody = /* @__PURE__ */ jsxs22(
7337
7421
  "div",
7338
7422
  {
7339
7423
  "data-combobox-dropdown": true,
7340
7424
  "data-state": open ? "open" : "closed",
7341
- id: `${resolvedId}-listbox`,
7342
7425
  className: "w-full rounded-2xl md:rounded-3xl overflow-hidden",
7343
7426
  children: [
7344
7427
  enableSearch && /* @__PURE__ */ jsx29("div", { className: cn("relative border-b border-border/30", size === "sm" ? "p-2" : size === "lg" ? "p-3" : "p-2.5"), children: /* @__PURE__ */ jsxs22("div", { className: "relative", children: [
@@ -7359,24 +7442,19 @@ var Combobox = ({
7359
7442
  onChange: (e) => {
7360
7443
  setQuery(e.target.value);
7361
7444
  setActiveIndex(null);
7445
+ scrollVirtualListToStart();
7362
7446
  },
7363
7447
  onKeyDown: (e) => {
7364
7448
  if (e.key === "ArrowDown") {
7365
7449
  e.preventDefault();
7366
- setActiveIndex((prev) => {
7367
- const next = prev === null ? 0 : prev + 1;
7368
- return next >= filteredOptions.length ? 0 : next;
7369
- });
7450
+ moveActiveIndex(1);
7370
7451
  } else if (e.key === "ArrowUp") {
7371
7452
  e.preventDefault();
7372
- setActiveIndex((prev) => {
7373
- const next = prev === null ? filteredOptions.length - 1 : prev - 1;
7374
- return next < 0 ? filteredOptions.length - 1 : next;
7375
- });
7453
+ moveActiveIndex(-1);
7376
7454
  } else if (e.key === "Enter") {
7377
7455
  e.preventDefault();
7378
- if (activeIndex !== null && filteredOptions[activeIndex] && !getOptionDisabled(filteredOptions[activeIndex])) {
7379
- handleSelect(filteredOptions[activeIndex]);
7456
+ if (activeIndex !== null && renderLimitedOptions[activeIndex] && !getOptionDisabled(renderLimitedOptions[activeIndex])) {
7457
+ handleSelect(renderLimitedOptions[activeIndex]);
7380
7458
  }
7381
7459
  } else if (e.key === "Escape") {
7382
7460
  e.preventDefault();
@@ -7399,7 +7477,10 @@ var Combobox = ({
7399
7477
  "button",
7400
7478
  {
7401
7479
  type: "button",
7402
- onClick: () => setQuery(""),
7480
+ onClick: () => {
7481
+ setQuery("");
7482
+ scrollVirtualListToStart();
7483
+ },
7403
7484
  className: "absolute right-3 top-1/2 -translate-y-1/2 p-0.5 rounded-md hover:bg-muted text-muted-foreground hover:text-foreground transition-colors",
7404
7485
  children: /* @__PURE__ */ jsx29(X9, { className: cn(size === "sm" ? "h-3 w-3" : size === "lg" ? "h-4 w-4" : "h-3.5 w-3.5") })
7405
7486
  }
@@ -7409,6 +7490,7 @@ var Combobox = ({
7409
7490
  "div",
7410
7491
  {
7411
7492
  ref: optionsViewportRef,
7493
+ id: `${resolvedId}-listbox`,
7412
7494
  role: "listbox",
7413
7495
  "aria-labelledby": labelId,
7414
7496
  className: "overflow-y-auto overscroll-contain",
@@ -7416,7 +7498,14 @@ var Combobox = ({
7416
7498
  children: /* @__PURE__ */ jsx29("div", { className: cn(size === "sm" ? "p-1" : size === "lg" ? "p-2" : "p-1.5"), children: loading2 ? /* @__PURE__ */ jsx29("div", { className: "px-3 py-10 text-center", children: /* @__PURE__ */ jsxs22("div", { className: "flex flex-col items-center gap-3 animate-in fade-in-0 zoom-in-95 duration-300", children: [
7417
7499
  /* @__PURE__ */ jsx29("div", { className: "relative", children: /* @__PURE__ */ jsx29("div", { className: "w-10 h-10 rounded-full border-2 border-primary/20 border-t-primary animate-spin" }) }),
7418
7500
  /* @__PURE__ */ jsx29("span", { className: "text-sm text-muted-foreground", children: loadingText })
7419
- ] }) }) : filteredOptions.length > 0 ? groupedOptions ? (
7501
+ ] }) }) : shouldPromptForSearch ? /* @__PURE__ */ jsx29("div", { className: "px-3 py-10 text-center", children: /* @__PURE__ */ jsxs22("div", { className: "flex flex-col items-center gap-3 animate-in fade-in-0 zoom-in-95 duration-300", children: [
7502
+ /* @__PURE__ */ jsx29("div", { className: "w-12 h-12 rounded-full bg-muted/50 flex items-center justify-center", children: /* @__PURE__ */ jsx29(Search4, { className: "h-6 w-6 text-muted-foreground/60" }) }),
7503
+ /* @__PURE__ */ jsx29("div", { className: "space-y-1", children: /* @__PURE__ */ jsxs22("span", { className: "block text-sm font-medium text-foreground", children: [
7504
+ "Type at least ",
7505
+ minSearchLength,
7506
+ " characters to search"
7507
+ ] }) })
7508
+ ] }) }) : renderLimitedOptions.length > 0 ? groupedOptions ? (
7420
7509
  // Render grouped options with global index tracking
7421
7510
  (() => {
7422
7511
  let globalIndex = 0;
@@ -7430,7 +7519,14 @@ var Combobox = ({
7430
7519
  })()
7431
7520
  ) : (
7432
7521
  // Render flat options
7433
- /* @__PURE__ */ jsx29("ul", { className: "space-y-0.5", children: filteredOptions.map((item, index) => renderOptionItem(item, index)) })
7522
+ /* @__PURE__ */ jsx29(
7523
+ "ul",
7524
+ {
7525
+ className: "space-y-0.5",
7526
+ style: canVirtualize ? { height: `${optionVirtualizer.getTotalSize()}px`, position: "relative" } : void 0,
7527
+ children: canVirtualize ? virtualItems.map((virtualItem) => renderOptionItem(renderLimitedOptions[virtualItem.index], virtualItem.index, virtualItem)) : renderLimitedOptions.map((item, index) => renderOptionItem(item, index))
7528
+ }
7529
+ )
7434
7530
  ) : /* @__PURE__ */ jsx29("div", { className: "px-3 py-10 text-center", children: /* @__PURE__ */ jsxs22("div", { className: "flex flex-col items-center gap-3 animate-in fade-in-0 zoom-in-95 duration-300", children: [
7435
7531
  /* @__PURE__ */ jsx29("div", { className: "w-12 h-12 rounded-full bg-muted/50 flex items-center justify-center", children: /* @__PURE__ */ jsx29(SearchX, { className: "h-6 w-6 text-muted-foreground/60" }) }),
7436
7532
  /* @__PURE__ */ jsxs22("div", { className: "space-y-1", children: [
@@ -7441,7 +7537,10 @@ var Combobox = ({
7441
7537
  "button",
7442
7538
  {
7443
7539
  type: "button",
7444
- onClick: () => setQuery(""),
7540
+ onClick: () => {
7541
+ setQuery("");
7542
+ scrollVirtualListToStart();
7543
+ },
7445
7544
  className: "px-3 py-1.5 text-xs font-medium text-primary bg-primary/10 rounded-full hover:bg-primary/20 transition-colors",
7446
7545
  children: "Clear search"
7447
7546
  }
@@ -7511,7 +7610,13 @@ var Combobox = ({
7511
7610
  tabIndex: 0,
7512
7611
  "aria-label": "Clear selection",
7513
7612
  onClick: handleClear,
7514
- onKeyDown: (e) => (e.key === "Enter" || e.key === " ") && handleClear(e),
7613
+ onKeyDown: (e) => {
7614
+ if (e.key === "Enter" || e.key === " ") {
7615
+ e.preventDefault();
7616
+ e.stopPropagation();
7617
+ clearValue();
7618
+ }
7619
+ },
7515
7620
  className: cn(
7516
7621
  "opacity-0 group-hover:opacity-100 transition-all duration-200",
7517
7622
  "p-1 rounded-lg hover:bg-destructive/10 text-muted-foreground hover:text-destructive"
@@ -8287,7 +8392,7 @@ function formatDateSmart(date, locale = "en") {
8287
8392
  }
8288
8393
 
8289
8394
  // src/components/DatePicker.tsx
8290
- import { Calendar, ChevronLeft as ChevronLeft2, ChevronRight as ChevronRight3, Sparkles as Sparkles2, X as XIcon } from "lucide-react";
8395
+ import { Calendar, ChevronLeft as ChevronLeft2, ChevronRight as ChevronRight3, Sparkles, X as XIcon } from "lucide-react";
8291
8396
  import * as React28 from "react";
8292
8397
  import { useId as useId7 } from "react";
8293
8398
  import { Fragment as Fragment6, jsx as jsx34, jsxs as jsxs24 } from "react/jsx-runtime";
@@ -8666,7 +8771,7 @@ var DatePicker = ({
8666
8771
  isDateDisabled(/* @__PURE__ */ new Date()) && "opacity-50 cursor-not-allowed hover:scale-100 active:scale-100"
8667
8772
  ),
8668
8773
  children: [
8669
- /* @__PURE__ */ jsx34(Sparkles2, { className: sizeStyles8[size].actionIcon }),
8774
+ /* @__PURE__ */ jsx34(Sparkles, { className: sizeStyles8[size].actionIcon }),
8670
8775
  todayLabel || t("today")
8671
8776
  ]
8672
8777
  }
@@ -9246,7 +9351,7 @@ var DateRangePicker = ({
9246
9351
  isTodayUnavailable && "opacity-50 cursor-not-allowed hover:scale-100 active:scale-100"
9247
9352
  ),
9248
9353
  children: [
9249
- /* @__PURE__ */ jsx34(Sparkles2, { className: sizeStyles8[size].actionIcon }),
9354
+ /* @__PURE__ */ jsx34(Sparkles, { className: sizeStyles8[size].actionIcon }),
9250
9355
  t("today")
9251
9356
  ]
9252
9357
  }
@@ -15391,7 +15496,8 @@ function CalendarTimeline({
15391
15496
  // src/components/MultiCombobox.tsx
15392
15497
  import * as React39 from "react";
15393
15498
  import { useId as useId9 } from "react";
15394
- import { ChevronDown as ChevronDown4, Search as Search5, Check as Check6, SearchX as SearchX2, Loader2 as Loader24, X as X13, Sparkles as Sparkles3 } from "lucide-react";
15499
+ import { useVirtualizer as useVirtualizer2 } from "@tanstack/react-virtual";
15500
+ import { ChevronDown as ChevronDown4, Search as Search5, Check as Check6, SearchX as SearchX2, Loader2 as Loader23, X as X13, Sparkles as Sparkles2 } from "lucide-react";
15395
15501
  import { Fragment as Fragment13, jsx as jsx45, jsxs as jsxs35 } from "react/jsx-runtime";
15396
15502
  var MultiCombobox = ({
15397
15503
  id,
@@ -15421,10 +15527,20 @@ var MultiCombobox = ({
15421
15527
  groupBy,
15422
15528
  renderOption,
15423
15529
  renderTag,
15530
+ selectedOptions: selectedOptionsProp,
15424
15531
  error,
15425
15532
  helperText,
15426
15533
  maxTagsVisible = 3,
15427
- useOverlayScrollbar = false
15534
+ useOverlayScrollbar = false,
15535
+ virtualized = false,
15536
+ estimatedItemHeight = 44,
15537
+ overscan = 8,
15538
+ searchMode = "auto",
15539
+ onSearchChange,
15540
+ searchDebounceMs = 0,
15541
+ minSearchLength = 0,
15542
+ maxInitialOptions,
15543
+ showSearchPromptWhenEmptyQuery = false
15428
15544
  }) => {
15429
15545
  const tv = useSmartTranslations("ValidationInput");
15430
15546
  const [query, setQuery] = React39.useState("");
@@ -15443,23 +15559,52 @@ var MultiCombobox = ({
15443
15559
  ),
15444
15560
  [options]
15445
15561
  );
15446
- const enableSearch = normalizedOptions.length > 10;
15447
- const filtered = React39.useMemo(
15448
- () => enableSearch ? normalizedOptions.filter(
15449
- (opt) => opt.label.toLowerCase().includes(query.trim().toLowerCase()) || opt.description?.toLowerCase().includes(query.trim().toLowerCase())
15450
- ) : normalizedOptions,
15451
- [normalizedOptions, query, enableSearch]
15452
- );
15562
+ const enableSearch = normalizedOptions.length > 10 || searchMode === "manual" || minSearchLength > 0 || !!onSearchChange;
15563
+ const trimmedQuery = query.trim();
15564
+ const queryMeetsMinimum = trimmedQuery.length >= minSearchLength;
15565
+ const shouldPromptForSearch = minSearchLength > 0 && !queryMeetsMinimum && (searchMode === "manual" || showSearchPromptWhenEmptyQuery);
15566
+ const filtered = React39.useMemo(() => {
15567
+ if (shouldPromptForSearch) return [];
15568
+ if (!enableSearch || searchMode === "manual") return normalizedOptions;
15569
+ const normalizedQuery = trimmedQuery.toLowerCase();
15570
+ if (!normalizedQuery) return normalizedOptions;
15571
+ return normalizedOptions.filter(
15572
+ (opt) => opt.label.toLowerCase().includes(normalizedQuery) || opt.description?.toLowerCase().includes(normalizedQuery)
15573
+ );
15574
+ }, [enableSearch, normalizedOptions, searchMode, shouldPromptForSearch, trimmedQuery]);
15575
+ const renderLimitedOptions = React39.useMemo(() => {
15576
+ if (trimmedQuery || maxInitialOptions === void 0 || maxInitialOptions < 1) {
15577
+ return filtered;
15578
+ }
15579
+ return filtered.slice(0, maxInitialOptions);
15580
+ }, [filtered, maxInitialOptions, trimmedQuery]);
15581
+ const canVirtualize = virtualized && !groupBy;
15582
+ const optionVirtualizer = useVirtualizer2({
15583
+ count: canVirtualize ? renderLimitedOptions.length : 0,
15584
+ getScrollElement: () => optionsListRef.current,
15585
+ estimateSize: () => estimatedItemHeight,
15586
+ initialRect: { width: 0, height: maxHeight },
15587
+ overscan,
15588
+ enabled: canVirtualize
15589
+ });
15590
+ const virtualItems = canVirtualize ? optionVirtualizer.getVirtualItems() : [];
15591
+ const scrollVirtualListToIndex = React39.useCallback((index) => {
15592
+ if (!canVirtualize || renderLimitedOptions.length === 0) return;
15593
+ optionVirtualizer.scrollToIndex(index, { align: "auto" });
15594
+ }, [canVirtualize, optionVirtualizer, renderLimitedOptions.length]);
15595
+ const scrollVirtualListToStart = React39.useCallback(() => {
15596
+ scrollVirtualListToIndex(0);
15597
+ }, [scrollVirtualListToIndex]);
15453
15598
  const groupedOptions = React39.useMemo(() => {
15454
15599
  if (!groupBy) return null;
15455
15600
  const groups = /* @__PURE__ */ new Map();
15456
- filtered.forEach((opt) => {
15601
+ renderLimitedOptions.forEach((opt) => {
15457
15602
  const group = groupBy(opt);
15458
15603
  if (!groups.has(group)) groups.set(group, []);
15459
15604
  groups.get(group).push(opt);
15460
15605
  });
15461
15606
  return groups;
15462
- }, [filtered, groupBy]);
15607
+ }, [renderLimitedOptions, groupBy]);
15463
15608
  const toggleSelect = (optionValue) => {
15464
15609
  const option = normalizedOptions.find((o) => o.value === optionValue);
15465
15610
  if (option?.disabled || disabledOptions.includes(optionValue)) return;
@@ -15477,11 +15622,26 @@ var MultiCombobox = ({
15477
15622
  };
15478
15623
  const handleKeyDown2 = (e) => {
15479
15624
  if (!open) setOpen(true);
15480
- if (e.key === "Enter") {
15625
+ if (e.key === "ArrowDown") {
15481
15626
  e.preventDefault();
15482
- if (activeIndex !== null && filtered[activeIndex]) {
15483
- toggleSelect(filtered[activeIndex].value);
15627
+ if (renderLimitedOptions.length === 0) return;
15628
+ const next = activeIndex === null ? 0 : (activeIndex + 1) % renderLimitedOptions.length;
15629
+ setActiveIndex(next);
15630
+ scrollVirtualListToIndex(next);
15631
+ } else if (e.key === "ArrowUp") {
15632
+ e.preventDefault();
15633
+ if (renderLimitedOptions.length === 0) return;
15634
+ const next = activeIndex === null ? renderLimitedOptions.length - 1 : (activeIndex - 1 + renderLimitedOptions.length) % renderLimitedOptions.length;
15635
+ setActiveIndex(next);
15636
+ scrollVirtualListToIndex(next);
15637
+ } else if (e.key === "Enter") {
15638
+ e.preventDefault();
15639
+ if (activeIndex !== null && renderLimitedOptions[activeIndex]) {
15640
+ toggleSelect(renderLimitedOptions[activeIndex].value);
15484
15641
  }
15642
+ } else if (e.key === "Escape") {
15643
+ e.preventDefault();
15644
+ setOpen(false);
15485
15645
  }
15486
15646
  };
15487
15647
  const handleClearAll = () => {
@@ -15498,8 +15658,24 @@ var MultiCombobox = ({
15498
15658
  setTimeout(() => {
15499
15659
  inputRef.current?.focus();
15500
15660
  }, 100);
15661
+ } else if (!open) {
15662
+ setQuery("");
15663
+ setActiveIndex(null);
15664
+ scrollVirtualListToStart();
15501
15665
  }
15502
- }, [open, enableSearch]);
15666
+ }, [enableSearch, open, scrollVirtualListToStart]);
15667
+ React39.useEffect(() => {
15668
+ if (!onSearchChange) return void 0;
15669
+ const timeoutId = window.setTimeout(() => onSearchChange(query), searchDebounceMs);
15670
+ return () => window.clearTimeout(timeoutId);
15671
+ }, [onSearchChange, query, searchDebounceMs]);
15672
+ React39.useEffect(() => {
15673
+ if (process.env.NODE_ENV !== "production" && normalizedOptions.length > 300 && !virtualized && searchMode !== "manual" && maxInitialOptions === void 0) {
15674
+ console.warn(
15675
+ '[Underverse UI] MultiCombobox received more than 300 options without virtualization, manual search, or maxInitialOptions. Use virtualized, searchMode="manual", or maxInitialOptions to avoid rendering a large dropdown.'
15676
+ );
15677
+ }
15678
+ }, [maxInitialOptions, normalizedOptions.length, searchMode, virtualized]);
15503
15679
  const sizeStyles8 = {
15504
15680
  sm: {
15505
15681
  trigger: "h-8 px-3 py-1.5 text-sm md:h-7 md:text-xs",
@@ -15533,25 +15709,38 @@ var MultiCombobox = ({
15533
15709
  const labelId = label ? `${resolvedId}-label` : void 0;
15534
15710
  const labelSize = size === "sm" ? "text-xs" : size === "lg" ? "text-base" : "text-sm";
15535
15711
  const listboxId = `${resolvedId}-listbox`;
15536
- const renderOptionItem = (item, index) => {
15712
+ const renderOptionItem = (item, index, virtualItem) => {
15537
15713
  const isSelected = value.includes(item.value);
15538
15714
  const isDisabled = item.disabled || disabledOptions.includes(item.value);
15539
15715
  const optionIcon = item.icon;
15540
15716
  const optionDesc = item.description;
15717
+ const itemStyle = {
15718
+ animationDelay: open ? `${Math.min(index * 20, 200)}ms` : "0ms",
15719
+ ...virtualItem ? {
15720
+ position: "absolute",
15721
+ top: 0,
15722
+ left: 0,
15723
+ width: "100%",
15724
+ transform: `translateY(${virtualItem.start}px)`
15725
+ } : {}
15726
+ };
15727
+ const measureRef = virtualItem ? optionVirtualizer.measureElement : void 0;
15541
15728
  if (renderOption) {
15542
15729
  return /* @__PURE__ */ jsx45(
15543
15730
  "li",
15544
15731
  {
15545
15732
  ref: (node) => {
15733
+ measureRef?.(node);
15546
15734
  listRef.current[index] = node;
15547
15735
  },
15736
+ "data-index": virtualItem?.index,
15737
+ style: itemStyle,
15548
15738
  onClick: (e) => {
15549
15739
  e.preventDefault();
15550
15740
  e.stopPropagation();
15551
15741
  if (!isDisabled) toggleSelect(item.value);
15552
15742
  inputRef.current?.focus();
15553
15743
  },
15554
- style: { animationDelay: open ? `${Math.min(index * 20, 200)}ms` : "0ms" },
15555
15744
  className: cn("dropdown-item", isDisabled && "opacity-50 cursor-not-allowed pointer-events-none"),
15556
15745
  children: renderOption(item, isSelected)
15557
15746
  },
@@ -15562,15 +15751,17 @@ var MultiCombobox = ({
15562
15751
  "li",
15563
15752
  {
15564
15753
  ref: (node) => {
15754
+ measureRef?.(node);
15565
15755
  listRef.current[index] = node;
15566
15756
  },
15757
+ "data-index": virtualItem?.index,
15758
+ style: itemStyle,
15567
15759
  onClick: (e) => {
15568
15760
  e.preventDefault();
15569
15761
  e.stopPropagation();
15570
15762
  if (!isDisabled) toggleSelect(item.value);
15571
15763
  inputRef.current?.focus();
15572
15764
  },
15573
- style: { animationDelay: open ? `${Math.min(index * 20, 200)}ms` : "0ms" },
15574
15765
  className: cn(
15575
15766
  "dropdown-item flex cursor-pointer items-center gap-3 rounded-full transition-all duration-200",
15576
15767
  sizeStyles8[size].item,
@@ -15634,6 +15825,7 @@ var MultiCombobox = ({
15634
15825
  onChange: (e) => {
15635
15826
  setQuery(e.target.value);
15636
15827
  setActiveIndex(null);
15828
+ scrollVirtualListToStart();
15637
15829
  },
15638
15830
  onKeyDown: handleKeyDown2,
15639
15831
  placeholder: searchPlaceholder,
@@ -15644,7 +15836,10 @@ var MultiCombobox = ({
15644
15836
  "button",
15645
15837
  {
15646
15838
  type: "button",
15647
- onClick: () => setQuery(""),
15839
+ onClick: () => {
15840
+ setQuery("");
15841
+ scrollVirtualListToStart();
15842
+ },
15648
15843
  className: "absolute right-3 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground transition-colors",
15649
15844
  children: /* @__PURE__ */ jsx45(X13, { className: "w-4 h-4" })
15650
15845
  }
@@ -15661,34 +15856,56 @@ var MultiCombobox = ({
15661
15856
  className: cn("overflow-y-auto p-1.5", size === "lg" ? "text-base" : size === "sm" ? "text-xs" : "text-sm"),
15662
15857
  children: loading2 ? /* @__PURE__ */ jsx45("li", { className: "px-3 py-8 text-center", children: /* @__PURE__ */ jsxs35("div", { className: "flex flex-col items-center gap-3 animate-in fade-in-0 zoom-in-95 duration-300", children: [
15663
15858
  /* @__PURE__ */ jsxs35("div", { className: "relative", children: [
15664
- /* @__PURE__ */ jsx45(Loader24, { className: "h-8 w-8 animate-spin text-primary" }),
15665
- /* @__PURE__ */ jsx45(Sparkles3, { className: "h-4 w-4 text-primary/60 absolute -top-1 -right-1 animate-pulse" })
15859
+ /* @__PURE__ */ jsx45(Loader23, { className: "h-8 w-8 animate-spin text-primary" }),
15860
+ /* @__PURE__ */ jsx45(Sparkles2, { className: "h-4 w-4 text-primary/60 absolute -top-1 -right-1 animate-pulse" })
15666
15861
  ] }),
15667
15862
  /* @__PURE__ */ jsx45("span", { className: "text-muted-foreground font-medium", children: loadingText })
15668
- ] }) }) : filtered.length ? groupedOptions ? (
15863
+ ] }) }) : shouldPromptForSearch ? /* @__PURE__ */ jsx45("li", { className: "px-3 py-8 text-center text-muted-foreground", children: /* @__PURE__ */ jsxs35("div", { className: "flex flex-col items-center gap-3 animate-in fade-in-0 zoom-in-95 duration-300", children: [
15864
+ /* @__PURE__ */ jsx45(Search5, { className: "h-10 w-10 opacity-30 text-muted-foreground" }),
15865
+ /* @__PURE__ */ jsxs35("span", { className: "font-medium block text-foreground", children: [
15866
+ "Type at least ",
15867
+ minSearchLength,
15868
+ " characters to search"
15869
+ ] })
15870
+ ] }) }) : renderLimitedOptions.length ? groupedOptions ? (
15669
15871
  // Render grouped options
15670
15872
  Array.from(groupedOptions.entries()).map(([group, items]) => /* @__PURE__ */ jsxs35("li", { className: "mb-2", children: [
15671
15873
  /* @__PURE__ */ jsx45("div", { className: "px-3 py-1.5 text-xs font-semibold text-muted-foreground uppercase tracking-wider sticky top-0 bg-popover/95 backdrop-blur-sm", children: group }),
15672
- /* @__PURE__ */ jsx45("ul", { children: items.map((item) => renderOptionItem(item, filtered.indexOf(item))) })
15874
+ /* @__PURE__ */ jsx45("ul", { children: items.map((item) => renderOptionItem(item, renderLimitedOptions.indexOf(item))) })
15673
15875
  ] }, group))
15674
15876
  ) : (
15675
15877
  // Render flat options
15676
- filtered.map((item, index) => renderOptionItem(item, index))
15878
+ canVirtualize ? /* @__PURE__ */ jsx45("li", { role: "presentation", className: "list-none p-0", children: /* @__PURE__ */ jsx45("ul", { className: "relative", style: { height: `${optionVirtualizer.getTotalSize()}px` }, children: virtualItems.map((virtualItem) => renderOptionItem(renderLimitedOptions[virtualItem.index], virtualItem.index, virtualItem)) }) }) : renderLimitedOptions.map((item, index) => renderOptionItem(item, index))
15677
15879
  ) : /* @__PURE__ */ jsx45("li", { className: cn("px-3 py-8 text-center text-muted-foreground"), children: /* @__PURE__ */ jsxs35("div", { className: "flex flex-col items-center gap-3 animate-in fade-in-0 zoom-in-95 duration-300", children: [
15678
15880
  /* @__PURE__ */ jsx45(SearchX2, { className: "h-10 w-10 opacity-30 text-muted-foreground" }),
15679
15881
  /* @__PURE__ */ jsxs35("div", { className: "space-y-1", children: [
15680
15882
  /* @__PURE__ */ jsx45("span", { className: "font-medium block", children: emptyText }),
15681
15883
  query && /* @__PURE__ */ jsx45("span", { className: "text-xs opacity-60", children: "Try a different search term" })
15682
15884
  ] }),
15683
- query && /* @__PURE__ */ jsxs35("button", { type: "button", onClick: () => setQuery(""), className: "text-xs text-primary hover:underline flex items-center gap-1", children: [
15684
- /* @__PURE__ */ jsx45(X13, { className: "w-3 h-3" }),
15685
- "Clear search"
15686
- ] })
15885
+ query && /* @__PURE__ */ jsxs35(
15886
+ "button",
15887
+ {
15888
+ type: "button",
15889
+ onClick: () => {
15890
+ setQuery("");
15891
+ scrollVirtualListToStart();
15892
+ },
15893
+ className: "text-xs text-primary hover:underline flex items-center gap-1",
15894
+ children: [
15895
+ /* @__PURE__ */ jsx45(X13, { className: "w-3 h-3" }),
15896
+ "Clear search"
15897
+ ]
15898
+ }
15899
+ )
15687
15900
  ] }) })
15688
15901
  }
15689
15902
  )
15690
15903
  ] });
15691
- const selectedOptions = value.map((v) => normalizedOptions.find((o) => o.value === v)).filter(Boolean);
15904
+ const selectedOptionFallbackMap = React39.useMemo(
15905
+ () => new Map((selectedOptionsProp ?? []).map((option) => [option.value, option])),
15906
+ [selectedOptionsProp]
15907
+ );
15908
+ const selectedOptions = value.map((v) => normalizedOptions.find((o) => o.value === v) ?? selectedOptionFallbackMap.get(v)).filter(Boolean);
15692
15909
  const visibleTags = maxTagsVisible ? selectedOptions.slice(0, maxTagsVisible) : selectedOptions;
15693
15910
  const hiddenCount = maxTagsVisible ? Math.max(0, selectedOptions.length - maxTagsVisible) : 0;
15694
15911
  const triggerButton = /* @__PURE__ */ jsxs35(
@@ -17732,8 +17949,8 @@ function CategoryTreeSelect(props) {
17732
17949
  }
17733
17950
 
17734
17951
  // src/components/ImageUpload.tsx
17735
- import { useState as useState32, useRef as useRef21, useCallback as useCallback16 } from "react";
17736
- import { Upload, X as X15, Image as ImageIcon, Loader2 as Loader25, Check as Check7 } from "lucide-react";
17952
+ import { useState as useState32, useRef as useRef21, useCallback as useCallback18 } from "react";
17953
+ import { Upload, X as X15, Image as ImageIcon, Loader2 as Loader24, Check as Check7 } from "lucide-react";
17737
17954
  import { jsx as jsx50, jsxs as jsxs40 } from "react/jsx-runtime";
17738
17955
  function ImageUpload({
17739
17956
  onUpload,
@@ -17761,7 +17978,7 @@ function ImageUpload({
17761
17978
  md: "w-24 h-24",
17762
17979
  lg: "w-32 h-32"
17763
17980
  };
17764
- const handleDragOver = useCallback16(
17981
+ const handleDragOver = useCallback18(
17765
17982
  (e) => {
17766
17983
  e.preventDefault();
17767
17984
  if (!disabled) {
@@ -17770,11 +17987,11 @@ function ImageUpload({
17770
17987
  },
17771
17988
  [disabled]
17772
17989
  );
17773
- const handleDragLeave = useCallback16((e) => {
17990
+ const handleDragLeave = useCallback18((e) => {
17774
17991
  e.preventDefault();
17775
17992
  setIsDragging(false);
17776
17993
  }, []);
17777
- const handleFiles = useCallback16(
17994
+ const handleFiles = useCallback18(
17778
17995
  async (files) => {
17779
17996
  if (files.length === 0) return;
17780
17997
  const validFiles = files.filter((file) => {
@@ -17841,7 +18058,7 @@ function ImageUpload({
17841
18058
  },
17842
18059
  [maxSize, addToast, onUpload]
17843
18060
  );
17844
- const handleDrop = useCallback16(
18061
+ const handleDrop = useCallback18(
17845
18062
  (e) => {
17846
18063
  e.preventDefault();
17847
18064
  setIsDragging(false);
@@ -17851,7 +18068,7 @@ function ImageUpload({
17851
18068
  },
17852
18069
  [disabled, handleFiles]
17853
18070
  );
17854
- const handleFileSelect = useCallback16(
18071
+ const handleFileSelect = useCallback18(
17855
18072
  (e) => {
17856
18073
  const files = Array.from(e.target.files || []);
17857
18074
  handleFiles(files);
@@ -17883,7 +18100,7 @@ function ImageUpload({
17883
18100
  onDrop: handleDrop,
17884
18101
  children: [
17885
18102
  uploading && /* @__PURE__ */ jsx50("div", { className: "absolute inset-0 bg-background/80 flex items-center justify-center rounded-2xl md:rounded-3xl", children: /* @__PURE__ */ jsxs40("div", { className: "flex items-center gap-3", children: [
17886
- /* @__PURE__ */ jsx50(Loader25, { className: "w-6 h-6 animate-spin text-primary" }),
18103
+ /* @__PURE__ */ jsx50(Loader24, { className: "w-6 h-6 animate-spin text-primary" }),
17887
18104
  /* @__PURE__ */ jsx50("span", { className: "text-sm font-medium", children: "Uploading..." })
17888
18105
  ] }) }),
17889
18106
  /* @__PURE__ */ jsxs40("div", { className: "space-y-4", children: [
@@ -17967,11 +18184,11 @@ import {
17967
18184
  FileSpreadsheet,
17968
18185
  FileText,
17969
18186
  FileVideo,
17970
- Loader2 as Loader26,
18187
+ Loader2 as Loader25,
17971
18188
  Trash2,
17972
18189
  Upload as Upload2
17973
18190
  } from "lucide-react";
17974
- import { useCallback as useCallback17, useMemo as useMemo20, useRef as useRef22, useState as useState33 } from "react";
18191
+ import { useCallback as useCallback19, useMemo as useMemo20, useRef as useRef22, useState as useState33 } from "react";
17975
18192
  import { Fragment as Fragment16, jsx as jsx51, jsxs as jsxs41 } from "react/jsx-runtime";
17976
18193
  var formatFileSize = (bytes) => {
17977
18194
  if (bytes === 0) return "0 Bytes";
@@ -18116,7 +18333,7 @@ function FileUpload({
18116
18333
  []
18117
18334
  );
18118
18335
  const currentSize = sizeConfig[size];
18119
- const handleDragOver = useCallback17(
18336
+ const handleDragOver = useCallback19(
18120
18337
  (e) => {
18121
18338
  e.preventDefault();
18122
18339
  e.stopPropagation();
@@ -18126,12 +18343,12 @@ function FileUpload({
18126
18343
  },
18127
18344
  [disabled]
18128
18345
  );
18129
- const handleDragLeave = useCallback17((e) => {
18346
+ const handleDragLeave = useCallback19((e) => {
18130
18347
  e.preventDefault();
18131
18348
  e.stopPropagation();
18132
18349
  setIsDragging(false);
18133
18350
  }, []);
18134
- const processFiles = useCallback17(
18351
+ const processFiles = useCallback19(
18135
18352
  async (fileList) => {
18136
18353
  if (fileList.length === 0) return;
18137
18354
  const remainingSlots = maxFiles - files.length;
@@ -18204,7 +18421,7 @@ function FileUpload({
18204
18421
  },
18205
18422
  [files, maxFiles, maxSize, uploadHandler, onUpload, onChange, addToast, t]
18206
18423
  );
18207
- const handleDrop = useCallback17(
18424
+ const handleDrop = useCallback19(
18208
18425
  (e) => {
18209
18426
  e.preventDefault();
18210
18427
  e.stopPropagation();
@@ -18215,7 +18432,7 @@ function FileUpload({
18215
18432
  },
18216
18433
  [disabled, processFiles]
18217
18434
  );
18218
- const handleFileSelect = useCallback17(
18435
+ const handleFileSelect = useCallback19(
18219
18436
  (e) => {
18220
18437
  const selectedFiles = Array.from(e.target.files || []);
18221
18438
  processFiles(selectedFiles);
@@ -18225,7 +18442,7 @@ function FileUpload({
18225
18442
  },
18226
18443
  [processFiles]
18227
18444
  );
18228
- const handleRemove = useCallback17(
18445
+ const handleRemove = useCallback19(
18229
18446
  (fileId) => {
18230
18447
  setFiles((prev) => {
18231
18448
  const fileToRemove = prev.find((f) => f.id === fileId);
@@ -18243,7 +18460,7 @@ function FileUpload({
18243
18460
  const handleBrowseClick = () => {
18244
18461
  fileInputRef.current?.click();
18245
18462
  };
18246
- const handleRetry = useCallback17(
18463
+ const handleRetry = useCallback19(
18247
18464
  (fileEntry) => {
18248
18465
  if (!uploadHandler || !fileEntry.file) return;
18249
18466
  processFiles([fileEntry.file]);
@@ -18282,7 +18499,7 @@ function FileUpload({
18282
18499
  /* @__PURE__ */ jsxs41("div", { className: "flex items-center gap-2", children: [
18283
18500
  /* @__PURE__ */ jsx51("span", { className: cn("text-muted-foreground", size === "lg" ? "text-sm" : "text-xs"), children: file.formattedSize }),
18284
18501
  file.status === "uploading" && /* @__PURE__ */ jsxs41("span", { className: "flex items-center gap-1 text-primary text-xs", children: [
18285
- /* @__PURE__ */ jsx51(Loader26, { className: "w-3 h-3 animate-spin" }),
18502
+ /* @__PURE__ */ jsx51(Loader25, { className: "w-3 h-3 animate-spin" }),
18286
18503
  file.progress,
18287
18504
  "%"
18288
18505
  ] }),
@@ -25924,7 +26141,7 @@ var EditorToolbar = ({
25924
26141
  };
25925
26142
 
25926
26143
  // src/components/UEditor/menus.tsx
25927
- import { useCallback as useCallback20, useEffect as useEffect34, useMemo as useMemo23, useRef as useRef31, useState as useState45 } from "react";
26144
+ import { useCallback as useCallback22, useEffect as useEffect34, useMemo as useMemo23, useRef as useRef31, useState as useState45 } from "react";
25928
26145
  import { createPortal as createPortal8 } from "react-dom";
25929
26146
  import {
25930
26147
  AlignCenter as AlignCenter2,
@@ -26216,7 +26433,7 @@ var CustomBubbleMenu = ({
26216
26433
  const menuRef = useRef31(null);
26217
26434
  const keepOpenRef = useRef31(false);
26218
26435
  const showTimeoutRef = useRef31(null);
26219
- const setKeepOpen = useCallback20((next) => {
26436
+ const setKeepOpen = useCallback22((next) => {
26220
26437
  keepOpenRef.current = next;
26221
26438
  if (next) setIsVisible(true);
26222
26439
  }, []);