mirror of
https://github.com/webstudio-is/webstudio.git
synced 2026-11-05 19:49:57 +09:00
feat: Close the dialog when a navigable link within it is clicked (#3329)
## Description Close the dialog when a navigable link within it is clicked Fixes the issue when we have anchor links inside Sheet ## Steps for reproduction ### Project Example: https://href.staging.webstudio.is/builder/95821bd9-040a-4294-aa7f-edf26d7f1ebe ### Published: https://link-inside-sheet-saas-sk7pk.wstd.work/ Open sheet Click links, links should work ## Code Review - [ ] hi @kof, I need you to do - conceptual review (architecture, feature-correctness) - detailed review (read every line) - test it on preview ## Before requesting a review - [ ] made a self-review - [ ] added inline comments where things may be not obvious (the "why", not "what") ## Before merging - [ ] tested locally and on preview environment (preview dev login: 5de6) - [ ] updated [test cases](https://github.com/webstudio-is/webstudio/blob/main/apps/builder/docs/test-cases.md) document - [ ] added tests - [ ] if any new env variables are added, added them to `.env.example` and the `builder/env-check.js` if mandatory
This commit is contained in:
1 parent
ce1f9026cd
commit
e806bcbf5c
1 file changed
+122
-2
@@ -7,15 +7,86 @@ import {
|
||||
forwardRef,
|
||||
Children,
|
||||
type ComponentProps,
|
||||
useEffect,
|
||||
useRef,
|
||||
useContext,
|
||||
} from "react";
|
||||
import * as DialogPrimitive from "@radix-ui/react-dialog";
|
||||
import { getClosestInstance, type Hook } from "@webstudio-is/react-sdk";
|
||||
import {
|
||||
ReactSdkContext,
|
||||
getClosestInstance,
|
||||
type Hook,
|
||||
} from "@webstudio-is/react-sdk";
|
||||
|
||||
/**
|
||||
* Naive heuristic to determine if a click event will cause navigate
|
||||
*/
|
||||
const willNavigate = (event: MouseEvent) => {
|
||||
const { target } = event;
|
||||
|
||||
if (target instanceof HTMLAnchorElement === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (target.hasAttribute("href") === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (target.href === "#") {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (target.hasAttribute("target") && target.target === "_blank") {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (event.ctrlKey || event.metaKey) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
// wrap in forwardRef because Root is functional component without ref
|
||||
export const Dialog = forwardRef<
|
||||
HTMLDivElement,
|
||||
Omit<ComponentPropsWithoutRef<typeof DialogPrimitive.Root>, "defaultOpen">
|
||||
>((props, _ref) => {
|
||||
const { open, onOpenChange } = props;
|
||||
const { renderer } = useContext(ReactSdkContext);
|
||||
|
||||
/**
|
||||
* Close the dialog when a navigable link within it is clicked.
|
||||
*/
|
||||
useEffect(() => {
|
||||
if (renderer !== undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (open === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
const handleClick = (event: MouseEvent) => {
|
||||
const { target } = event;
|
||||
|
||||
if (willNavigate(event) === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (target instanceof HTMLAnchorElement === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (target.closest('[role="dialog"]')) {
|
||||
onOpenChange?.(false);
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener("click", handleClick);
|
||||
return () => window.removeEventListener("click", handleClick);
|
||||
}, [open, onOpenChange, renderer]);
|
||||
|
||||
return <DialogPrimitive.Root {...props} />;
|
||||
});
|
||||
|
||||
@@ -49,7 +120,56 @@ export const DialogOverlay = forwardRef<
|
||||
);
|
||||
});
|
||||
|
||||
export const DialogContent = DialogPrimitive.Content;
|
||||
export const DialogContent = forwardRef<
|
||||
HTMLDivElement,
|
||||
ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
|
||||
>((props, ref) => {
|
||||
const preventAutoFocusOnClose = useRef(false);
|
||||
const { renderer } = useContext(ReactSdkContext);
|
||||
|
||||
/**
|
||||
* Prevent focusing on the trigger after a navigable link in a dialog is clicked and closes the dialog.
|
||||
*/
|
||||
useEffect(() => {
|
||||
if (renderer !== undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
preventAutoFocusOnClose.current = false;
|
||||
|
||||
const handleClick = (event: MouseEvent) => {
|
||||
const { target } = event;
|
||||
|
||||
if (willNavigate(event) === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (target instanceof HTMLAnchorElement === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (target.closest('[role="dialog"]')) {
|
||||
preventAutoFocusOnClose.current = true;
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener("click", handleClick);
|
||||
return () => window.removeEventListener("click", handleClick);
|
||||
}, [renderer]);
|
||||
|
||||
return (
|
||||
<DialogPrimitive.Content
|
||||
ref={ref}
|
||||
{...props}
|
||||
onCloseAutoFocus={(event) => {
|
||||
if (preventAutoFocusOnClose.current) {
|
||||
event.preventDefault();
|
||||
}
|
||||
}}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
export const DialogClose = DialogPrimitive.Close;
|
||||
|
||||
type Tag = "h1" | "h2" | "h3" | "h4" | "h5" | "h6";
|
||||
|
||||
Reference in new issue
Block a user