import type { OccupiedSpace } from "constants/CanvasEditorConstants"; import { Colors } from "constants/Colors"; import type { DefaultDimensionMap } from "constants/WidgetConstants"; import React from "react"; import type { CSSProperties, ReactNode } from "react"; import { animated } from "react-spring"; import { useDrag } from "react-use-gesture"; import type { GridProps, ReflowDirection } from "reflow/reflowTypes"; import type { StyledComponent } from "styled-components"; import styled from "styled-components"; import type { LayoutDirection, ResponsiveBehavior, } from "utils/autoLayout/constants"; import { getNearestParentCanvas } from "utils/generators"; import memoize from "micro-memoize"; const resizeBorderPadding = 1; const resizeBorder = 1; const resizeBoxShadow = 1; const resizeOutline = 1; export const RESIZE_BORDER_BUFFER = resizeBorderPadding + resizeBorder + resizeBoxShadow + resizeOutline; export const ResizeWrapper = styled(animated.div)``; export const getWrapperStyle = memoize( ( inverted: boolean, showBoundaries: boolean, allowResize: boolean, isHovered: boolean, ): CSSProperties => { return { borderRadius: allowResize ? inverted ? "4px 4px 0px 4px" : "4px 0px 4px 4px" : "4px", border: `${resizeBorder}px solid`, padding: `${resizeBorderPadding}px`, borderColor: `${showBoundaries ? Colors.GREY_1 : "transparent"}`, boxShadow: `${ showBoundaries ? "0px 0px 0px " + resizeBoxShadow + "px " + (isHovered ? Colors.WATUSI : "#f86a2b") : "none" }`, }; }, ); // export const ResizeWrapper = (props: { // $prevents: boolean; // isHovered: boolean; // showBoundaries: boolean; // inverted?: boolean; // children: ReactNode; // className?: string; // id?: string; // ref?: any; // style?: any; // }) => { // const wrapperStyle: CSSProperties = useMemo(getWrapperStyle, [ // props.inverted, // props.showBoundaries, // props.isHovered, // props.style, // ]); // return ( // // {props.children} // // ); // }; // export const ResizeWrapper = styled(animated.div)<{ // $prevents: boolean; // isHovered: boolean; // showBoundaries: boolean; // inverted?: boolean; // }>` // display: block; // & { // * { // pointer-events: ${(props) => !props.$prevents && "none"}; // } // } // border-radius: 4px 0px 4px 4px; // ${(props) => { // if (props.inverted) { // return `border-radius: 4px 4px 4px 0px;`; // } else { // return `border-radius: 0px 4px 4px 4px;`; // } // }} // border: ${resizeBorder}px solid; // padding: ${resizeBorderPadding}px; // outline: ${resizeOutline}px solid !important; // outline-offset: 1px; // ${(props) => { // if (props.showBoundaries) { // return ` // box-shadow: 0px 0px 0px ${resizeBoxShadow}px ${ // props.isHovered ? Colors.WATUSI : "#f86a2b" // }; // outline-color: ${Colors.GREY_1} !important; // border-color: ${Colors.GREY_1}; // `; // } else { // return ` // outline-color: transparent !important; // border-color: transparent; // `; // } // }}} // `; const getSnappedValues = ( x: number, y: number, snapGrid: { x: number; y: number }, ) => { return { x: Math.round(x / snapGrid.x) * snapGrid.x, y: Math.round(y / snapGrid.y) * snapGrid.y, }; }; export type DimensionUpdateProps = { width: number; height: number; x: number; y: number; reset?: boolean; direction: ReflowDirection; X?: number; Y?: number; reflectPosition: boolean; reflectDimension: boolean; }; type ResizableHandleProps = { allowResize: boolean; scrollParent: HTMLDivElement | null; disableDot: boolean; isHovered: boolean; checkForCollision: (widgetNewSize: { left: number; top: number; bottom: number; right: number; }) => boolean; dragCallback: (x: number, y: number) => void; component: StyledComponent<"div", Record>; onStart: () => void; onStop: () => void; snapGrid: { x: number; y: number; }; direction?: ReflowDirection; }; export function ResizableHandle(props: ResizableHandleProps) { const bind = useDrag((state) => { const { first, last, dragging, memo, movement: [mx, my], } = state; if (!props.allowResize || props.disableDot) { return; } const scrollParent = getNearestParentCanvas(props.scrollParent); const initialScrollTop = memo ? memo.scrollTop : 0; const currentScrollTop = scrollParent?.scrollTop || 0; const deltaScrolledHeight = currentScrollTop - initialScrollTop; const deltaY = my + deltaScrolledHeight; const snapped = getSnappedValues(mx, deltaY, props.snapGrid); if (first) { props.onStart(); return { scrollTop: currentScrollTop, snapped }; } const { snapped: snappedMemo } = memo; if ( dragging && snappedMemo && (snapped.x !== snappedMemo.x || snapped.y !== snappedMemo.y) ) { props.dragCallback(snapped.x, snapped.y); } if (last) { props.onStop(); } return { ...memo, snapped }; }); const propsToPass = { ...bind(), showAsBorder: !props.allowResize, disableDot: props.disableDot, isHovered: props.isHovered, }; return ( ); } export type ResizableProps = { allowResize: boolean; handles: { left?: StyledComponent<"div", Record>; top?: StyledComponent<"div", Record>; bottom?: StyledComponent<"div", Record>; right?: StyledComponent<"div", Record>; bottomRight?: StyledComponent<"div", Record>; topLeft?: StyledComponent<"div", Record>; topRight?: StyledComponent<"div", Record>; bottomLeft?: StyledComponent<"div", Record>; }; componentWidth: number; componentHeight: number; children: ReactNode; updateBottomRow: (bottomRow: number) => void; getResizedPositions: (resizedPositions: OccupiedSpace) => { canResizeHorizontally: boolean; canResizeVertically: boolean; }; maxHeightInPx: number; // Maximum height in pixels, the child can have. autoHeight: boolean; // true if we don't want a pixel height specified for the child originalPositions: OccupiedSpace; onStart: (affectsWidth?: boolean) => void; onStop: ( size: { width: number; height: number }, position: { x: number; y: number }, dimensionMap?: typeof DefaultDimensionMap, ) => void; snapGrid: { x: number; y: number }; enableVerticalResize: boolean; enableHorizontalResize: boolean; className?: string; parentId?: string; widgetId: string; gridProps: GridProps; zWidgetType?: string; zWidgetId?: string; isFlexChild?: boolean; isHovered: boolean; responsiveBehavior?: ResponsiveBehavior; direction?: LayoutDirection; paddingOffset: number; isMobile: boolean; showResizeBoundary: boolean; topRow: number; };