From ebfeb3dd45b72af37389b2d760f53b59caa310b0 Mon Sep 17 00:00:00 2001 From: Chris Villa Date: Thu, 21 Mar 2024 19:27:03 +0100 Subject: [PATCH] feat: auto-scroll iframes --- .../fluid-scroller/get-iframe-scroll.ts | 67 +++++++++++++++++++ .../auto-scroller/fluid-scroller/index.ts | 2 +- .../auto-scroller/fluid-scroller/scroll.ts | 17 ++++- src/view/window/scroll-window.ts | 6 +- 4 files changed, 87 insertions(+), 5 deletions(-) create mode 100644 src/state/auto-scroller/fluid-scroller/get-iframe-scroll.ts diff --git a/src/state/auto-scroller/fluid-scroller/get-iframe-scroll.ts b/src/state/auto-scroller/fluid-scroller/get-iframe-scroll.ts new file mode 100644 index 000000000..3516e6eba --- /dev/null +++ b/src/state/auto-scroller/fluid-scroller/get-iframe-scroll.ts @@ -0,0 +1,67 @@ +import { BoxModel, getBox } from 'css-box-model'; +import { DraggableDimension, DraggingState } from '../../../types'; +import querySelectorAllIframe from '../../../view/iframe/query-selector-all-iframe'; +import getScroll from './get-scroll'; +import { AutoScrollerOptions } from './auto-scroller-options-types'; + +const resetToOrigin = (box: BoxModel) => ({ + width: box.marginBox.width, + height: box.marginBox.height, + top: 0, + left: 0, + right: box.marginBox.width, + bottom: box.marginBox.height, + center: { + x: box.marginBox.width / 2, + y: box.marginBox.height / 2, + }, + x: 0, + y: 0, +}); + +/** + * Get the scroll for a draggable inside an iframe + * + * - Since iframes are not fully managed by the state, we have to access the elements directly. + * - This will not work with multiple draggable contexts + */ +export default ({ + draggable, + dragStartTime, + getAutoScrollerOptions, + shouldUseTimeDampening, + state, +}: { + state: DraggingState; + draggable: DraggableDimension; + dragStartTime: number; + shouldUseTimeDampening: boolean; + getAutoScrollerOptions: () => AutoScrollerOptions; +}) => { + const el = querySelectorAllIframe( + `[data-rfd-draggable-id="${state.critical.draggable.id}"]`, + )[0]; + + const win = el?.ownerDocument.defaultView || window; + + const isInIframe = win !== window; + + if (isInIframe) { + const iframe = win.frameElement as HTMLIFrameElement; + const viewportBox = getBox(iframe); + const box = getBox(el); + + const change = getScroll({ + dragStartTime, + container: resetToOrigin(viewportBox), // Reset to origin because we don't care about position of the iframe + subject: draggable.client.marginBox, + center: box.borderBox.center, + shouldUseTimeDampening, + getAutoScrollerOptions, + }); + + return { change, window: win }; + } + + return null; +}; diff --git a/src/state/auto-scroller/fluid-scroller/index.ts b/src/state/auto-scroller/fluid-scroller/index.ts index e6c0d3da4..2a8b2fef9 100644 --- a/src/state/auto-scroller/fluid-scroller/index.ts +++ b/src/state/auto-scroller/fluid-scroller/index.ts @@ -8,7 +8,7 @@ import { AutoScrollerOptions } from './auto-scroller-options-types'; import { defaultAutoScrollerOptions } from './config'; export interface PublicArgs { - scrollWindow: (change: Position) => void; + scrollWindow: (change: Position, win?: Window) => void; scrollDroppable: (id: DroppableId, change: Position) => void; getAutoScrollerOptions?: () => AutoScrollerOptions; } diff --git a/src/state/auto-scroller/fluid-scroller/scroll.ts b/src/state/auto-scroller/fluid-scroller/scroll.ts index 9509e1f4a..042a6c6c5 100644 --- a/src/state/auto-scroller/fluid-scroller/scroll.ts +++ b/src/state/auto-scroller/fluid-scroller/scroll.ts @@ -11,12 +11,13 @@ import whatIsDraggedOver from '../../droppable/what-is-dragged-over'; import getWindowScrollChange from './get-window-scroll-change'; import getDroppableScrollChange from './get-droppable-scroll-change'; import { AutoScrollerOptions } from './auto-scroller-options-types'; +import getIframeScroll from './get-iframe-scroll'; interface Args { state: DraggingState; dragStartTime: number; shouldUseTimeDampening: boolean; - scrollWindow: (scroll: Position) => void; + scrollWindow: (scroll: Position, win?: Window) => void; scrollDroppable: (id: DroppableId, scroll: Position) => void; getAutoScrollerOptions: () => AutoScrollerOptions; } @@ -51,6 +52,20 @@ export default ({ } } + const iframeScroll = getIframeScroll({ + state, + dragStartTime, + shouldUseTimeDampening, + getAutoScrollerOptions, + draggable, + }); + + if (iframeScroll?.change) { + scrollWindow(iframeScroll.change, iframeScroll.window); + + return; + } + const droppable: DroppableDimension | null = getBestScrollableDroppable({ center, destination: whatIsDraggedOver(state.impact), diff --git a/src/view/window/scroll-window.ts b/src/view/window/scroll-window.ts index 1e96e18b0..68f48bac5 100644 --- a/src/view/window/scroll-window.ts +++ b/src/view/window/scroll-window.ts @@ -1,6 +1,6 @@ import type { Position } from 'css-box-model'; -// Not guarenteed to scroll by the entire amount -export default (change: Position): void => { - window.scrollBy(change.x, change.y); +// Not guaranteed to scroll by the entire amount +export default (change: Position, win: Window = window): void => { + win.scrollBy(change.x, change.y); };