mirror of
https://github.com/outline/outline.git
synced 2026-06-13 03:14:59 +03:00
fix: Event propagation from input in SidebarLink (#11105)
* fix: Event propagation from input in SidebarLink closes #11093 * Include keyboard
This commit is contained in:
@@ -2,6 +2,7 @@ import * as React from "react";
|
||||
import { toast } from "sonner";
|
||||
import styled from "styled-components";
|
||||
import { s, ellipsis } from "@shared/styles";
|
||||
import EventBoundary from "@shared/components/EventBoundary";
|
||||
|
||||
type Props = Omit<React.HTMLAttributes<HTMLInputElement>, "onSubmit"> & {
|
||||
/** A callback when the title is submitted. */
|
||||
@@ -141,11 +142,12 @@ function EditableTitle(
|
||||
return (
|
||||
<>
|
||||
{isEditing ? (
|
||||
<form onSubmit={handleSave}>
|
||||
<EventBoundary as="form" onSubmit={handleSave}>
|
||||
<Input
|
||||
dir="auto"
|
||||
type="text"
|
||||
lang=""
|
||||
name="title"
|
||||
value={value}
|
||||
onClick={stopPropagation}
|
||||
onKeyDown={handleKeyDown}
|
||||
@@ -155,7 +157,7 @@ function EditableTitle(
|
||||
autoFocus
|
||||
{...rest}
|
||||
/>
|
||||
</form>
|
||||
</EventBoundary>
|
||||
) : (
|
||||
<Text
|
||||
onDoubleClick={canUpdate ? handleDoubleClick : undefined}
|
||||
|
||||
@@ -139,7 +139,7 @@ const NavLink = ({
|
||||
(event: React.MouseEvent<HTMLAnchorElement>) => {
|
||||
onClick?.(event);
|
||||
|
||||
if (isActive) {
|
||||
if (isActive && !event.defaultPrevented) {
|
||||
onActiveClick?.(event);
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -11,7 +11,7 @@
|
||||
"build": "yarn clean && yarn vite:build && yarn build:i18n && yarn build:server",
|
||||
"start": "node ./build/server/index.js",
|
||||
"dev": "NODE_ENV=development yarn concurrently -n api,collaboration -c \"blue,magenta\" \"node --inspect=0.0.0.0 build/server/index.js --services=cron,collaboration,websockets,admin,web,worker\"",
|
||||
"dev:backend": "NODE_ENV=development nodemon --quiet --exec \"yarn build:server && yarn dev\" -e js,ts,tsx --watch server --watch shared --watch plugins --watch .env --watch .env.local --watch .env.development --ignore \"plugins/client/**/*.ts\" --ignore \"**/*.test.ts\" --ignore data/ --ignore build/ --ignore app/ --ignore shared/editor --ignore server/migrations",
|
||||
"dev:backend": "NODE_ENV=development nodemon --quiet --exec \"yarn build:server && yarn dev\" -e js,ts,tsx --watch server --watch shared --watch plugins --watch .env --watch .env.local --watch .env.development --ignore \"shared/components/**/*.tsx?\" --ignore \"plugins/client/**/*.tsx?\" --ignore \"**/*.test.ts\" --ignore data/ --ignore build/ --ignore app/ --ignore shared/editor --ignore server/migrations",
|
||||
"dev:watch": "NODE_ENV=development yarn concurrently -n backend,frontend \"yarn dev:backend\" \"yarn vite:dev\"",
|
||||
"lint": "oxlint --type-aware app server shared plugins",
|
||||
"lint:changed": "git diff --name-only --diff-filter=ACMRTUXB | grep -E '\\.(js|jsx|ts|tsx)$' | xargs -r oxlint",
|
||||
|
||||
@@ -1,47 +1,75 @@
|
||||
import * as React from "react";
|
||||
|
||||
type Props = {
|
||||
/**
|
||||
* Props for the EventBoundary component.
|
||||
*/
|
||||
export interface Props<T extends React.ElementType> {
|
||||
/** The element or component to render as. */
|
||||
as?: T;
|
||||
/** The children to be rendered within the boundary. */
|
||||
children?: React.ReactNode;
|
||||
/** Optional CSS class name. */
|
||||
className?: string;
|
||||
/**
|
||||
* Capture all events, pointer events, or click events.
|
||||
* @default "all"
|
||||
*/
|
||||
captureEvents?: "all" | "pointer" | "click";
|
||||
};
|
||||
captureEvents?: "all" | "pointer" | "click" | "mouse" | "keyboard";
|
||||
}
|
||||
|
||||
/**
|
||||
* EventBoundary is a component that prevents events from propagating to parent elements.
|
||||
* This is useful for preventing clicks or other interactions from bubbling up the DOM tree.
|
||||
*
|
||||
* @param props - the properties of the component.
|
||||
* @return a React component that captures events.
|
||||
*/
|
||||
const EventBoundary: React.FC<Props> = ({
|
||||
export const EventBoundary = <T extends React.ElementType = "span">({
|
||||
as,
|
||||
children,
|
||||
className,
|
||||
captureEvents = "all",
|
||||
}: Props) => {
|
||||
...rest
|
||||
}: Props<T> & Omit<React.ComponentPropsWithoutRef<T>, keyof Props<T>>) => {
|
||||
const Component = as || "span";
|
||||
|
||||
const stopEvent = React.useCallback((event: React.SyntheticEvent) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}, []);
|
||||
|
||||
let props = {};
|
||||
const eventHandlers: {
|
||||
onPointerDown?: React.PointerEventHandler;
|
||||
onPointerUp?: React.PointerEventHandler;
|
||||
onClick?: React.MouseEventHandler;
|
||||
onMouseDown?: React.MouseEventHandler;
|
||||
onMouseUp?: React.MouseEventHandler;
|
||||
onKeyDown?: React.KeyboardEventHandler;
|
||||
onKeyUp?: React.KeyboardEventHandler;
|
||||
} = {};
|
||||
|
||||
if (captureEvents === "all" || captureEvents === "keyboard") {
|
||||
eventHandlers.onKeyDown = stopEvent;
|
||||
eventHandlers.onKeyUp = stopEvent;
|
||||
}
|
||||
|
||||
if (captureEvents === "all" || captureEvents === "mouse") {
|
||||
eventHandlers.onMouseDown = stopEvent;
|
||||
eventHandlers.onMouseUp = stopEvent;
|
||||
}
|
||||
|
||||
if (captureEvents === "all" || captureEvents === "pointer") {
|
||||
props = {
|
||||
onPointerDown: stopEvent,
|
||||
onPointerUp: stopEvent,
|
||||
};
|
||||
eventHandlers.onPointerDown = stopEvent;
|
||||
eventHandlers.onPointerUp = stopEvent;
|
||||
}
|
||||
|
||||
if (captureEvents === "all" || captureEvents === "click") {
|
||||
props = {
|
||||
...props,
|
||||
onClick: stopEvent,
|
||||
};
|
||||
eventHandlers.onClick = stopEvent;
|
||||
}
|
||||
|
||||
return (
|
||||
<span {...props} className={className}>
|
||||
<Component {...rest} {...eventHandlers} className={className}>
|
||||
{children}
|
||||
</span>
|
||||
</Component>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user