diff --git a/src/components/App.tsx b/src/components/App.tsx index 2eedb684..35519e19 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 ac246af9..f458705f 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 f54be271..963e5039 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 522abf7b..23469809 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 ffef9912..49c86126 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 52823a8f..3278169e 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 4cf26f8d..e2d52f82 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 11959d5c..68c6e19b 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; +};