Ver Fonte

feat: Expose the API to calculate offsets and remove offsetTop and offsetLeft props (#3265)

* feat: Expose the API to calculate offsets and remove offsetTop and offsetLeft props

* update

* fix tests

* fix

* update readme and changelog

* fix

* better
Aakansha Doshi há 4 anos atrás
pai
commit
de99484a1f

+ 15 - 36
src/components/App.tsx

@@ -274,6 +274,7 @@ export type ExcalidrawImperativeAPI = {
   setScrollToContent: InstanceType<typeof App>["setScrollToContent"];
   getSceneElements: InstanceType<typeof App>["getSceneElements"];
   getAppState: () => InstanceType<typeof App>["state"];
+  setCanvasOffsets: InstanceType<typeof App>["setCanvasOffsets"];
   readyPromise: ResolvablePromise<ExcalidrawImperativeAPI>;
   ready: true;
 };
@@ -297,8 +298,6 @@ class App extends React.Component<ExcalidrawProps, AppState> {
     const {
       width = window.innerWidth,
       height = window.innerHeight,
-      offsetLeft,
-      offsetTop,
       excalidrawRef,
       viewModeEnabled = false,
       zenModeEnabled = false,
@@ -311,7 +310,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
       isLoading: true,
       width,
       height,
-      ...this.getCanvasOffsets({ offsetLeft, offsetTop }),
+      ...this.getCanvasOffsets(),
       viewModeEnabled,
       zenModeEnabled,
       gridSize: gridModeEnabled ? GRID_SIZE : null,
@@ -333,6 +332,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
         setScrollToContent: this.setScrollToContent,
         getSceneElements: this.getSceneElements,
         getAppState: () => this.state,
+        setCanvasOffsets: this.setCanvasOffsets,
       } as const;
       if (typeof excalidrawRef === "function") {
         excalidrawRef(api);
@@ -751,14 +751,8 @@ class App extends React.Component<ExcalidrawProps, AppState> {
     if (searchParams.has("web-share-target")) {
       // Obtain a file that was shared via the Web Share Target API.
       this.restoreFileFromShare();
-    } else if (
-      typeof this.props.offsetLeft === "number" &&
-      typeof this.props.offsetTop === "number"
-    ) {
-      // Optimization to avoid extra render on init.
-      this.initializeScene();
     } else {
-      this.setState(this.getCanvasOffsets(this.props), () => {
+      this.setState(this.getCanvasOffsets(), () => {
         this.initializeScene();
       });
     }
@@ -863,16 +857,12 @@ class App extends React.Component<ExcalidrawProps, AppState> {
 
     if (
       prevProps.width !== this.props.width ||
-      prevProps.height !== this.props.height ||
-      (typeof this.props.offsetLeft === "number" &&
-        prevProps.offsetLeft !== this.props.offsetLeft) ||
-      (typeof this.props.offsetTop === "number" &&
-        prevProps.offsetTop !== this.props.offsetTop)
+      prevProps.height !== this.props.height
     ) {
       this.setState({
         width: this.props.width ?? window.innerWidth,
         height: this.props.height ?? window.innerHeight,
-        ...this.getCanvasOffsets(this.props),
+        ...this.getCanvasOffsets(),
       });
     }
 
@@ -4035,33 +4025,22 @@ class App extends React.Component<ExcalidrawProps, AppState> {
     }
   }, 300);
 
-  private getCanvasOffsets(offsets?: {
-    offsetLeft?: number;
-    offsetTop?: number;
-  }): Pick<AppState, "offsetTop" | "offsetLeft"> {
-    if (
-      typeof offsets?.offsetLeft === "number" &&
-      typeof offsets?.offsetTop === "number"
-    ) {
-      return {
-        offsetLeft: offsets.offsetLeft,
-        offsetTop: offsets.offsetTop,
-      };
-    }
+  public setCanvasOffsets = () => {
+    this.setState({ ...this.getCanvasOffsets() });
+  };
+
+  private getCanvasOffsets(): Pick<AppState, "offsetTop" | "offsetLeft"> {
     if (this.excalidrawContainerRef?.current?.parentElement) {
       const parentElement = this.excalidrawContainerRef.current.parentElement;
       const { left, top } = parentElement.getBoundingClientRect();
       return {
-        offsetLeft:
-          typeof offsets?.offsetLeft === "number" ? offsets.offsetLeft : left,
-        offsetTop:
-          typeof offsets?.offsetTop === "number" ? offsets.offsetTop : top,
+        offsetLeft: left,
+        offsetTop: top,
       };
     }
     return {
-      offsetLeft:
-        typeof offsets?.offsetLeft === "number" ? offsets.offsetLeft : 0,
-      offsetTop: typeof offsets?.offsetTop === "number" ? offsets.offsetTop : 0,
+      offsetLeft: 0,
+      offsetTop: 0,
     };
   }
 

+ 3 - 0
src/packages/excalidraw/CHANGELOG.md

@@ -18,6 +18,9 @@ Please add the latest change on the top under the correct section.
 
 ### Features
 
+- Export API `setCanvasOffsets` via `ref` to set the offsets for Excalidraw[#3265](https://github.com/excalidraw/excalidraw/pull/3265).
+  #### BREAKING CHANGE
+  - `offsetLeft` and `offsetTop` props have been removed now so you have to use the `setCanvasOffsets` via `ref` to achieve the same.
 - Export API to export the drawing to canvas, svg and blob [#3258](https://github.com/excalidraw/excalidraw/pull/3258). For more info you can check the [readme](https://github.com/excalidraw/excalidraw/tree/master/src/packages/excalidraw/README.md#user-content-export-utils)
 - Add a `theme` prop to indicate Excalidraw's theme. [#3228](https://github.com/excalidraw/excalidraw/pull/3228). When this prop is passed, the theme is fully controlled by host app.
 - Support `libraryReturnUrl` prop to indicate what URL to install libraries to [#3227](https://github.com/excalidraw/excalidraw/pull/3227).

+ 1 - 10
src/packages/excalidraw/README.md

@@ -362,8 +362,6 @@ export default function IndexPage() {
 | --- | --- | --- | --- |
 | [`width`](#width) | Number | `window.innerWidth` | The width of Excalidraw component |
 | [`height`](#height) | Number | `window.innerHeight` | The height of Excalidraw component |
-| [`offsetLeft`](#offsetLeft) | Number | `0` | left position relative to which Excalidraw should be rendered |
-| [`offsetTop`](#offsetTop) | Number | `0` | top position relative to which Excalidraw should render |
 | [`onChange`](#onChange) | Function |  | This callback is triggered whenever the component updates due to any change. This callback will receive the excalidraw elements and the current app state. |
 | [`initialData`](#initialData) | <pre>{elements?: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L78">ExcalidrawElement[]</a>, appState?: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L37">AppState<a> } </pre> | null | The initial data with which app loads. |
 | [`ref`](#ref) | [`createRef`](https://reactjs.org/docs/refs-and-the-dom.html#creating-refs) or [`callbackRef`](https://reactjs.org/docs/refs-and-the-dom.html#callback-refs) or <pre>{ current: { readyPromise: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/utils.ts#L317">resolvablePromise</a> } }</pre> |  | Ref to be passed to Excalidraw |
@@ -387,14 +385,6 @@ This props defines the `width` of the Excalidraw component. Defaults to `window.
 
 This props defines the `height` of the Excalidraw component. Defaults to `window.innerHeight` if not passed.
 
-#### `offsetLeft`
-
-This prop defines `left` position relative to which Excalidraw should be rendered. Defaults to `0` if not passed.
-
-#### `offsetTop`
-
-This prop defines `top` position relative to which Excalidraw should be rendered. Defaults to `0` if not passed.
-
 #### `onChange`
 
 Every time component updates, this callback if passed will get triggered and has the below signature.
@@ -465,6 +455,7 @@ You can pass a `ref` when you want to access some excalidraw APIs. We expose the
 | getAppState | <pre> () => <a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L37">AppState</a></pre> | Returns current appState |
 | history | `{ clear: () => void }` | This is the history API. `history.clear()` will clear the history |
 | setScrollToContent | <pre> (<a href="https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L78">ExcalidrawElement[]</a>) => void </pre> | Scroll to the nearest element to center |
+| setCanvasOffsets | `() => void` | Updates the offsets for the Excalidraw component so that the coordinates are computed correctly (for example the cursor position). You should call this API when your app changes the dimensions/position of the Excalidraw container, such as when toggling a sidebar. You don't have to call this when the position is changed on page scroll (we handled that ourselves). |
 
 #### `readyPromise`
 

+ 0 - 4
src/packages/excalidraw/index.tsx

@@ -15,8 +15,6 @@ const Excalidraw = (props: ExcalidrawProps) => {
   const {
     width,
     height,
-    offsetLeft,
-    offsetTop,
     onChange,
     initialData,
     excalidrawRef,
@@ -57,8 +55,6 @@ const Excalidraw = (props: ExcalidrawProps) => {
         <App
           width={width}
           height={height}
-          offsetLeft={offsetLeft}
-          offsetTop={offsetTop}
           onChange={onChange}
           initialData={initialData}
           excalidrawRef={excalidrawRef}

+ 33 - 18
src/tests/scroll.test.tsx

@@ -15,26 +15,40 @@ describe("appState", () => {
     const ELEM_WIDTH = 100;
     const ELEM_HEIGHT = 60;
 
+    const originalGetBoundingClientRect =
+      global.window.HTMLDivElement.prototype.getBoundingClientRect;
+    // override getBoundingClientRect as by default it will always return all values as 0 even if customized in html
+    global.window.HTMLDivElement.prototype.getBoundingClientRect = () => ({
+      top: OFFSET_TOP,
+      left: OFFSET_LEFT,
+      bottom: 10,
+      right: 10,
+      width: 100,
+      x: 10,
+      y: 20,
+      height: 100,
+      toJSON: () => {},
+    });
+
     await render(
-      <Excalidraw
-        width={WIDTH}
-        height={HEIGHT}
-        offsetLeft={OFFSET_LEFT}
-        offsetTop={OFFSET_TOP}
-        initialData={{
-          elements: [
-            API.createElement({
-              type: "rectangle",
-              id: "A",
-              width: ELEM_WIDTH,
-              height: ELEM_HEIGHT,
-            }),
-          ],
-          scrollToContent: true,
-        }}
-      />,
+      <div>
+        <Excalidraw
+          width={WIDTH}
+          height={HEIGHT}
+          initialData={{
+            elements: [
+              API.createElement({
+                type: "rectangle",
+                id: "A",
+                width: ELEM_WIDTH,
+                height: ELEM_HEIGHT,
+              }),
+            ],
+            scrollToContent: true,
+          }}
+        />
+      </div>,
     );
-
     await waitFor(() => {
       expect(h.state.width).toBe(WIDTH);
       expect(h.state.height).toBe(HEIGHT);
@@ -45,5 +59,6 @@ describe("appState", () => {
       expect(h.state.scrollX).toBe(WIDTH / 2 - ELEM_WIDTH / 2);
       expect(h.state.scrollY).toBe(HEIGHT / 2 - ELEM_HEIGHT / 2);
     });
+    global.window.HTMLDivElement.prototype.getBoundingClientRect = originalGetBoundingClientRect;
   });
 });

+ 0 - 4
src/types.ts

@@ -162,10 +162,6 @@ export type ExcalidrawAPIRefValue =
 export interface ExcalidrawProps {
   width?: number;
   height?: number;
-  /** if not supplied, calculated by Excalidraw */
-  offsetLeft?: number;
-  /** if not supplied, calculated by Excalidraw */
-  offsetTop?: number;
   onChange?: (
     elements: readonly ExcalidrawElement[],
     appState: AppState,