@sesionario/blocks 1.8.0 → 1.10.0

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.cjs CHANGED
@@ -521,6 +521,9 @@ function ExpandableText({
521
521
  var MIN_SCALE = 1;
522
522
  var MAX_SCALE = 4;
523
523
  var DOUBLE_TAP_MS = 300;
524
+ var SWIPE_LOCK_PX = 8;
525
+ var SWIPE_COMMIT_PX = 56;
526
+ var SWIPE_SNAP_MS = 180;
524
527
  var IDENTITY = { scale: 1, tx: 0, ty: 0 };
525
528
  function clamp(value, min, max) {
526
529
  return Math.min(max, Math.max(min, value));
@@ -538,6 +541,10 @@ function zoomToward(prev, nextScale, originX, originY) {
538
541
  function pointerDistance(a, b) {
539
542
  return Math.hypot(b.x - a.x, b.y - a.y);
540
543
  }
544
+ function prefersReducedMotion() {
545
+ if (typeof window === "undefined" || !window.matchMedia) return false;
546
+ return window.matchMedia("(prefers-reduced-motion: reduce)").matches;
547
+ }
541
548
  function ImageLightbox({
542
549
  images,
543
550
  index,
@@ -553,15 +560,24 @@ function ImageLightbox({
553
560
  const [transform, setTransform] = React6.useState(IDENTITY);
554
561
  const transformRef = React6.useRef(transform);
555
562
  transformRef.current = transform;
563
+ const [isSwipeDragging, setIsSwipeDragging] = React6.useState(false);
564
+ const [snapBack, setSnapBack] = React6.useState(false);
556
565
  const stageRef = React6.useRef(null);
557
566
  const pointersRef = React6.useRef(/* @__PURE__ */ new Map());
558
567
  const pinchStartRef = React6.useRef(null);
559
568
  const panRef = React6.useRef(null);
569
+ const swipeRef = React6.useRef(null);
560
570
  const lastTapRef = React6.useRef(
561
571
  null
562
572
  );
573
+ const indexRef = React6.useRef(index);
574
+ indexRef.current = index;
575
+ const imagesLengthRef = React6.useRef(images.length);
576
+ imagesLengthRef.current = images.length;
563
577
  const resetZoom = React6.useCallback(() => {
564
578
  setTransform(IDENTITY);
579
+ setIsSwipeDragging(false);
580
+ setSnapBack(false);
565
581
  }, []);
566
582
  React6.useEffect(() => {
567
583
  resetZoom();
@@ -618,6 +634,10 @@ function ImageLightbox({
618
634
  stage.addEventListener("wheel", handleWheel, { passive: false });
619
635
  return () => stage.removeEventListener("wheel", handleWheel);
620
636
  }, [stageOrigin]);
637
+ function clearSwipe() {
638
+ swipeRef.current = null;
639
+ setIsSwipeDragging(false);
640
+ }
621
641
  function beginPinch() {
622
642
  const pts = [...pointersRef.current.values()];
623
643
  const [a, b] = pts;
@@ -632,13 +652,18 @@ function ImageLightbox({
632
652
  transform: { ...transformRef.current }
633
653
  };
634
654
  panRef.current = null;
655
+ clearSwipe();
656
+ setSnapBack(false);
635
657
  }
636
658
  function onPointerDown(e) {
637
659
  e.stopPropagation();
638
660
  const stage = stageRef.current;
639
661
  if (!stage) return;
640
- stage.setPointerCapture(e.pointerId);
662
+ if (typeof stage.setPointerCapture === "function") {
663
+ stage.setPointerCapture(e.pointerId);
664
+ }
641
665
  pointersRef.current.set(e.pointerId, { x: e.clientX, y: e.clientY });
666
+ setSnapBack(false);
642
667
  if (pointersRef.current.size === 2) {
643
668
  beginPinch();
644
669
  return;
@@ -652,6 +677,17 @@ function ImageLightbox({
652
677
  originTy: transformRef.current.ty,
653
678
  moved: false
654
679
  };
680
+ return;
681
+ }
682
+ if (hasMultiple) {
683
+ swipeRef.current = {
684
+ pointerId: e.pointerId,
685
+ startX: e.clientX,
686
+ startY: e.clientY,
687
+ moved: false,
688
+ axis: null
689
+ };
690
+ setIsSwipeDragging(true);
655
691
  }
656
692
  }
657
693
  function onPointerMove(e) {
@@ -676,19 +712,62 @@ function ImageLightbox({
676
712
  }
677
713
  const pan = panRef.current;
678
714
  if (pan && pan.pointerId === e.pointerId) {
679
- const dx = e.clientX - pan.startX;
680
- const dy = e.clientY - pan.startY;
681
- if (Math.abs(dx) > 2 || Math.abs(dy) > 2) pan.moved = true;
715
+ const dx2 = e.clientX - pan.startX;
716
+ const dy2 = e.clientY - pan.startY;
717
+ if (Math.abs(dx2) > 2 || Math.abs(dy2) > 2) pan.moved = true;
682
718
  setTransform((prev) => ({
683
719
  ...prev,
684
- tx: pan.originTx + dx,
685
- ty: pan.originTy + dy
720
+ tx: pan.originTx + dx2,
721
+ ty: pan.originTy + dy2
686
722
  }));
723
+ return;
724
+ }
725
+ const swipe = swipeRef.current;
726
+ if (!swipe || swipe.pointerId !== e.pointerId) return;
727
+ const dx = e.clientX - swipe.startX;
728
+ const dy = e.clientY - swipe.startY;
729
+ if (Math.abs(dx) > 2 || Math.abs(dy) > 2) swipe.moved = true;
730
+ if (swipe.axis === null) {
731
+ const absX = Math.abs(dx);
732
+ const absY = Math.abs(dy);
733
+ if (absX < SWIPE_LOCK_PX && absY < SWIPE_LOCK_PX) return;
734
+ swipe.axis = absX >= absY ? "x" : "y";
735
+ if (swipe.axis === "y") {
736
+ clearSwipe();
737
+ setTransform(IDENTITY);
738
+ return;
739
+ }
740
+ }
741
+ if (swipe.axis === "x") {
742
+ setTransform({ scale: MIN_SCALE, tx: dx, ty: 0 });
743
+ }
744
+ }
745
+ function finishSwipe(e) {
746
+ const swipe = swipeRef.current;
747
+ if (!swipe || swipe.pointerId !== e.pointerId) return false;
748
+ const dx = e.clientX - swipe.startX;
749
+ const wasMoved = swipe.moved;
750
+ const axis = swipe.axis;
751
+ clearSwipe();
752
+ if (axis === "x" && Math.abs(dx) >= SWIPE_COMMIT_PX) {
753
+ const length = imagesLengthRef.current;
754
+ const current = indexRef.current;
755
+ if (dx < 0) {
756
+ onIndexChange((current + 1) % length);
757
+ } else {
758
+ onIndexChange((current - 1 + length) % length);
759
+ }
760
+ return true;
687
761
  }
762
+ if (wasMoved || transformRef.current.tx !== 0) {
763
+ if (!prefersReducedMotion()) setSnapBack(true);
764
+ setTransform(IDENTITY);
765
+ }
766
+ return wasMoved;
688
767
  }
689
768
  function onPointerUp(e) {
690
769
  const stage = stageRef.current;
691
- if (stage?.hasPointerCapture(e.pointerId)) {
770
+ if (stage && typeof stage.hasPointerCapture === "function" && stage.hasPointerCapture(e.pointerId) && typeof stage.releasePointerCapture === "function") {
692
771
  stage.releasePointerCapture(e.pointerId);
693
772
  }
694
773
  pointersRef.current.delete(e.pointerId);
@@ -709,6 +788,8 @@ function ImageLightbox({
709
788
  };
710
789
  }
711
790
  }
791
+ const swipeHandled = finishSwipe(e);
792
+ if (swipeHandled) return;
712
793
  const pan = panRef.current;
713
794
  if (pan && pan.pointerId === e.pointerId) {
714
795
  const wasMoved = pan.moved;
@@ -730,6 +811,7 @@ function ImageLightbox({
730
811
  e.stopPropagation();
731
812
  }
732
813
  const { scale, tx, ty } = transform;
814
+ const transition = snapBack && !isSwipeDragging && !prefersReducedMotion() ? `transform ${SWIPE_SNAP_MS}ms ease-out` : void 0;
733
815
  return reactDom.createPortal(
734
816
  /* @__PURE__ */ jsxRuntime.jsxs(
735
817
  "div",
@@ -769,6 +851,7 @@ function ImageLightbox({
769
851
  ref: stageRef,
770
852
  className: "relative flex max-h-full max-w-full touch-none items-center justify-center",
771
853
  "data-scale": scale,
854
+ "data-swipe-dragging": isSwipeDragging ? "true" : "false",
772
855
  onClick: stopDialogClose,
773
856
  onPointerDown,
774
857
  onPointerMove,
@@ -783,8 +866,10 @@ function ImageLightbox({
783
866
  className: "max-h-[calc(100dvh-2rem)] max-w-full select-none rounded-lg object-contain",
784
867
  style: {
785
868
  transform: `translate(${tx}px, ${ty}px) scale(${scale})`,
869
+ transition,
786
870
  cursor: scale > MIN_SCALE ? "grab" : "default"
787
- }
871
+ },
872
+ onTransitionEnd: () => setSnapBack(false)
788
873
  }
789
874
  )
790
875
  }
@@ -1411,7 +1496,7 @@ function LanguageSwitcher({
1411
1496
  const sizes = sizeClasses2[size];
1412
1497
  const shapeClass = shapeClasses2[shape];
1413
1498
  const reduceMotion = framerMotion.useReducedMotion();
1414
- const defaultLabels4 = {
1499
+ const defaultLabels5 = {
1415
1500
  language: labels.language ?? "Language"
1416
1501
  };
1417
1502
  if (variant === "mini") {
@@ -1421,7 +1506,7 @@ function LanguageSwitcher({
1421
1506
  {
1422
1507
  variant: "ghost",
1423
1508
  size: "icon",
1424
- "aria-label": defaultLabels4.language,
1509
+ "aria-label": defaultLabels5.language,
1425
1510
  className: cn(sizes.buttonMini, shapeClass, className),
1426
1511
  children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Languages, { className: sizes.iconMini })
1427
1512
  }
@@ -3710,6 +3795,19 @@ var PLAYBACK_RATES = [0.75, 1, 1.25, 1.5, 2];
3710
3795
  var CONTROLS_HIDE_MS = 3e3;
3711
3796
  var LIVE_EDGE_THRESHOLD_S = 8;
3712
3797
  var SEEK_STEP_S = 5;
3798
+ function asWebkitVideo(video) {
3799
+ return video;
3800
+ }
3801
+ function hasWebkitVideoFullscreen(video) {
3802
+ const webkit = asWebkitVideo(video);
3803
+ return typeof webkit?.webkitEnterFullscreen === "function";
3804
+ }
3805
+ function hasStandardFullscreen() {
3806
+ if (typeof document === "undefined") return false;
3807
+ if (document.fullscreenEnabled) return true;
3808
+ const doc = document;
3809
+ return !!doc.webkitFullscreenEnabled;
3810
+ }
3713
3811
  function formatTime(seconds) {
3714
3812
  if (!Number.isFinite(seconds) || seconds < 0) return "0:00";
3715
3813
  const total = Math.floor(seconds);
@@ -3783,10 +3881,10 @@ function VideoPlayer({
3783
3881
  null
3784
3882
  );
3785
3883
  const [behindLive, setBehindLive] = React6__namespace.useState(false);
3884
+ const [webkitFullscreenSupported, setWebkitFullscreenSupported] = React6__namespace.useState(false);
3786
3885
  const hideTimerRef = React6__namespace.useRef(null);
3787
3886
  const pipSupported = typeof document !== "undefined" && "pictureInPictureEnabled" in document && document.pictureInPictureEnabled;
3788
- const fullscreenSupported = typeof document !== "undefined" && (!!document.fullscreenEnabled || // Safari
3789
- "webkitFullscreenEnabled" in document);
3887
+ const fullscreenSupported = hasStandardFullscreen() || webkitFullscreenSupported;
3790
3888
  React6__namespace.useEffect(() => {
3791
3889
  const video = videoRef.current;
3792
3890
  if (!video || !active) return;
@@ -3820,11 +3918,26 @@ function VideoPlayer({
3820
3918
  video.removeEventListener("ratechange", onRate);
3821
3919
  };
3822
3920
  }, [videoRef, active, mode]);
3921
+ React6__namespace.useEffect(() => {
3922
+ setWebkitFullscreenSupported(hasWebkitVideoFullscreen(videoRef.current));
3923
+ }, [videoRef, active]);
3823
3924
  React6__namespace.useEffect(() => {
3824
3925
  const onFsChange = () => setIsFullscreen(document.fullscreenElement === containerRef.current);
3825
3926
  document.addEventListener("fullscreenchange", onFsChange);
3826
3927
  return () => document.removeEventListener("fullscreenchange", onFsChange);
3827
3928
  }, []);
3929
+ React6__namespace.useEffect(() => {
3930
+ const video = asWebkitVideo(videoRef.current);
3931
+ if (!video) return;
3932
+ const onBegin = () => setIsFullscreen(true);
3933
+ const onEnd = () => setIsFullscreen(false);
3934
+ video.addEventListener("webkitbeginfullscreen", onBegin);
3935
+ video.addEventListener("webkitendfullscreen", onEnd);
3936
+ return () => {
3937
+ video.removeEventListener("webkitbeginfullscreen", onBegin);
3938
+ video.removeEventListener("webkitendfullscreen", onEnd);
3939
+ };
3940
+ }, [videoRef, active]);
3828
3941
  React6__namespace.useEffect(() => {
3829
3942
  const video = videoRef.current;
3830
3943
  if (!video) return;
@@ -3889,11 +4002,21 @@ function VideoPlayer({
3889
4002
  }
3890
4003
  function toggleFullscreen() {
3891
4004
  const el = containerRef.current;
3892
- if (!el) return;
3893
- if (document.fullscreenElement === el) {
4005
+ const video = asWebkitVideo(videoRef.current);
4006
+ if (el && document.fullscreenElement === el) {
3894
4007
  void document.exitFullscreen();
3895
- } else {
3896
- void el.requestFullscreen?.();
4008
+ return;
4009
+ }
4010
+ if (video?.webkitDisplayingFullscreen && video.webkitExitFullscreen) {
4011
+ video.webkitExitFullscreen();
4012
+ return;
4013
+ }
4014
+ if (el && hasStandardFullscreen() && typeof el.requestFullscreen === "function") {
4015
+ void el.requestFullscreen();
4016
+ return;
4017
+ }
4018
+ if (video && typeof video.webkitEnterFullscreen === "function") {
4019
+ video.webkitEnterFullscreen();
3897
4020
  }
3898
4021
  }
3899
4022
  function togglePip() {
@@ -4348,6 +4471,18 @@ function useDebouncedValueStrict(value, delayMs) {
4348
4471
  }, [value, delayMs]);
4349
4472
  return debounced;
4350
4473
  }
4474
+ function useMediaQuery2(query) {
4475
+ const [matches, setMatches] = React6.useState(false);
4476
+ React6.useEffect(() => {
4477
+ if (typeof window === "undefined" || !window.matchMedia) return;
4478
+ const mql = window.matchMedia(query);
4479
+ const onChange = () => setMatches(mql.matches);
4480
+ onChange();
4481
+ mql.addEventListener("change", onChange);
4482
+ return () => mql.removeEventListener("change", onChange);
4483
+ }, [query]);
4484
+ return matches;
4485
+ }
4351
4486
  function useSearchHotkey(options = {}) {
4352
4487
  const { defaultOpen = false, disabled = false } = options;
4353
4488
  const [open, setOpen] = React6.useState(defaultOpen);
@@ -4365,6 +4500,22 @@ function useSearchHotkey(options = {}) {
4365
4500
  }, [disabled]);
4366
4501
  return { open, setOpen, toggle };
4367
4502
  }
4503
+
4504
+ // src/components/search/fuzzy.ts
4505
+ function normalize(value) {
4506
+ return value.toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "");
4507
+ }
4508
+ function fuzzyMatch(query, target) {
4509
+ const q = normalize(query.trim());
4510
+ if (!q) return true;
4511
+ const t = normalize(target);
4512
+ if (t.includes(q)) return true;
4513
+ let qi = 0;
4514
+ for (let ti = 0; ti < t.length && qi < q.length; ti++) {
4515
+ if (t[ti] === q[qi]) qi++;
4516
+ }
4517
+ return qi === q.length;
4518
+ }
4368
4519
  function Command({
4369
4520
  className,
4370
4521
  ...props
@@ -4482,6 +4633,19 @@ function CommandGroup({
4482
4633
  }
4483
4634
  );
4484
4635
  }
4636
+ function CommandSeparator({
4637
+ className,
4638
+ ...props
4639
+ }) {
4640
+ return /* @__PURE__ */ jsxRuntime.jsx(
4641
+ cmdk.Command.Separator,
4642
+ {
4643
+ "data-slot": "command-separator",
4644
+ className: cn("-mx-1 h-px bg-border", className),
4645
+ ...props
4646
+ }
4647
+ );
4648
+ }
4485
4649
  function CommandItem({
4486
4650
  className,
4487
4651
  ...props
@@ -4498,23 +4662,584 @@ function CommandItem({
4498
4662
  }
4499
4663
  );
4500
4664
  }
4501
-
4502
- // src/components/search/fuzzy.ts
4503
- function normalize(value) {
4504
- return value.toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "");
4665
+ function OptionRow({
4666
+ entry,
4667
+ selected,
4668
+ onSelect,
4669
+ renderOption
4670
+ }) {
4671
+ return /* @__PURE__ */ jsxRuntime.jsxs(
4672
+ CommandItem,
4673
+ {
4674
+ value: entry.value,
4675
+ keywords: [entry.label],
4676
+ onSelect,
4677
+ "data-checked": selected || void 0,
4678
+ children: [
4679
+ renderOption ? renderOption(entry.item, { selected }) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: "min-w-0 flex-1 truncate", children: entry.label }),
4680
+ /* @__PURE__ */ jsxRuntime.jsx(
4681
+ lucideReact.CheckIcon,
4682
+ {
4683
+ className: cn(
4684
+ "ml-auto size-4 shrink-0",
4685
+ selected ? "opacity-100" : "opacity-0"
4686
+ )
4687
+ }
4688
+ )
4689
+ ]
4690
+ },
4691
+ entry.value
4692
+ );
4505
4693
  }
4506
- function fuzzyMatch(query, target) {
4507
- const q = normalize(query.trim());
4508
- if (!q) return true;
4509
- const t = normalize(target);
4510
- if (t.includes(q)) return true;
4511
- let qi = 0;
4512
- for (let ti = 0; ti < t.length && qi < q.length; ti++) {
4513
- if (t[ti] === q[qi]) qi++;
4514
- }
4515
- return qi === q.length;
4694
+ function SearchablePickerList({
4695
+ query,
4696
+ onQueryChange,
4697
+ labels,
4698
+ isLoading,
4699
+ hasError,
4700
+ recommended,
4701
+ recent,
4702
+ results,
4703
+ selectedValues,
4704
+ mode,
4705
+ allowEmpty,
4706
+ emptyLabel,
4707
+ onSelectEmpty,
4708
+ onSelectItem,
4709
+ renderOption,
4710
+ createOption,
4711
+ onLoadMore,
4712
+ hasMore,
4713
+ isLoadingMore,
4714
+ listClassName
4715
+ }) {
4716
+ const hasPinned = recommended.length > 0 || recent.length > 0;
4717
+ const hasResults = results.length > 0;
4718
+ const showEmpty = !isLoading && !hasError && !hasPinned && !hasResults && !(allowEmpty && mode === "single");
4719
+ return /* @__PURE__ */ jsxRuntime.jsxs(Command, { shouldFilter: false, className: "flex h-full min-h-0 flex-col", children: [
4720
+ /* @__PURE__ */ jsxRuntime.jsx(
4721
+ CommandInput,
4722
+ {
4723
+ placeholder: labels.searchPlaceholder,
4724
+ value: query,
4725
+ onValueChange: onQueryChange
4726
+ }
4727
+ ),
4728
+ /* @__PURE__ */ jsxRuntime.jsxs(CommandList, { className: cn("min-h-0 flex-1", listClassName), children: [
4729
+ isLoading ? /* @__PURE__ */ jsxRuntime.jsxs(
4730
+ "div",
4731
+ {
4732
+ className: "flex items-center justify-center gap-2 py-6 text-sm text-muted-foreground",
4733
+ role: "status",
4734
+ children: [
4735
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { className: "size-4 animate-spin", "aria-hidden": true }),
4736
+ labels.loading
4737
+ ]
4738
+ }
4739
+ ) : null,
4740
+ hasError && !isLoading ? /* @__PURE__ */ jsxRuntime.jsx(
4741
+ "div",
4742
+ {
4743
+ className: "py-6 text-center text-sm text-destructive",
4744
+ role: "alert",
4745
+ children: labels.error
4746
+ }
4747
+ ) : null,
4748
+ showEmpty ? /* @__PURE__ */ jsxRuntime.jsx(CommandEmpty, { children: labels.empty }) : null,
4749
+ allowEmpty && mode === "single" && !query.trim() ? /* @__PURE__ */ jsxRuntime.jsx(CommandGroup, { children: /* @__PURE__ */ jsxRuntime.jsxs(
4750
+ CommandItem,
4751
+ {
4752
+ value: "__empty__",
4753
+ onSelect: () => onSelectEmpty?.(),
4754
+ "data-checked": !selectedValues.size || void 0,
4755
+ children: [
4756
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "min-w-0 flex-1 truncate text-muted-foreground", children: emptyLabel }),
4757
+ /* @__PURE__ */ jsxRuntime.jsx(
4758
+ lucideReact.CheckIcon,
4759
+ {
4760
+ className: cn(
4761
+ "ml-auto size-4 shrink-0",
4762
+ selectedValues.size === 0 ? "opacity-100" : "opacity-0"
4763
+ )
4764
+ }
4765
+ )
4766
+ ]
4767
+ }
4768
+ ) }) : null,
4769
+ recommended.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx(CommandGroup, { heading: labels.recommended, children: recommended.map((entry) => /* @__PURE__ */ jsxRuntime.jsx(
4770
+ OptionRow,
4771
+ {
4772
+ entry,
4773
+ selected: selectedValues.has(entry.value),
4774
+ onSelect: () => onSelectItem(entry.value),
4775
+ renderOption
4776
+ },
4777
+ `rec-${entry.value}`
4778
+ )) }) : null,
4779
+ recent.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx(CommandGroup, { heading: labels.recent, children: recent.map((entry) => /* @__PURE__ */ jsxRuntime.jsx(
4780
+ OptionRow,
4781
+ {
4782
+ entry,
4783
+ selected: selectedValues.has(entry.value),
4784
+ onSelect: () => onSelectItem(entry.value),
4785
+ renderOption
4786
+ },
4787
+ `recent-${entry.value}`
4788
+ )) }) : null,
4789
+ hasPinned && hasResults ? /* @__PURE__ */ jsxRuntime.jsx(CommandSeparator, {}) : null,
4790
+ hasResults ? /* @__PURE__ */ jsxRuntime.jsx(CommandGroup, { heading: hasPinned ? labels.results : void 0, children: results.map((entry) => /* @__PURE__ */ jsxRuntime.jsx(
4791
+ OptionRow,
4792
+ {
4793
+ entry,
4794
+ selected: selectedValues.has(entry.value),
4795
+ onSelect: () => onSelectItem(entry.value),
4796
+ renderOption
4797
+ },
4798
+ `res-${entry.value}`
4799
+ )) }) : null,
4800
+ hasMore && onLoadMore ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "p-2", children: /* @__PURE__ */ jsxRuntime.jsx(
4801
+ Button,
4802
+ {
4803
+ type: "button",
4804
+ variant: "ghost",
4805
+ size: "sm",
4806
+ className: "w-full",
4807
+ disabled: isLoadingMore,
4808
+ onClick: () => void onLoadMore(),
4809
+ children: isLoadingMore ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
4810
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { className: "size-4 animate-spin", "aria-hidden": true }),
4811
+ labels.loading
4812
+ ] }) : labels.loadMore
4813
+ }
4814
+ ) }) : null,
4815
+ createOption ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
4816
+ /* @__PURE__ */ jsxRuntime.jsx(CommandSeparator, {}),
4817
+ /* @__PURE__ */ jsxRuntime.jsx(CommandGroup, { children: /* @__PURE__ */ jsxRuntime.jsx(
4818
+ CommandItem,
4819
+ {
4820
+ value: "__create__",
4821
+ onSelect: () => createOption.onSelect(),
4822
+ children: createOption.label
4823
+ }
4824
+ ) })
4825
+ ] }) : null
4826
+ ] })
4827
+ ] });
4516
4828
  }
4517
4829
  var defaultLabels3 = {
4830
+ placeholder: "Seleccionar\u2026",
4831
+ searchPlaceholder: "Buscar\u2026",
4832
+ empty: "No se encontraron resultados",
4833
+ loading: "Cargando\u2026",
4834
+ error: "No se pudo cargar la lista",
4835
+ recommended: "Recomendados",
4836
+ recent: "Recientes",
4837
+ results: "Resultados",
4838
+ done: "Listo",
4839
+ cancel: "Cancelar",
4840
+ clear: "Limpiar",
4841
+ selectedCount: (count) => count === 1 ? "1 seleccionado" : `${count} seleccionados`,
4842
+ emptyOption: "Ninguno",
4843
+ loadMore: "Cargar m\xE1s",
4844
+ dialogTitle: "Seleccionar"
4845
+ };
4846
+ function toListItems(items, getOptionValue, getOptionLabel) {
4847
+ if (!items?.length) return [];
4848
+ return items.map((item) => ({
4849
+ item,
4850
+ value: getOptionValue(item),
4851
+ label: getOptionLabel(item)
4852
+ }));
4853
+ }
4854
+ function dedupeByValue(items, exclude = /* @__PURE__ */ new Set()) {
4855
+ const seen = new Set(exclude);
4856
+ const out = [];
4857
+ for (const entry of items) {
4858
+ if (seen.has(entry.value)) continue;
4859
+ seen.add(entry.value);
4860
+ out.push(entry);
4861
+ }
4862
+ return out;
4863
+ }
4864
+ function matchesQuery(entry, query, getOptionKeywords) {
4865
+ if (!query.trim()) return true;
4866
+ if (fuzzyMatch(query, entry.label)) return true;
4867
+ return getOptionKeywords?.(entry.item)?.some(
4868
+ (keyword) => fuzzyMatch(query, keyword)
4869
+ ) ?? false;
4870
+ }
4871
+ function SearchablePicker(props) {
4872
+ const {
4873
+ options,
4874
+ fetchOptions,
4875
+ getOptionValue,
4876
+ getOptionLabel,
4877
+ getOptionKeywords,
4878
+ renderOption,
4879
+ renderValue,
4880
+ pinned,
4881
+ labels: userLabels,
4882
+ minQueryLength = 0,
4883
+ debounceMs = 200,
4884
+ disabled,
4885
+ open: openProp,
4886
+ onOpenChange,
4887
+ clearable,
4888
+ allowEmpty,
4889
+ emptyLabel,
4890
+ keepOpenOnSelect,
4891
+ maxSelected,
4892
+ createOption,
4893
+ onLoadMore,
4894
+ hasMore,
4895
+ className,
4896
+ triggerClassName: triggerClassName2,
4897
+ id,
4898
+ "aria-labelledby": ariaLabelledBy,
4899
+ "aria-label": ariaLabel
4900
+ } = props;
4901
+ const mode = props.mode ?? "single";
4902
+ const labels = { ...defaultLabels3, ...userLabels };
4903
+ const resolvedEmptyLabel = emptyLabel ?? labels.emptyOption;
4904
+ const isMobile = useMediaQuery2("(max-width: 767px)");
4905
+ const generatedId = React6.useId();
4906
+ const triggerId = id ?? generatedId;
4907
+ const [uncontrolledOpen, setUncontrolledOpen] = React6.useState(false);
4908
+ const open = openProp ?? uncontrolledOpen;
4909
+ const setOpen = React6.useCallback(
4910
+ (next) => {
4911
+ onOpenChange?.(next);
4912
+ if (openProp === void 0) setUncontrolledOpen(next);
4913
+ },
4914
+ [onOpenChange, openProp]
4915
+ );
4916
+ const [query, setQuery] = React6.useState("");
4917
+ const [remoteItems, setRemoteItems] = React6.useState([]);
4918
+ const [isLoading, setIsLoading] = React6.useState(false);
4919
+ const [hasError, setHasError] = React6.useState(false);
4920
+ const [isLoadingMore, setIsLoadingMore] = React6.useState(false);
4921
+ const debouncedQuery = useDebouncedValue(query, debounceMs);
4922
+ const requestIdRef = React6.useRef(0);
4923
+ const knownItemsRef = React6.useRef(/* @__PURE__ */ new Map());
4924
+ const rememberItems = React6.useCallback(
4925
+ (items) => {
4926
+ for (const item of items) {
4927
+ knownItemsRef.current.set(getOptionValue(item), item);
4928
+ }
4929
+ },
4930
+ [getOptionValue]
4931
+ );
4932
+ React6.useEffect(() => {
4933
+ if (options) rememberItems(options);
4934
+ if (pinned?.recommended) rememberItems(pinned.recommended);
4935
+ if (pinned?.recent) rememberItems(pinned.recent);
4936
+ }, [options, pinned, rememberItems]);
4937
+ React6.useEffect(() => {
4938
+ if (!open) {
4939
+ setQuery("");
4940
+ setRemoteItems([]);
4941
+ setHasError(false);
4942
+ setIsLoading(false);
4943
+ }
4944
+ }, [open]);
4945
+ const trimmedDebounced = debouncedQuery.trim();
4946
+ const shouldFetch = Boolean(fetchOptions) && open && trimmedDebounced.length >= minQueryLength && (trimmedDebounced.length > 0 || !options);
4947
+ React6.useEffect(() => {
4948
+ if (!shouldFetch || !fetchOptions) {
4949
+ if (!trimmedDebounced) {
4950
+ setRemoteItems([]);
4951
+ setHasError(false);
4952
+ setIsLoading(false);
4953
+ }
4954
+ return;
4955
+ }
4956
+ const requestId = ++requestIdRef.current;
4957
+ setIsLoading(true);
4958
+ setHasError(false);
4959
+ fetchOptions(trimmedDebounced).then((result) => {
4960
+ if (requestIdRef.current !== requestId) return;
4961
+ setRemoteItems(result);
4962
+ rememberItems(result);
4963
+ }).catch(() => {
4964
+ if (requestIdRef.current !== requestId) return;
4965
+ setRemoteItems([]);
4966
+ setHasError(true);
4967
+ }).finally(() => {
4968
+ if (requestIdRef.current !== requestId) return;
4969
+ setIsLoading(false);
4970
+ });
4971
+ }, [shouldFetch, fetchOptions, trimmedDebounced, rememberItems]);
4972
+ const selectedValues = React6.useMemo(() => {
4973
+ if (mode === "multiple") {
4974
+ const multiValue = props.value ?? [];
4975
+ return new Set(multiValue);
4976
+ }
4977
+ const singleValue = props.value;
4978
+ return singleValue ? /* @__PURE__ */ new Set([singleValue]) : /* @__PURE__ */ new Set();
4979
+ }, [mode, props]);
4980
+ const selectedValueKey = mode === "multiple" ? (props.value ?? []).join("\0") : props.value ?? "";
4981
+ const selectedItems = React6.useMemo(() => {
4982
+ const values = mode === "multiple" ? selectedValueKey ? selectedValueKey.split("\0") : [] : selectedValueKey ? [selectedValueKey] : [];
4983
+ return values.map((value) => knownItemsRef.current.get(value)).filter((item) => item !== void 0);
4984
+ }, [mode, selectedValueKey, options, pinned, remoteItems]);
4985
+ const queryForFilter = query.trim();
4986
+ const recommended = React6.useMemo(() => {
4987
+ if (queryForFilter) return [];
4988
+ const pinnedValues = /* @__PURE__ */ new Set();
4989
+ const items = dedupeByValue(
4990
+ toListItems(pinned?.recommended, getOptionValue, getOptionLabel)
4991
+ );
4992
+ for (const entry of items) pinnedValues.add(entry.value);
4993
+ return items;
4994
+ }, [queryForFilter, pinned?.recommended, getOptionValue, getOptionLabel]);
4995
+ const recent = React6.useMemo(() => {
4996
+ if (queryForFilter) return [];
4997
+ const exclude = new Set(recommended.map((e) => e.value));
4998
+ return dedupeByValue(
4999
+ toListItems(pinned?.recent, getOptionValue, getOptionLabel),
5000
+ exclude
5001
+ );
5002
+ }, [
5003
+ queryForFilter,
5004
+ pinned?.recent,
5005
+ recommended,
5006
+ getOptionValue,
5007
+ getOptionLabel
5008
+ ]);
5009
+ const pinnedValueSet = React6.useMemo(() => {
5010
+ const set = /* @__PURE__ */ new Set();
5011
+ for (const entry of recommended) set.add(entry.value);
5012
+ for (const entry of recent) set.add(entry.value);
5013
+ return set;
5014
+ }, [recommended, recent]);
5015
+ const results = React6.useMemo(() => {
5016
+ const usingRemote = Boolean(fetchOptions) && trimmedDebounced.length >= minQueryLength && (trimmedDebounced.length > 0 || !options);
5017
+ if (usingRemote) {
5018
+ return dedupeByValue(
5019
+ toListItems(remoteItems, getOptionValue, getOptionLabel),
5020
+ queryForFilter ? /* @__PURE__ */ new Set() : pinnedValueSet
5021
+ );
5022
+ }
5023
+ const staticItems = toListItems(options, getOptionValue, getOptionLabel);
5024
+ const filtered = staticItems.filter(
5025
+ (entry) => matchesQuery(entry, queryForFilter, getOptionKeywords)
5026
+ );
5027
+ return dedupeByValue(filtered, queryForFilter ? /* @__PURE__ */ new Set() : pinnedValueSet);
5028
+ }, [
5029
+ fetchOptions,
5030
+ trimmedDebounced,
5031
+ minQueryLength,
5032
+ options,
5033
+ remoteItems,
5034
+ getOptionValue,
5035
+ getOptionLabel,
5036
+ getOptionKeywords,
5037
+ queryForFilter,
5038
+ pinnedValueSet
5039
+ ]);
5040
+ const closeOnSelect = keepOpenOnSelect !== void 0 ? !keepOpenOnSelect : mode === "single";
5041
+ const handleSelectEmpty = React6.useCallback(() => {
5042
+ if (mode === "single") {
5043
+ props.onValueChange?.(
5044
+ null
5045
+ );
5046
+ }
5047
+ if (closeOnSelect) setOpen(false);
5048
+ }, [mode, props, closeOnSelect, setOpen]);
5049
+ const handleSelectItem = React6.useCallback(
5050
+ (value) => {
5051
+ if (mode === "multiple") {
5052
+ const current = props.value ?? [];
5053
+ const onChange = props.onValueChange;
5054
+ const exists = current.includes(value);
5055
+ if (exists) {
5056
+ onChange?.(current.filter((v) => v !== value));
5057
+ } else if (maxSelected !== void 0 && current.length >= maxSelected) {
5058
+ return;
5059
+ } else {
5060
+ onChange?.([...current, value]);
5061
+ }
5062
+ if (closeOnSelect) setOpen(false);
5063
+ return;
5064
+ }
5065
+ props.onValueChange?.(
5066
+ value
5067
+ );
5068
+ if (closeOnSelect) setOpen(false);
5069
+ },
5070
+ [mode, props, maxSelected, closeOnSelect, setOpen]
5071
+ );
5072
+ const handleClear = React6.useCallback(
5073
+ (event) => {
5074
+ event.preventDefault();
5075
+ event.stopPropagation();
5076
+ if (mode === "multiple") {
5077
+ props.onValueChange?.(
5078
+ []
5079
+ );
5080
+ } else {
5081
+ props.onValueChange?.(null);
5082
+ }
5083
+ },
5084
+ [mode, props]
5085
+ );
5086
+ const handleLoadMore = React6.useCallback(async () => {
5087
+ if (!onLoadMore || isLoadingMore) return;
5088
+ setIsLoadingMore(true);
5089
+ try {
5090
+ await onLoadMore();
5091
+ } finally {
5092
+ setIsLoadingMore(false);
5093
+ }
5094
+ }, [onLoadMore, isLoadingMore]);
5095
+ const triggerLabel = React6.useMemo(() => {
5096
+ if (renderValue) {
5097
+ if (mode === "multiple") {
5098
+ return renderValue(selectedItems);
5099
+ }
5100
+ return renderValue(selectedItems[0] ?? null);
5101
+ }
5102
+ if (mode === "multiple") {
5103
+ const count = selectedValues.size;
5104
+ if (count === 0) {
5105
+ return /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground", children: labels.placeholder });
5106
+ }
5107
+ if (selectedItems.length === 1) {
5108
+ return getOptionLabel(selectedItems[0]);
5109
+ }
5110
+ return labels.selectedCount(count);
5111
+ }
5112
+ if (selectedValues.size === 0) {
5113
+ return /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground", children: allowEmpty ? resolvedEmptyLabel : labels.placeholder });
5114
+ }
5115
+ if (selectedItems[0]) {
5116
+ return getOptionLabel(selectedItems[0]);
5117
+ }
5118
+ const only = [...selectedValues][0];
5119
+ return only ?? labels.placeholder;
5120
+ }, [
5121
+ renderValue,
5122
+ mode,
5123
+ selectedItems,
5124
+ selectedValues,
5125
+ labels,
5126
+ getOptionLabel,
5127
+ allowEmpty,
5128
+ resolvedEmptyLabel
5129
+ ]);
5130
+ const showClear = clearable && !disabled && (mode === "multiple" ? selectedValues.size > 0 : selectedValues.size > 0);
5131
+ const list = /* @__PURE__ */ jsxRuntime.jsx(
5132
+ SearchablePickerList,
5133
+ {
5134
+ query,
5135
+ onQueryChange: setQuery,
5136
+ labels,
5137
+ isLoading,
5138
+ hasError,
5139
+ recommended,
5140
+ recent,
5141
+ results,
5142
+ selectedValues,
5143
+ mode,
5144
+ allowEmpty,
5145
+ emptyLabel: resolvedEmptyLabel,
5146
+ onSelectEmpty: handleSelectEmpty,
5147
+ onSelectItem: handleSelectItem,
5148
+ renderOption,
5149
+ createOption,
5150
+ onLoadMore: onLoadMore ? handleLoadMore : void 0,
5151
+ hasMore,
5152
+ isLoadingMore,
5153
+ listClassName: isMobile ? "max-h-[min(50vh,360px)]" : "max-h-72"
5154
+ }
5155
+ );
5156
+ const triggerButton = /* @__PURE__ */ jsxRuntime.jsxs(
5157
+ Button,
5158
+ {
5159
+ type: "button",
5160
+ variant: "outline",
5161
+ role: "combobox",
5162
+ id: triggerId,
5163
+ "aria-expanded": open,
5164
+ "aria-labelledby": ariaLabelledBy,
5165
+ "aria-label": ariaLabel ?? labels.placeholder,
5166
+ disabled,
5167
+ className: cn("w-full justify-between font-normal", triggerClassName2),
5168
+ children: [
5169
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "min-w-0 flex-1 truncate text-left", children: triggerLabel }),
5170
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronsUpDown, { className: "ml-2 size-4 shrink-0 opacity-50", "aria-hidden": true })
5171
+ ]
5172
+ }
5173
+ );
5174
+ const clearButton = showClear ? /* @__PURE__ */ jsxRuntime.jsx(
5175
+ Button,
5176
+ {
5177
+ type: "button",
5178
+ variant: "ghost",
5179
+ size: "icon-sm",
5180
+ "aria-label": labels.clear,
5181
+ disabled,
5182
+ className: "shrink-0",
5183
+ onClick: handleClear,
5184
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { className: "size-4" })
5185
+ }
5186
+ ) : null;
5187
+ if (isMobile) {
5188
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("flex w-full items-center gap-1", className), children: [
5189
+ /* @__PURE__ */ jsxRuntime.jsx(
5190
+ "div",
5191
+ {
5192
+ className: "min-w-0 flex-1",
5193
+ onClick: () => {
5194
+ if (!disabled) setOpen(true);
5195
+ },
5196
+ children: triggerButton
5197
+ }
5198
+ ),
5199
+ clearButton,
5200
+ /* @__PURE__ */ jsxRuntime.jsx(Drawer, { open, onOpenChange: setOpen, children: /* @__PURE__ */ jsxRuntime.jsxs(DrawerContent, { className: "max-h-[90vh]", children: [
5201
+ /* @__PURE__ */ jsxRuntime.jsx(DrawerHeader, { className: "text-left", children: /* @__PURE__ */ jsxRuntime.jsx(DrawerTitle, { children: labels.dialogTitle }) }),
5202
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "min-h-0 flex-1 overflow-hidden px-2 pb-2", children: list }),
5203
+ mode === "multiple" ? /* @__PURE__ */ jsxRuntime.jsxs(DrawerFooter, { className: "flex-row gap-2", children: [
5204
+ /* @__PURE__ */ jsxRuntime.jsx(
5205
+ Button,
5206
+ {
5207
+ type: "button",
5208
+ variant: "outline",
5209
+ className: "flex-1",
5210
+ onClick: () => setOpen(false),
5211
+ children: labels.cancel
5212
+ }
5213
+ ),
5214
+ /* @__PURE__ */ jsxRuntime.jsx(
5215
+ Button,
5216
+ {
5217
+ type: "button",
5218
+ className: "flex-1",
5219
+ onClick: () => setOpen(false),
5220
+ children: labels.done
5221
+ }
5222
+ )
5223
+ ] }) : null
5224
+ ] }) })
5225
+ ] });
5226
+ }
5227
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("flex w-full items-center gap-1", className), children: [
5228
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "min-w-0 flex-1", children: /* @__PURE__ */ jsxRuntime.jsxs(Popover, { open, onOpenChange: setOpen, modal: true, children: [
5229
+ /* @__PURE__ */ jsxRuntime.jsx(PopoverTrigger, { asChild: true, children: triggerButton }),
5230
+ /* @__PURE__ */ jsxRuntime.jsx(
5231
+ PopoverContent,
5232
+ {
5233
+ className: "w-[var(--radix-popover-trigger-width)] min-w-[240px] p-0",
5234
+ align: "start",
5235
+ children: list
5236
+ }
5237
+ )
5238
+ ] }) }),
5239
+ clearButton
5240
+ ] });
5241
+ }
5242
+ var defaultLabels4 = {
4518
5243
  placeholder: "Buscar en toda la plataforma\u2026",
4519
5244
  dialogTitle: "Buscar",
4520
5245
  dialogDescription: "Busca funcionalidades, eventos, documentos y m\xE1s",
@@ -4548,7 +5273,7 @@ function SearchCommand({
4548
5273
  debounceMs = 200,
4549
5274
  className
4550
5275
  }) {
4551
- const labels = { ...defaultLabels3, ...userLabels };
5276
+ const labels = { ...defaultLabels4, ...userLabels };
4552
5277
  const [query, setQuery] = React6.useState("");
4553
5278
  const [hits, setHits] = React6.useState([]);
4554
5279
  const [isLoading, setIsLoading] = React6.useState(false);
@@ -5429,18 +6154,6 @@ function ChatMessageList({
5429
6154
  /* @__PURE__ */ jsxRuntime.jsx(MessageScrollerButton, {})
5430
6155
  ] }) });
5431
6156
  }
5432
- function useMediaQuery2(query) {
5433
- const [matches, setMatches] = React6.useState(false);
5434
- React6.useEffect(() => {
5435
- if (typeof window === "undefined" || !window.matchMedia) return;
5436
- const mql = window.matchMedia(query);
5437
- const onChange = () => setMatches(mql.matches);
5438
- onChange();
5439
- mql.addEventListener("change", onChange);
5440
- return () => mql.removeEventListener("change", onChange);
5441
- }, [query]);
5442
- return matches;
5443
- }
5444
6157
  var RECONNECT_DELAY_MS = 2e3;
5445
6158
  function useSupportConversation({
5446
6159
  client,
@@ -6117,6 +6830,7 @@ exports.MexicoMap = MexicoMap;
6117
6830
  exports.OrgSwitcher = OrgSwitcher;
6118
6831
  exports.PhoneInput = PhoneInput;
6119
6832
  exports.SearchCommand = SearchCommand;
6833
+ exports.SearchablePicker = SearchablePicker;
6120
6834
  exports.SupportMessengerProvider = SupportMessengerProvider;
6121
6835
  exports.ThemeSwitcher = ThemeSwitcher;
6122
6836
  exports.VerifiedProfessionalBadge = VerifiedProfessionalBadge;
@@ -6140,6 +6854,7 @@ exports.useDebouncedValue = useDebouncedValue;
6140
6854
  exports.useDebouncedValueStrict = useDebouncedValueStrict;
6141
6855
  exports.useHlsPlayback = useHlsPlayback;
6142
6856
  exports.useLanguageContext = useLanguageContext;
6857
+ exports.useMediaQuery = useMediaQuery2;
6143
6858
  exports.usePlaybackStats = usePlaybackStats;
6144
6859
  exports.useSearchHotkey = useSearchHotkey;
6145
6860
  exports.useSupportConversation = useSupportConversation;