mirror of
				https://github.com/excalidraw/excalidraw.git
				synced 2025-11-03 20:34:40 +01:00 
			
		
		
		
	Resize handler detection should not be active when moving multip… (#767)
* Fix bug. * Implement `getSelectedElements`. * Explicit condition. * Respect variable naming. * Keep state consistent. * Use `isSomeElementSelected` abstraction. * Missing ones.
This commit is contained in:
		@@ -1,5 +1,5 @@
 | 
			
		||||
import { Action } from "./types";
 | 
			
		||||
import { deleteSelectedElements } from "../scene";
 | 
			
		||||
import { deleteSelectedElements, isSomeElementSelected } from "../scene";
 | 
			
		||||
import { KEYS } from "../keys";
 | 
			
		||||
 | 
			
		||||
export const actionDeleteSelected: Action = {
 | 
			
		||||
@@ -12,6 +12,6 @@ export const actionDeleteSelected: Action = {
 | 
			
		||||
  },
 | 
			
		||||
  contextItemLabel: "labels.delete",
 | 
			
		||||
  contextMenuOrder: 3,
 | 
			
		||||
  commitToHistory: (_, elements) => elements.some(el => el.isSelected),
 | 
			
		||||
  commitToHistory: (_, elements) => isSomeElementSelected(elements),
 | 
			
		||||
  keyTest: event => event.key === KEYS.BACKSPACE || event.key === KEYS.DELETE,
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,10 @@
 | 
			
		||||
import React from "react";
 | 
			
		||||
import { Action } from "./types";
 | 
			
		||||
import { ExcalidrawElement, ExcalidrawTextElement } from "../element/types";
 | 
			
		||||
import { getCommonAttributeOfSelectedElements } from "../scene";
 | 
			
		||||
import {
 | 
			
		||||
  getCommonAttributeOfSelectedElements,
 | 
			
		||||
  isSomeElementSelected,
 | 
			
		||||
} from "../scene";
 | 
			
		||||
import { ButtonSelect } from "../components/ButtonSelect";
 | 
			
		||||
import { isTextElement, redrawTextBoundingBox } from "../element";
 | 
			
		||||
import { ColorPicker } from "../components/ColorPicker";
 | 
			
		||||
@@ -28,7 +31,7 @@ const getFormValue = function<T>(
 | 
			
		||||
): T | null {
 | 
			
		||||
  return (
 | 
			
		||||
    (editingElement && getAttribute(editingElement)) ??
 | 
			
		||||
    (elements.some(element => element.isSelected)
 | 
			
		||||
    (isSomeElementSelected(elements)
 | 
			
		||||
      ? getCommonAttributeOfSelectedElements(elements, getAttribute)
 | 
			
		||||
      : defaultValue) ??
 | 
			
		||||
    null
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,5 @@
 | 
			
		||||
import { ExcalidrawElement } from "./element/types";
 | 
			
		||||
import { getSelectedElements } from "./scene";
 | 
			
		||||
 | 
			
		||||
let CLIPBOARD = "";
 | 
			
		||||
let PREFER_APP_CLIPBOARD = false;
 | 
			
		||||
@@ -19,9 +20,7 @@ export async function copyToAppClipboard(
 | 
			
		||||
  elements: readonly ExcalidrawElement[],
 | 
			
		||||
) {
 | 
			
		||||
  CLIPBOARD = JSON.stringify(
 | 
			
		||||
    elements
 | 
			
		||||
      .filter(element => element.isSelected)
 | 
			
		||||
      .map(({ shape, ...el }) => el),
 | 
			
		||||
    getSelectedElements(elements).map(({ shape, ...el }) => el),
 | 
			
		||||
  );
 | 
			
		||||
  try {
 | 
			
		||||
    // when copying to in-app clipboard, clear system clipboard so that if
 | 
			
		||||
 
 | 
			
		||||
@@ -16,6 +16,7 @@ import { t } from "../i18n";
 | 
			
		||||
import { KEYS } from "../keys";
 | 
			
		||||
 | 
			
		||||
import { probablySupportsClipboardBlob } from "../clipboard";
 | 
			
		||||
import { getSelectedElements, isSomeElementSelected } from "../scene";
 | 
			
		||||
 | 
			
		||||
const scales = [1, 2, 3];
 | 
			
		||||
const defaultScale = scales.includes(devicePixelRatio) ? devicePixelRatio : 1;
 | 
			
		||||
@@ -46,7 +47,7 @@ function ExportModal({
 | 
			
		||||
  onExportToBackend: ExportCB;
 | 
			
		||||
  onCloseRequest: () => void;
 | 
			
		||||
}) {
 | 
			
		||||
  const someElementIsSelected = elements.some(element => element.isSelected);
 | 
			
		||||
  const someElementIsSelected = isSomeElementSelected(elements);
 | 
			
		||||
  const [scale, setScale] = useState(defaultScale);
 | 
			
		||||
  const [exportSelected, setExportSelected] = useState(someElementIsSelected);
 | 
			
		||||
  const previewRef = useRef<HTMLDivElement>(null);
 | 
			
		||||
@@ -56,7 +57,7 @@ function ExportModal({
 | 
			
		||||
  const onlySelectedInput = useRef<HTMLInputElement>(null);
 | 
			
		||||
 | 
			
		||||
  const exportedElements = exportSelected
 | 
			
		||||
    ? elements.filter(element => element.isSelected)
 | 
			
		||||
    ? getSelectedElements(elements)
 | 
			
		||||
    : elements;
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,7 @@
 | 
			
		||||
import React from "react";
 | 
			
		||||
import { t } from "../i18n";
 | 
			
		||||
import { ExcalidrawElement } from "../element/types";
 | 
			
		||||
import { getSelectedElements } from "../scene";
 | 
			
		||||
 | 
			
		||||
import "./HintViewer.css";
 | 
			
		||||
 | 
			
		||||
@@ -20,7 +21,7 @@ const getHints = ({ elementType, multiMode, isResizing, elements }: Hint) => {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (isResizing) {
 | 
			
		||||
    const selectedElements = elements.filter(el => el.isSelected);
 | 
			
		||||
    const selectedElements = getSelectedElements(elements);
 | 
			
		||||
    if (
 | 
			
		||||
      selectedElements.length === 1 &&
 | 
			
		||||
      (selectedElements[0].type === "arrow" ||
 | 
			
		||||
 
 | 
			
		||||
@@ -36,6 +36,8 @@ import {
 | 
			
		||||
  loadFromBlob,
 | 
			
		||||
  getZoomOrigin,
 | 
			
		||||
  getNormalizedZoom,
 | 
			
		||||
  getSelectedElements,
 | 
			
		||||
  isSomeElementSelected,
 | 
			
		||||
} from "./scene";
 | 
			
		||||
 | 
			
		||||
import { renderScene } from "./renderer";
 | 
			
		||||
@@ -275,7 +277,7 @@ const LayerUI = React.memo(
 | 
			
		||||
      const { elementType, editingElement } = appState;
 | 
			
		||||
      const targetElements = editingElement
 | 
			
		||||
        ? [editingElement]
 | 
			
		||||
        : elements.filter(el => el.isSelected);
 | 
			
		||||
        : getSelectedElements(elements);
 | 
			
		||||
      if (!targetElements.length && elementType === "selection") {
 | 
			
		||||
        return null;
 | 
			
		||||
      }
 | 
			
		||||
@@ -1046,11 +1048,15 @@ export class App extends React.Component<any, AppState> {
 | 
			
		||||
                  { x, y },
 | 
			
		||||
                  this.state.zoom,
 | 
			
		||||
                );
 | 
			
		||||
                this.setState({
 | 
			
		||||
                  resizingElement: resizeElement ? resizeElement.element : null,
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                if (resizeElement) {
 | 
			
		||||
                const selectedElements = getSelectedElements(elements);
 | 
			
		||||
                if (selectedElements.length === 1 && resizeElement) {
 | 
			
		||||
                  this.setState({
 | 
			
		||||
                    resizingElement: resizeElement
 | 
			
		||||
                      ? resizeElement.element
 | 
			
		||||
                      : null,
 | 
			
		||||
                  });
 | 
			
		||||
 | 
			
		||||
                  resizeHandle = resizeElement.resizeHandle;
 | 
			
		||||
                  document.documentElement.style.cursor = getCursorForResizingElement(
 | 
			
		||||
                    resizeElement,
 | 
			
		||||
@@ -1087,13 +1093,11 @@ export class App extends React.Component<any, AppState> {
 | 
			
		||||
                          ...element,
 | 
			
		||||
                          isSelected: false,
 | 
			
		||||
                        })),
 | 
			
		||||
                        ...elements
 | 
			
		||||
                          .filter(element => element.isSelected)
 | 
			
		||||
                          .map(element => {
 | 
			
		||||
                            const newElement = duplicateElement(element);
 | 
			
		||||
                            newElement.isSelected = true;
 | 
			
		||||
                            return newElement;
 | 
			
		||||
                          }),
 | 
			
		||||
                        ...getSelectedElements(elements).map(element => {
 | 
			
		||||
                          const newElement = duplicateElement(element);
 | 
			
		||||
                          newElement.isSelected = true;
 | 
			
		||||
                          return newElement;
 | 
			
		||||
                        }),
 | 
			
		||||
                      ];
 | 
			
		||||
                    }
 | 
			
		||||
                  }
 | 
			
		||||
@@ -1328,7 +1332,7 @@ export class App extends React.Component<any, AppState> {
 | 
			
		||||
                if (isResizingElements && this.state.resizingElement) {
 | 
			
		||||
                  this.setState({ isResizing: true });
 | 
			
		||||
                  const el = this.state.resizingElement;
 | 
			
		||||
                  const selectedElements = elements.filter(el => el.isSelected);
 | 
			
		||||
                  const selectedElements = getSelectedElements(elements);
 | 
			
		||||
                  if (selectedElements.length === 1) {
 | 
			
		||||
                    const { x, y } = viewportCoordsToSceneCoords(
 | 
			
		||||
                      e,
 | 
			
		||||
@@ -1555,8 +1559,8 @@ export class App extends React.Component<any, AppState> {
 | 
			
		||||
                  // Marking that click was used for dragging to check
 | 
			
		||||
                  // if elements should be deselected on mouseup
 | 
			
		||||
                  draggingOccurred = true;
 | 
			
		||||
                  const selectedElements = elements.filter(el => el.isSelected);
 | 
			
		||||
                  if (selectedElements.length) {
 | 
			
		||||
                  const selectedElements = getSelectedElements(elements);
 | 
			
		||||
                  if (selectedElements.length > 0) {
 | 
			
		||||
                    const { x, y } = viewportCoordsToSceneCoords(
 | 
			
		||||
                      e,
 | 
			
		||||
                      this.state,
 | 
			
		||||
@@ -1638,7 +1642,7 @@ export class App extends React.Component<any, AppState> {
 | 
			
		||||
                draggingElement.shape = null;
 | 
			
		||||
 | 
			
		||||
                if (this.state.elementType === "selection") {
 | 
			
		||||
                  if (!e.shiftKey && elements.some(el => el.isSelected)) {
 | 
			
		||||
                  if (!e.shiftKey && isSomeElementSelected(elements)) {
 | 
			
		||||
                    elements = clearSelection(elements);
 | 
			
		||||
                  }
 | 
			
		||||
                  const elementsWithinSelection = getElementsWithinSelection(
 | 
			
		||||
@@ -1772,7 +1776,7 @@ export class App extends React.Component<any, AppState> {
 | 
			
		||||
 | 
			
		||||
                if (
 | 
			
		||||
                  elementType !== "selection" ||
 | 
			
		||||
                  elements.some(el => el.isSelected)
 | 
			
		||||
                  isSomeElementSelected(elements)
 | 
			
		||||
                ) {
 | 
			
		||||
                  history.resumeRecording();
 | 
			
		||||
                }
 | 
			
		||||
@@ -1941,9 +1945,8 @@ export class App extends React.Component<any, AppState> {
 | 
			
		||||
                return;
 | 
			
		||||
              }
 | 
			
		||||
 | 
			
		||||
              const selectedElements = elements.filter(e => e.isSelected)
 | 
			
		||||
                .length;
 | 
			
		||||
              if (selectedElements === 1) {
 | 
			
		||||
              const selectedElements = getSelectedElements(elements);
 | 
			
		||||
              if (selectedElements.length === 1) {
 | 
			
		||||
                const resizeElement = getElementWithResizeHandler(
 | 
			
		||||
                  elements,
 | 
			
		||||
                  { x, y },
 | 
			
		||||
 
 | 
			
		||||
@@ -12,6 +12,7 @@ import {
 | 
			
		||||
  SCROLLBAR_WIDTH,
 | 
			
		||||
} from "../scene/scrollbars";
 | 
			
		||||
import { getZoomTranslation } from "../scene/zoom";
 | 
			
		||||
import { getSelectedElements } from "../scene/selection";
 | 
			
		||||
 | 
			
		||||
import { renderElement, renderElementToSvg } from "./renderElement";
 | 
			
		||||
 | 
			
		||||
@@ -135,7 +136,7 @@ export function renderScene(
 | 
			
		||||
 | 
			
		||||
  // Pain selected elements
 | 
			
		||||
  if (renderSelection) {
 | 
			
		||||
    const selectedElements = elements.filter(element => element.isSelected);
 | 
			
		||||
    const selectedElements = getSelectedElements(elements);
 | 
			
		||||
    const dashledLinePadding = 4 / sceneState.zoom;
 | 
			
		||||
 | 
			
		||||
    context.save();
 | 
			
		||||
 
 | 
			
		||||
@@ -3,9 +3,10 @@ export {
 | 
			
		||||
  clearSelection,
 | 
			
		||||
  getSelectedIndices,
 | 
			
		||||
  deleteSelectedElements,
 | 
			
		||||
  someElementIsSelected,
 | 
			
		||||
  isSomeElementSelected,
 | 
			
		||||
  getElementsWithinSelection,
 | 
			
		||||
  getCommonAttributeOfSelectedElements,
 | 
			
		||||
  getSelectedElements,
 | 
			
		||||
} from "./selection";
 | 
			
		||||
export {
 | 
			
		||||
  exportCanvas,
 | 
			
		||||
 
 | 
			
		||||
@@ -55,8 +55,11 @@ export function getSelectedIndices(elements: readonly ExcalidrawElement[]) {
 | 
			
		||||
  return selectedIndices;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const someElementIsSelected = (elements: readonly ExcalidrawElement[]) =>
 | 
			
		||||
  elements.some(element => element.isSelected);
 | 
			
		||||
export function isSomeElementSelected(
 | 
			
		||||
  elements: readonly ExcalidrawElement[],
 | 
			
		||||
): boolean {
 | 
			
		||||
  return elements.some(element => element.isSelected);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Returns common attribute (picked by `getAttribute` callback) of selected
 | 
			
		||||
@@ -68,10 +71,14 @@ export function getCommonAttributeOfSelectedElements<T>(
 | 
			
		||||
): T | null {
 | 
			
		||||
  const attributes = Array.from(
 | 
			
		||||
    new Set(
 | 
			
		||||
      elements
 | 
			
		||||
        .filter(element => element.isSelected)
 | 
			
		||||
        .map(element => getAttribute(element)),
 | 
			
		||||
      getSelectedElements(elements).map(element => getAttribute(element)),
 | 
			
		||||
    ),
 | 
			
		||||
  );
 | 
			
		||||
  return attributes.length === 1 ? attributes[0] : null;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function getSelectedElements(
 | 
			
		||||
  elements: readonly ExcalidrawElement[],
 | 
			
		||||
): readonly ExcalidrawElement[] {
 | 
			
		||||
  return elements.filter(element => element.isSelected);
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user