fix: Support Excalidraw inside scrollable container (#3018)
* refactor: remove position fixed from excalidraw container, modal and stats * remove unused css * remove position fixed from toast and scroll to content * Make excal interactable by fixing offsets and set popover as fixed since position needs to be calculate from viewport top * Assign 200px less than height of Excalidraw to the selected shapes actions o UI doesn't overflow * update changelog, readme and package.json
This commit is contained in:
parent
5b343a9d46
commit
830fb64a25
@ -51,6 +51,7 @@ import {
|
|||||||
LINE_CONFIRM_THRESHOLD,
|
LINE_CONFIRM_THRESHOLD,
|
||||||
MIME_TYPES,
|
MIME_TYPES,
|
||||||
POINTER_BUTTON,
|
POINTER_BUTTON,
|
||||||
|
SCROLL_TIMEOUT,
|
||||||
TAP_TWICE_TIMEOUT,
|
TAP_TWICE_TIMEOUT,
|
||||||
TEXT_TO_CENTER_SNAP_THRESHOLD,
|
TEXT_TO_CENTER_SNAP_THRESHOLD,
|
||||||
TOUCH_CTX_MENU_TIMEOUT,
|
TOUCH_CTX_MENU_TIMEOUT,
|
||||||
@ -825,6 +826,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
|
|
||||||
document.addEventListener(EVENT.PASTE, this.pasteFromClipboard);
|
document.addEventListener(EVENT.PASTE, this.pasteFromClipboard);
|
||||||
document.addEventListener(EVENT.CUT, this.onCut);
|
document.addEventListener(EVENT.CUT, this.onCut);
|
||||||
|
document.addEventListener(EVENT.SCROLL, this.onScroll);
|
||||||
|
|
||||||
window.addEventListener(EVENT.RESIZE, this.onResize, false);
|
window.addEventListener(EVENT.RESIZE, this.onResize, false);
|
||||||
window.addEventListener(EVENT.UNLOAD, this.onUnload, false);
|
window.addEventListener(EVENT.UNLOAD, this.onUnload, false);
|
||||||
@ -998,6 +1000,10 @@ class App extends React.Component<ExcalidrawProps, AppState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private onScroll = debounce(() => {
|
||||||
|
this.setState({ ...this.getCanvasOffsets() });
|
||||||
|
}, SCROLL_TIMEOUT);
|
||||||
|
|
||||||
// Copy/paste
|
// Copy/paste
|
||||||
|
|
||||||
private onCut = withBatchedUpdates((event: ClipboardEvent) => {
|
private onCut = withBatchedUpdates((event: ClipboardEvent) => {
|
||||||
|
@ -442,7 +442,15 @@ const LayerUI = ({
|
|||||||
"transition-left": zenModeEnabled,
|
"transition-left": zenModeEnabled,
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<Island className={CLASSES.SHAPE_ACTIONS_MENU} padding={2}>
|
<Island
|
||||||
|
className={CLASSES.SHAPE_ACTIONS_MENU}
|
||||||
|
padding={2}
|
||||||
|
style={{
|
||||||
|
// we want to make sure this doesn't overflow so substracting 200
|
||||||
|
// which is approximately height of zoom footer and top left menu items with some buffer
|
||||||
|
maxHeight: `${appState.height - 200}px`,
|
||||||
|
}}
|
||||||
|
>
|
||||||
<SelectedShapeActions
|
<SelectedShapeActions
|
||||||
appState={appState}
|
appState={appState}
|
||||||
elements={elements}
|
elements={elements}
|
||||||
@ -603,18 +611,6 @@ const LayerUI = ({
|
|||||||
>
|
>
|
||||||
{t("buttons.exitZenMode")}
|
{t("buttons.exitZenMode")}
|
||||||
</button>
|
</button>
|
||||||
{appState.scrolledOutside && (
|
|
||||||
<button
|
|
||||||
className="scroll-back-to-content"
|
|
||||||
onClick={() => {
|
|
||||||
setAppState({
|
|
||||||
...calculateScrollCenter(elements, appState, canvas),
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{t("buttons.scrollBackToContent")}
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</footer>
|
</footer>
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -677,6 +673,18 @@ const LayerUI = ({
|
|||||||
{renderBottomAppMenu()}
|
{renderBottomAppMenu()}
|
||||||
{renderGitHubCorner()}
|
{renderGitHubCorner()}
|
||||||
{renderFooter()}
|
{renderFooter()}
|
||||||
|
{appState.scrolledOutside && (
|
||||||
|
<button
|
||||||
|
className="scroll-back-to-content"
|
||||||
|
onClick={() => {
|
||||||
|
setAppState({
|
||||||
|
...calculateScrollCenter(elements, appState, canvas),
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t("buttons.scrollBackToContent")}
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,8 +1,13 @@
|
|||||||
@import "../css/variables.module";
|
@import "../css/variables.module";
|
||||||
|
|
||||||
.excalidraw {
|
.excalidraw {
|
||||||
|
&.excalidraw-modal-container {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
.Modal {
|
.Modal {
|
||||||
position: fixed;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
@ -15,7 +20,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.Modal__background {
|
.Modal__background {
|
||||||
position: fixed;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
@ -82,7 +87,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.Modal__content {
|
.Modal__content {
|
||||||
position: fixed;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
|
@ -54,7 +54,7 @@ const useBodyRoot = () => {
|
|||||||
?.classList.contains("Appearance_dark");
|
?.classList.contains("Appearance_dark");
|
||||||
const div = document.createElement("div");
|
const div = document.createElement("div");
|
||||||
|
|
||||||
div.classList.add("excalidraw");
|
div.classList.add("excalidraw", "excalidraw-modal-container");
|
||||||
|
|
||||||
if (isDarkTheme) {
|
if (isDarkTheme) {
|
||||||
div.classList.add("Appearance_dark");
|
div.classList.add("Appearance_dark");
|
||||||
|
@ -1,14 +1,6 @@
|
|||||||
.excalidraw {
|
.excalidraw {
|
||||||
.popover {
|
.popover {
|
||||||
position: absolute;
|
position: fixed;
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
.popover .cover {
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
@import "../css/variables.module";
|
@import "../css/variables.module";
|
||||||
|
|
||||||
.Stats {
|
.Stats {
|
||||||
position: fixed;
|
position: absolute;
|
||||||
top: 64px;
|
top: 64px;
|
||||||
right: 12px;
|
right: 12px;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
left: 50%;
|
left: 50%;
|
||||||
margin-left: -150px;
|
margin-left: -150px;
|
||||||
padding: 4px 0;
|
padding: 4px 0;
|
||||||
position: fixed;
|
position: absolute;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
width: 300px;
|
width: 300px;
|
||||||
z-index: 999999;
|
z-index: 999999;
|
||||||
|
@ -47,6 +47,7 @@ export enum EVENT {
|
|||||||
TOUCH_END = "touchend",
|
TOUCH_END = "touchend",
|
||||||
HASHCHANGE = "hashchange",
|
HASHCHANGE = "hashchange",
|
||||||
VISIBILITY_CHANGE = "visibilitychange",
|
VISIBILITY_CHANGE = "visibilitychange",
|
||||||
|
SCROLL = "scroll",
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ENV = {
|
export const ENV = {
|
||||||
@ -92,6 +93,7 @@ export const TOUCH_CTX_MENU_TIMEOUT = 500;
|
|||||||
export const TITLE_TIMEOUT = 10000;
|
export const TITLE_TIMEOUT = 10000;
|
||||||
export const TOAST_TIMEOUT = 5000;
|
export const TOAST_TIMEOUT = 5000;
|
||||||
export const VERSION_TIMEOUT = 30000;
|
export const VERSION_TIMEOUT = 30000;
|
||||||
|
export const SCROLL_TIMEOUT = 500;
|
||||||
|
|
||||||
export const ZOOM_STEP = 0.1;
|
export const ZOOM_STEP = 0.1;
|
||||||
|
|
||||||
|
@ -10,7 +10,6 @@
|
|||||||
.excalidraw {
|
.excalidraw {
|
||||||
color: var(--text-color-primary);
|
color: var(--text-color-primary);
|
||||||
display: flex;
|
display: flex;
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
top: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
@ -362,7 +361,6 @@
|
|||||||
|
|
||||||
.App-menu__left {
|
.App-menu__left {
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
max-height: calc(100vh - 236px);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.dropdown-select {
|
.dropdown-select {
|
||||||
@ -434,7 +432,7 @@
|
|||||||
|
|
||||||
.scroll-back-to-content {
|
.scroll-back-to-content {
|
||||||
color: var(--popup-text-color);
|
color: var(--popup-text-color);
|
||||||
position: fixed;
|
position: absolute;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
bottom: 30px;
|
bottom: 30px;
|
||||||
transform: translateX(-50%);
|
transform: translateX(-50%);
|
||||||
|
@ -12,6 +12,20 @@ The change should be grouped under one of the below section and must contain PR
|
|||||||
Please add the latest change on the top under the correct section.
|
Please add the latest change on the top under the correct section.
|
||||||
-->
|
-->
|
||||||
|
|
||||||
|
## 0.3.1
|
||||||
|
|
||||||
|
## Excalidraw API
|
||||||
|
|
||||||
|
### Fixes
|
||||||
|
|
||||||
|
- Support Excalidraw inside scrollable container [#3018](https://github.com/excalidraw/excalidraw/pull/3018)
|
||||||
|
|
||||||
|
## Excalidraw Library
|
||||||
|
|
||||||
|
### Fixes
|
||||||
|
|
||||||
|
- Allow to toggle between modes when view only mode to make UI consistent [#3009](https://github.com/excalidraw/excalidraw/pull/3009)
|
||||||
|
|
||||||
## 0.3.0
|
## 0.3.0
|
||||||
|
|
||||||
## Excalidraw API
|
## Excalidraw API
|
||||||
|
@ -37,18 +37,18 @@ You can update the value of `PUBLIC_URL` if you want to serve it from a differen
|
|||||||
1. If you are using a Web bundler (for instance, Webpack), you can import it as an ES6 module as shown below
|
1. If you are using a Web bundler (for instance, Webpack), you can import it as an ES6 module as shown below
|
||||||
|
|
||||||
```js
|
```js
|
||||||
import React, { useEffect, useState, createRef } from "react";
|
import React, { useEffect, useState, useRef } from "react";
|
||||||
import Excalidraw from "@excalidraw/excalidraw";
|
import Excalidraw from "@excalidraw/excalidraw";
|
||||||
import InitialData from "./initialData";
|
import InitialData from "./initialData";
|
||||||
|
|
||||||
import "./styles.css";
|
import "./styles.scss";
|
||||||
|
|
||||||
export default function App() {
|
export default function App() {
|
||||||
const excalidrawRef = createRef();
|
const excalidrawRef = useRef(null);
|
||||||
|
const excalidrawWrapperRef = useRef(null);
|
||||||
const [dimensions, setDimensions] = useState({
|
const [dimensions, setDimensions] = useState({
|
||||||
width: window.innerWidth,
|
width: undefined,
|
||||||
height: window.innerHeight,
|
height: undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
const [viewModeEnabled, setViewModeEnabled] = useState(false);
|
const [viewModeEnabled, setViewModeEnabled] = useState(false);
|
||||||
@ -56,17 +56,21 @@ export default function App() {
|
|||||||
const [gridModeEnabled, setGridModeEnabled] = useState(false);
|
const [gridModeEnabled, setGridModeEnabled] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
setDimensions({
|
||||||
|
width: excalidrawWrapperRef.current.getBoundingClientRect().width,
|
||||||
|
height: excalidrawWrapperRef.current.getBoundingClientRect().height,
|
||||||
|
});
|
||||||
const onResize = () => {
|
const onResize = () => {
|
||||||
setDimensions({
|
setDimensions({
|
||||||
width: window.innerWidth,
|
width: excalidrawWrapperRef.current.getBoundingClientRect().width,
|
||||||
height: window.innerHeight,
|
height: excalidrawWrapperRef.current.getBoundingClientRect().height,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
window.addEventListener("resize", onResize);
|
window.addEventListener("resize", onResize);
|
||||||
|
|
||||||
return () => window.removeEventListener("resize", onResize);
|
return () => window.removeEventListener("resize", onResize);
|
||||||
}, []);
|
}, [excalidrawWrapperRef]);
|
||||||
|
|
||||||
const updateScene = () => {
|
const updateScene = () => {
|
||||||
const sceneData = {
|
const sceneData = {
|
||||||
@ -102,6 +106,7 @@ export default function App() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="App">
|
<div className="App">
|
||||||
|
<h1> Excalidraw Example</h1>
|
||||||
<div className="button-wrapper">
|
<div className="button-wrapper">
|
||||||
<button className="update-scene" onClick={updateScene}>
|
<button className="update-scene" onClick={updateScene}>
|
||||||
Update Scene
|
Update Scene
|
||||||
@ -139,7 +144,7 @@ export default function App() {
|
|||||||
Grid mode
|
Grid mode
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div className="excalidraw-wrapper">
|
<div className="excalidraw-wrapper" ref={excalidrawWrapperRef}>
|
||||||
<Excalidraw
|
<Excalidraw
|
||||||
ref={excalidrawRef}
|
ref={excalidrawRef}
|
||||||
width={dimensions.width}
|
width={dimensions.width}
|
||||||
@ -148,7 +153,6 @@ export default function App() {
|
|||||||
onChange={(elements, state) =>
|
onChange={(elements, state) =>
|
||||||
console.log("Elements :", elements, "State : ", state)
|
console.log("Elements :", elements, "State : ", state)
|
||||||
}
|
}
|
||||||
user={{ name: "Excalidraw User" }}
|
|
||||||
onPointerUpdate={(payload) => console.log(payload)}
|
onPointerUpdate={(payload) => console.log(payload)}
|
||||||
onCollabButtonClick={() =>
|
onCollabButtonClick={() =>
|
||||||
window.alert("You clicked on collab button")
|
window.alert("You clicked on collab button")
|
||||||
|
2
src/packages/excalidraw/package-lock.json
generated
2
src/packages/excalidraw/package-lock.json
generated
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@excalidraw/excalidraw",
|
"name": "@excalidraw/excalidraw",
|
||||||
"version": "0.3.0",
|
"version": "0.3.1",
|
||||||
"lockfileVersion": 1,
|
"lockfileVersion": 1,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@excalidraw/excalidraw",
|
"name": "@excalidraw/excalidraw",
|
||||||
"version": "0.3.0",
|
"version": "0.3.1",
|
||||||
"main": "dist/excalidraw.min.js",
|
"main": "dist/excalidraw.min.js",
|
||||||
"files": [
|
"files": [
|
||||||
"dist/*"
|
"dist/*"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user