Skip to content

Commit

Permalink
Revert to class-based TouchCaptor and TouchDragHandle
Browse files Browse the repository at this point in the history
  • Loading branch information
tcobbs-bentley committed Jun 25, 2024
1 parent 75085d5 commit c5c0a97
Showing 1 changed file with 200 additions and 73 deletions.
273 changes: 200 additions & 73 deletions src/mobile-ui-react/ResizablePanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -373,46 +373,97 @@ interface TouchCaptorProps extends CommonProps {
}

// This is a copy of PointerCaptor that has been changed to work with touch events instead.
// 2024-06-24: Converted to a function due to compile errors that I couldn't understand after
// updating to iTwin 4.7.x.
function TouchCaptor(props: TouchCaptorProps) {
const { className, children, isTouchStarted, onTouchStart, onTouchMove, onTouchEnd } = props;
const handleTouchStart = React.useCallback((e: React.TouchEvent<HTMLDivElement>) => {
onTouchStart?.(e.nativeEvent);
}, [onTouchStart]);
const handleDocumentTouchEnd = React.useCallback((e: TouchEvent) => {
if (!isTouchStarted)
class TouchCaptor extends React.PureComponent<TouchCaptorProps> {
public override componentDidMount() {
document.addEventListener("touchend", this._handleDocumentTouchEnd);
document.addEventListener("touchmove", this._handleDocumentTouchMove);
}

public override componentWillUnmount() {
document.removeEventListener("touchend", this._handleDocumentTouchEnd);
document.removeEventListener("touchmove", this._handleDocumentTouchMove);
}

public override render() {
const className = classnames(
"nz-base-pointerCaptor",
this.props.isTouchStarted && "nz-captured",
this.props.className);
return (
<div
className={className}
onTouchStart={this._handleTouchStart}
style={this.props.style}
role="presentation"
>
<div className="nz-overlay" />
{this.props.children}
</div>
);
}

private _handleTouchStart = (e: React.TouchEvent<HTMLDivElement>) => {
this.props.onTouchStart && this.props.onTouchStart(e.nativeEvent);
};

private _handleDocumentTouchEnd = (e: TouchEvent) => {
if (!this.props.isTouchStarted)
return;
onTouchEnd?.(e);
}, [onTouchEnd, isTouchStarted]);
const handleDocumentTouchMove = React.useCallback((e: TouchEvent) => {
if (!isTouchStarted)
this.props.onTouchEnd && this.props.onTouchEnd(e);
};

private _handleDocumentTouchMove = (e: TouchEvent) => {
if (!this.props.isTouchStarted)
return;
onTouchMove?.(e);
}, [onTouchMove, isTouchStarted]);
React.useEffect(() => {
document.addEventListener("touchend", handleDocumentTouchEnd);
document.addEventListener("touchmove", handleDocumentTouchMove);
return () => {
document.removeEventListener("touchend", handleDocumentTouchEnd);
document.removeEventListener("touchmove", handleDocumentTouchMove);
};
}, [handleDocumentTouchEnd, handleDocumentTouchMove]);
const fullClassName = classnames(
"nz-base-pointerCaptor",
isTouchStarted && "nz-captured",
className);
return (
<div
className={fullClassName}
onTouchStart={handleTouchStart}
style={props.style}
role="presentation"
>
<div className="nz-overlay" />
{children}
</div>
);
this.props.onTouchMove && this.props.onTouchMove(e);
};
}

// @todo Use the functional version of TouchCaptor below after sufficient testing.
// // 2024-06-24: Converted to a function due to compile errors that I couldn't understand after
// // updating to iTwin 4.7.x.
// function TouchCaptor(props: TouchCaptorProps) {
// const { className, children, isTouchStarted, onTouchStart, onTouchMove, onTouchEnd } = props;
// const handleTouchStart = React.useCallback((e: React.TouchEvent<HTMLDivElement>) => {
// onTouchStart?.(e.nativeEvent);
// }, [onTouchStart]);
// const handleDocumentTouchEnd = React.useCallback((e: TouchEvent) => {
// if (!isTouchStarted)
// return;
// onTouchEnd?.(e);
// }, [onTouchEnd, isTouchStarted]);
// const handleDocumentTouchMove = React.useCallback((e: TouchEvent) => {
// if (!isTouchStarted)
// return;
// onTouchMove?.(e);
// }, [onTouchMove, isTouchStarted]);
// React.useEffect(() => {
// document.addEventListener("touchend", handleDocumentTouchEnd);
// document.addEventListener("touchmove", handleDocumentTouchMove);
// return () => {
// document.removeEventListener("touchend", handleDocumentTouchEnd);
// document.removeEventListener("touchmove", handleDocumentTouchMove);
// };
// }, [handleDocumentTouchEnd, handleDocumentTouchMove]);
// const fullClassName = classnames(
// "nz-base-pointerCaptor",
// isTouchStarted && "nz-captured",
// className);
// return (
// <div
// className={fullClassName}
// onTouchStart={handleTouchStart}
// style={props.style}
// role="presentation"
// >
// <div className="nz-overlay" />
// {children}
// </div>
// );
// }

interface TouchDragHandleState {
isPointerDown: boolean;
}

interface TouchDragHandleProps extends CommonProps {
Expand All @@ -429,54 +480,130 @@ interface TouchDragHandleProps extends CommonProps {
children?: React.ReactNode;
}

// A copy of the DragHandle component that uses TouchCaptor instead of PointerCaptor and only allows single touches during drags.
// 2024-06-24: Converted to a function due to compile errors that I couldn't understand after
// updating to iTwin 4.7.x.
function TouchDragHandle(props: TouchDragHandleProps) {
const { className, style, lastPosition, onDrag, onDragStart, onDragEnd, children } = props;
const [ isPointerDown, setIsPointerDown ] = React.useState(false);
const [ initial, setInitial ] = React.useState<XAndY>();
const handlePointerDown = React.useCallback((e: TouchEvent) => {
// A copy of the DragHandle class that uses TouchCaptor instead of PointerCaptor and only allows single touches during drags.
class TouchDragHandle extends React.PureComponent<TouchDragHandleProps, TouchDragHandleState> {
private _initial: XAndY | undefined = undefined;
// private _isDragged = false;

public override readonly state: TouchDragHandleState = {
isPointerDown: false,
};

public override render() {
const { style, children, className, lastPosition } = this.props;
return (
<TouchCaptor
className={className}
isTouchStarted={lastPosition === undefined ? this.state.isPointerDown : true}
// onClick={this._handleClick}
onTouchStart={this._handlePointerDown}
onTouchEnd={this._handlePointerUp}
onTouchMove={this._handlePointerMove}
style={style}
>
{children}
</TouchCaptor>
);
}

private _handlePointerDown = (e: TouchEvent) => {
// if (e.target instanceof Element) {
// e.target.releasePointerCapture(e.pointerId);
// }

if (e.touches.length !== 1)
return;
setIsPointerDown(true);
this.setState({ isPointerDown: true });

e.preventDefault();
// this._isDragged = false;
const touch = e.touches[0];

setInitial({x: touch.clientX, y: touch.clientY});
}, []);
const handlePointerMove = React.useCallback((e: TouchEvent) => {
this._initial = {x: touch.clientX, y: touch.clientY};
};

private _handlePointerMove = (e: TouchEvent) => {
if (e.touches.length !== 1)
return;
const touch = e.touches[0];
const current = new Point2d(touch.clientX, touch.clientY);
if (lastPosition) {
const dragged = current.minus(lastPosition);
onDrag?.(dragged);
if (this.props.lastPosition) {
const dragged = current.minus(this.props.lastPosition);
this.props.onDrag?.(dragged);
return;
}

if (initial && current.distance(initial) >= 6) {
onDragStart?.(initial);
if (this._initial && current.distance(this._initial) >= 6) {
// this._isDragged = true;
this.props.onDragStart?.(this._initial);
}
}, [lastPosition, initial, onDragStart, onDrag]);
const handlePointerUp = React.useCallback(() => {
setIsPointerDown(false);
setInitial(undefined);
if (lastPosition) {
onDragEnd?.();
};

private _handlePointerUp = () => {
this.setState({ isPointerDown: false });
this._initial = undefined;
if (this.props.lastPosition) {
this.props.onDragEnd?.();
return;
}
}, [lastPosition, onDragEnd]);
return (
<TouchCaptor
children={children} // eslint-disable-line react/no-children-prop
className={className}
isTouchStarted={lastPosition === undefined ? isPointerDown : true}
onTouchStart={handlePointerDown}
onTouchEnd={handlePointerUp}
onTouchMove={handlePointerMove}
style={style}
/>
);
};

// private _handleClick = () => {
// if (this._isDragged)
// return;
// this.props.onClick?.();
// }
}

// @todo Use the functional version of TouchDragHandle below after sufficient testing.
// // 2024-06-24: Converted to a function due to compile errors that I couldn't understand after
// // updating to iTwin 4.7.x.
// function TouchDragHandle(props: TouchDragHandleProps) {
// const { className, style, lastPosition, onDrag, onDragStart, onDragEnd, children } = props;
// const [ isPointerDown, setIsPointerDown ] = React.useState(false);
// const [ initial, setInitial ] = React.useState<XAndY>();
// const handlePointerDown = React.useCallback((e: TouchEvent) => {
// if (e.touches.length !== 1)
// return;
// setIsPointerDown(true);

// e.preventDefault();
// const touch = e.touches[0];

// setInitial({x: touch.clientX, y: touch.clientY});
// }, []);
// const handlePointerMove = React.useCallback((e: TouchEvent) => {
// if (e.touches.length !== 1)
// return;
// const touch = e.touches[0];
// const current = new Point2d(touch.clientX, touch.clientY);
// if (lastPosition) {
// const dragged = current.minus(lastPosition);
// onDrag?.(dragged);
// return;
// }

// if (initial && current.distance(initial) >= 6) {
// onDragStart?.(initial);
// }
// }, [lastPosition, initial, onDragStart, onDrag]);
// const handlePointerUp = React.useCallback(() => {
// setIsPointerDown(false);
// setInitial(undefined);
// if (lastPosition) {
// onDragEnd?.();
// }
// }, [lastPosition, onDragEnd]);
// return (
// <TouchCaptor
// className={className}
// isTouchStarted={lastPosition === undefined ? isPointerDown : true}
// onTouchStart={handlePointerDown}
// onTouchEnd={handlePointerUp}
// onTouchMove={handlePointerMove}
// style={style}
// >
// {children}
// </TouchCaptor>
// );
// }

0 comments on commit c5c0a97

Please sign in to comment.