diff --git a/src/components/App.tsx b/src/components/App.tsx index 2eedb684c2..35519e193b 100644 --- a/src/components/App.tsx +++ b/src/components/App.tsx @@ -168,6 +168,7 @@ import { AppProps, AppState, Gesture, GestureEvent, SceneData } from "../types"; import { debounce, distance, + getNearestScrollableContainer, isInputLike, isToolIcon, isWritableElement, @@ -303,6 +304,7 @@ class App extends React.Component { private scene: Scene; private resizeObserver: ResizeObserver | undefined; + private nearestScrollableContainer: HTMLElement | Document | undefined; constructor(props: AppProps) { super(props); const defaultAppState = getDefaultAppState(); @@ -841,6 +843,10 @@ class App extends React.Component { document.removeEventListener(EVENT.COPY, this.onCopy); document.removeEventListener(EVENT.PASTE, this.pasteFromClipboard); document.removeEventListener(EVENT.CUT, this.onCut); + this.nearestScrollableContainer?.removeEventListener( + EVENT.SCROLL, + this.onScroll, + ); document.removeEventListener(EVENT.KEYDOWN, this.onKeyDown, false); document.removeEventListener( @@ -907,8 +913,15 @@ class App extends React.Component { document.addEventListener(EVENT.PASTE, this.pasteFromClipboard); document.addEventListener(EVENT.CUT, this.onCut); - document.addEventListener(EVENT.SCROLL, this.onScroll); - + if (this.props.detectScroll) { + this.nearestScrollableContainer = getNearestScrollableContainer( + this.excalidrawContainerRef.current!, + ); + this.nearestScrollableContainer.addEventListener( + EVENT.SCROLL, + this.onScroll, + ); + } window.addEventListener(EVENT.RESIZE, this.onResize, false); window.addEventListener(EVENT.UNLOAD, this.onUnload, false); window.addEventListener(EVENT.BLUR, this.onBlur, false); diff --git a/src/constants.ts b/src/constants.ts index ac246af9b5..f458705fa6 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -102,7 +102,6 @@ export const TITLE_TIMEOUT = 10000; export const TOAST_TIMEOUT = 5000; export const VERSION_TIMEOUT = 30000; export const SCROLL_TIMEOUT = 100; - export const ZOOM_STEP = 0.1; // Report a user inactive after IDLE_THRESHOLD milliseconds diff --git a/src/excalidraw-app/index.tsx b/src/excalidraw-app/index.tsx index f54be2713a..963e503982 100644 --- a/src/excalidraw-app/index.tsx +++ b/src/excalidraw-app/index.tsx @@ -324,6 +324,7 @@ const ExcalidrawWrapper = () => { renderFooter={renderFooter} langCode={langCode} renderCustomStats={renderCustomStats} + detectScroll={false} /> {excalidrawAPI && } {errorMessage && ( diff --git a/src/packages/excalidraw/CHANGELOG.md b/src/packages/excalidraw/CHANGELOG.md index 522abf7be8..23469809ac 100644 --- a/src/packages/excalidraw/CHANGELOG.md +++ b/src/packages/excalidraw/CHANGELOG.md @@ -15,6 +15,7 @@ Please add the latest change on the top under the correct section. ## Excalidraw API +- Recompute offsets on `scroll` of the nearest scrollable container [#3408](https://github.com/excalidraw/excalidraw/pull/3408). This can be disabled by setting [`detectScroll`](https://github.com/excalidraw/excalidraw/blob/master/src/packages/excalidraw/README.md#detectScroll) to `false`. - Add `onPaste` prop to handle custom clipboard behaviours [#3420](https://github.com/excalidraw/excalidraw/pull/3420). ## Excalidraw Library diff --git a/src/packages/excalidraw/README_NEXT.md b/src/packages/excalidraw/README_NEXT.md index ffef99121b..49c86126da 100644 --- a/src/packages/excalidraw/README_NEXT.md +++ b/src/packages/excalidraw/README_NEXT.md @@ -365,6 +365,7 @@ To view the full example visit :point_down: | [`name`](#name) | string | | Name of the drawing | | [`UIOptions`](#UIOptions) |
{ canvasActions:  CanvasActions }
| [DEFAULT UI OPTIONS](https://github.com/excalidraw/excalidraw/blob/master/src/constants.ts#L129) | To customise UI options. Currently we support customising [`canvas actions`](#canvasActions) | | [`onPaste`](#onPaste) |
(data: ClipboardData, event: ClipboardEvent | null) => boolean
| | Callback to be triggered if passed when the something is pasted in to the scene | +| [`detectScroll`](#detectScroll) | boolean | true | Indicates whether to update the offsets when nearest ancestor is scrolled. | ### Dimensions of Excalidraw @@ -587,6 +588,10 @@ useEffect(() => { Try out the [Demo](#Demo) to see it in action. +### detectScroll + +Indicates whether Excalidraw should listen for `scroll` event on the nearest scrollable container in the DOM tree and recompute the coordinates (e.g. to correctly handle the cursor) when the component's position changes. You can disable this when you either know this doesn't affect your app or you want to take care of it yourself (calling the [`refresh()`](#ref) method). + ### Extra API's #### `getSceneVersion` diff --git a/src/packages/excalidraw/index.tsx b/src/packages/excalidraw/index.tsx index 52823a8f20..3278169e0c 100644 --- a/src/packages/excalidraw/index.tsx +++ b/src/packages/excalidraw/index.tsx @@ -30,6 +30,7 @@ const Excalidraw = (props: ExcalidrawProps) => { name, renderCustomStats, onPaste, + detectScroll = true, } = props; const canvasActions = props.UIOptions?.canvasActions; @@ -80,6 +81,7 @@ const Excalidraw = (props: ExcalidrawProps) => { renderCustomStats={renderCustomStats} UIOptions={UIOptions} onPaste={onPaste} + detectScroll={detectScroll} /> ); @@ -102,11 +104,6 @@ const areEqual = ( ); }; -Excalidraw.defaultProps = { - lanCode: defaultLang.code, - UIOptions: DEFAULT_UI_OPTIONS, -}; - const forwardedRefComp = forwardRef< ExcalidrawAPIRefValue, PublicExcalidrawProps diff --git a/src/types.ts b/src/types.ts index 4cf26f8dcd..e2d52f8245 100644 --- a/src/types.ts +++ b/src/types.ts @@ -195,6 +195,7 @@ export interface ExcalidrawProps { appState: AppState, ) => JSX.Element; UIOptions?: UIOptions; + detectScroll?: boolean; } export type SceneData = { @@ -228,4 +229,5 @@ export type AppProps = ExcalidrawProps & { UIOptions: { canvasActions: Required; }; + detectScroll: boolean; }; diff --git a/src/utils.ts b/src/utils.ts index 11959d5cf3..68c6e19b8f 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -406,3 +406,24 @@ export const supportsEmoji = () => { ctx.fillText("😀", 0, 0); return ctx.getImageData(offset, offset, 1, 1).data[0] !== 0; }; + +export const getNearestScrollableContainer = ( + element: HTMLElement, +): HTMLElement | Document => { + let parent = element.parentElement; + while (parent) { + if (parent === document.body) { + return document; + } + const { overflowY } = window.getComputedStyle(parent); + const hasScrollableContent = parent.scrollHeight > parent.clientHeight; + if ( + hasScrollableContent && + (overflowY === "auto" || overflowY === "scroll") + ) { + return parent; + } + parent = parent.parentElement; + } + return document; +};