@underverse-ui/underverse 1.0.107 → 1.0.110

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) => {
@@ -7178,6 +7179,20 @@ var getOptionDisabled = (option) => {
7178
7179
  var findOptionByValue = (options, value) => {
7179
7180
  return options.find((opt) => getOptionValue(opt) === value);
7180
7181
  };
7182
+ var comboboxScrollClassName = [
7183
+ "scrollbar-thin",
7184
+ "[scrollbar-width:thin]",
7185
+ "[scrollbar-color:color-mix(in_oklch,var(--muted-foreground)_28%,transparent)_transparent]",
7186
+ "[&::-webkit-scrollbar]:w-2",
7187
+ "[&::-webkit-scrollbar-track]:bg-transparent",
7188
+ "[&::-webkit-scrollbar-thumb]:rounded-full",
7189
+ "[&::-webkit-scrollbar-thumb]:border-2",
7190
+ "[&::-webkit-scrollbar-thumb]:border-solid",
7191
+ "[&::-webkit-scrollbar-thumb]:border-transparent",
7192
+ "[&::-webkit-scrollbar-thumb]:bg-clip-padding",
7193
+ "[&::-webkit-scrollbar-thumb]:bg-muted-foreground/25",
7194
+ "[&::-webkit-scrollbar-thumb:hover]:bg-muted-foreground/45"
7195
+ ].join(" ");
7181
7196
  var Combobox = ({
7182
7197
  id,
7183
7198
  options,
@@ -7203,9 +7218,19 @@ var Combobox = ({
7203
7218
  groupBy,
7204
7219
  renderOption,
7205
7220
  renderValue,
7221
+ selectedOption: selectedOptionProp,
7206
7222
  error,
7207
7223
  helperText,
7208
- useOverlayScrollbar = false
7224
+ useOverlayScrollbar = true,
7225
+ virtualized = false,
7226
+ estimatedItemHeight = 44,
7227
+ overscan = 8,
7228
+ searchMode = "auto",
7229
+ onSearchChange,
7230
+ searchDebounceMs = 0,
7231
+ minSearchLength = 0,
7232
+ maxInitialOptions,
7233
+ showSearchPromptWhenEmptyQuery = false
7209
7234
  }) => {
7210
7235
  const tv = useSmartTranslations("ValidationInput");
7211
7236
  const [open, setOpen] = React24.useState(false);
@@ -7215,16 +7240,57 @@ var Combobox = ({
7215
7240
  useShadCNAnimations();
7216
7241
  const inputRef = React24.useRef(null);
7217
7242
  const optionsViewportRef = React24.useRef(null);
7218
- useOverlayScrollbarTarget(optionsViewportRef, { enabled: useOverlayScrollbar });
7243
+ useOverlayScrollbarTarget(optionsViewportRef, { enabled: useOverlayScrollbar && !virtualized });
7219
7244
  const autoId = useId6();
7220
7245
  const resolvedId = id ? String(id) : `combobox-${autoId}`;
7221
7246
  const labelId = label ? `${resolvedId}-label` : void 0;
7222
- const enableSearch = options.length > 10;
7247
+ const enableSearch = options.length > 10 || searchMode === "manual" || minSearchLength > 0 || !!onSearchChange;
7248
+ const trimmedQuery = query.trim();
7249
+ const queryMeetsMinimum = trimmedQuery.length >= minSearchLength;
7250
+ const shouldPromptForSearch = minSearchLength > 0 && !queryMeetsMinimum && (searchMode === "manual" || showSearchPromptWhenEmptyQuery);
7223
7251
  const filteredOptions = React24.useMemo(
7224
- () => enableSearch ? options.filter((o) => getOptionLabel(o).toLowerCase().includes(query.trim().toLowerCase())) : options,
7225
- [options, query, enableSearch]
7252
+ () => {
7253
+ if (shouldPromptForSearch) return [];
7254
+ if (!enableSearch || searchMode === "manual") return options;
7255
+ const normalizedQuery = trimmedQuery.toLowerCase();
7256
+ if (!normalizedQuery) return options;
7257
+ return options.filter((o) => getOptionLabel(o).toLowerCase().includes(normalizedQuery));
7258
+ },
7259
+ [enableSearch, options, searchMode, shouldPromptForSearch, trimmedQuery]
7226
7260
  );
7261
+ const renderLimitedOptions = React24.useMemo(
7262
+ () => {
7263
+ if (trimmedQuery || maxInitialOptions === void 0 || maxInitialOptions < 1) {
7264
+ return filteredOptions;
7265
+ }
7266
+ return filteredOptions.slice(0, maxInitialOptions);
7267
+ },
7268
+ [filteredOptions, maxInitialOptions, trimmedQuery]
7269
+ );
7270
+ const canVirtualize = virtualized && !groupBy;
7271
+ const optionVirtualizer = useVirtualizer({
7272
+ count: canVirtualize ? renderLimitedOptions.length : 0,
7273
+ getScrollElement: () => optionsViewportRef.current,
7274
+ estimateSize: () => estimatedItemHeight,
7275
+ initialRect: { width: 0, height: maxHeight },
7276
+ overscan,
7277
+ enabled: canVirtualize
7278
+ });
7279
+ const virtualItems = canVirtualize ? optionVirtualizer.getVirtualItems() : [];
7227
7280
  const triggerRef = React24.useRef(null);
7281
+ const scrollVirtualListToIndex = React24.useCallback((index) => {
7282
+ if (!canVirtualize || renderLimitedOptions.length === 0) return;
7283
+ optionVirtualizer.scrollToIndex(index, { align: "auto" });
7284
+ }, [canVirtualize, optionVirtualizer, renderLimitedOptions.length]);
7285
+ const scrollVirtualListToStart = React24.useCallback(() => {
7286
+ scrollVirtualListToIndex(0);
7287
+ }, [scrollVirtualListToIndex]);
7288
+ const moveActiveIndex = React24.useCallback((direction) => {
7289
+ if (renderLimitedOptions.length === 0) return;
7290
+ const next = activeIndex === null ? direction === 1 ? 0 : renderLimitedOptions.length - 1 : (activeIndex + direction + renderLimitedOptions.length) % renderLimitedOptions.length;
7291
+ setActiveIndex(next);
7292
+ scrollVirtualListToIndex(next);
7293
+ }, [activeIndex, renderLimitedOptions.length, scrollVirtualListToIndex]);
7228
7294
  const handleSelect = (option) => {
7229
7295
  if (getOptionDisabled(option)) return;
7230
7296
  const val = getOptionValue(option);
@@ -7237,6 +7303,9 @@ var Combobox = ({
7237
7303
  };
7238
7304
  const handleClear = (e) => {
7239
7305
  e.stopPropagation();
7306
+ clearValue();
7307
+ };
7308
+ const clearValue = () => {
7240
7309
  onChange(null);
7241
7310
  setOpen(false);
7242
7311
  };
@@ -7244,13 +7313,26 @@ var Combobox = ({
7244
7313
  if (!open) {
7245
7314
  setQuery("");
7246
7315
  setActiveIndex(null);
7316
+ scrollVirtualListToStart();
7247
7317
  } else if (enableSearch) {
7248
7318
  setTimeout(() => {
7249
7319
  inputRef.current?.focus();
7250
7320
  }, 100);
7251
7321
  }
7252
- }, [open, enableSearch]);
7253
- const selectedOption = findOptionByValue(options, value);
7322
+ }, [enableSearch, open, scrollVirtualListToStart]);
7323
+ React24.useEffect(() => {
7324
+ if (!onSearchChange) return void 0;
7325
+ const timeoutId = window.setTimeout(() => onSearchChange(query), searchDebounceMs);
7326
+ return () => window.clearTimeout(timeoutId);
7327
+ }, [onSearchChange, query, searchDebounceMs]);
7328
+ React24.useEffect(() => {
7329
+ if (process.env.NODE_ENV !== "production" && options.length > 300 && !virtualized && searchMode !== "manual" && maxInitialOptions === void 0) {
7330
+ console.warn(
7331
+ '[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.'
7332
+ );
7333
+ }
7334
+ }, [maxInitialOptions, options.length, searchMode, virtualized]);
7335
+ const selectedOption = findOptionByValue(options, value) ?? (selectedOptionProp && getOptionValue(selectedOptionProp) === value ? selectedOptionProp : void 0);
7254
7336
  const displayValue = selectedOption ? getOptionLabel(selectedOption) : "";
7255
7337
  const selectedIcon = selectedOption ? getOptionIcon(selectedOption) : void 0;
7256
7338
  const hasValue = value !== void 0 && value !== null && value !== "";
@@ -7263,13 +7345,13 @@ var Combobox = ({
7263
7345
  const groupedOptions = React24.useMemo(() => {
7264
7346
  if (!groupBy) return null;
7265
7347
  const groups = {};
7266
- filteredOptions.forEach((opt) => {
7348
+ renderLimitedOptions.forEach((opt) => {
7267
7349
  const group = groupBy(opt);
7268
7350
  if (!groups[group]) groups[group] = [];
7269
7351
  groups[group].push(opt);
7270
7352
  });
7271
7353
  return groups;
7272
- }, [filteredOptions, groupBy]);
7354
+ }, [renderLimitedOptions, groupBy]);
7273
7355
  const itemSizeStyles = {
7274
7356
  sm: "px-2.5 py-1.5 text-xs gap-2",
7275
7357
  md: "px-3 py-2.5 text-sm gap-3",
@@ -7285,60 +7367,75 @@ var Combobox = ({
7285
7367
  md: "h-4 w-4",
7286
7368
  lg: "h-5 w-5"
7287
7369
  };
7288
- const renderOptionItem = (item, index) => {
7370
+ const renderOptionItem = (item, index, virtualItem) => {
7289
7371
  const itemValue = getOptionValue(item);
7290
7372
  const itemLabel = getOptionLabel(item);
7291
7373
  const itemIcon = getOptionIcon(item);
7292
7374
  const itemDescription = getOptionDescription(item);
7293
7375
  const itemDisabled = getOptionDisabled(item);
7294
7376
  const isSelected = itemValue === value;
7295
- return /* @__PURE__ */ jsx29("li", { className: "list-none", children: /* @__PURE__ */ jsxs22(
7296
- "button",
7377
+ return /* @__PURE__ */ jsx29(
7378
+ "li",
7297
7379
  {
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}`);
7380
+ ref: virtualItem ? optionVirtualizer.measureElement : void 0,
7381
+ "data-index": virtualItem?.index,
7382
+ className: "list-none",
7383
+ style: virtualItem ? {
7384
+ position: "absolute",
7385
+ top: 0,
7386
+ left: 0,
7387
+ width: "100%",
7388
+ transform: `translateY(${virtualItem.start}px)`
7389
+ } : void 0,
7390
+ children: /* @__PURE__ */ jsxs22(
7391
+ "button",
7392
+ {
7393
+ id: `${resolvedId}-item-${index}`,
7394
+ type: "button",
7395
+ role: "option",
7396
+ tabIndex: -1,
7397
+ disabled: itemDisabled,
7398
+ "aria-selected": isSelected,
7399
+ onClick: () => handleSelect(item),
7400
+ style: {
7401
+ animationDelay: open ? `${Math.min(index * 15, 150)}ms` : "0ms"
7402
+ },
7403
+ className: cn(
7404
+ "dropdown-item group flex w-full items-center rounded-full text-left",
7405
+ itemSizeStyles[size],
7406
+ "outline-none focus:outline-none focus-visible:outline-none",
7407
+ "transition-all duration-150",
7408
+ !itemDisabled && "cursor-pointer hover:bg-accent/70 hover:shadow-sm",
7409
+ !itemDisabled && "focus:bg-accent/80 focus:text-accent-foreground",
7410
+ index === activeIndex && !itemDisabled && "bg-accent/60",
7411
+ isSelected && "bg-primary/10 text-primary font-medium",
7412
+ itemDisabled && "opacity-50 cursor-not-allowed"
7413
+ ),
7414
+ children: [
7415
+ itemIcon && /* @__PURE__ */ jsx29(
7416
+ "span",
7417
+ {
7418
+ className: cn("shrink-0 flex items-center justify-center", iconSizeStyles[size], isSelected ? "text-primary" : "text-muted-foreground"),
7419
+ children: itemIcon
7420
+ }
7421
+ ),
7422
+ renderOption ? /* @__PURE__ */ jsx29("div", { className: "flex-1 min-w-0", children: renderOption(item, isSelected) }) : /* @__PURE__ */ jsxs22("div", { className: "flex-1 min-w-0", children: [
7423
+ /* @__PURE__ */ jsx29("span", { className: "block truncate", children: itemLabel }),
7424
+ itemDescription && /* @__PURE__ */ jsx29("span", { className: cn("block text-muted-foreground truncate mt-0.5", size === "sm" ? "text-[10px]" : "text-xs"), children: itemDescription })
7425
+ ] }),
7426
+ isSelected && showSelectedIcon && /* @__PURE__ */ jsx29("span", { className: "shrink-0 ml-auto", children: /* @__PURE__ */ jsx29(Check3, { className: cn(checkIconSizeStyles[size], "text-primary") }) })
7427
+ ]
7428
+ }
7429
+ )
7430
+ },
7431
+ `${itemValue}-${index}`
7432
+ );
7335
7433
  };
7336
7434
  const dropdownBody = /* @__PURE__ */ jsxs22(
7337
7435
  "div",
7338
7436
  {
7339
7437
  "data-combobox-dropdown": true,
7340
7438
  "data-state": open ? "open" : "closed",
7341
- id: `${resolvedId}-listbox`,
7342
7439
  className: "w-full rounded-2xl md:rounded-3xl overflow-hidden",
7343
7440
  children: [
7344
7441
  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 +7456,19 @@ var Combobox = ({
7359
7456
  onChange: (e) => {
7360
7457
  setQuery(e.target.value);
7361
7458
  setActiveIndex(null);
7459
+ scrollVirtualListToStart();
7362
7460
  },
7363
7461
  onKeyDown: (e) => {
7364
7462
  if (e.key === "ArrowDown") {
7365
7463
  e.preventDefault();
7366
- setActiveIndex((prev) => {
7367
- const next = prev === null ? 0 : prev + 1;
7368
- return next >= filteredOptions.length ? 0 : next;
7369
- });
7464
+ moveActiveIndex(1);
7370
7465
  } else if (e.key === "ArrowUp") {
7371
7466
  e.preventDefault();
7372
- setActiveIndex((prev) => {
7373
- const next = prev === null ? filteredOptions.length - 1 : prev - 1;
7374
- return next < 0 ? filteredOptions.length - 1 : next;
7375
- });
7467
+ moveActiveIndex(-1);
7376
7468
  } else if (e.key === "Enter") {
7377
7469
  e.preventDefault();
7378
- if (activeIndex !== null && filteredOptions[activeIndex] && !getOptionDisabled(filteredOptions[activeIndex])) {
7379
- handleSelect(filteredOptions[activeIndex]);
7470
+ if (activeIndex !== null && renderLimitedOptions[activeIndex] && !getOptionDisabled(renderLimitedOptions[activeIndex])) {
7471
+ handleSelect(renderLimitedOptions[activeIndex]);
7380
7472
  }
7381
7473
  } else if (e.key === "Escape") {
7382
7474
  e.preventDefault();
@@ -7399,7 +7491,10 @@ var Combobox = ({
7399
7491
  "button",
7400
7492
  {
7401
7493
  type: "button",
7402
- onClick: () => setQuery(""),
7494
+ onClick: () => {
7495
+ setQuery("");
7496
+ scrollVirtualListToStart();
7497
+ },
7403
7498
  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
7499
  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
7500
  }
@@ -7409,14 +7504,22 @@ var Combobox = ({
7409
7504
  "div",
7410
7505
  {
7411
7506
  ref: optionsViewportRef,
7507
+ id: `${resolvedId}-listbox`,
7412
7508
  role: "listbox",
7413
7509
  "aria-labelledby": labelId,
7414
- className: "overflow-y-auto overscroll-contain",
7510
+ className: cn("overflow-y-auto overscroll-contain", (!useOverlayScrollbar || virtualized) && comboboxScrollClassName),
7415
7511
  style: { maxHeight },
7416
7512
  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
7513
  /* @__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
7514
  /* @__PURE__ */ jsx29("span", { className: "text-sm text-muted-foreground", children: loadingText })
7419
- ] }) }) : filteredOptions.length > 0 ? groupedOptions ? (
7515
+ ] }) }) : 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: [
7516
+ /* @__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" }) }),
7517
+ /* @__PURE__ */ jsx29("div", { className: "space-y-1", children: /* @__PURE__ */ jsxs22("span", { className: "block text-sm font-medium text-foreground", children: [
7518
+ "Type at least ",
7519
+ minSearchLength,
7520
+ " characters to search"
7521
+ ] }) })
7522
+ ] }) }) : renderLimitedOptions.length > 0 ? groupedOptions ? (
7420
7523
  // Render grouped options with global index tracking
7421
7524
  (() => {
7422
7525
  let globalIndex = 0;
@@ -7430,7 +7533,14 @@ var Combobox = ({
7430
7533
  })()
7431
7534
  ) : (
7432
7535
  // Render flat options
7433
- /* @__PURE__ */ jsx29("ul", { className: "space-y-0.5", children: filteredOptions.map((item, index) => renderOptionItem(item, index)) })
7536
+ /* @__PURE__ */ jsx29(
7537
+ "ul",
7538
+ {
7539
+ className: "space-y-0.5",
7540
+ style: canVirtualize ? { height: `${optionVirtualizer.getTotalSize()}px`, position: "relative" } : void 0,
7541
+ children: canVirtualize ? virtualItems.map((virtualItem) => renderOptionItem(renderLimitedOptions[virtualItem.index], virtualItem.index, virtualItem)) : renderLimitedOptions.map((item, index) => renderOptionItem(item, index))
7542
+ }
7543
+ )
7434
7544
  ) : /* @__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
7545
  /* @__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
7546
  /* @__PURE__ */ jsxs22("div", { className: "space-y-1", children: [
@@ -7441,7 +7551,10 @@ var Combobox = ({
7441
7551
  "button",
7442
7552
  {
7443
7553
  type: "button",
7444
- onClick: () => setQuery(""),
7554
+ onClick: () => {
7555
+ setQuery("");
7556
+ scrollVirtualListToStart();
7557
+ },
7445
7558
  className: "px-3 py-1.5 text-xs font-medium text-primary bg-primary/10 rounded-full hover:bg-primary/20 transition-colors",
7446
7559
  children: "Clear search"
7447
7560
  }
@@ -7511,7 +7624,13 @@ var Combobox = ({
7511
7624
  tabIndex: 0,
7512
7625
  "aria-label": "Clear selection",
7513
7626
  onClick: handleClear,
7514
- onKeyDown: (e) => (e.key === "Enter" || e.key === " ") && handleClear(e),
7627
+ onKeyDown: (e) => {
7628
+ if (e.key === "Enter" || e.key === " ") {
7629
+ e.preventDefault();
7630
+ e.stopPropagation();
7631
+ clearValue();
7632
+ }
7633
+ },
7515
7634
  className: cn(
7516
7635
  "opacity-0 group-hover:opacity-100 transition-all duration-200",
7517
7636
  "p-1 rounded-lg hover:bg-destructive/10 text-muted-foreground hover:text-destructive"
@@ -8287,7 +8406,7 @@ function formatDateSmart(date, locale = "en") {
8287
8406
  }
8288
8407
 
8289
8408
  // src/components/DatePicker.tsx
8290
- import { Calendar, ChevronLeft as ChevronLeft2, ChevronRight as ChevronRight3, Sparkles as Sparkles2, X as XIcon } from "lucide-react";
8409
+ import { Calendar, ChevronLeft as ChevronLeft2, ChevronRight as ChevronRight3, Sparkles, X as XIcon } from "lucide-react";
8291
8410
  import * as React28 from "react";
8292
8411
  import { useId as useId7 } from "react";
8293
8412
  import { Fragment as Fragment6, jsx as jsx34, jsxs as jsxs24 } from "react/jsx-runtime";
@@ -8666,7 +8785,7 @@ var DatePicker = ({
8666
8785
  isDateDisabled(/* @__PURE__ */ new Date()) && "opacity-50 cursor-not-allowed hover:scale-100 active:scale-100"
8667
8786
  ),
8668
8787
  children: [
8669
- /* @__PURE__ */ jsx34(Sparkles2, { className: sizeStyles8[size].actionIcon }),
8788
+ /* @__PURE__ */ jsx34(Sparkles, { className: sizeStyles8[size].actionIcon }),
8670
8789
  todayLabel || t("today")
8671
8790
  ]
8672
8791
  }
@@ -9246,7 +9365,7 @@ var DateRangePicker = ({
9246
9365
  isTodayUnavailable && "opacity-50 cursor-not-allowed hover:scale-100 active:scale-100"
9247
9366
  ),
9248
9367
  children: [
9249
- /* @__PURE__ */ jsx34(Sparkles2, { className: sizeStyles8[size].actionIcon }),
9368
+ /* @__PURE__ */ jsx34(Sparkles, { className: sizeStyles8[size].actionIcon }),
9250
9369
  t("today")
9251
9370
  ]
9252
9371
  }
@@ -15391,8 +15510,23 @@ function CalendarTimeline({
15391
15510
  // src/components/MultiCombobox.tsx
15392
15511
  import * as React39 from "react";
15393
15512
  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";
15513
+ import { useVirtualizer as useVirtualizer2 } from "@tanstack/react-virtual";
15514
+ 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
15515
  import { Fragment as Fragment13, jsx as jsx45, jsxs as jsxs35 } from "react/jsx-runtime";
15516
+ var comboboxScrollClassName2 = [
15517
+ "scrollbar-thin",
15518
+ "[scrollbar-width:thin]",
15519
+ "[scrollbar-color:color-mix(in_oklch,var(--muted-foreground)_28%,transparent)_transparent]",
15520
+ "[&::-webkit-scrollbar]:w-2",
15521
+ "[&::-webkit-scrollbar-track]:bg-transparent",
15522
+ "[&::-webkit-scrollbar-thumb]:rounded-full",
15523
+ "[&::-webkit-scrollbar-thumb]:border-2",
15524
+ "[&::-webkit-scrollbar-thumb]:border-solid",
15525
+ "[&::-webkit-scrollbar-thumb]:border-transparent",
15526
+ "[&::-webkit-scrollbar-thumb]:bg-clip-padding",
15527
+ "[&::-webkit-scrollbar-thumb]:bg-muted-foreground/25",
15528
+ "[&::-webkit-scrollbar-thumb:hover]:bg-muted-foreground/45"
15529
+ ].join(" ");
15396
15530
  var MultiCombobox = ({
15397
15531
  id,
15398
15532
  options,
@@ -15421,10 +15555,20 @@ var MultiCombobox = ({
15421
15555
  groupBy,
15422
15556
  renderOption,
15423
15557
  renderTag,
15558
+ selectedOptions: selectedOptionsProp,
15424
15559
  error,
15425
15560
  helperText,
15426
15561
  maxTagsVisible = 3,
15427
- useOverlayScrollbar = false
15562
+ useOverlayScrollbar = true,
15563
+ virtualized = false,
15564
+ estimatedItemHeight = 44,
15565
+ overscan = 8,
15566
+ searchMode = "auto",
15567
+ onSearchChange,
15568
+ searchDebounceMs = 0,
15569
+ minSearchLength = 0,
15570
+ maxInitialOptions,
15571
+ showSearchPromptWhenEmptyQuery = false
15428
15572
  }) => {
15429
15573
  const tv = useSmartTranslations("ValidationInput");
15430
15574
  const [query, setQuery] = React39.useState("");
@@ -15434,7 +15578,7 @@ var MultiCombobox = ({
15434
15578
  const inputRef = React39.useRef(null);
15435
15579
  const listRef = React39.useRef([]);
15436
15580
  const optionsListRef = React39.useRef(null);
15437
- useOverlayScrollbarTarget(optionsListRef, { enabled: useOverlayScrollbar });
15581
+ useOverlayScrollbarTarget(optionsListRef, { enabled: useOverlayScrollbar && !virtualized });
15438
15582
  const triggerRef = React39.useRef(null);
15439
15583
  useShadCNAnimations();
15440
15584
  const normalizedOptions = React39.useMemo(
@@ -15443,23 +15587,52 @@ var MultiCombobox = ({
15443
15587
  ),
15444
15588
  [options]
15445
15589
  );
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
- );
15590
+ const enableSearch = normalizedOptions.length > 10 || searchMode === "manual" || minSearchLength > 0 || !!onSearchChange;
15591
+ const trimmedQuery = query.trim();
15592
+ const queryMeetsMinimum = trimmedQuery.length >= minSearchLength;
15593
+ const shouldPromptForSearch = minSearchLength > 0 && !queryMeetsMinimum && (searchMode === "manual" || showSearchPromptWhenEmptyQuery);
15594
+ const filtered = React39.useMemo(() => {
15595
+ if (shouldPromptForSearch) return [];
15596
+ if (!enableSearch || searchMode === "manual") return normalizedOptions;
15597
+ const normalizedQuery = trimmedQuery.toLowerCase();
15598
+ if (!normalizedQuery) return normalizedOptions;
15599
+ return normalizedOptions.filter(
15600
+ (opt) => opt.label.toLowerCase().includes(normalizedQuery) || opt.description?.toLowerCase().includes(normalizedQuery)
15601
+ );
15602
+ }, [enableSearch, normalizedOptions, searchMode, shouldPromptForSearch, trimmedQuery]);
15603
+ const renderLimitedOptions = React39.useMemo(() => {
15604
+ if (trimmedQuery || maxInitialOptions === void 0 || maxInitialOptions < 1) {
15605
+ return filtered;
15606
+ }
15607
+ return filtered.slice(0, maxInitialOptions);
15608
+ }, [filtered, maxInitialOptions, trimmedQuery]);
15609
+ const canVirtualize = virtualized && !groupBy;
15610
+ const optionVirtualizer = useVirtualizer2({
15611
+ count: canVirtualize ? renderLimitedOptions.length : 0,
15612
+ getScrollElement: () => optionsListRef.current,
15613
+ estimateSize: () => estimatedItemHeight,
15614
+ initialRect: { width: 0, height: maxHeight },
15615
+ overscan,
15616
+ enabled: canVirtualize
15617
+ });
15618
+ const virtualItems = canVirtualize ? optionVirtualizer.getVirtualItems() : [];
15619
+ const scrollVirtualListToIndex = React39.useCallback((index) => {
15620
+ if (!canVirtualize || renderLimitedOptions.length === 0) return;
15621
+ optionVirtualizer.scrollToIndex(index, { align: "auto" });
15622
+ }, [canVirtualize, optionVirtualizer, renderLimitedOptions.length]);
15623
+ const scrollVirtualListToStart = React39.useCallback(() => {
15624
+ scrollVirtualListToIndex(0);
15625
+ }, [scrollVirtualListToIndex]);
15453
15626
  const groupedOptions = React39.useMemo(() => {
15454
15627
  if (!groupBy) return null;
15455
15628
  const groups = /* @__PURE__ */ new Map();
15456
- filtered.forEach((opt) => {
15629
+ renderLimitedOptions.forEach((opt) => {
15457
15630
  const group = groupBy(opt);
15458
15631
  if (!groups.has(group)) groups.set(group, []);
15459
15632
  groups.get(group).push(opt);
15460
15633
  });
15461
15634
  return groups;
15462
- }, [filtered, groupBy]);
15635
+ }, [renderLimitedOptions, groupBy]);
15463
15636
  const toggleSelect = (optionValue) => {
15464
15637
  const option = normalizedOptions.find((o) => o.value === optionValue);
15465
15638
  if (option?.disabled || disabledOptions.includes(optionValue)) return;
@@ -15477,11 +15650,26 @@ var MultiCombobox = ({
15477
15650
  };
15478
15651
  const handleKeyDown2 = (e) => {
15479
15652
  if (!open) setOpen(true);
15480
- if (e.key === "Enter") {
15653
+ if (e.key === "ArrowDown") {
15654
+ e.preventDefault();
15655
+ if (renderLimitedOptions.length === 0) return;
15656
+ const next = activeIndex === null ? 0 : (activeIndex + 1) % renderLimitedOptions.length;
15657
+ setActiveIndex(next);
15658
+ scrollVirtualListToIndex(next);
15659
+ } else if (e.key === "ArrowUp") {
15481
15660
  e.preventDefault();
15482
- if (activeIndex !== null && filtered[activeIndex]) {
15483
- toggleSelect(filtered[activeIndex].value);
15661
+ if (renderLimitedOptions.length === 0) return;
15662
+ const next = activeIndex === null ? renderLimitedOptions.length - 1 : (activeIndex - 1 + renderLimitedOptions.length) % renderLimitedOptions.length;
15663
+ setActiveIndex(next);
15664
+ scrollVirtualListToIndex(next);
15665
+ } else if (e.key === "Enter") {
15666
+ e.preventDefault();
15667
+ if (activeIndex !== null && renderLimitedOptions[activeIndex]) {
15668
+ toggleSelect(renderLimitedOptions[activeIndex].value);
15484
15669
  }
15670
+ } else if (e.key === "Escape") {
15671
+ e.preventDefault();
15672
+ setOpen(false);
15485
15673
  }
15486
15674
  };
15487
15675
  const handleClearAll = () => {
@@ -15498,8 +15686,24 @@ var MultiCombobox = ({
15498
15686
  setTimeout(() => {
15499
15687
  inputRef.current?.focus();
15500
15688
  }, 100);
15689
+ } else if (!open) {
15690
+ setQuery("");
15691
+ setActiveIndex(null);
15692
+ scrollVirtualListToStart();
15693
+ }
15694
+ }, [enableSearch, open, scrollVirtualListToStart]);
15695
+ React39.useEffect(() => {
15696
+ if (!onSearchChange) return void 0;
15697
+ const timeoutId = window.setTimeout(() => onSearchChange(query), searchDebounceMs);
15698
+ return () => window.clearTimeout(timeoutId);
15699
+ }, [onSearchChange, query, searchDebounceMs]);
15700
+ React39.useEffect(() => {
15701
+ if (process.env.NODE_ENV !== "production" && normalizedOptions.length > 300 && !virtualized && searchMode !== "manual" && maxInitialOptions === void 0) {
15702
+ console.warn(
15703
+ '[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.'
15704
+ );
15501
15705
  }
15502
- }, [open, enableSearch]);
15706
+ }, [maxInitialOptions, normalizedOptions.length, searchMode, virtualized]);
15503
15707
  const sizeStyles8 = {
15504
15708
  sm: {
15505
15709
  trigger: "h-8 px-3 py-1.5 text-sm md:h-7 md:text-xs",
@@ -15533,25 +15737,38 @@ var MultiCombobox = ({
15533
15737
  const labelId = label ? `${resolvedId}-label` : void 0;
15534
15738
  const labelSize = size === "sm" ? "text-xs" : size === "lg" ? "text-base" : "text-sm";
15535
15739
  const listboxId = `${resolvedId}-listbox`;
15536
- const renderOptionItem = (item, index) => {
15740
+ const renderOptionItem = (item, index, virtualItem) => {
15537
15741
  const isSelected = value.includes(item.value);
15538
15742
  const isDisabled = item.disabled || disabledOptions.includes(item.value);
15539
15743
  const optionIcon = item.icon;
15540
15744
  const optionDesc = item.description;
15745
+ const itemStyle = {
15746
+ animationDelay: open ? `${Math.min(index * 20, 200)}ms` : "0ms",
15747
+ ...virtualItem ? {
15748
+ position: "absolute",
15749
+ top: 0,
15750
+ left: 0,
15751
+ width: "100%",
15752
+ transform: `translateY(${virtualItem.start}px)`
15753
+ } : {}
15754
+ };
15755
+ const measureRef = virtualItem ? optionVirtualizer.measureElement : void 0;
15541
15756
  if (renderOption) {
15542
15757
  return /* @__PURE__ */ jsx45(
15543
15758
  "li",
15544
15759
  {
15545
15760
  ref: (node) => {
15761
+ measureRef?.(node);
15546
15762
  listRef.current[index] = node;
15547
15763
  },
15764
+ "data-index": virtualItem?.index,
15765
+ style: itemStyle,
15548
15766
  onClick: (e) => {
15549
15767
  e.preventDefault();
15550
15768
  e.stopPropagation();
15551
15769
  if (!isDisabled) toggleSelect(item.value);
15552
15770
  inputRef.current?.focus();
15553
15771
  },
15554
- style: { animationDelay: open ? `${Math.min(index * 20, 200)}ms` : "0ms" },
15555
15772
  className: cn("dropdown-item", isDisabled && "opacity-50 cursor-not-allowed pointer-events-none"),
15556
15773
  children: renderOption(item, isSelected)
15557
15774
  },
@@ -15562,15 +15779,17 @@ var MultiCombobox = ({
15562
15779
  "li",
15563
15780
  {
15564
15781
  ref: (node) => {
15782
+ measureRef?.(node);
15565
15783
  listRef.current[index] = node;
15566
15784
  },
15785
+ "data-index": virtualItem?.index,
15786
+ style: itemStyle,
15567
15787
  onClick: (e) => {
15568
15788
  e.preventDefault();
15569
15789
  e.stopPropagation();
15570
15790
  if (!isDisabled) toggleSelect(item.value);
15571
15791
  inputRef.current?.focus();
15572
15792
  },
15573
- style: { animationDelay: open ? `${Math.min(index * 20, 200)}ms` : "0ms" },
15574
15793
  className: cn(
15575
15794
  "dropdown-item flex cursor-pointer items-center gap-3 rounded-full transition-all duration-200",
15576
15795
  sizeStyles8[size].item,
@@ -15634,6 +15853,7 @@ var MultiCombobox = ({
15634
15853
  onChange: (e) => {
15635
15854
  setQuery(e.target.value);
15636
15855
  setActiveIndex(null);
15856
+ scrollVirtualListToStart();
15637
15857
  },
15638
15858
  onKeyDown: handleKeyDown2,
15639
15859
  placeholder: searchPlaceholder,
@@ -15644,7 +15864,10 @@ var MultiCombobox = ({
15644
15864
  "button",
15645
15865
  {
15646
15866
  type: "button",
15647
- onClick: () => setQuery(""),
15867
+ onClick: () => {
15868
+ setQuery("");
15869
+ scrollVirtualListToStart();
15870
+ },
15648
15871
  className: "absolute right-3 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground transition-colors",
15649
15872
  children: /* @__PURE__ */ jsx45(X13, { className: "w-4 h-4" })
15650
15873
  }
@@ -15658,37 +15881,63 @@ var MultiCombobox = ({
15658
15881
  "aria-multiselectable": "true",
15659
15882
  ref: optionsListRef,
15660
15883
  style: { maxHeight },
15661
- className: cn("overflow-y-auto p-1.5", size === "lg" ? "text-base" : size === "sm" ? "text-xs" : "text-sm"),
15884
+ className: cn(
15885
+ "overflow-y-auto p-1.5",
15886
+ (!useOverlayScrollbar || virtualized) && comboboxScrollClassName2,
15887
+ size === "lg" ? "text-base" : size === "sm" ? "text-xs" : "text-sm"
15888
+ ),
15662
15889
  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
15890
  /* @__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" })
15891
+ /* @__PURE__ */ jsx45(Loader23, { className: "h-8 w-8 animate-spin text-primary" }),
15892
+ /* @__PURE__ */ jsx45(Sparkles2, { className: "h-4 w-4 text-primary/60 absolute -top-1 -right-1 animate-pulse" })
15666
15893
  ] }),
15667
15894
  /* @__PURE__ */ jsx45("span", { className: "text-muted-foreground font-medium", children: loadingText })
15668
- ] }) }) : filtered.length ? groupedOptions ? (
15895
+ ] }) }) : 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: [
15896
+ /* @__PURE__ */ jsx45(Search5, { className: "h-10 w-10 opacity-30 text-muted-foreground" }),
15897
+ /* @__PURE__ */ jsxs35("span", { className: "font-medium block text-foreground", children: [
15898
+ "Type at least ",
15899
+ minSearchLength,
15900
+ " characters to search"
15901
+ ] })
15902
+ ] }) }) : renderLimitedOptions.length ? groupedOptions ? (
15669
15903
  // Render grouped options
15670
15904
  Array.from(groupedOptions.entries()).map(([group, items]) => /* @__PURE__ */ jsxs35("li", { className: "mb-2", children: [
15671
15905
  /* @__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))) })
15906
+ /* @__PURE__ */ jsx45("ul", { children: items.map((item) => renderOptionItem(item, renderLimitedOptions.indexOf(item))) })
15673
15907
  ] }, group))
15674
15908
  ) : (
15675
15909
  // Render flat options
15676
- filtered.map((item, index) => renderOptionItem(item, index))
15910
+ 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
15911
  ) : /* @__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
15912
  /* @__PURE__ */ jsx45(SearchX2, { className: "h-10 w-10 opacity-30 text-muted-foreground" }),
15679
15913
  /* @__PURE__ */ jsxs35("div", { className: "space-y-1", children: [
15680
15914
  /* @__PURE__ */ jsx45("span", { className: "font-medium block", children: emptyText }),
15681
15915
  query && /* @__PURE__ */ jsx45("span", { className: "text-xs opacity-60", children: "Try a different search term" })
15682
15916
  ] }),
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
- ] })
15917
+ query && /* @__PURE__ */ jsxs35(
15918
+ "button",
15919
+ {
15920
+ type: "button",
15921
+ onClick: () => {
15922
+ setQuery("");
15923
+ scrollVirtualListToStart();
15924
+ },
15925
+ className: "text-xs text-primary hover:underline flex items-center gap-1",
15926
+ children: [
15927
+ /* @__PURE__ */ jsx45(X13, { className: "w-3 h-3" }),
15928
+ "Clear search"
15929
+ ]
15930
+ }
15931
+ )
15687
15932
  ] }) })
15688
15933
  }
15689
15934
  )
15690
15935
  ] });
15691
- const selectedOptions = value.map((v) => normalizedOptions.find((o) => o.value === v)).filter(Boolean);
15936
+ const selectedOptionFallbackMap = React39.useMemo(
15937
+ () => new Map((selectedOptionsProp ?? []).map((option) => [option.value, option])),
15938
+ [selectedOptionsProp]
15939
+ );
15940
+ const selectedOptions = value.map((v) => normalizedOptions.find((o) => o.value === v) ?? selectedOptionFallbackMap.get(v)).filter(Boolean);
15692
15941
  const visibleTags = maxTagsVisible ? selectedOptions.slice(0, maxTagsVisible) : selectedOptions;
15693
15942
  const hiddenCount = maxTagsVisible ? Math.max(0, selectedOptions.length - maxTagsVisible) : 0;
15694
15943
  const triggerButton = /* @__PURE__ */ jsxs35(
@@ -17732,8 +17981,8 @@ function CategoryTreeSelect(props) {
17732
17981
  }
17733
17982
 
17734
17983
  // 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";
17984
+ import { useState as useState32, useRef as useRef21, useCallback as useCallback18 } from "react";
17985
+ import { Upload, X as X15, Image as ImageIcon, Loader2 as Loader24, Check as Check7 } from "lucide-react";
17737
17986
  import { jsx as jsx50, jsxs as jsxs40 } from "react/jsx-runtime";
17738
17987
  function ImageUpload({
17739
17988
  onUpload,
@@ -17761,7 +18010,7 @@ function ImageUpload({
17761
18010
  md: "w-24 h-24",
17762
18011
  lg: "w-32 h-32"
17763
18012
  };
17764
- const handleDragOver = useCallback16(
18013
+ const handleDragOver = useCallback18(
17765
18014
  (e) => {
17766
18015
  e.preventDefault();
17767
18016
  if (!disabled) {
@@ -17770,11 +18019,11 @@ function ImageUpload({
17770
18019
  },
17771
18020
  [disabled]
17772
18021
  );
17773
- const handleDragLeave = useCallback16((e) => {
18022
+ const handleDragLeave = useCallback18((e) => {
17774
18023
  e.preventDefault();
17775
18024
  setIsDragging(false);
17776
18025
  }, []);
17777
- const handleFiles = useCallback16(
18026
+ const handleFiles = useCallback18(
17778
18027
  async (files) => {
17779
18028
  if (files.length === 0) return;
17780
18029
  const validFiles = files.filter((file) => {
@@ -17841,7 +18090,7 @@ function ImageUpload({
17841
18090
  },
17842
18091
  [maxSize, addToast, onUpload]
17843
18092
  );
17844
- const handleDrop = useCallback16(
18093
+ const handleDrop = useCallback18(
17845
18094
  (e) => {
17846
18095
  e.preventDefault();
17847
18096
  setIsDragging(false);
@@ -17851,7 +18100,7 @@ function ImageUpload({
17851
18100
  },
17852
18101
  [disabled, handleFiles]
17853
18102
  );
17854
- const handleFileSelect = useCallback16(
18103
+ const handleFileSelect = useCallback18(
17855
18104
  (e) => {
17856
18105
  const files = Array.from(e.target.files || []);
17857
18106
  handleFiles(files);
@@ -17883,7 +18132,7 @@ function ImageUpload({
17883
18132
  onDrop: handleDrop,
17884
18133
  children: [
17885
18134
  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" }),
18135
+ /* @__PURE__ */ jsx50(Loader24, { className: "w-6 h-6 animate-spin text-primary" }),
17887
18136
  /* @__PURE__ */ jsx50("span", { className: "text-sm font-medium", children: "Uploading..." })
17888
18137
  ] }) }),
17889
18138
  /* @__PURE__ */ jsxs40("div", { className: "space-y-4", children: [
@@ -17967,11 +18216,11 @@ import {
17967
18216
  FileSpreadsheet,
17968
18217
  FileText,
17969
18218
  FileVideo,
17970
- Loader2 as Loader26,
18219
+ Loader2 as Loader25,
17971
18220
  Trash2,
17972
18221
  Upload as Upload2
17973
18222
  } from "lucide-react";
17974
- import { useCallback as useCallback17, useMemo as useMemo20, useRef as useRef22, useState as useState33 } from "react";
18223
+ import { useCallback as useCallback19, useMemo as useMemo20, useRef as useRef22, useState as useState33 } from "react";
17975
18224
  import { Fragment as Fragment16, jsx as jsx51, jsxs as jsxs41 } from "react/jsx-runtime";
17976
18225
  var formatFileSize = (bytes) => {
17977
18226
  if (bytes === 0) return "0 Bytes";
@@ -18116,7 +18365,7 @@ function FileUpload({
18116
18365
  []
18117
18366
  );
18118
18367
  const currentSize = sizeConfig[size];
18119
- const handleDragOver = useCallback17(
18368
+ const handleDragOver = useCallback19(
18120
18369
  (e) => {
18121
18370
  e.preventDefault();
18122
18371
  e.stopPropagation();
@@ -18126,12 +18375,12 @@ function FileUpload({
18126
18375
  },
18127
18376
  [disabled]
18128
18377
  );
18129
- const handleDragLeave = useCallback17((e) => {
18378
+ const handleDragLeave = useCallback19((e) => {
18130
18379
  e.preventDefault();
18131
18380
  e.stopPropagation();
18132
18381
  setIsDragging(false);
18133
18382
  }, []);
18134
- const processFiles = useCallback17(
18383
+ const processFiles = useCallback19(
18135
18384
  async (fileList) => {
18136
18385
  if (fileList.length === 0) return;
18137
18386
  const remainingSlots = maxFiles - files.length;
@@ -18204,7 +18453,7 @@ function FileUpload({
18204
18453
  },
18205
18454
  [files, maxFiles, maxSize, uploadHandler, onUpload, onChange, addToast, t]
18206
18455
  );
18207
- const handleDrop = useCallback17(
18456
+ const handleDrop = useCallback19(
18208
18457
  (e) => {
18209
18458
  e.preventDefault();
18210
18459
  e.stopPropagation();
@@ -18215,7 +18464,7 @@ function FileUpload({
18215
18464
  },
18216
18465
  [disabled, processFiles]
18217
18466
  );
18218
- const handleFileSelect = useCallback17(
18467
+ const handleFileSelect = useCallback19(
18219
18468
  (e) => {
18220
18469
  const selectedFiles = Array.from(e.target.files || []);
18221
18470
  processFiles(selectedFiles);
@@ -18225,7 +18474,7 @@ function FileUpload({
18225
18474
  },
18226
18475
  [processFiles]
18227
18476
  );
18228
- const handleRemove = useCallback17(
18477
+ const handleRemove = useCallback19(
18229
18478
  (fileId) => {
18230
18479
  setFiles((prev) => {
18231
18480
  const fileToRemove = prev.find((f) => f.id === fileId);
@@ -18243,7 +18492,7 @@ function FileUpload({
18243
18492
  const handleBrowseClick = () => {
18244
18493
  fileInputRef.current?.click();
18245
18494
  };
18246
- const handleRetry = useCallback17(
18495
+ const handleRetry = useCallback19(
18247
18496
  (fileEntry) => {
18248
18497
  if (!uploadHandler || !fileEntry.file) return;
18249
18498
  processFiles([fileEntry.file]);
@@ -18282,7 +18531,7 @@ function FileUpload({
18282
18531
  /* @__PURE__ */ jsxs41("div", { className: "flex items-center gap-2", children: [
18283
18532
  /* @__PURE__ */ jsx51("span", { className: cn("text-muted-foreground", size === "lg" ? "text-sm" : "text-xs"), children: file.formattedSize }),
18284
18533
  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" }),
18534
+ /* @__PURE__ */ jsx51(Loader25, { className: "w-3 h-3 animate-spin" }),
18286
18535
  file.progress,
18287
18536
  "%"
18288
18537
  ] }),
@@ -25924,7 +26173,7 @@ var EditorToolbar = ({
25924
26173
  };
25925
26174
 
25926
26175
  // src/components/UEditor/menus.tsx
25927
- import { useCallback as useCallback20, useEffect as useEffect34, useMemo as useMemo23, useRef as useRef31, useState as useState45 } from "react";
26176
+ import { useCallback as useCallback22, useEffect as useEffect34, useMemo as useMemo23, useRef as useRef31, useState as useState45 } from "react";
25928
26177
  import { createPortal as createPortal8 } from "react-dom";
25929
26178
  import {
25930
26179
  AlignCenter as AlignCenter2,
@@ -26216,7 +26465,7 @@ var CustomBubbleMenu = ({
26216
26465
  const menuRef = useRef31(null);
26217
26466
  const keepOpenRef = useRef31(false);
26218
26467
  const showTimeoutRef = useRef31(null);
26219
- const setKeepOpen = useCallback20((next) => {
26468
+ const setKeepOpen = useCallback22((next) => {
26220
26469
  keepOpenRef.current = next;
26221
26470
  if (next) setIsVisible(true);
26222
26471
  }, []);