Repository: excalidraw/excalidraw
File: src/components/App.tsx (TypeScript)

Code before:
    // potential issues, this fixes a case where the tab isn't focused during
    // init, which would trigger onChange with empty elements, which would then
    // override whatever is in localStorage currently.
    if (!this.state.isLoading) {
      this.props.onChange?.(
        this.scene.getElementsIncludingDeleted(),
        this.state,
      );
    }
  }

  private onScroll = debounce(() => {
    const { offsetTop, offsetLeft } = this.getCanvasOffsets();
    this.setState((state) => {
      if (state.offsetLeft === offsetLeft && state.offsetTop === offsetTop) {
        return null;
      }
      return { offsetTop, offsetLeft };
    });
  }, SCROLL_TIMEOUT);

  // Copy/paste

  private onCut = withBatchedUpdates((event: ClipboardEvent) => {
    const isExcalidrawActive =
      document.activeElement === this.excalidrawContainerRef.current;
    if (!isExcalidrawActive || isWritableElement(event.target)) {
      return;
    }
    this.cutAll();
    event.preventDefault();
  });

  private onCopy = withBatchedUpdates((event: ClipboardEvent) => {
    const activeSelection = document.getSelection();
    // if there's a selected text is outside the component, prevent our copy
    // action
    if (
      activeSelection?.anchorNode &&
      // it can happen that certain interactions will create a selection
      // outside (or potentially inside) the component without actually
      // selecting anything (i.e. the selection range is collapsed). Copying
      // in such case wouldn't copy anything to the clipboard anyway, so prevent
      // our copy handler only if the selection isn't collapsed
      !activeSelection.isCollapsed &&
      !this.excalidrawContainerRef.current!.contains(activeSelection.anchorNode)
    ) {
      return;
    }
    if (isWritableElement(event.target)) {
      return;

Review comment (bug):
Let's instead do `this.excalidrawContainerRef.current.closest(document.activeElement)` to catch edge cases where the focus is on some child.

Code after:
    // potential issues, this fixes a case where the tab isn't focused during
    // init, which would trigger onChange with empty elements, which would then
    // override whatever is in localStorage currently.
    if (!this.state.isLoading) {
      this.props.onChange?.(
        this.scene.getElementsIncludingDeleted(),
        this.state,
      );
    }
  }

  private onScroll = debounce(() => {
    const { offsetTop, offsetLeft } = this.getCanvasOffsets();
    this.setState((state) => {
      if (state.offsetLeft === offsetLeft && state.offsetTop === offsetTop) {
        return null;
      }
      return { offsetTop, offsetLeft };
    });
  }, SCROLL_TIMEOUT);

  // Copy/paste

  private onCut = withBatchedUpdates((event: ClipboardEvent) => {
    const isExcalidrawActive = this.excalidrawContainerRef.current?.contains(
      document.activeElement,
    );
    if (!isExcalidrawActive || isWritableElement(event.target)) {
      return;
    }
    this.cutAll();
    event.preventDefault();
  });

  private onCopy = withBatchedUpdates((event: ClipboardEvent) => {
    const isExcalidrawActive = this.excalidrawContainerRef.current?.contains(
      document.activeElement,
    );
    if (!isExcalidrawActive || isWritableElement(event.target)) {
      return;
    }
    this.copyAll();
    event.preventDefault();
  });

  private cutAll = () => {
    this.copyAll();
    this.actionManager.executeAction(actionDeleteSelected);
  };

  private copyAll = () => {