mirror of
https://github.com/excalidraw/excalidraw.git
synced 2025-09-19 15:31:04 +02:00
remove refactored popups
This commit is contained in:
@@ -1,122 +0,0 @@
|
|||||||
import React, { useEffect, useRef, useState } from "react";
|
|
||||||
|
|
||||||
import { CLASSES, capitalizeString } from "@excalidraw/common";
|
|
||||||
import { trackEvent } from "../analytics";
|
|
||||||
import { t } from "../i18n";
|
|
||||||
import { ToolButton } from "./ToolButton";
|
|
||||||
import { ArrowIcon, LineIcon } from "./icons";
|
|
||||||
|
|
||||||
import type { AppClassProperties } from "../types";
|
|
||||||
|
|
||||||
import "./ConvertElementTypePopup.scss";
|
|
||||||
|
|
||||||
const LINEAR_ELEMENT_TYPES = [
|
|
||||||
{ type: "arrow", icon: ArrowIcon },
|
|
||||||
{ type: "line", icon: LineIcon },
|
|
||||||
] as const;
|
|
||||||
|
|
||||||
type LinearElementType = "arrow" | "line";
|
|
||||||
|
|
||||||
type LinearElementTypePopupProps = {
|
|
||||||
app: AppClassProperties;
|
|
||||||
triggerElement: HTMLElement | null;
|
|
||||||
isOpen: boolean;
|
|
||||||
onClose: () => void;
|
|
||||||
currentType: LinearElementType;
|
|
||||||
onChange?: (type: LinearElementType) => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const LinearElementTypePopup = ({
|
|
||||||
app,
|
|
||||||
triggerElement,
|
|
||||||
isOpen,
|
|
||||||
onClose,
|
|
||||||
onChange,
|
|
||||||
currentType,
|
|
||||||
}: LinearElementTypePopupProps) => {
|
|
||||||
const [panelPosition, setPanelPosition] = useState({ x: 0, y: 0 });
|
|
||||||
const panelRef = useRef<HTMLDivElement>(null);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!isOpen || !triggerElement) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const updatePosition = () => {
|
|
||||||
const triggerRect = triggerElement.getBoundingClientRect();
|
|
||||||
const panelRect = panelRef.current?.getBoundingClientRect();
|
|
||||||
const panelWidth = panelRect?.width ?? 0;
|
|
||||||
const panelHeight = panelRect?.height ?? 0;
|
|
||||||
setPanelPosition({
|
|
||||||
x: triggerRect.x + triggerRect.width / 2 - panelWidth / 2,
|
|
||||||
y: triggerRect.top - panelHeight - 16,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
updatePosition();
|
|
||||||
|
|
||||||
const handleClick = (event: MouseEvent) => {
|
|
||||||
const target = event.target as Node | null;
|
|
||||||
const panelEl = panelRef.current;
|
|
||||||
const triggerEl = triggerElement;
|
|
||||||
if (!target) {
|
|
||||||
onClose();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const clickedInsidePanel = !!panelEl && panelEl.contains(target);
|
|
||||||
const clickedTrigger = !!triggerEl && triggerEl.contains(target);
|
|
||||||
if (!clickedInsidePanel && !clickedTrigger) {
|
|
||||||
onClose();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// use capture to ensure we run before potential re-renders hide elements
|
|
||||||
document.addEventListener("pointerdown", handleClick, true);
|
|
||||||
document.addEventListener("pointerup", handleClick, true);
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
document.removeEventListener("pointerdown", handleClick, true);
|
|
||||||
document.removeEventListener("pointerup", handleClick, true);
|
|
||||||
};
|
|
||||||
}, [isOpen, triggerElement, onClose]);
|
|
||||||
|
|
||||||
if (!isOpen) return null;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
ref={panelRef}
|
|
||||||
tabIndex={-1}
|
|
||||||
style={{
|
|
||||||
position: "fixed",
|
|
||||||
top: `${panelPosition.y}px`,
|
|
||||||
left: `${panelPosition.x}px`,
|
|
||||||
zIndex: 2,
|
|
||||||
}}
|
|
||||||
className={CLASSES.CONVERT_ELEMENT_TYPE_POPUP}
|
|
||||||
>
|
|
||||||
{LINEAR_ELEMENT_TYPES.map(({ type, icon }) => (
|
|
||||||
<ToolButton
|
|
||||||
className="LinearElement"
|
|
||||||
key={type}
|
|
||||||
type="radio"
|
|
||||||
icon={icon}
|
|
||||||
checked={currentType === type}
|
|
||||||
name="linearElementType-option"
|
|
||||||
title={capitalizeString(t(`toolBar.${type}`))}
|
|
||||||
keyBindingLabel=""
|
|
||||||
aria-label={capitalizeString(t(`toolBar.${type}`))}
|
|
||||||
data-testid={`toolbar-${type}`}
|
|
||||||
onChange={() => {
|
|
||||||
if (app.state.activeTool.type !== type) {
|
|
||||||
trackEvent("toolbar", type, "ui");
|
|
||||||
}
|
|
||||||
app.setActiveTool({ type: type as any });
|
|
||||||
onChange?.(type);
|
|
||||||
// Intentionally NOT calling onClose here; popup should stay open
|
|
||||||
// until user clicks outside trigger or popup per requirements.
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
@@ -1,122 +0,0 @@
|
|||||||
import React, { useEffect, useRef, useState } from "react";
|
|
||||||
|
|
||||||
import { CLASSES, capitalizeString } from "@excalidraw/common";
|
|
||||||
import { trackEvent } from "../analytics";
|
|
||||||
import { t } from "../i18n";
|
|
||||||
import { ToolButton } from "./ToolButton";
|
|
||||||
import { SelectionIcon, LassoIcon } from "./icons";
|
|
||||||
|
|
||||||
import type { AppClassProperties } from "../types";
|
|
||||||
|
|
||||||
import "./ConvertElementTypePopup.scss";
|
|
||||||
|
|
||||||
const SELECTION_TYPES = [
|
|
||||||
{ type: "selection", icon: SelectionIcon },
|
|
||||||
{ type: "lasso", icon: LassoIcon },
|
|
||||||
] as const;
|
|
||||||
|
|
||||||
type SelectionType = "selection" | "lasso";
|
|
||||||
|
|
||||||
type SelectionTypePopupProps = {
|
|
||||||
app: AppClassProperties;
|
|
||||||
triggerElement: HTMLElement | null;
|
|
||||||
isOpen: boolean;
|
|
||||||
onClose: () => void;
|
|
||||||
currentType: SelectionType;
|
|
||||||
onChange?: (type: SelectionType) => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const SelectionTypePopup = ({
|
|
||||||
app,
|
|
||||||
triggerElement,
|
|
||||||
isOpen,
|
|
||||||
onClose,
|
|
||||||
onChange,
|
|
||||||
currentType,
|
|
||||||
}: SelectionTypePopupProps) => {
|
|
||||||
const [panelPosition, setPanelPosition] = useState({ x: 0, y: 0 });
|
|
||||||
const panelRef = useRef<HTMLDivElement>(null);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!isOpen || !triggerElement) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const updatePosition = () => {
|
|
||||||
const triggerRect = triggerElement.getBoundingClientRect();
|
|
||||||
const panelRect = panelRef.current?.getBoundingClientRect();
|
|
||||||
const panelWidth = panelRect?.width ?? 0;
|
|
||||||
const panelHeight = panelRect?.height ?? 0;
|
|
||||||
setPanelPosition({
|
|
||||||
x: triggerRect.x + triggerRect.width / 2 - panelWidth / 2,
|
|
||||||
y: triggerRect.top - panelHeight - 16,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
updatePosition();
|
|
||||||
|
|
||||||
const handleClick = (event: MouseEvent) => {
|
|
||||||
const target = event.target as Node | null;
|
|
||||||
const panelEl = panelRef.current;
|
|
||||||
const triggerEl = triggerElement;
|
|
||||||
if (!target) {
|
|
||||||
onClose();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const clickedInsidePanel = !!panelEl && panelEl.contains(target);
|
|
||||||
const clickedTrigger = !!triggerEl && triggerEl.contains(target);
|
|
||||||
if (!clickedInsidePanel && !clickedTrigger) {
|
|
||||||
onClose();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// use capture to ensure we run before potential re-renders hide elements
|
|
||||||
document.addEventListener("pointerdown", handleClick, true);
|
|
||||||
document.addEventListener("pointerup", handleClick, true);
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
document.removeEventListener("pointerdown", handleClick, true);
|
|
||||||
document.removeEventListener("pointerup", handleClick, true);
|
|
||||||
};
|
|
||||||
}, [isOpen, triggerElement, onClose]);
|
|
||||||
|
|
||||||
if (!isOpen) return null;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
ref={panelRef}
|
|
||||||
tabIndex={-1}
|
|
||||||
style={{
|
|
||||||
position: "fixed",
|
|
||||||
top: `${panelPosition.y}px`,
|
|
||||||
left: `${panelPosition.x}px`,
|
|
||||||
zIndex: 2,
|
|
||||||
}}
|
|
||||||
className={CLASSES.CONVERT_ELEMENT_TYPE_POPUP}
|
|
||||||
>
|
|
||||||
{SELECTION_TYPES.map(({ type, icon }) => (
|
|
||||||
<ToolButton
|
|
||||||
className="Selection"
|
|
||||||
key={type}
|
|
||||||
type="radio"
|
|
||||||
icon={icon}
|
|
||||||
checked={currentType === type}
|
|
||||||
name="selectionType-option"
|
|
||||||
title={capitalizeString(t(`toolBar.${type}`))}
|
|
||||||
keyBindingLabel=""
|
|
||||||
aria-label={capitalizeString(t(`toolBar.${type}`))}
|
|
||||||
data-testid={`toolbar-${type}`}
|
|
||||||
onChange={() => {
|
|
||||||
if (app.state.activeTool.type !== type) {
|
|
||||||
trackEvent("toolbar", type, "ui");
|
|
||||||
}
|
|
||||||
app.setActiveTool({ type: type as any });
|
|
||||||
onChange?.(type);
|
|
||||||
// Intentionally NOT calling onClose here; popup should stay open
|
|
||||||
// until user clicks outside trigger or popup per requirements.
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
@@ -1,123 +0,0 @@
|
|||||||
import React, { useEffect, useRef, useState } from "react";
|
|
||||||
|
|
||||||
import { CLASSES, capitalizeString } from "@excalidraw/common";
|
|
||||||
import { trackEvent } from "../analytics";
|
|
||||||
import { t } from "../i18n";
|
|
||||||
import { ToolButton } from "./ToolButton";
|
|
||||||
import { DiamondIcon, EllipseIcon, RectangleIcon } from "./icons";
|
|
||||||
|
|
||||||
import type { AppClassProperties } from "../types";
|
|
||||||
|
|
||||||
import "./ConvertElementTypePopup.scss";
|
|
||||||
|
|
||||||
const GAP_HORIZONTAL = 44;
|
|
||||||
const GAP_VERTICAL = -94;
|
|
||||||
|
|
||||||
const GENERIC_SHAPES = [
|
|
||||||
{ type: "rectangle", icon: RectangleIcon },
|
|
||||||
{ type: "diamond", icon: DiamondIcon },
|
|
||||||
{ type: "ellipse", icon: EllipseIcon },
|
|
||||||
] as const;
|
|
||||||
|
|
||||||
type ShapeTypePopupProps = {
|
|
||||||
app: AppClassProperties;
|
|
||||||
triggerElement: HTMLElement | null;
|
|
||||||
isOpen: boolean;
|
|
||||||
onClose: () => void;
|
|
||||||
currentType: string;
|
|
||||||
onChange?: (type: "rectangle" | "diamond" | "ellipse") => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const ShapeTypePopup = ({
|
|
||||||
app,
|
|
||||||
triggerElement,
|
|
||||||
isOpen,
|
|
||||||
onClose,
|
|
||||||
onChange,
|
|
||||||
currentType,
|
|
||||||
}: ShapeTypePopupProps) => {
|
|
||||||
const [panelPosition, setPanelPosition] = useState({ x: 0, y: 0 });
|
|
||||||
const panelRef = useRef<HTMLDivElement>(null);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!isOpen || !triggerElement) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const updatePosition = () => {
|
|
||||||
const triggerRect = triggerElement.getBoundingClientRect();
|
|
||||||
const panelRect = panelRef.current?.getBoundingClientRect();
|
|
||||||
const panelWidth = panelRect?.width ?? 0;
|
|
||||||
const panelHeight = panelRect?.height ?? 0;
|
|
||||||
setPanelPosition({
|
|
||||||
x: triggerRect.x + triggerRect.width / 2 - panelWidth / 2,
|
|
||||||
y: triggerRect.top - panelHeight - 16,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
updatePosition();
|
|
||||||
|
|
||||||
// Outside click handling (capture pointer events for reliability on mobile)
|
|
||||||
const handlePointer = (event: PointerEvent) => {
|
|
||||||
const target = event.target as Node | null;
|
|
||||||
const panelEl = panelRef.current;
|
|
||||||
const triggerEl = triggerElement;
|
|
||||||
if (!target) {
|
|
||||||
onClose();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const insidePanel = !!panelEl && panelEl.contains(target);
|
|
||||||
const onTrigger = !!triggerEl && triggerEl.contains(target);
|
|
||||||
if (!insidePanel && !onTrigger) {
|
|
||||||
onClose();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
document.addEventListener("pointerdown", handlePointer, true);
|
|
||||||
document.addEventListener("pointerup", handlePointer, true);
|
|
||||||
return () => {
|
|
||||||
document.removeEventListener("pointerdown", handlePointer, true);
|
|
||||||
document.removeEventListener("pointerup", handlePointer, true);
|
|
||||||
};
|
|
||||||
}, [isOpen, triggerElement, onClose]);
|
|
||||||
|
|
||||||
if (!isOpen) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
ref={panelRef}
|
|
||||||
tabIndex={-1}
|
|
||||||
style={{
|
|
||||||
position: "fixed",
|
|
||||||
top: `${panelPosition.y}px`,
|
|
||||||
left: `${panelPosition.x}px`,
|
|
||||||
zIndex: 2,
|
|
||||||
}}
|
|
||||||
className={CLASSES.CONVERT_ELEMENT_TYPE_POPUP}
|
|
||||||
>
|
|
||||||
{GENERIC_SHAPES.map(({ type, icon }) => (
|
|
||||||
<ToolButton
|
|
||||||
className="Shape"
|
|
||||||
key={type}
|
|
||||||
type="radio"
|
|
||||||
icon={icon}
|
|
||||||
checked={currentType === type}
|
|
||||||
name="shapeType-option"
|
|
||||||
title={capitalizeString(t(`toolBar.${type}`))}
|
|
||||||
keyBindingLabel=""
|
|
||||||
aria-label={capitalizeString(t(`toolBar.${type}`))}
|
|
||||||
data-testid={`toolbar-${type}`}
|
|
||||||
onChange={() => {
|
|
||||||
if (app.state.activeTool.type !== type) {
|
|
||||||
trackEvent("toolbar", type, "ui");
|
|
||||||
}
|
|
||||||
app.setActiveTool({ type: type as any });
|
|
||||||
onChange?.(type);
|
|
||||||
// Do NOT close here; keep popup open until user clicks outside (parity with SelectionTypePopup)
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
Reference in New Issue
Block a user