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:
parent
add1785ace
commit
de99484a1f
@ -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 (
|
|
||||||
typeof offsets?.offsetLeft === "number" &&
|
|
||||||
typeof offsets?.offsetTop === "number"
|
|
||||||
) {
|
|
||||||
return {
|
|
||||||
offsetLeft: offsets.offsetLeft,
|
|
||||||
offsetTop: offsets.offsetTop,
|
|
||||||
};
|
};
|
||||||
}
|
|
||||||
|
private getCanvasOffsets(): Pick<AppState, "offsetTop" | "offsetLeft"> {
|
||||||
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,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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).
|
||||||
|
@ -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`
|
||||||
|
|
||||||
|
@ -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}
|
||||||
|
@ -15,12 +15,26 @@ describe("appState", () => {
|
|||||||
const ELEM_WIDTH = 100;
|
const ELEM_WIDTH = 100;
|
||||||
const ELEM_HEIGHT = 60;
|
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(
|
await render(
|
||||||
|
<div>
|
||||||
<Excalidraw
|
<Excalidraw
|
||||||
width={WIDTH}
|
width={WIDTH}
|
||||||
height={HEIGHT}
|
height={HEIGHT}
|
||||||
offsetLeft={OFFSET_LEFT}
|
|
||||||
offsetTop={OFFSET_TOP}
|
|
||||||
initialData={{
|
initialData={{
|
||||||
elements: [
|
elements: [
|
||||||
API.createElement({
|
API.createElement({
|
||||||
@ -32,9 +46,9 @@ describe("appState", () => {
|
|||||||
],
|
],
|
||||||
scrollToContent: true,
|
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;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -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,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user