@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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "package": "@underverse-ui/underverse",
3
- "version": "1.0.160",
3
+ "version": "1.0.161",
4
4
  "sourceEntry": "src/index.ts",
5
5
  "totalExports": 232,
6
6
  "exports": [
package/dist/index.cjs CHANGED
@@ -7779,6 +7779,23 @@ var DropdownMenu = ({
7779
7779
  const itemsRef = import_react14.default.useRef([]);
7780
7780
  const [activeIndex, setActiveIndex] = useResettingIndex(open);
7781
7781
  const parentMenu = import_react14.default.useContext(DropdownMenuContext);
7782
+ const closeMenu = import_react14.default.useCallback(() => {
7783
+ setOpen(false);
7784
+ parentMenu?.closeMenu();
7785
+ }, [parentMenu, setOpen]);
7786
+ const getEnabledMenuItems = import_react14.default.useCallback(() => {
7787
+ const menuEl = menuRef.current;
7788
+ if (!menuEl) return [];
7789
+ return Array.from(menuEl.querySelectorAll("[data-dropdown-menu-item]")).filter((el) => !el.disabled);
7790
+ }, []);
7791
+ const focusMenuItem = import_react14.default.useCallback((index) => {
7792
+ const enabled = getEnabledMenuItems();
7793
+ const item = enabled[index];
7794
+ if (!item) return;
7795
+ setActiveIndex(index);
7796
+ item.focus();
7797
+ item.scrollIntoView({ block: "nearest" });
7798
+ }, [getEnabledMenuItems, setActiveIndex]);
7782
7799
  useShadCNAnimations();
7783
7800
  import_react14.default.useEffect(() => {
7784
7801
  if (!open) return;
@@ -7789,38 +7806,34 @@ var DropdownMenu = ({
7789
7806
  if (!active || !triggerEl || !menuEl) return;
7790
7807
  const isInMenu = menuEl.contains(active);
7791
7808
  const isOnTrigger = triggerEl.contains(active);
7792
- if (!isInMenu && !isOnTrigger) return;
7793
- const enabled = itemsRef.current.filter((el) => el && !el.disabled);
7809
+ const enabled = getEnabledMenuItems();
7794
7810
  if (enabled.length === 0) return;
7811
+ const currentIndex = enabled.findIndex((el) => el === active);
7812
+ const baseIndex = currentIndex >= 0 ? currentIndex : activeIndex;
7795
7813
  if (e.key === "ArrowDown") {
7796
7814
  e.preventDefault();
7797
- const next = (activeIndex + 1 + enabled.length) % enabled.length;
7798
- setActiveIndex(next);
7799
- enabled[next]?.focus();
7815
+ const next = (baseIndex + 1 + enabled.length) % enabled.length;
7816
+ focusMenuItem(next);
7800
7817
  } else if (e.key === "ArrowUp") {
7801
7818
  e.preventDefault();
7802
- const prev = (activeIndex - 1 + enabled.length) % enabled.length;
7803
- setActiveIndex(prev);
7804
- enabled[prev]?.focus();
7819
+ const prev = (baseIndex - 1 + enabled.length) % enabled.length;
7820
+ focusMenuItem(prev);
7805
7821
  } else if (e.key === "Home") {
7806
7822
  e.preventDefault();
7807
- setActiveIndex(0);
7808
- enabled[0]?.focus();
7823
+ focusMenuItem(0);
7809
7824
  } else if (e.key === "End") {
7810
7825
  e.preventDefault();
7811
- setActiveIndex(enabled.length - 1);
7812
- enabled[enabled.length - 1]?.focus();
7826
+ focusMenuItem(enabled.length - 1);
7827
+ } else if (e.key === "Escape" && (isInMenu || isOnTrigger)) {
7828
+ e.preventDefault();
7829
+ closeMenu();
7813
7830
  }
7814
7831
  };
7815
- document.addEventListener("keydown", handleKeyNav);
7832
+ document.addEventListener("keydown", handleKeyNav, true);
7816
7833
  return () => {
7817
- document.removeEventListener("keydown", handleKeyNav);
7834
+ document.removeEventListener("keydown", handleKeyNav, true);
7818
7835
  };
7819
- }, [open, activeIndex, setActiveIndex]);
7820
- const closeMenu = import_react14.default.useCallback(() => {
7821
- setOpen(false);
7822
- parentMenu?.closeMenu();
7823
- }, [parentMenu, setOpen]);
7836
+ }, [open, activeIndex, closeMenu, focusMenuItem, getEnabledMenuItems]);
7824
7837
  const menuContext = import_react14.default.useMemo(
7825
7838
  () => ({
7826
7839
  closeMenu,
@@ -7845,6 +7858,7 @@ var DropdownMenu = ({
7845
7858
  onClick: () => handleItemClick(item.onClick),
7846
7859
  disabled: item.disabled,
7847
7860
  role: "menuitem",
7861
+ "data-dropdown-menu-item": "",
7848
7862
  tabIndex: -1,
7849
7863
  style: {
7850
7864
  animationDelay: open ? `${Math.min(index * 20, 200)}ms` : "0ms"
@@ -7880,13 +7894,13 @@ var DropdownMenu = ({
7880
7894
  if (e.key === "ArrowDown") {
7881
7895
  e.preventDefault();
7882
7896
  setOpen(true);
7883
- requestAnimationFrame(() => itemsRef.current.find((el) => el && !el.disabled)?.focus());
7897
+ requestAnimationFrame(() => focusMenuItem(0));
7884
7898
  } else if (e.key === "ArrowUp") {
7885
7899
  e.preventDefault();
7886
7900
  setOpen(true);
7887
7901
  requestAnimationFrame(() => {
7888
- const enabled = itemsRef.current.filter((el) => el && !el.disabled);
7889
- enabled[enabled.length - 1]?.focus();
7902
+ const enabled = getEnabledMenuItems();
7903
+ focusMenuItem(enabled.length - 1);
7890
7904
  });
7891
7905
  } else if (e.key === "Escape") {
7892
7906
  e.preventDefault();
@@ -7937,6 +7951,8 @@ var DropdownMenuItem = ({
7937
7951
  },
7938
7952
  disabled,
7939
7953
  onMouseDown: (e) => e.preventDefault(),
7954
+ "data-dropdown-menu-item": "",
7955
+ tabIndex: -1,
7940
7956
  className: cn(
7941
7957
  "flex w-full items-center gap-2 px-3 py-2 text-sm rounded-lg transition-colors group cursor-pointer",
7942
7958
  "hover:bg-accent hover:text-accent-foreground",
@@ -32210,9 +32226,14 @@ var CustomBubbleMenu = ({
32210
32226
  }) => {
32211
32227
  const SHOW_DELAY_MS = 180;
32212
32228
  const BUBBLE_MENU_OFFSET = 16;
32229
+ const BUBBLE_MENU_ESTIMATED_HEIGHT = 44;
32213
32230
  const [isVisible, setIsVisible] = (0, import_react64.useState)(false);
32214
32231
  const [linkInputOpen, setLinkInputOpen] = (0, import_react64.useState)(false);
32215
- const [position, setPosition] = (0, import_react64.useState)({ top: 0, left: 0 });
32232
+ const [position, setPosition] = (0, import_react64.useState)({
32233
+ top: 0,
32234
+ left: 0,
32235
+ placement: "top"
32236
+ });
32216
32237
  const menuRef = (0, import_react64.useRef)(null);
32217
32238
  const keepOpenRef = (0, import_react64.useRef)(false);
32218
32239
  const showTimeoutRef = (0, import_react64.useRef)(null);
@@ -32269,12 +32290,18 @@ var CustomBubbleMenu = ({
32269
32290
  return;
32270
32291
  }
32271
32292
  const viewportPadding = 8;
32293
+ const menuHeight = menuRef.current?.getBoundingClientRect().height || BUBBLE_MENU_ESTIMATED_HEIGHT;
32294
+ const editorTop = view.dom.getBoundingClientRect().top;
32295
+ const selectionTop = Math.min(start.top, end.top);
32296
+ const selectionBottom = Math.max(start.bottom, end.bottom);
32297
+ const hasRoomAboveEditor = selectionTop - BUBBLE_MENU_OFFSET - menuHeight >= editorTop + viewportPadding;
32298
+ const placement = hasRoomAboveEditor ? "top" : "bottom";
32272
32299
  const left = Math.min(
32273
32300
  window.innerWidth - viewportPadding,
32274
32301
  Math.max(viewportPadding, (start.left + end.left) / 2)
32275
32302
  );
32276
- const top = Math.max(viewportPadding, start.top - BUBBLE_MENU_OFFSET);
32277
- setPosition({ top, left });
32303
+ const top = placement === "top" ? Math.max(viewportPadding, selectionTop - BUBBLE_MENU_OFFSET) : Math.min(window.innerHeight - viewportPadding, selectionBottom + BUBBLE_MENU_OFFSET);
32304
+ setPosition({ top, left, placement });
32278
32305
  if (keepOpenRef.current) {
32279
32306
  clearShowTimeout();
32280
32307
  setIsVisible(true);
@@ -32319,7 +32346,7 @@ var CustomBubbleMenu = ({
32319
32346
  style: {
32320
32347
  top: `${position.top}px`,
32321
32348
  left: `${position.left}px`,
32322
- transform: "translate(-50%, -100%)"
32349
+ transform: position.placement === "top" ? "translate(-50%, -100%)" : "translate(-50%, 0)"
32323
32350
  },
32324
32351
  onMouseDown: (e) => {
32325
32352
  const target = e.target;