@protobase/ui 0.43.2 → 0.44.1

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,30 +1,22 @@
1
1
  import { text } from "./messages.js";
2
- import { useEffect, useRef, useState } from "react";
2
+ import { usePopoverMenu } from "../primitives/popover-menu.js";
3
3
  import { MoreHorizontal } from "lucide-react";
4
4
  import { jsx, jsxs } from "react/jsx-runtime";
5
+ import { createPortal } from "react-dom";
5
6
  //#region src/data-grid/header-menu.tsx
6
7
  /** Why a column header does nothing when pressed: a list sorts only on the columns its view makes sortable. */
7
8
  const unsortableText = () => text("unsortable");
8
- /** The "..." in a column header: pinning the column to the left edge, and saying so when the list can't sort by it. */
9
+ /**
10
+ * The "..." in a column header: pinning the column to the left edge, and saying so when the list can't sort by it. The
11
+ * menu is portalled to the body, as the row menu is: the header row clips, so a menu inside it would be cut off at the
12
+ * header's bottom edge, with the rows showing where it should be.
13
+ */
9
14
  const HeaderMenu = ({ header, pinned, sortable, onTogglePin }) => {
10
- const [open, setOpen] = useState(false);
11
- const root = useRef(null);
12
- useEffect(() => {
13
- if (!open) return;
14
- const close = (event) => {
15
- if (event instanceof KeyboardEvent ? event.key === "Escape" : !root.current?.contains(event.target)) setOpen(false);
16
- };
17
- document.addEventListener("mousedown", close);
18
- document.addEventListener("keydown", close);
19
- return () => {
20
- document.removeEventListener("mousedown", close);
21
- document.removeEventListener("keydown", close);
22
- };
23
- }, [open]);
15
+ const { open, setOpen, place, button, menu } = usePopoverMenu();
24
16
  return /* @__PURE__ */ jsxs("div", {
25
- ref: root,
26
17
  className: "relative ml-auto shrink-0",
27
18
  children: [/* @__PURE__ */ jsx("button", {
19
+ ref: button,
28
20
  type: "button",
29
21
  "aria-label": text("columnMenu", header),
30
22
  "aria-haspopup": "menu",
@@ -32,9 +24,11 @@ const HeaderMenu = ({ header, pinned, sortable, onTogglePin }) => {
32
24
  onClick: () => setOpen(!open),
33
25
  className: "relative inline-flex size-5 items-center justify-center rounded text-faint-foreground hover:bg-muted hover:text-foreground focus-visible:opacity-100 after:absolute after:-inset-2 after:content-[''] md:opacity-0 md:group-hover/cell:opacity-100 aria-expanded:opacity-100",
34
26
  children: /* @__PURE__ */ jsx(MoreHorizontal, { className: "size-3.5" })
35
- }), open && /* @__PURE__ */ jsxs("ul", {
27
+ }), open && createPortal(/* @__PURE__ */ jsxs("ul", {
28
+ ref: menu,
36
29
  role: "menu",
37
- className: "absolute right-0 top-full z-30 mt-1 min-w-32 rounded-md border bg-background py-1 text-body font-normal text-foreground shadow-pop",
30
+ style: place ?? { visibility: "hidden" },
31
+ className: "fixed z-50 min-w-32 rounded-md border bg-background py-1 text-body font-normal text-foreground shadow-pop",
38
32
  children: [!sortable && /* @__PURE__ */ jsx("li", {
39
33
  role: "none",
40
34
  children: /* @__PURE__ */ jsx("span", {
@@ -56,7 +50,7 @@ const HeaderMenu = ({ header, pinned, sortable, onTogglePin }) => {
56
50
  children: pinned ? text("unpin") : text("pinToLeft")
57
51
  })
58
52
  })]
59
- })]
53
+ }), document.body)]
60
54
  });
61
55
  };
62
56
  //#endregion
package/dist/package.js CHANGED
@@ -1,5 +1,5 @@
1
1
  //#region package.json
2
- var version = "0.43.2";
2
+ var version = "0.44.1";
3
3
  var protobase = { "pluginApi": 1 };
4
4
  //#endregion
5
5
  export { protobase, version };
@@ -1,12 +1,15 @@
1
1
  import { useEffect, useLayoutEffect, useRef, useState } from "react";
2
2
  //#region src/primitives/popover-menu.ts
3
3
  const gap = 4;
4
- /** Below the button, or above it when the viewport has no room underneath; its right edge at the button's. */
5
- const placeMenu = (button, menuHeight) => {
4
+ /**
5
+ * Below the button, or above it when the viewport has no room underneath; its right edge at the button's, or further
6
+ * right when that would push its left edge off the viewport, as for a button near the left edge.
7
+ */
8
+ const placeMenu = (button, menu) => {
6
9
  const below = button.bottom + gap;
7
10
  return {
8
- top: below + menuHeight > window.innerHeight - gap ? Math.max(gap, button.top - gap - menuHeight) : below,
9
- right: window.innerWidth - button.right
11
+ top: below + menu.height > window.innerHeight - gap ? Math.max(gap, button.top - gap - menu.height) : below,
12
+ right: Math.max(gap, Math.min(window.innerWidth - button.right, window.innerWidth - gap - menu.width))
10
13
  };
11
14
  };
12
15
  /**
@@ -24,7 +27,10 @@ const usePopoverMenu = () => {
24
27
  setPlace(void 0);
25
28
  return;
26
29
  }
27
- setPlace(placeMenu(button.current.getBoundingClientRect(), menu.current.offsetHeight));
30
+ setPlace(placeMenu(button.current.getBoundingClientRect(), {
31
+ width: menu.current.offsetWidth,
32
+ height: menu.current.offsetHeight
33
+ }));
28
34
  }, [open]);
29
35
  useEffect(() => {
30
36
  if (!open) return;