Support resize for mini apps modal (#5319)
This commit is contained in:
parent
50621e91d3
commit
984a058e38
@ -24,6 +24,78 @@
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.resizeHandle {
|
||||
position: absolute;
|
||||
background: transparent;
|
||||
z-index: var(--z-resize-grip);
|
||||
|
||||
&.top,
|
||||
&.bottom {
|
||||
left: 0;
|
||||
right: 0;
|
||||
width: 100%;
|
||||
height: 0.5rem;
|
||||
cursor: ns-resize;
|
||||
}
|
||||
|
||||
&.left,
|
||||
&.right {
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: 0.5rem;
|
||||
height: 100%;
|
||||
cursor: ew-resize;
|
||||
}
|
||||
|
||||
&.top {
|
||||
top: 0;
|
||||
}
|
||||
|
||||
&.bottom {
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
&.left {
|
||||
left: 0;
|
||||
}
|
||||
|
||||
&.right {
|
||||
right: 0;
|
||||
}
|
||||
|
||||
&.topLeft,
|
||||
&.topRight,
|
||||
&.bottomLeft,
|
||||
&.bottomRight {
|
||||
width: 0.5rem;
|
||||
height: 0.5rem;
|
||||
}
|
||||
|
||||
&.topLeft {
|
||||
top: 0;
|
||||
left: 0;
|
||||
cursor: nwse-resize;
|
||||
}
|
||||
|
||||
&.topRight {
|
||||
top: 0;
|
||||
right: 0;
|
||||
cursor: nesw-resize;
|
||||
}
|
||||
|
||||
&.bottomLeft {
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
cursor: nesw-resize;
|
||||
}
|
||||
|
||||
&.bottomRight {
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
cursor: nwse-resize;
|
||||
}
|
||||
}
|
||||
|
||||
.modal-container {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
@ -12,6 +12,7 @@ import type { TabState, WebApp } from '../../../global/types';
|
||||
import type { ThemeKey } from '../../../types';
|
||||
import type { WebAppOutboundEvent } from '../../../types/webapp';
|
||||
|
||||
import { RESIZE_HANDLE_CLASS_NAME } from '../../../config';
|
||||
import { getWebAppKey } from '../../../global/helpers/bots';
|
||||
import {
|
||||
selectCurrentChat, selectTheme, selectUser,
|
||||
@ -65,6 +66,10 @@ type StateProps = {
|
||||
const PROLONG_INTERVAL = 45000; // 45s
|
||||
const LUMA_THRESHOLD = 128;
|
||||
|
||||
const MINIMIZED_STATE_SIZE = { width: 300, height: 40 };
|
||||
const DEFAULT_MAXIMIZED_STATE_SIZE = { width: 420, height: 730 };
|
||||
const MAXIMIZED_STATE_MINIMUM_SIZE = { width: 300, height: 300 };
|
||||
|
||||
const WebAppModal: FC<OwnProps & StateProps> = ({
|
||||
modal,
|
||||
chat,
|
||||
@ -85,22 +90,18 @@ const WebAppModal: FC<OwnProps & StateProps> = ({
|
||||
closeMoreAppsTab,
|
||||
} = getActions();
|
||||
|
||||
const maximizedStateSize = useMemo(() => {
|
||||
return { width: 420, height: 730 };
|
||||
}, []);
|
||||
const minimizedStateSize = useMemo(() => {
|
||||
return { width: 300, height: 40 };
|
||||
}, []);
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const [getFrameSize, setFrameSize] = useSignal(
|
||||
{ width: maximizedStateSize.width, height: maximizedStateSize.height - minimizedStateSize.height },
|
||||
const [getMaximizedStateSize, setMaximizedStateSize] = useSignal(
|
||||
{ width: DEFAULT_MAXIMIZED_STATE_SIZE.width, height: DEFAULT_MAXIMIZED_STATE_SIZE.height },
|
||||
);
|
||||
|
||||
function getSize() {
|
||||
if (modal?.modalState === 'fullScreen') return windowSize.get();
|
||||
if (modal?.modalState === 'maximized') return maximizedStateSize;
|
||||
return minimizedStateSize;
|
||||
if (modal?.modalState === 'maximized') return getMaximizedStateSize();
|
||||
return MINIMIZED_STATE_SIZE;
|
||||
}
|
||||
function getMinimumSize() {
|
||||
if (modal?.modalState === 'maximized') return MAXIMIZED_STATE_MINIMUM_SIZE;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const {
|
||||
@ -169,6 +170,7 @@ const WebAppModal: FC<OwnProps & StateProps> = ({
|
||||
|
||||
const {
|
||||
isDragging,
|
||||
isResizing,
|
||||
style: draggableStyle,
|
||||
size,
|
||||
} = useDraggable(
|
||||
@ -177,28 +179,19 @@ const WebAppModal: FC<OwnProps & StateProps> = ({
|
||||
isDraggingEnabled,
|
||||
getSize(),
|
||||
isFullScreen,
|
||||
getMinimumSize(),
|
||||
);
|
||||
|
||||
const currentSize = size || getSize();
|
||||
|
||||
const currentWidth = currentSize.width;
|
||||
const currentHeight = currentSize.height;
|
||||
|
||||
useEffect(() => {
|
||||
if (currentHeight === minimizedStateSize.height && currentWidth === minimizedStateSize.width) return;
|
||||
if (isMaximizedState) {
|
||||
const height = currentHeight - minimizedStateSize.height;
|
||||
setFrameSize({ width: currentWidth, height });
|
||||
if (isResizing) {
|
||||
setMaximizedStateSize({ width: currentWidth, height: currentHeight });
|
||||
}
|
||||
if (isFullScreen) {
|
||||
setFrameSize({ width: window.innerWidth, height: window.innerHeight });
|
||||
}
|
||||
}, [currentWidth,
|
||||
currentHeight,
|
||||
isMaximizedState,
|
||||
minimizedStateSize,
|
||||
setFrameSize,
|
||||
isFullScreen,
|
||||
isMinimizedState]);
|
||||
}, [currentHeight, currentWidth, isResizing, setMaximizedStateSize]);
|
||||
|
||||
const oldLang = useOldLang();
|
||||
const lang = useLang();
|
||||
@ -646,6 +639,25 @@ const WebAppModal: FC<OwnProps & StateProps> = ({
|
||||
);
|
||||
}
|
||||
|
||||
function buildResizeHandleClass(handleClassName: string) {
|
||||
return buildClassName(RESIZE_HANDLE_CLASS_NAME, handleClassName);
|
||||
}
|
||||
|
||||
function renderResizeHandles() {
|
||||
return (
|
||||
<>
|
||||
<div className={buildResizeHandleClass('top')} />
|
||||
<div className={buildResizeHandleClass('bottom')} />
|
||||
<div className={buildResizeHandleClass('left')} />
|
||||
<div className={buildResizeHandleClass('right')} />
|
||||
<div className={buildResizeHandleClass('topLeft')} />
|
||||
<div className={buildResizeHandleClass('topRight')} />
|
||||
<div className={buildResizeHandleClass('bottomLeft')} />
|
||||
<div className={buildResizeHandleClass('bottomRight')} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal
|
||||
dialogRef={ref}
|
||||
@ -656,6 +668,7 @@ const WebAppModal: FC<OwnProps & StateProps> = ({
|
||||
isFullScreen && styles.fullScreen,
|
||||
)}
|
||||
dialogStyle={supportMultiTabMode ? draggableStyle : undefined}
|
||||
dialogContent={isDraggingEnabled ? renderResizeHandles() : undefined}
|
||||
isOpen={isOpen}
|
||||
isLowStackPriority
|
||||
onClose={handleModalClose}
|
||||
@ -672,9 +685,10 @@ const WebAppModal: FC<OwnProps & StateProps> = ({
|
||||
registerSendEventCallback={registerSendEventCallback}
|
||||
registerReloadFrameCallback={registerReloadFrameCallback}
|
||||
webApp={openedWebApps[key]}
|
||||
isDragging={isDragging}
|
||||
isTransforming={isDragging || isResizing}
|
||||
onContextMenuButtonClick={handleContextMenu}
|
||||
isMultiTabSupported={supportMultiTabMode}
|
||||
modalHeight={currentHeight}
|
||||
/>
|
||||
))}
|
||||
{ isMoreAppsTabActive && (<MoreAppsTabContent />)}
|
||||
|
||||
@ -21,11 +21,9 @@ import {
|
||||
selectWebApp,
|
||||
} from '../../../global/selectors';
|
||||
import buildClassName from '../../../util/buildClassName';
|
||||
import buildStyle from '../../../util/buildStyle';
|
||||
import download from '../../../util/download';
|
||||
import { extractCurrentThemeParams, validateHexColor } from '../../../util/themeStyle';
|
||||
import { callApi } from '../../../api/gramjs';
|
||||
import { REM } from '../../common/helpers/mediaDimensions';
|
||||
import renderText from '../../common/helpers/renderText';
|
||||
|
||||
import { getIsWebAppsFullscreenSupported } from '../../../hooks/useAppLayout';
|
||||
@ -65,9 +63,9 @@ export type OwnProps = {
|
||||
registerSendEventCallback: (callback: (event: WebAppOutboundEvent) => void) => void;
|
||||
registerReloadFrameCallback: (callback: (url: string) => void) => void;
|
||||
onContextMenuButtonClick: (e: React.MouseEvent) => void;
|
||||
isDragging?: boolean;
|
||||
frameSize?: { width: number; height: number };
|
||||
isTransforming?: boolean;
|
||||
isMultiTabSupported? : boolean;
|
||||
modalHeight: number;
|
||||
};
|
||||
|
||||
type StateProps = {
|
||||
@ -113,12 +111,12 @@ const WebAppModalTabContent: FC<OwnProps & StateProps> = ({
|
||||
paymentStatus,
|
||||
registerSendEventCallback,
|
||||
registerReloadFrameCallback,
|
||||
isDragging,
|
||||
isTransforming,
|
||||
modalState,
|
||||
frameSize,
|
||||
isMultiTabSupported,
|
||||
onContextMenuButtonClick,
|
||||
botAppSettings,
|
||||
modalHeight,
|
||||
}) => {
|
||||
const {
|
||||
closeActiveWebApp,
|
||||
@ -700,10 +698,11 @@ const WebAppModalTabContent: FC<OwnProps & StateProps> = ({
|
||||
setTimeout(() => {
|
||||
sendViewport();
|
||||
sendSafeArea();
|
||||
}, ANIMATION_WAIT);
|
||||
}, isTransforming ? 0 : ANIMATION_WAIT);
|
||||
}, [shouldShowSecondaryButton, shouldHideSecondaryButton,
|
||||
shouldShowMainButton, shouldShowMainButton,
|
||||
secondaryButton?.position, sendViewport, sendSafeArea]);
|
||||
secondaryButton?.position, sendViewport, isTransforming, modalHeight,
|
||||
sendSafeArea]);
|
||||
|
||||
const isVerticalLayout = secondaryButtonCurrentPosition === 'top' || secondaryButtonCurrentPosition === 'bottom';
|
||||
const isHorizontalLayout = !isVerticalLayout;
|
||||
@ -798,16 +797,7 @@ const WebAppModalTabContent: FC<OwnProps & StateProps> = ({
|
||||
}
|
||||
}, [setShouldDecreaseWebFrameSize, shouldShowSecondaryButton, shouldShowMainButton]);
|
||||
|
||||
const frameWidth = frameSize?.width || 0;
|
||||
let frameHeight = frameSize?.height || 0;
|
||||
if (shouldDecreaseWebFrameSize) { frameHeight -= 4 * REM; }
|
||||
const frameStyle = frameSize ? buildStyle(
|
||||
`left: ${0}px;`,
|
||||
`top: ${0}px;`,
|
||||
`width: ${frameWidth}px;`,
|
||||
`height: ${frameHeight}px;`,
|
||||
isDragging ? 'pointer-events: none;' : '',
|
||||
) : isDragging ? 'pointer-events: none;' : '';
|
||||
const frameStyle = isTransforming ? 'pointer-events: none;' : '';
|
||||
|
||||
const handleBackClick = useLastCallback(() => {
|
||||
if (isBackButtonVisible) {
|
||||
|
||||
@ -40,6 +40,7 @@ export type OwnProps = {
|
||||
dialogStyle?: string;
|
||||
dialogRef?: React.RefObject<HTMLDivElement>;
|
||||
isLowStackPriority?: boolean;
|
||||
dialogContent?: React.ReactNode;
|
||||
onClose: () => void;
|
||||
onCloseAnimationEnd?: () => void;
|
||||
onEnter?: () => void;
|
||||
@ -62,6 +63,7 @@ const Modal: FC<OwnProps> = ({
|
||||
style,
|
||||
dialogStyle,
|
||||
isLowStackPriority,
|
||||
dialogContent,
|
||||
onClose,
|
||||
onCloseAnimationEnd,
|
||||
onEnter,
|
||||
@ -171,6 +173,7 @@ const Modal: FC<OwnProps> = ({
|
||||
<div className="modal-backdrop" onClick={!noBackdropClose ? onClose : undefined} />
|
||||
<div className="modal-dialog" ref={dialogRef} style={dialogStyle}>
|
||||
{renderHeader()}
|
||||
{dialogContent}
|
||||
<div className={buildClassName('modal-content custom-scroll', contentClassName)} style={style}>
|
||||
{children}
|
||||
</div>
|
||||
|
||||
@ -148,6 +148,9 @@ export const CUSTOM_APPENDIX_ATTRIBUTE = 'data-has-custom-appendix';
|
||||
export const MESSAGE_CONTENT_CLASS_NAME = 'message-content';
|
||||
export const MESSAGE_CONTENT_SELECTOR = '.message-content';
|
||||
|
||||
export const RESIZE_HANDLE_CLASS_NAME = 'resizeHandle';
|
||||
export const RESIZE_HANDLE_SELECTOR = `.${RESIZE_HANDLE_CLASS_NAME}`;
|
||||
|
||||
export const SNAP_EFFECT_CONTAINER_ID = 'snap-effect-container';
|
||||
export const SNAP_EFFECT_ID = 'snap-effect';
|
||||
|
||||
|
||||
@ -3,6 +3,7 @@ import {
|
||||
useEffect, useSignal, useState,
|
||||
} from '../lib/teact/teact';
|
||||
|
||||
import { RESIZE_HANDLE_SELECTOR } from '../config';
|
||||
import buildStyle from '../util/buildStyle';
|
||||
import { captureEvents } from '../util/captureEvents';
|
||||
import useFlag from './useFlag';
|
||||
@ -13,6 +14,33 @@ export interface Size {
|
||||
height: number;
|
||||
}
|
||||
|
||||
export enum ResizeHandleType {
|
||||
Top,
|
||||
Bottom,
|
||||
Left,
|
||||
Right,
|
||||
TopLeft,
|
||||
TopRight,
|
||||
BottomLeft,
|
||||
BottomRight,
|
||||
}
|
||||
|
||||
type ResizeHandleSelectorType = 'top' | 'bottom' | 'left'
|
||||
| 'right' | 'topLeft' | 'topRight' | 'bottomLeft' | 'bottomRight';
|
||||
|
||||
const resizeHandleSelectorsMap: Record<ResizeHandleSelectorType, ResizeHandleType> = {
|
||||
top: ResizeHandleType.Top,
|
||||
bottom: ResizeHandleType.Bottom,
|
||||
left: ResizeHandleType.Left,
|
||||
right: ResizeHandleType.Right,
|
||||
topLeft: ResizeHandleType.TopLeft,
|
||||
topRight: ResizeHandleType.TopRight,
|
||||
bottomLeft: ResizeHandleType.BottomLeft,
|
||||
bottomRight: ResizeHandleType.BottomRight,
|
||||
};
|
||||
|
||||
const resizeHandleSelectors = Object.keys(resizeHandleSelectorsMap) as ResizeHandleSelectorType[];
|
||||
|
||||
export interface Point {
|
||||
x: number;
|
||||
y: number;
|
||||
@ -27,15 +55,17 @@ export default function useDraggable(
|
||||
isDragEnabled: boolean = true,
|
||||
originalSize: Size,
|
||||
isFullscreen: boolean = false,
|
||||
minimumSize: Size = { width: 0, height: 0 },
|
||||
) {
|
||||
const [elementCurrentPosition, setElementCurrentPosition] = useState<Point | undefined>(undefined);
|
||||
const [elementCurrentSize, setElementCurrentSize] = useState<Size | undefined>(undefined);
|
||||
|
||||
const [getElementPositionOnStartDrag, setElementPositionOnStartDrag] = useSignal({ x: 0, y: 0 });
|
||||
const [getDragStartPoint, setDragStartPoint] = useSignal({ x: 0, y: 0 });
|
||||
const [getElementPositionOnStartTransform, setElementPositionOnStartTransform] = useSignal({ x: 0, y: 0 });
|
||||
const [getElementSizeOnStartTransform, setElementSizeOnStartTransform] = useSignal({ width: 0, height: 0 });
|
||||
const [getTransformStartPoint, setTransformStartPoint] = useSignal({ x: 0, y: 0 });
|
||||
|
||||
const elementPositionOnStartDrag = getElementPositionOnStartDrag();
|
||||
const dragStartPoint = getDragStartPoint();
|
||||
const elementPositionOnStartTransform = getElementPositionOnStartTransform();
|
||||
const transformStartPoint = getTransformStartPoint();
|
||||
|
||||
const element = ref.current;
|
||||
const dragHandleElement = dragHandleElementRef.current;
|
||||
@ -43,8 +73,11 @@ export default function useDraggable(
|
||||
const [isInitiated, setIsInitiated] = useFlag(false);
|
||||
const [wasElementShown, setWasElementShown] = useFlag(false);
|
||||
const [isDragging, startDragging, stopDragging] = useFlag(false);
|
||||
const [isResizing, startResizing, stopResizing] = useFlag(false);
|
||||
const [isWindowsResizing, startWindowResizing, stopWindowResizing] = useFlag(false);
|
||||
|
||||
const [hitResizeHandle, setHitResizeHandle] = useState<ResizeHandleType | undefined>(undefined);
|
||||
|
||||
function getVisibleArea() {
|
||||
return {
|
||||
width: window.innerWidth,
|
||||
@ -89,6 +122,10 @@ export default function useDraggable(
|
||||
}, [elementCurrentSize, isInitiated, element]);
|
||||
|
||||
const handleStartDrag = useLastCallback((event: MouseEvent | TouchEvent) => {
|
||||
if (event instanceof MouseEvent && event.button !== 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const targetElement = event.target as HTMLElement;
|
||||
if (targetElement.closest('.no-drag') || !element) {
|
||||
return;
|
||||
@ -96,16 +133,56 @@ export default function useDraggable(
|
||||
const { pageX, pageY } = ('touches' in event) ? event.touches[0] : event;
|
||||
|
||||
const { left, top } = element.getBoundingClientRect();
|
||||
setElementPositionOnStartDrag({ x: left, y: top });
|
||||
setDragStartPoint({ x: pageX, y: pageY });
|
||||
setElementPositionOnStartTransform({ x: left, y: top });
|
||||
setTransformStartPoint({ x: pageX, y: pageY });
|
||||
|
||||
startDragging();
|
||||
});
|
||||
|
||||
const handleRelease = useLastCallback(() => {
|
||||
function getResizeHandleFromTarget(targetElement: HTMLElement) {
|
||||
const closest = (selector: string) => targetElement.closest(selector);
|
||||
|
||||
if (!closest(RESIZE_HANDLE_SELECTOR)) return undefined;
|
||||
for (const selector of resizeHandleSelectors) {
|
||||
if (closest(`.${selector}`)) { return resizeHandleSelectorsMap[selector]; }
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const handleStartResize = useLastCallback((event: MouseEvent | TouchEvent) => {
|
||||
if (event instanceof MouseEvent && event.button !== 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const targetElement = event.target as HTMLElement;
|
||||
if (!element || !targetElement) {
|
||||
return;
|
||||
}
|
||||
const resizeHandle = getResizeHandleFromTarget(targetElement);
|
||||
|
||||
if (resizeHandle === undefined) return;
|
||||
setHitResizeHandle(resizeHandle);
|
||||
|
||||
const { pageX, pageY } = ('touches' in event) ? event.touches[0] : event;
|
||||
|
||||
const {
|
||||
left, right, top, bottom,
|
||||
} = element.getBoundingClientRect();
|
||||
setElementPositionOnStartTransform({ x: left, y: top });
|
||||
setElementSizeOnStartTransform({ width: right - left, height: bottom - top });
|
||||
setTransformStartPoint({ x: pageX, y: pageY });
|
||||
|
||||
startResizing();
|
||||
});
|
||||
|
||||
const handleDragRelease = useLastCallback(() => {
|
||||
stopDragging();
|
||||
});
|
||||
|
||||
const handleResizeRelease = useLastCallback(() => {
|
||||
stopResizing();
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (!isDragEnabled) {
|
||||
stopDragging();
|
||||
@ -145,29 +222,33 @@ export default function useDraggable(
|
||||
|
||||
const visibleArea = getVisibleArea();
|
||||
|
||||
newSize.width = Math.min(visibleArea.width, Math.max(originalSize.width, newSize.width));
|
||||
newSize.height = Math.min(visibleArea.height, Math.max(originalSize.height, newSize.height));
|
||||
const originalWidth = originalSize.width;
|
||||
const originalHeight = originalSize.height;
|
||||
newSize.width = Math.min(visibleArea.width, Math.max(originalWidth, newSize.width));
|
||||
newSize.height = Math.min(visibleArea.height, Math.max(originalHeight, newSize.height));
|
||||
|
||||
return newSize;
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (isResizing) return;
|
||||
const newSize = ensureSizeInVisibleArea({ width: originalSize.width, height: originalSize.height });
|
||||
if (newSize) setElementCurrentSize(newSize);
|
||||
}, [originalSize]);
|
||||
}, [originalSize, isResizing]);
|
||||
|
||||
const adjustSizeWithinBounds = useLastCallback(() => {
|
||||
if (!elementCurrentSize) return;
|
||||
if (!elementCurrentSize || isResizing) return;
|
||||
const newSize = ensureSizeInVisibleArea(elementCurrentSize);
|
||||
if (newSize) setElementCurrentSize(newSize);
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (isResizing) return;
|
||||
adjustPositionWithinBounds();
|
||||
}, [elementCurrentSize]);
|
||||
}, [elementCurrentSize, isResizing]);
|
||||
|
||||
useEffect(() => {
|
||||
const handleResize = () => {
|
||||
const handleWindowResize = () => {
|
||||
startWindowResizing();
|
||||
adjustSizeWithinBounds();
|
||||
adjustPositionWithinBounds();
|
||||
@ -181,12 +262,12 @@ export default function useDraggable(
|
||||
}, 250);
|
||||
};
|
||||
|
||||
window.addEventListener('resize', handleResize);
|
||||
window.addEventListener('resize', handleWindowResize);
|
||||
|
||||
return () => {
|
||||
clearTimeout(resizeTimeout);
|
||||
resizeTimeout = undefined;
|
||||
window.removeEventListener('resize', handleResize);
|
||||
window.removeEventListener('resize', handleWindowResize);
|
||||
};
|
||||
}, [adjustPositionWithinBounds]);
|
||||
|
||||
@ -194,28 +275,112 @@ export default function useDraggable(
|
||||
if (!isDragging || !element) return;
|
||||
const { pageX, pageY } = ('touches' in event) ? event.touches[0] : event;
|
||||
|
||||
const offsetX = pageX - dragStartPoint.x;
|
||||
const offsetY = pageY - dragStartPoint.y;
|
||||
const offsetX = pageX - transformStartPoint.x;
|
||||
const offsetY = pageY - transformStartPoint.y;
|
||||
|
||||
const newX = elementPositionOnStartDrag.x + offsetX;
|
||||
const newY = elementPositionOnStartDrag.y + offsetY;
|
||||
const newX = elementPositionOnStartTransform.x + offsetX;
|
||||
const newY = elementPositionOnStartTransform.y + offsetY;
|
||||
|
||||
if (elementCurrentSize) setElementCurrentPosition(ensurePositionInVisibleArea(newX, newY));
|
||||
});
|
||||
|
||||
const handleResize = useLastCallback((event: MouseEvent | TouchEvent) => {
|
||||
if (!isResizing || !element || hitResizeHandle === undefined) return;
|
||||
const { pageX, pageY } = ('touches' in event) ? event.touches[0] : event;
|
||||
const sizeOnStartTransform = getElementSizeOnStartTransform();
|
||||
|
||||
const pageVisibleX = Math.min(Math.max(0, pageX), getVisibleArea().width);
|
||||
const pageVisibleY = Math.min(Math.max(0, pageY), getVisibleArea().height);
|
||||
|
||||
const offsetX = pageVisibleX - transformStartPoint.x;
|
||||
const offsetY = pageVisibleY - transformStartPoint.y;
|
||||
|
||||
const maxX = elementPositionOnStartTransform.x + sizeOnStartTransform.width - minimumSize.width;
|
||||
const maxY = elementPositionOnStartTransform.y + sizeOnStartTransform.height - minimumSize.height;
|
||||
|
||||
const originalBounds = {
|
||||
x: elementPositionOnStartTransform.x,
|
||||
y: elementPositionOnStartTransform.y,
|
||||
width: sizeOnStartTransform.width,
|
||||
height: sizeOnStartTransform.height,
|
||||
};
|
||||
|
||||
const newBounds = { ...originalBounds };
|
||||
|
||||
if (hitResizeHandle === ResizeHandleType.Left
|
||||
|| hitResizeHandle === ResizeHandleType.TopLeft
|
||||
|| hitResizeHandle === ResizeHandleType.BottomLeft
|
||||
) {
|
||||
newBounds.width = Math.max(sizeOnStartTransform.width - offsetX, minimumSize.width);
|
||||
newBounds.x = Math.min(newBounds.x + offsetX, maxX);
|
||||
}
|
||||
|
||||
if (hitResizeHandle === ResizeHandleType.Right
|
||||
|| hitResizeHandle === ResizeHandleType.TopRight
|
||||
|| hitResizeHandle === ResizeHandleType.BottomRight
|
||||
) {
|
||||
newBounds.width = Math.max(sizeOnStartTransform.width + offsetX, minimumSize.width);
|
||||
}
|
||||
|
||||
if (hitResizeHandle === ResizeHandleType.Top
|
||||
|| hitResizeHandle === ResizeHandleType.TopLeft
|
||||
|| hitResizeHandle === ResizeHandleType.TopRight
|
||||
) {
|
||||
newBounds.height = Math.max(sizeOnStartTransform.height - offsetY, minimumSize.height);
|
||||
newBounds.y = Math.min(newBounds.y + offsetY, maxY);
|
||||
}
|
||||
|
||||
if (hitResizeHandle === ResizeHandleType.Bottom
|
||||
|| hitResizeHandle === ResizeHandleType.BottomLeft
|
||||
|| hitResizeHandle === ResizeHandleType.BottomRight
|
||||
) {
|
||||
newBounds.height = Math.max(sizeOnStartTransform.height + offsetY, minimumSize.height);
|
||||
}
|
||||
|
||||
setElementCurrentSize({ width: newBounds.width, height: newBounds.height });
|
||||
setElementCurrentPosition({ x: newBounds.x, y: newBounds.y });
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
let cleanup: NoneToVoidFunction | undefined;
|
||||
if (dragHandleElement && isDragEnabled) {
|
||||
cleanup = captureEvents(dragHandleElement, {
|
||||
onCapture: handleStartDrag,
|
||||
onDrag: handleDrag,
|
||||
onRelease: handleRelease,
|
||||
onClick: handleRelease,
|
||||
onDoubleClick: handleRelease,
|
||||
onRelease: handleDragRelease,
|
||||
onClick: handleDragRelease,
|
||||
onDoubleClick: handleDragRelease,
|
||||
});
|
||||
}
|
||||
return cleanup;
|
||||
}, [handleDrag, handleStartDrag, isDragEnabled, dragHandleElement]);
|
||||
}, [isDragEnabled, dragHandleElement]);
|
||||
|
||||
useEffect(() => {
|
||||
const cleanups: NoneToVoidFunction[] = [];
|
||||
if (element && isDragEnabled) {
|
||||
for (const selector of resizeHandleSelectors) {
|
||||
const resizeHandler = element.querySelector(`.resizeHandle.${selector}`) as HTMLElement;
|
||||
|
||||
if (resizeHandler) {
|
||||
const cleanup = captureEvents(resizeHandler, {
|
||||
onCapture: handleStartResize,
|
||||
onDrag: handleResize,
|
||||
onRelease: handleResizeRelease,
|
||||
onClick: handleResizeRelease,
|
||||
onDoubleClick: handleResizeRelease,
|
||||
});
|
||||
|
||||
if (cleanup) {
|
||||
cleanups.push(cleanup);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return () => {
|
||||
cleanups.forEach((cleanup) => cleanup());
|
||||
};
|
||||
}, [isDragEnabled, element]);
|
||||
|
||||
const cursorStyle = isDragging ? 'cursor: grabbing !important; ' : '';
|
||||
|
||||
@ -234,7 +399,7 @@ export default function useDraggable(
|
||||
!isFullscreen && `max-width: ${elementCurrentSize.width}px;`,
|
||||
!isFullscreen && `max-height: ${elementCurrentSize.height}px;`,
|
||||
'position: fixed;',
|
||||
(isDragging || isWindowsResizing) && 'transition: none !important;',
|
||||
(isDragging || isResizing || isWindowsResizing) && 'transition: none !important;',
|
||||
cursorStyle,
|
||||
);
|
||||
|
||||
@ -242,6 +407,7 @@ export default function useDraggable(
|
||||
position: elementCurrentPosition,
|
||||
size: elementCurrentSize,
|
||||
isDragging,
|
||||
isResizing,
|
||||
style,
|
||||
};
|
||||
}
|
||||
|
||||
@ -253,6 +253,7 @@ $color-message-story-mention-to: #74bcff;
|
||||
--z-header-menu-backdrop: 980;
|
||||
--z-modal: 1510;
|
||||
--z-modal-menu: 1600;
|
||||
--z-resize-grip: 1000;
|
||||
--z-media-viewer: 1500;
|
||||
--z-modal-low-priority: 1400;
|
||||
--z-video-player-controls: 3;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user