Skip to content
Merged
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 37 additions & 1 deletion apps/roam/src/components/DiscourseNodeMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ type Props = {
extensionAPI: OnloadArgs["extensionAPI"];
trigger?: JSX.Element;
isShift?: boolean;
menuMaxHeight?: number;
};

const NodeMenu = ({
Expand All @@ -44,6 +45,7 @@ const NodeMenu = ({
extensionAPI,
trigger,
isShift,
menuMaxHeight,
}: { onClose: () => void } & Props) => {
const isInitialTextSelected =
!!textarea && textarea.selectionStart !== textarea.selectionEnd;
Expand Down Expand Up @@ -71,6 +73,23 @@ const NodeMenu = ({
const [activeIndex, setActiveIndex] = useState(0);
const [isOpen, setIsOpen] = useState(!trigger);

useEffect(() => {
const container = menuRef.current;
if (!container) return;
const activeItem = container.children[activeIndex] as
| HTMLElement
| undefined;
if (!activeItem) return;
const containerRect = container.getBoundingClientRect();
const itemRect = activeItem.getBoundingClientRect();
if (
itemRect.bottom > containerRect.bottom ||
itemRect.top < containerRect.top
) {
activeItem.scrollIntoView({ block: "nearest", behavior: "auto" });
}
}, [activeIndex]);

const onSelect = useCallback(
(index: number) => {
const menuItem =
Expand Down Expand Up @@ -259,7 +278,11 @@ const NodeMenu = ({
enforceFocus={false}
onInteraction={trigger ? handlePopoverInteraction : undefined}
content={
<Menu ulRef={menuRef} data-active-index={activeIndex}>
<Menu
ulRef={menuRef}
data-active-index={activeIndex}
style={{ overflowY: "auto", maxHeight: menuMaxHeight }}
>
{discourseNodes.map((item, i) => {
const nodeColor =
formatHexColor(item?.canvasSettings?.color) || "#000";
Expand Down Expand Up @@ -302,15 +325,22 @@ const NodeMenu = ({

export const render = (props: Props) => {
if (!props.textarea) return;
if (props.textarea.parentElement?.querySelector("[data-discourse-node-menu]"))
return;
const parent = document.createElement("span");
parent.setAttribute("data-discourse-node-menu", "true");
const coords = getCoordsFromTextarea(props.textarea);
parent.style.position = "absolute";
parent.style.left = `${coords.left}px`;
parent.style.top = `${coords.top}px`;
props.textarea.parentElement?.insertBefore(parent, props.textarea);
const parentTop =
props.textarea.parentElement?.getBoundingClientRect().top ?? 0;
const menuMaxHeight = window.innerHeight - (parentTop + coords.top) - 24;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The menuMaxHeight calculation can result in negative or extremely small values when there is insufficient vertical space below the menu position. This will cause the menu to be unusable or invisible.

Unlike the second calculation at lines 377-380 which uses Math.max(..., 100) to ensure a minimum height, this calculation has no safeguard.

Fix:

const menuMaxHeight = Math.max(
  window.innerHeight - (parentTop + coords.top) - 24,
  100
);
Suggested change
const parentTop =
props.textarea.parentElement?.getBoundingClientRect().top ?? 0;
const menuMaxHeight = window.innerHeight - (parentTop + coords.top) - 24;
const parentTop =
props.textarea.parentElement?.getBoundingClientRect().top ?? 0;
const menuMaxHeight = Math.max(
window.innerHeight - (parentTop + coords.top) - 24,
100
);

Spotted by Graphite

Fix in Graphite


Is this helpful? React 👍 or 👎 to let us know.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i actually prefer without the max option, because that means all nodes will be visible within the view
image

if we set the max between the calculated value and 100, then user might still be unable to see the bottom of the list

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes I enabled flipping from the Popover and here's the updated behavior
https://www.loom.com/share/1dfdf25d641f410b8f0d9f971d943820

ReactDOM.render(
<NodeMenu
{...props}
menuMaxHeight={menuMaxHeight}
onClose={() => {
ReactDOM.unmountComponentAtNode(parent);
parent.remove();
Expand Down Expand Up @@ -355,13 +385,19 @@ export const TextSelectionNodeMenu = ({
/>
);

const menuMaxHeight = Math.max(
window.innerHeight - textarea.getBoundingClientRect().bottom - 8,
100,
);

return (
<NodeMenu
textarea={textarea}
extensionAPI={extensionAPI}
trigger={trigger}
onClose={onClose}
isShift
menuMaxHeight={menuMaxHeight}
/>
);
};
Expand Down
Loading