@underverse-ui/underverse 1.0.160 → 1.0.161

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
@@ -7593,6 +7593,23 @@ var DropdownMenu = ({
7593
7593
  const itemsRef = React25.useRef([]);
7594
7594
  const [activeIndex, setActiveIndex] = useResettingIndex(open);
7595
7595
  const parentMenu = React25.useContext(DropdownMenuContext);
7596
+ const closeMenu = React25.useCallback(() => {
7597
+ setOpen(false);
7598
+ parentMenu?.closeMenu();
7599
+ }, [parentMenu, setOpen]);
7600
+ const getEnabledMenuItems = React25.useCallback(() => {
7601
+ const menuEl = menuRef.current;
7602
+ if (!menuEl) return [];
7603
+ return Array.from(menuEl.querySelectorAll("[data-dropdown-menu-item]")).filter((el) => !el.disabled);
7604
+ }, []);
7605
+ const focusMenuItem = React25.useCallback((index) => {
7606
+ const enabled = getEnabledMenuItems();
7607
+ const item = enabled[index];
7608
+ if (!item) return;
7609
+ setActiveIndex(index);
7610
+ item.focus();
7611
+ item.scrollIntoView({ block: "nearest" });
7612
+ }, [getEnabledMenuItems, setActiveIndex]);
7596
7613
  useShadCNAnimations();
7597
7614
  React25.useEffect(() => {
7598
7615
  if (!open) return;
@@ -7603,38 +7620,34 @@ var DropdownMenu = ({
7603
7620
  if (!active || !triggerEl || !menuEl) return;
7604
7621
  const isInMenu = menuEl.contains(active);
7605
7622
  const isOnTrigger = triggerEl.contains(active);
7606
- if (!isInMenu && !isOnTrigger) return;
7607
- const enabled = itemsRef.current.filter((el) => el && !el.disabled);
7623
+ const enabled = getEnabledMenuItems();
7608
7624
  if (enabled.length === 0) return;
7625
+ const currentIndex = enabled.findIndex((el) => el === active);
7626
+ const baseIndex = currentIndex >= 0 ? currentIndex : activeIndex;
7609
7627
  if (e.key === "ArrowDown") {
7610
7628
  e.preventDefault();
7611
- const next = (activeIndex + 1 + enabled.length) % enabled.length;
7612
- setActiveIndex(next);
7613
- enabled[next]?.focus();
7629
+ const next = (baseIndex + 1 + enabled.length) % enabled.length;
7630
+ focusMenuItem(next);
7614
7631
  } else if (e.key === "ArrowUp") {
7615
7632
  e.preventDefault();
7616
- const prev = (activeIndex - 1 + enabled.length) % enabled.length;
7617
- setActiveIndex(prev);
7618
- enabled[prev]?.focus();
7633
+ const prev = (baseIndex - 1 + enabled.length) % enabled.length;
7634
+ focusMenuItem(prev);
7619
7635
  } else if (e.key === "Home") {
7620
7636
  e.preventDefault();
7621
- setActiveIndex(0);
7622
- enabled[0]?.focus();
7637
+ focusMenuItem(0);
7623
7638
  } else if (e.key === "End") {
7624
7639
  e.preventDefault();
7625
- setActiveIndex(enabled.length - 1);
7626
- enabled[enabled.length - 1]?.focus();
7640
+ focusMenuItem(enabled.length - 1);
7641
+ } else if (e.key === "Escape" && (isInMenu || isOnTrigger)) {
7642
+ e.preventDefault();
7643
+ closeMenu();
7627
7644
  }
7628
7645
  };
7629
- document.addEventListener("keydown", handleKeyNav);
7646
+ document.addEventListener("keydown", handleKeyNav, true);
7630
7647
  return () => {
7631
- document.removeEventListener("keydown", handleKeyNav);
7648
+ document.removeEventListener("keydown", handleKeyNav, true);
7632
7649
  };
7633
- }, [open, activeIndex, setActiveIndex]);
7634
- const closeMenu = React25.useCallback(() => {
7635
- setOpen(false);
7636
- parentMenu?.closeMenu();
7637
- }, [parentMenu, setOpen]);
7650
+ }, [open, activeIndex, closeMenu, focusMenuItem, getEnabledMenuItems]);
7638
7651
  const menuContext = React25.useMemo(
7639
7652
  () => ({
7640
7653
  closeMenu,
@@ -7659,6 +7672,7 @@ var DropdownMenu = ({
7659
7672
  onClick: () => handleItemClick(item.onClick),
7660
7673
  disabled: item.disabled,
7661
7674
  role: "menuitem",
7675
+ "data-dropdown-menu-item": "",
7662
7676
  tabIndex: -1,
7663
7677
  style: {
7664
7678
  animationDelay: open ? `${Math.min(index * 20, 200)}ms` : "0ms"
@@ -7694,13 +7708,13 @@ var DropdownMenu = ({
7694
7708
  if (e.key === "ArrowDown") {
7695
7709
  e.preventDefault();
7696
7710
  setOpen(true);
7697
- requestAnimationFrame(() => itemsRef.current.find((el) => el && !el.disabled)?.focus());
7711
+ requestAnimationFrame(() => focusMenuItem(0));
7698
7712
  } else if (e.key === "ArrowUp") {
7699
7713
  e.preventDefault();
7700
7714
  setOpen(true);
7701
7715
  requestAnimationFrame(() => {
7702
- const enabled = itemsRef.current.filter((el) => el && !el.disabled);
7703
- enabled[enabled.length - 1]?.focus();
7716
+ const enabled = getEnabledMenuItems();
7717
+ focusMenuItem(enabled.length - 1);
7704
7718
  });
7705
7719
  } else if (e.key === "Escape") {
7706
7720
  e.preventDefault();
@@ -7751,6 +7765,8 @@ var DropdownMenuItem = ({
7751
7765
  },
7752
7766
  disabled,
7753
7767
  onMouseDown: (e) => e.preventDefault(),
7768
+ "data-dropdown-menu-item": "",
7769
+ tabIndex: -1,
7754
7770
  className: cn(
7755
7771
  "flex w-full items-center gap-2 px-3 py-2 text-sm rounded-lg transition-colors group cursor-pointer",
7756
7772
  "hover:bg-accent hover:text-accent-foreground",
@@ -32123,9 +32139,14 @@ var CustomBubbleMenu = ({
32123
32139
  }) => {
32124
32140
  const SHOW_DELAY_MS = 180;
32125
32141
  const BUBBLE_MENU_OFFSET = 16;
32142
+ const BUBBLE_MENU_ESTIMATED_HEIGHT = 44;
32126
32143
  const [isVisible, setIsVisible] = useState48(false);
32127
32144
  const [linkInputOpen, setLinkInputOpen] = useState48(false);
32128
- const [position, setPosition] = useState48({ top: 0, left: 0 });
32145
+ const [position, setPosition] = useState48({
32146
+ top: 0,
32147
+ left: 0,
32148
+ placement: "top"
32149
+ });
32129
32150
  const menuRef = useRef33(null);
32130
32151
  const keepOpenRef = useRef33(false);
32131
32152
  const showTimeoutRef = useRef33(null);
@@ -32182,12 +32203,18 @@ var CustomBubbleMenu = ({
32182
32203
  return;
32183
32204
  }
32184
32205
  const viewportPadding = 8;
32206
+ const menuHeight = menuRef.current?.getBoundingClientRect().height || BUBBLE_MENU_ESTIMATED_HEIGHT;
32207
+ const editorTop = view.dom.getBoundingClientRect().top;
32208
+ const selectionTop = Math.min(start.top, end.top);
32209
+ const selectionBottom = Math.max(start.bottom, end.bottom);
32210
+ const hasRoomAboveEditor = selectionTop - BUBBLE_MENU_OFFSET - menuHeight >= editorTop + viewportPadding;
32211
+ const placement = hasRoomAboveEditor ? "top" : "bottom";
32185
32212
  const left = Math.min(
32186
32213
  window.innerWidth - viewportPadding,
32187
32214
  Math.max(viewportPadding, (start.left + end.left) / 2)
32188
32215
  );
32189
- const top = Math.max(viewportPadding, start.top - BUBBLE_MENU_OFFSET);
32190
- setPosition({ top, left });
32216
+ const top = placement === "top" ? Math.max(viewportPadding, selectionTop - BUBBLE_MENU_OFFSET) : Math.min(window.innerHeight - viewportPadding, selectionBottom + BUBBLE_MENU_OFFSET);
32217
+ setPosition({ top, left, placement });
32191
32218
  if (keepOpenRef.current) {
32192
32219
  clearShowTimeout();
32193
32220
  setIsVisible(true);
@@ -32232,7 +32259,7 @@ var CustomBubbleMenu = ({
32232
32259
  style: {
32233
32260
  top: `${position.top}px`,
32234
32261
  left: `${position.left}px`,
32235
- transform: "translate(-50%, -100%)"
32262
+ transform: position.placement === "top" ? "translate(-50%, -100%)" : "translate(-50%, 0)"
32236
32263
  },
32237
32264
  onMouseDown: (e) => {
32238
32265
  const target = e.target;