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
This commit is contained in:
Aakansha Doshi 2021-03-20 13:00:49 +05:30 committed by GitHub
parent add1785ace
commit de99484a1f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 53 additions and 73 deletions

View File

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

View File

@ -18,6 +18,9 @@ Please add the latest change on the top under the correct section.
### Features ### 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) - 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. - 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). - Support `libraryReturnUrl` prop to indicate what URL to install libraries to [#3227](https://github.com/excalidraw/excalidraw/pull/3227).

View File

@ -362,8 +362,6 @@ export default function IndexPage() {
| --- | --- | --- | --- | | --- | --- | --- | --- |
| [`width`](#width) | Number | `window.innerWidth` | The width of Excalidraw component | | [`width`](#width) | Number | `window.innerWidth` | The width of Excalidraw component |
| [`height`](#height) | Number | `window.innerHeight` | The height 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. | | [`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. | | [`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 | | [`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. 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` #### `onChange`
Every time component updates, this callback if passed will get triggered and has the below signature. 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 | | 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 | | 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 | | 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` #### `readyPromise`

View File

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

View File

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

View File

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