react-use-gesture#useGesture TypeScript Examples
The following examples show how to use
react-use-gesture#useGesture.
You can vote up the ones you like or vote down the ones you don't like,
and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: drag-handle.component.tsx From react-sortful with MIT License | 6 votes |
DragHandleComponent = (props: Props) => {
const itemContext = React.useContext(ItemContext);
// Checks `props.children` has one React node.
React.useEffect(() => {
React.Children.only(props.children);
}, [props.children]);
const draggableBinder = useGesture({
onDragStart: (state: any) => {
if (itemContext.isLocked) return;
const event: React.SyntheticEvent = state.event;
event.persist();
event.stopPropagation();
itemContext.dragHandlers.onDragStart();
},
onDrag: ({ down, movement }) => {
if (itemContext.isLocked) return;
itemContext.dragHandlers.onDrag(down, movement);
},
onDragEnd: () => {
if (itemContext.isLocked) return;
itemContext.dragHandlers.onDragEnd();
},
});
return (
<div className={props.className} {...draggableBinder()}>
{props.children}
</div>
);
}
Example #2
Source File: LeftBar.tsx From binaural-meet with GNU General Public License v3.0 | 5 votes |
LeftBar: React.FC<BMProps> = (props) => {
const classes = styleForSplit()
const [scale, doSetScale] = useState<number>(1)
const setScale = (scale:number) => {
Object.assign(textLineStyle, defaultTextLineHeight)
textLineStyle.fontSize *= scale
textLineStyle.lineHeight *= scale
doSetScale(scale)
}
const bind = useGesture(
{
onPinch: ({da: [d, a], origin, event, memo}) => {
if (memo === undefined) {
return [d, a]
}
const [md] = memo
const MIN_D = 10
const scaleChange = d > MIN_D ? d / md : d < -MIN_D ? md / d : (1 + (d - md) / MIN_D)
setScale(limitScale(scale, scaleChange))
// console.log(`Pinch: da:${[d, a]} origin:${origin} memo:${memo} scale:${scale}`)
return [d, a]
},
},
{
eventOptions:{passive:false}, // This prevents default zoom by browser when pinch.
},
)
return (
<div {...bind()}>
<SplitPane split="horizontal" defaultSize="80%" resizerClassName = {classes.resizerHorizontal}
paneStyle = {{overflowY: 'auto', overflowX: 'hidden', width:'100%'}} >
<SplitPane split="horizontal" defaultSize="50%" resizerClassName = {classes.resizerHorizontal}
paneStyle = {{overflowY: 'auto', overflowX: 'hidden', width:'100%'}} >
<ParticipantList {...props} {...textLineStyle} />
<ContentList {...props} {...textLineStyle} />
</SplitPane >
<ChatInBar {...props} {...textLineStyle} />
</SplitPane >
</div>
)
}
Example #3
Source File: index.tsx From cuiswap with GNU General Public License v3.0 | 5 votes |
export default function Modal({
isOpen,
onDismiss,
minHeight = false,
maxHeight = 50,
initialFocusRef = null,
children
}: ModalProps) {
const fadeTransition = useTransition(isOpen, null, {
config: { duration: 200 },
from: { opacity: 0 },
enter: { opacity: 1 },
leave: { opacity: 0 }
})
const [{ y }, set] = useSpring(() => ({ y: 0, config: { mass: 1, tension: 210, friction: 20 } }))
const bind = useGesture({
onDrag: state => {
set({
y: state.down ? state.movement[1] : 0
})
if (state.movement[1] > 300 || (state.velocity > 3 && state.direction[1] > 0)) {
onDismiss()
}
}
})
return (
<>
{fadeTransition.map(
({ item, key, props }) =>
item && (
<StyledDialogOverlay key={key} style={props} onDismiss={onDismiss} initialFocusRef={initialFocusRef}>
<StyledDialogContent
{...(isMobile
? {
...bind(),
style: { transform: y.interpolate(y => `translateY(${y > 0 ? y : 0}px)`) }
}
: {})}
aria-label="dialog content"
minHeight={minHeight}
maxHeight={maxHeight}
mobile={isMobile}
>
{/* prevents the automatic focusing of inputs on mobile by the reach dialog */}
{!initialFocusRef && isMobile ? <div tabIndex={1} /> : null}
{children}
</StyledDialogContent>
</StyledDialogOverlay>
)
)}
</>
)
}
Example #4
Source File: index.tsx From sybil-interface with GNU General Public License v3.0 | 5 votes |
export default function Modal({
isOpen,
onDismiss,
minHeight = false,
maxHeight = 90,
initialFocusRef,
children,
}: ModalProps): JSX.Element {
const fadeTransition = useTransition(isOpen, null, {
config: { duration: 200 },
from: { opacity: 0 },
enter: { opacity: 1 },
leave: { opacity: 0 },
})
const [{ y }, set] = useSpring(() => ({ y: 0, config: { mass: 1, tension: 210, friction: 20 } }))
const bind = useGesture({
onDrag: (state) => {
set({
y: state.down ? state.movement[1] : 0,
})
if (state.movement[1] > 300 || (state.velocity > 3 && state.direction[1] > 0)) {
onDismiss()
}
},
})
return (
<>
{fadeTransition.map(
({ item, key, props }) =>
item && (
<StyledDialogOverlay key={key} style={props} onDismiss={onDismiss} initialFocusRef={initialFocusRef}>
<StyledDialogContent
{...(isMobile
? {
...bind(),
style: { transform: y.interpolate((y: any) => `translateY(${y > 0 ? y : 0}px)`) },
}
: {})}
aria-label="dialog content"
minHeight={minHeight}
maxHeight={maxHeight}
mobile={isMobile}
>
{/* prevents the automatic focusing of inputs on mobile by the reach dialog */}
{!initialFocusRef && isMobile ? <div tabIndex={1} /> : null}
{children}
</StyledDialogContent>
</StyledDialogOverlay>
)
)}
</>
)
}
Example #5
Source File: index.tsx From dyp with Do What The F*ck You Want To Public License | 5 votes |
export default function Modal({
isOpen,
onDismiss,
minHeight = false,
maxHeight = 90,
initialFocusRef,
children
}: ModalProps) {
const fadeTransition = useTransition(isOpen, null, {
config: { duration: 200 },
from: { opacity: 0 },
enter: { opacity: 1 },
leave: { opacity: 0 }
})
const [{ y }, set] = useSpring(() => ({ y: 0, config: { mass: 1, tension: 210, friction: 20 } }))
const bind = useGesture({
onDrag: state => {
set({
y: state.down ? state.movement[1] : 0
})
if (state.movement[1] > 300 || (state.velocity > 3 && state.direction[1] > 0)) {
onDismiss()
}
}
})
return (
<>
{fadeTransition.map(
({ item, key, props }) =>
item && (
<StyledDialogOverlay key={key} style={props} onDismiss={onDismiss} initialFocusRef={initialFocusRef}>
<StyledDialogContent
{...(isMobile
? {
...bind(),
style: { transform: y.interpolate(y => `translateY(${y > 0 ? y : 0}px)`) }
}
: {})}
aria-label="dialog content"
minHeight={minHeight}
maxHeight={maxHeight}
mobile={isMobile}
>
{/* prevents the automatic focusing of inputs on mobile by the reach dialog */}
{!initialFocusRef && isMobile ? <div tabIndex={1} /> : null}
{children}
</StyledDialogContent>
</StyledDialogOverlay>
)
)}
</>
)
}
Example #6
Source File: index.tsx From limit-orders-lib with GNU General Public License v3.0 | 5 votes |
export default function Modal({
isOpen,
onDismiss,
minHeight = false,
maxHeight = 90,
initialFocusRef,
children,
}: ModalProps) {
const fadeTransition = useTransition(isOpen, null, {
config: { duration: 200 },
from: { opacity: 0 },
enter: { opacity: 1 },
leave: { opacity: 0 },
});
const [{ y }, set] = useSpring(() => ({
y: 0,
config: { mass: 1, tension: 210, friction: 20 },
}));
const bind = useGesture({
onDrag: (state) => {
set({
y: state.down ? state.movement[1] : 0,
});
if (
state.movement[1] > 300 ||
(state.velocity > 3 && state.direction[1] > 0)
) {
onDismiss();
}
},
});
return (
<Fragment>
{fadeTransition.map(
({ item, key, props }) =>
item && (
<StyledDialogOverlay
key={key}
style={props}
onDismiss={onDismiss}
initialFocusRef={initialFocusRef}
unstable_lockFocusAcrossFrames={false}
>
<StyledDialogContent
{...(isMobile
? {
...bind(),
style: {
transform: y.interpolate(
(y) => `translateY(${(y as number) > 0 ? y : 0}px)`
),
},
}
: {})}
aria-label="dialog content"
minHeight={minHeight}
maxHeight={maxHeight}
mobile={isMobile}
>
{/* prevents the automatic focusing of inputs on mobile by the reach dialog */}
{!initialFocusRef && isMobile ? <div tabIndex={1} /> : null}
{children}
</StyledDialogContent>
</StyledDialogOverlay>
)
)}
</Fragment>
);
}
Example #7
Source File: index.tsx From sushiswap-exchange with GNU General Public License v3.0 | 5 votes |
export default function Modal({
isOpen,
onDismiss,
minHeight = false,
maxHeight = 50,
initialFocusRef,
children
}: ModalProps) {
const fadeTransition = useTransition(isOpen, null, {
config: { duration: 200 },
from: { opacity: 0 },
enter: { opacity: 1 },
leave: { opacity: 0 }
})
const [{ y }, set] = useSpring(() => ({ y: 0, config: { mass: 1, tension: 210, friction: 20 } }))
const bind = useGesture({
onDrag: state => {
set({
y: state.down ? state.movement[1] : 0
})
if (state.movement[1] > 300 || (state.velocity > 3 && state.direction[1] > 0)) {
onDismiss()
}
}
})
return (
<>
{fadeTransition.map(
({ item, key, props }) =>
item && (
<StyledDialogOverlay key={key} style={props} onDismiss={onDismiss} initialFocusRef={initialFocusRef}>
<StyledDialogContent
{...(isMobile
? {
...bind(),
style: { transform: y.interpolate(y => `translateY(${y > 0 ? y : 0}px)`) }
}
: {})}
aria-label="dialog content"
minHeight={minHeight}
maxHeight={maxHeight}
mobile={isMobile}
>
{/* prevents the automatic focusing of inputs on mobile by the reach dialog */}
{!initialFocusRef && isMobile ? <div tabIndex={1} /> : null}
{children}
</StyledDialogContent>
</StyledDialogOverlay>
)
)}
</>
)
}
Example #8
Source File: index.tsx From luaswap-interface with GNU General Public License v3.0 | 5 votes |
export default function Modal({
isOpen,
onDismiss = () => {},
minHeight = false,
maxHeight = 90,
initialFocusRef,
children
}: ModalProps) {
const fadeTransition = useTransition(isOpen, null, {
config: { duration: 200 },
from: { opacity: 0 },
enter: { opacity: 1 },
leave: { opacity: 0 }
})
const [{ y }, set] = useSpring(() => ({ y: 0, config: { mass: 1, tension: 210, friction: 20 } }))
const bind = useGesture({
onDrag: state => {
set({
y: state.down ? state.movement[1] : 0
})
if (state.movement[1] > 300 || (state.velocity > 3 && state.direction[1] > 0)) {
onDismiss()
}
}
})
return (
<>
{fadeTransition.map(
({ item, key, props }) =>
item && (
<StyledDialogOverlay key={key} style={props} onDismiss={onDismiss} initialFocusRef={initialFocusRef}>
<StyledDialogContent
{...(isMobile
? {
...bind(),
style: { transform: y.interpolate(y => `translateY(${y > 0 ? y : 0}px)`) }
}
: {})}
aria-label="dialog content"
minHeight={minHeight}
maxHeight={maxHeight}
mobile={isMobile}
>
{/* prevents the automatic focusing of inputs on mobile by the reach dialog */}
{!initialFocusRef && isMobile ? <div tabIndex={1} /> : null}
{children}
</StyledDialogContent>
</StyledDialogOverlay>
)
)}
</>
)
}
Example #9
Source File: Base.tsx From binaural-meet with GNU General Public License v3.0 | 4 votes |
Base: React.FC<MapProps> = (props: MapProps) => { const {map, participants} = props.stores const matrix = useObserver(() => map.matrix) const container = useRef<HTMLDivElement>(null) const outer = useRef<HTMLDivElement>(null) function offset():[number, number] { return map.offset } const thirdPersonView = useObserver(() => participants.local.thirdPersonView) const memRef = useRef<BaseMember>(new BaseMember()) const mem = memRef.current const center = transformPoint2D(matrix, participants.local.pose.position) if (thirdPersonView !== mem.prebThirdPersonView) { mem.prebThirdPersonView = thirdPersonView if (thirdPersonView) { const mapRot = radian2Degree(extractRotation(matrix)) if (mapRot) { const newMatrix = rotateMap(-mapRot, center) map.setCommittedMatrix(newMatrix) } }else { const avatarRot = participants.local.pose.orientation const mapRot = radian2Degree(extractRotation(matrix)) if (avatarRot + mapRot) { const newMatrix = rotateMap(-(avatarRot + mapRot), center) map.setCommittedMatrix(newMatrix) } } } // utility function rotateMap(degree:number, center:[number, number]) { const changeMatrix = (new DOMMatrix()).rotateSelf(0, 0, degree) const newMatrix = transfromAt(center, changeMatrix, matrix) map.setMatrix(newMatrix) return newMatrix } // Mouse and touch operations ---------------------------------------------- const MOUSE_LEFT = 1 const MOUSE_RIGHT = 2 // zoom by scrollwheel function wheelHandler(event:React.WheelEvent) { if (!event.ctrlKey) { /* // translate map const diff = mulV2(0.2, rotateVector2D(matrix.inverse(), [event.deltaX, event.deltaY])) const newMatrix = matrix.translate(-diff[0], -diff[1]) map.setMatrix(newMatrix)*/ // zoom map let scale = Math.pow(1.2, event.deltaY / 100) scale = limitScale(extractScaleX(map.matrix), scale) // console.log(`zoom scale:${scale}`) if (scale === 1){ return } // console.log(`Wheel: ${movement} scale=${scale}`) const newMatrix = map.matrix.scale(scale, scale, 1, ...transformPoint2D(map.matrix.inverse(), map.mouse)) map.setMatrix(newMatrix) map.setCommittedMatrix(newMatrix) } } function moveParticipant(move: boolean, givenTarget?:[number,number]) { const local = participants.local let target = givenTarget if (!target){ target = map.mouseOnMap } const diff = subV2(target, local.pose.position) if (normV(diff) > (givenTarget ? PARTICIPANT_SIZE*2 : PARTICIPANT_SIZE / 2)) { const dir = mulV2(20 / normV(diff), diff) local.pose.orientation = Math.atan2(dir[0], -dir[1]) * 180 / Math.PI if (move) { local.pose.position = addV2(local.pose.position, dir) } local.savePhysicsToStorage(false) } } /* function moveParticipantPeriodically(move: boolean, target?:[number,number]) { moveParticipant(move, target) const TIMER_INTERVAL = move ? 33 : 300 setTimeout(() => { if (mem.mouseDown) { moveParticipantPeriodically(true) } }, TIMER_INTERVAL) // move to mouse position }*/ const bind = useGesture( { onDragStart: ({buttons}) => { document.body.focus() mem.dragging = true mem.mouseDown = true // console.log('Base StartDrag:') if (buttons === MOUSE_LEFT) { // It seems facing and moving to the mouse cursor induce unintended movements of the avatar. // move participant to mouse position // moveParticipantPeriodically(false) } }, onDrag: ({down, delta, xy, buttons}) => { if (delta[0] || delta[1]) { mem.mouseDown = false } // if (map.keyInputUsers.size) { return } if (mem.dragging && down && outer.current) { if (!thirdPersonView && buttons === MOUSE_RIGHT) { // right mouse drag - rotate map const center = transformPoint2D(matrix, participants.local.pose.position) const target:[number, number] = addV2(xy, offset()) const radius1 = subV2(target, center) const radius2 = subV2(radius1, delta) const cosAngle = crossProduct(radius1, radius2) / (vectorLength(radius1) * vectorLength(radius2)) const flag = crossProduct(rotate90ClockWise(radius1), delta) > 0 ? -1 : 1 const angle = Math.acos(cosAngle) * flag if (isNaN(angle)) { // due to accuracy, angle might be NaN when cosAngle is larger than 1 return // no need to update matrix } const newMatrix = rotateMap(radian2Degree(angle), center) participants.local.pose.orientation = -radian2Degree(extractRotation(newMatrix)) } else { // left mouse drag or touch screen drag - translate map const diff = rotateVector2D(matrix.inverse(), delta) const newMatrix = matrix.translate(...diff) map.setMatrix(newMatrix) // rotate and direct participant to the mouse position. if (delta[0] || delta[1]){ moveParticipant(false, map.centerOnMap) } //console.log('Base onDrag:', delta) } } }, onDragEnd: () => { if (matrix.toString() !== map.committedMatrix.toString()) { map.setCommittedMatrix(matrix) moveParticipant(false, map.centerOnMap) //console.log(`Base onDragEnd: (${map.centerOnMap})`) } mem.dragging = false mem.mouseDown = false }, onPinch: ({da: [d, a], origin, event, memo}) => { if (memo === undefined) { return [d, a] } const [md, ma] = memo const center = addV2(origin as [number, number], offset()) const MIN_D = 10 let scale = d > MIN_D ? d / md : d < -MIN_D ? md / d : (1 + (d - md) / MIN_D) //console.log(`Pinch: da:${[d, a]} origin:${origin} memo:${memo} scale:${scale}`) scale = limitScale(extractScaleX(matrix), scale) const changeMatrix = thirdPersonView ? (new DOMMatrix()).scaleSelf(scale, scale, 1) : (new DOMMatrix()).scaleSelf(scale, scale, 1).rotateSelf(0, 0, a - ma) const newMatrix = transfromAt(center, changeMatrix, matrix) map.setMatrix(newMatrix) if (!thirdPersonView) { participants.local.pose.orientation = -radian2Degree(extractRotation(newMatrix)) } return [d, a] }, onPinchEnd: () => map.setCommittedMatrix(matrix), onMove:({xy}) => { map.setMouse(xy) participants.local.mouse.position = Object.assign({}, map.mouseOnMap) }, onTouchStart:(ev) => { map.setMouse([ev.touches[0].clientX, ev.touches[0].clientY]) participants.local.mouse.position = Object.assign({}, map.mouseOnMap) }, }, { eventOptions:{passive:false}, // This prevents default zoom by browser when pinch. }, ) // setClientRect of the outer. useEffect( () => { onResizeOuter() }, // eslint-disable-next-line react-hooks/exhaustive-deps [], ) // Prevent browser's zoom useEffect( () => { function topWindowHandler(event:WheelEvent) { //console.log(event) if (event.ctrlKey) { if (window.visualViewport && window.visualViewport.scale > 1){ if (event.deltaY < 0){ event.preventDefault() // console.log('prevent', event.deltaY) }else{ // console.log('through', event.deltaY) } }else{ event.preventDefault() } // console.log('CTRL + mouse wheel = zoom prevented.', event) } } window.document.body.addEventListener('wheel', topWindowHandler, {passive: false}) return () => { window.document.body.removeEventListener('wheel', topWindowHandler) } }, [], ) /* // This has no effect for iframe and other cases can be handled by onMove. So this is useless // preview mouse move on outer useEffect( () => { function handler(ev:MouseEvent) { map.setMouse([ev.clientX, ev.clientY]) } if (outer.current) { outer.current.addEventListener('mousemove', handler, {capture:true}) } return () => { if (outer.current) { outer.current.removeEventListener('mousemove', handler) } } }, [outer]) */ // Event handlers when use scroll ---------------------------------------------- // Move to center when root div is created. /* useEffect( () => { if (outer.current) { const elem = outer.current console.log('useEffect[outer] called') elem.scrollTo((MAP_SIZE - elem.clientWidth) * HALF, (MAP_SIZE - elem.clientHeight) * HALF) } }, [outer], ) if (!showScrollbar) { const elem = outer.current if (elem) { elem.scrollTo((MAP_SIZE - elem.clientWidth) * HALF, (MAP_SIZE - elem.clientHeight) * HALF) } } */ // scroll range /* useEffect( () => { const orgMat = new DOMMatrix(matrix.toString()) setMatrix(orgMat) }, [outer], ) */ // update offset const onResizeOuter = useRef( () => { if (outer.current) { let cur = outer.current as HTMLElement let offsetLeft = 0 while (cur) { offsetLeft += cur.offsetLeft cur = cur.offsetParent as HTMLElement } // console.log(`sc:[${outer.current.clientWidth}, ${outer.current.clientHeight}] left:${offsetLeft}`) map.setScreenSize([outer.current.clientWidth, outer.current.clientHeight]) map.setLeft(offsetLeft) // map.setOffset([outer.current.scrollLeft, outer.current.scrollTop]) // when use scroll } } ).current const styleProps: StyleProps = { matrix, } const classes = useStyles(styleProps) return ( <div className={classes.root} ref={outer} {...bind()}> <ResizeObserver onResize = { onResizeOuter } /> <div className={classes.center} onWheel={wheelHandler}> <div id="map-transform" className={classes.transform} ref={container}> {props.children} </div> </div> </div> ) }
Example #10
Source File: RndContent.tsx From binaural-meet with GNU General Public License v3.0 | 4 votes |
RndContent: React.FC<RndContentProps> = (props:RndContentProps) => {
/*
function rotateG2C(gv: [number, number]) {
const lv = mapData.rotateFromWindow(gv)
const cv = rotateVector2DByDegree(-pose.orientation, lv)
// console.log('rotateG2C called ori', pose.orientation, ' tran:', transform.rotation)
return cv
}*/
/*
function rotateG2L(gv: [number, number]) {
const lv = transform.rotateG2L(gv)
return lv
}
function rotateC2G(cv: [number, number]) {
const lv = rotateVector2DByDegree(pose.orientation, cv)
const gv = transform.rotateL2G(lv)
return gv
}*/
// states
const [pose, setPose] = useState(props.content.pose) // pose of content
const [size, setSize] = useState(props.content.size) // size of content
const [resizeBase, setResizeBase] = useState(size) // size when resize start
const [resizeBasePos, setResizeBasePos] = useState(pose.position) // position when resize start
const [showTitle, setShowTitle] = useState(!props.autoHideTitle || !props.content.pinned)
const [showForm, setShowForm] = useState(false)
const [preciseOrientation, setPreciseOrientation] = useState(pose.orientation)
const [dragging, setDragging] = useState(false)
const rnd = useRef<Rnd>(null) // ref to rnd to update position and size
const {contents, map} = props.stores
const editing = useObserver(() => contents.editing === props.content.id)
const zoomed = useObserver(() => map.zoomed)
function setEditing(flag: boolean) { contents.setEditing(flag ? props.content.id : '') }
const memberRef = useRef<RndContentMember>(new RndContentMember())
const member = memberRef.current
useEffect( // update pose
()=> {
member.dragCanceled = true
if (!_.isEqual(size, props.content.size)) {
setSize(_.cloneDeep(props.content.size))
}
if (!_.isEqual(pose, props.content.pose)) {
setPose(_.cloneDeep(props.content.pose))
}
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[props.content],
)
function setPoseAndSizeToRnd(){
if (rnd.current) { rnd.current.resizable.orientation = pose.orientation + map.rotation }
const titleHeight = showTitle ? TITLE_HEIGHT : 0
rnd.current?.updatePosition({x:pose.position[0], y:pose.position[1] - titleHeight})
rnd.current?.updateSize({width:size[0], height:size[1] + titleHeight})
}
useLayoutEffect( // reflect pose etc. to rnd size
() => {
setPoseAndSizeToRnd()
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[pose, size, showTitle, map.rotation],
)
// handlers
function stop(ev:MouseOrTouch|React.PointerEvent) {
ev.stopPropagation()
ev.preventDefault()
}
function onClickShare(evt: MouseOrTouch) {
stop(evt)
props.onShare?.call(null, evt)
}
function onClickClose(evt: MouseOrTouch) {
stop(evt)
props.onClose?.call(null, evt)
}
function onClickEdit(evt: MouseOrTouch) {
stop(evt)
setEditing(!editing)
}
function onClickMoveToTop(evt: MouseOrTouch) {
stop(evt)
moveContentToTop(props.content)
props.updateAndSend(props.content)
}
function onClickMoveToBottom(evt: MouseOrTouch) {
stop(evt)
moveContentToBottom(props.content)
props.updateAndSend(props.content)
}
function onClickPin(evt: MouseOrTouch) {
stop(evt)
props.content.pinned = !props.content.pinned
props.updateAndSend(props.content)
}
function onClickCopy(evt: MouseOrTouch){
stop(evt)
copyContentToClipboard(props.content)
}
function onClickMaximize(evt: MouseOrTouch){
stop(evt)
if (map.zoomed){
map.restoreZoom()
}else{
map.zoomTo(props.content)
}
}
function onClickMore(evt: MouseOrTouch){
stop(evt)
map.keyInputUsers.add('contentForm')
setShowForm(true)
}
function onCloseForm(){
setShowForm(false)
if (props.content.pinned){
setShowTitle(false)
}
map.keyInputUsers.delete('contentForm')
props.updateAndSend(props.content)
}
function updateHandler() {
if (JSON.stringify(pose) !== JSON.stringify(props.content.pose) ||
JSON.stringify(size) !== JSON.stringify(props.content.size)) {
props.content.size = [...size] // Must be new object to compare the pose or size object.
props.content.pose = {...pose} // Must be new object to compare the pose or size object.
props.updateAndSend(props.content)
}
}
// drag for title area
function dragHandler(delta:[number, number], buttons:number, event:any) {
if (member.dragCanceled){ return }
const ROTATION_IN_DEGREE = 360
const ROTATION_STEP = 15
if (buttons === MOUSE_RIGHT) {
setPreciseOrientation((preciseOrientation + delta[0] + delta[1]) % ROTATION_IN_DEGREE)
let newOri
if (event?.shiftKey || event?.ctrlKey) {
newOri = preciseOrientation
}else {
newOri = preciseOrientation - preciseOrientation % ROTATION_STEP
}
// mat.translateSelf(...addV2(props.pose.position, mulV(0.5, size)))
const CENTER_IN_RATIO = 0.5
const center = addV2(pose.position, mulV(CENTER_IN_RATIO, size))
pose.position = addV2(pose.position,
subV2(rotateVector2DByDegree(pose.orientation - newOri, center), center))
pose.orientation = newOri
setPose(Object.assign({}, pose))
}else {
const lv = map.rotateFromWindow(delta)
const cv = rotateVector2DByDegree(-pose.orientation, lv)
pose.position = addV2(pose.position, cv)
setPose(Object.assign({}, pose))
}
}
const isFixed = (props.autoHideTitle && props.content.pinned)
const handlerForTitle:UserHandlersPartial = {
onDoubleClick: (evt)=>{
if (isContentEditable(props.content)){
stop(evt)
setEditing(!editing)
}
},
onDrag: ({down, delta, event, xy, buttons}) => {
// console.log('onDragTitle:', delta)
if (isFixed) { return }
event?.stopPropagation()
if (down) {
// event?.preventDefault()
dragHandler(delta, buttons, event)
}
},
onDragStart: ({event, currentTarget, delta, buttons}) => { // to detect click
// console.log(`dragStart delta=${delta} buttons=${buttons}`)
setDragging(true)
member.buttons = buttons
member.dragCanceled = false
if (currentTarget instanceof Element && event instanceof PointerEvent){
currentTarget.setPointerCapture(event?.pointerId)
}
},
onDragEnd: ({event, currentTarget, delta, buttons}) => {
// console.log(`dragEnd delta=${delta} buttons=${buttons}`)
setDragging(false)
if (!member.dragCanceled){ updateHandler() }
member.dragCanceled = false
if (currentTarget instanceof Element && event instanceof PointerEvent){
currentTarget.releasePointerCapture(event?.pointerId)
}
if (!map.keyInputUsers.size && member.buttons === MOUSE_RIGHT){ // right click
setShowForm(true)
map.keyInputUsers.add('contentForm')
}
member.buttons = 0
},
onPointerUp: (arg) => { if(editing) {arg.stopPropagation()} },
onPointerDown: (arg) => { if(editing) {arg.stopPropagation()} },
onMouseUp: (arg) => { if(editing) {arg.stopPropagation()} },
onMouseDown: (arg) => { if(editing) {arg.stopPropagation()} },
onTouchStart: (arg) => { if(editing) {arg.stopPropagation() }},
onTouchEnd: (arg) => { if(editing) {arg.stopPropagation()} },
}
const handlerForContent:UserHandlersPartial = Object.assign({}, handlerForTitle)
handlerForContent.onDrag = (args: FullGestureState<'drag'>) => {
// console.log('onDragBody:', args.delta)
if (isFixed || map.keyInputUsers.has(props.content.id)) { return }
handlerForTitle.onDrag?.call(this, args)
}
function onResizeStart(evt:React.MouseEvent<HTMLDivElement> | React.TouchEvent<HTMLDivElement>){
member.dragCanceled = false
evt.stopPropagation(); evt.preventDefault()
setResizeBase(size)
setResizeBasePos(pose.position)
}
function onResizeStop(){
if (!member.dragCanceled){ updateHandler() }
member.dragCanceled = false
}
function onResize(evt:MouseEvent | TouchEvent, dir: any, elem:HTMLDivElement, delta:any, pos:any) {
evt.stopPropagation(); evt.preventDefault()
// console.log(`dragcancel:${member.dragCanceled}`)
if (member.dragCanceled) {
setPoseAndSizeToRnd()
return
}
const scale = (extractScaleX(map.matrix) + extractScaleY(map.matrix)) / 2
const cd:[number, number] = [delta.width / scale, delta.height / scale]
// console.log('resize dir:', dir, ' delta:', delta, ' d:', d, ' pos:', pos)
if (dir === 'left' || dir === 'right') {
cd[1] = 0
}
if (dir === 'top' || dir === 'bottom') {
cd[0] = 0
}
let posChange = false
const deltaPos: [number, number] = [0, 0]
if (dir === 'left' || dir === 'topLeft' || dir === 'bottomLeft') {
deltaPos[0] = -cd[0]
posChange = posChange || cd[0] !== 0
}
if (dir === 'top' || dir === 'topLeft' || dir === 'topRight') {
deltaPos[1] = -cd[1]
posChange = posChange || cd[1] !== 0
}
if (posChange) {
pose.position = addV2(resizeBasePos, deltaPos)
setPose(Object.assign({}, pose))
//console.log(`setPose ${pose.position}`)
}
const newSize = addV2(resizeBase, cd)
if (props.content.originalSize[0]) {
const ratio = props.content.originalSize[0] / props.content.originalSize[1]
if (newSize[0] > ratio * newSize[1]) { newSize[0] = ratio * newSize[1] }
if (newSize[0] < ratio * newSize[1]) { newSize[1] = newSize[0] / ratio }
}
setSize(newSize)
}
const classes = useStyles({props, pose, size, showTitle, pinned:props.content.pinned, dragging, editing})
// console.log('render: TITLE_HEIGHT:', TITLE_HEIGHT)
const contentRef = React.useRef<HTMLDivElement>(null)
const formRef = React.useRef<HTMLDivElement>(null)
const gestureForContent = useGesture(handlerForContent)
const gestureForTitle = useGesture(handlerForTitle)
const theContent =
<div className={classes.rndContainer} {...gestureForContent()}>
<div className={classes.titlePosition} {...gestureForTitle() /* title can be placed out of Rnd */}>
<div className={classes.titleContainer}
onMouseEnter = {() => { if (props.autoHideTitle) { setShowTitle(true) } }}
onMouseLeave = {() => {
if (props.autoHideTitle && !editing && props.content.pinned) { setShowTitle(false) } }}
onTouchStart = {() => {
if (props.autoHideTitle) {
if (!showTitle) {
setShowTitle(true)
}else if (props.content.pinned) {
setShowTitle(false)
}
}
}}
onContextMenu = {() => {
setShowForm(true)
map.keyInputUsers.add('contentForm')
}}
>
<div className={classes.type}>
{contentTypeIcons(props.content.type, TITLE_HEIGHT, TITLE_HEIGHT*1.1)}
</div>
<Tooltip placement="top" title={props.content.pinned ? t('ctUnpin') : t('ctPin')} >
<div className={classes.pin} onClick={onClickPin} onTouchStart={stop}>
<Icon icon={props.content.pinned ? pinIcon : pinOffIcon} height={TITLE_HEIGHT} width={TITLE_HEIGHT*1.1} />
</div></Tooltip>
<Tooltip placement="top" title={editButtonTip(editing, props.content)} >
<div className={classes.edit} onClick={onClickEdit} onTouchStart={stop}>
{
editing ? <DoneIcon style={{fontSize:TITLE_HEIGHT}} />
: <EditIcon style={{fontSize:TITLE_HEIGHT}} />}
</div>
</Tooltip>
{props.content.pinned ? undefined :
<Tooltip placement="top" title={t('ctMoveTop')} >
<div className={classes.titleButton} onClick={onClickMoveToTop}
onTouchStart={stop}><FlipToFrontIcon /></div></Tooltip>}
{props.content.pinned ? undefined :
<Tooltip placement="top" title={t('ctMoveBottom')} >
<div className={classes.titleButton} onClick={onClickMoveToBottom}
onTouchStart={stop}><FlipToBackIcon /></div></Tooltip>}
{/*(props.content.pinned || !canContentBeAWallpaper(props.content)) ? undefined :
<div className={classes.titleButton} onClick={onClickWallpaper}
onTouchStart={stop}>
<Tooltip placement="top" title={isContentWallpaper(props.content) ?
t('ctUnWallpaper') : t('ctWallpaper')}>
<div><WallpaperIcon />{isContentWallpaper(props.content) ?
<CloseRoundedIcon style={{marginLeft:'-1em'}} /> : undefined }</div>
</Tooltip>
</div> */}
<Tooltip placement="top" title={t('ctCopyToClipboard')} >
<div className={classes.titleButton} onClick={onClickCopy}
onTouchStart={stop}>
<Icon icon={clipboardCopy} height={TITLE_HEIGHT}/>
</div>
</Tooltip>
{isContentMaximizable(props.content) ?
<Tooltip placement="top" title={zoomed ? t('ctUnMaximize') : t('ctMaximize')} >
<div className={classes.titleButton} onClick={onClickMaximize}
onTouchStart={stop}>
<Icon icon={zoomed ? minimizeIcon: maximizeIcon} height={TITLE_HEIGHT}/>
</div>
</Tooltip> : undefined}
<div className={classes.titleButton} onClick={onClickMore} onTouchStart={stop} ref={formRef}>
<MoreHorizIcon />
</div>
<SharedContentForm open={showForm} {...props} close={onCloseForm}
anchorEl={contentRef.current} anchorOrigin={{vertical:'top', horizontal:'right'}}
/>
<div className={classes.note} onClick={onClickShare} onTouchStart={stop}>Share</div>
{props.content.playback ? <div className={classes.close} ><PlayArrowIcon htmlColor="#0C0" /></div> :
(props.content.pinned || isContentWallpaper(props.content)) ? undefined :
<div className={classes.close} onClick={onClickClose} onTouchStart={stop}>
<CloseRoundedIcon /></div>}
</div>
</div>
<div className={classes.content} ref={contentRef}
onFocus={()=>{
if (doseContentEditingUseKeyinput(props.content) && editing){
map.keyInputUsers.add(props.content.id)
}
}}
onBlur={()=>{
if (doseContentEditingUseKeyinput(props.content) && editing){
map.keyInputUsers.delete(props.content.id)
}
}}
>
<Content {...props}/>
</div>
</div>
// console.log('Rnd rendered.')
return (
<div className={classes.container} style={{zIndex:props.content.zIndex}} onContextMenu={
(evt) => {
evt.stopPropagation()
evt.preventDefault()
}
}>
<Rnd className={classes.rndCls} enableResizing={isFixed ? resizeDisable : resizeEnable}
disableDragging={isFixed} ref={rnd}
onResizeStart = {onResizeStart}
onResize = {onResize}
onResizeStop = {onResizeStop}
>
{theContent}
</Rnd>
</div >
)
}
Example #11
Source File: item.component.tsx From react-sortful with MIT License | 4 votes |
Item = <T extends ItemIdentifier>(props: Props<T>) => { const listContext = React.useContext(ListContext); const groupContext = React.useContext(GroupContext); const wrapperElementRef = React.useRef<HTMLDivElement>(null); const ancestorIdentifiers = [...groupContext.ancestorIdentifiers, props.identifier]; const isGroup = props.isGroup ?? false; const isLocked = (listContext.isDisabled || props.isLocked) ?? false; const isLonley = props.isLonely ?? false; const isUsedCustomDragHandlers = props.isUsedCustomDragHandlers ?? false; // Registers an identifier to the group context. const childIdentifiersRef = React.useRef<Set<ItemIdentifier>>(new Set()); React.useEffect(() => { groupContext.childIdentifiersRef.current.add(props.identifier); return () => { groupContext.childIdentifiersRef.current.delete(props.identifier); }; }, []); // Clears timers. const clearingDraggingNodeTimeoutIdRef = React.useRef<number>(); React.useEffect( () => () => { window.clearTimeout(clearingDraggingNodeTimeoutIdRef.current); }, [], ); const onDragStart = React.useCallback(() => { const element = wrapperElementRef.current; if (element == undefined) return; setBodyStyle(document.body, listContext.draggingCursorStyle); initializeGhostElementStyle( element, listContext.ghostWrapperElementRef.current ?? undefined, listContext.itemSpacing, listContext.direction, ); // Sets contexts to values. const nodeMeta = getNodeMeta(element, props.identifier, groupContext.identifier, ancestorIdentifiers, props.index, isGroup); listContext.setDraggingNodeMeta(nodeMeta); // Calls callbacks. listContext.onDragStart?.({ identifier: nodeMeta.identifier, groupIdentifier: nodeMeta.groupIdentifier, index: nodeMeta.index, isGroup: nodeMeta.isGroup, }); }, [ listContext.itemSpacing, listContext.direction, listContext.onDragStart, listContext.draggingCursorStyle, groupContext.identifier, props.identifier, props.index, ancestorIdentifiers, isGroup, ]); const onDrag = React.useCallback((isDown: boolean, absoluteXY: [number, number]) => { if (!isDown) return; moveGhostElement(listContext.ghostWrapperElementRef.current ?? undefined, absoluteXY); }, []); const onDragEnd = React.useCallback(() => { clearBodyStyle(document.body); clearGhostElementStyle(listContext.ghostWrapperElementRef.current ?? undefined); // Calls callbacks. const destinationMeta = listContext.destinationMetaRef.current; listContext.onDragEnd({ identifier: props.identifier, groupIdentifier: groupContext.identifier, index: props.index, isGroup, nextGroupIdentifier: destinationMeta != undefined ? destinationMeta.groupIdentifier : groupContext.identifier, nextIndex: destinationMeta != undefined ? destinationMeta.index : props.index, }); // Resets context values. listContext.setDraggingNodeMeta(undefined); listContext.setIsVisibleDropLineElement(false); listContext.setStackedGroupIdentifier(undefined); listContext.hoveredNodeMetaRef.current = undefined; listContext.destinationMetaRef.current = undefined; }, [listContext.onDragEnd, groupContext.identifier, props.identifier, props.index, isGroup]); const onHover = React.useCallback( (element: HTMLElement) => { // Initialize if the dragging item is this item or an ancestor group of this item. const draggingNodeMeta = listContext.draggingNodeMeta; const isNeededInitialization = draggingNodeMeta == undefined || props.identifier === draggingNodeMeta.identifier || checkIsAncestorItem(draggingNodeMeta.identifier, ancestorIdentifiers); if (isNeededInitialization) { listContext.setIsVisibleDropLineElement(false); listContext.hoveredNodeMetaRef.current = undefined; listContext.destinationMetaRef.current = undefined; return; } listContext.setIsVisibleDropLineElement(true); listContext.hoveredNodeMetaRef.current = getNodeMeta( element, props.identifier, groupContext.identifier, ancestorIdentifiers, props.index, isGroup, ); }, [listContext.draggingNodeMeta, groupContext.identifier, props.identifier, props.index, ancestorIdentifiers, isGroup], ); const onMoveForStackableGroup = React.useCallback( <T extends ItemIdentifier>(hoveredNodeMeta: NodeMeta<T>) => { // Sets contexts to values. listContext.setIsVisibleDropLineElement(false); listContext.setStackedGroupIdentifier(props.identifier); listContext.destinationMetaRef.current = { groupIdentifier: props.identifier, index: undefined, }; // Calls callbacks. listContext.onStackGroup?.({ identifier: props.identifier, groupIdentifier: groupContext.identifier, index: props.index, isGroup, nextGroupIdentifier: hoveredNodeMeta.identifier, }); }, [listContext.stackableAreaThreshold, listContext.onStackGroup, groupContext.identifier, props.identifier, props.index], ); const onMoveForItems = React.useCallback( (draggingNodeMeta: NodeMeta<T>, hoveredNodeMeta: NodeMeta<T>, absoluteXY: [number, number]) => { if (isLonley) { listContext.setIsVisibleDropLineElement(false); listContext.destinationMetaRef.current = undefined; return; } if (draggingNodeMeta.index !== hoveredNodeMeta.index) listContext.setIsVisibleDropLineElement(true); const dropLineElement = listContext.dropLineElementRef.current ?? undefined; setDropLineElementStyle(dropLineElement, absoluteXY, hoveredNodeMeta, listContext.direction); // Calculates the next index. const dropLineDirection = getDropLineDirectionFromXY(absoluteXY, hoveredNodeMeta, listContext.direction); const nextIndex = getDropLinePositionItemIndex( dropLineDirection, draggingNodeMeta.index, draggingNodeMeta.groupIdentifier, hoveredNodeMeta.index, hoveredNodeMeta.groupIdentifier, ); // Calls callbacks if needed. const destinationMeta = listContext.destinationMetaRef.current; const isComeFromStackedGroup = destinationMeta != undefined && destinationMeta.groupIdentifier != undefined && destinationMeta.index == undefined; if (isComeFromStackedGroup) { listContext.onStackGroup?.({ identifier: props.identifier, groupIdentifier: groupContext.identifier, index: props.index, isGroup, nextGroupIdentifier: undefined, }); } // Sets contexts to values. listContext.setStackedGroupIdentifier(undefined); listContext.destinationMetaRef.current = { groupIdentifier: groupContext.identifier, index: nextIndex }; }, [ listContext.direction, listContext.onStackGroup, groupContext.identifier, props.identifier, props.index, isGroup, isLonley, ], ); const onMove = React.useCallback( (absoluteXY: [number, number]) => { const draggingNodeMeta = listContext.draggingNodeMeta; if (draggingNodeMeta == undefined) return; const hoveredNodeMeta = listContext.hoveredNodeMetaRef.current; if (hoveredNodeMeta == undefined) return; const hasNoItems = childIdentifiersRef.current.size === 0; if ( isGroup && hasNoItems && checkIsInStackableArea(absoluteXY, hoveredNodeMeta, listContext.stackableAreaThreshold, listContext.direction) ) { onMoveForStackableGroup(hoveredNodeMeta); } else { onMoveForItems(draggingNodeMeta, hoveredNodeMeta, absoluteXY); } }, [listContext.draggingNodeMeta, listContext.direction, onMoveForStackableGroup, onMoveForItems, isGroup], ); const onLeave = React.useCallback(() => { if (listContext.draggingNodeMeta == undefined) return; // Clears a dragging node after 50ms in order to prevent setting and clearing at the same time. window.clearTimeout(clearingDraggingNodeTimeoutIdRef.current); clearingDraggingNodeTimeoutIdRef.current = window.setTimeout(() => { if (listContext.hoveredNodeMetaRef.current?.identifier !== props.identifier) return; listContext.setIsVisibleDropLineElement(false); listContext.setStackedGroupIdentifier(undefined); listContext.hoveredNodeMetaRef.current = undefined; listContext.destinationMetaRef.current = undefined; }, 50); }, [listContext.draggingNodeMeta, props.identifier]); const binder = useGesture({ onHover: ({ event }) => { if (listContext.draggingNodeMeta == undefined) return; const element = event?.currentTarget; if (!(element instanceof HTMLElement)) return; event?.stopPropagation(); onHover(element); }, onMove: ({ xy }) => { if (listContext.draggingNodeMeta == undefined) return; // Skips if this item is an ancestor group of the dragging item. const hasItems = childIdentifiersRef.current.size > 0; const hoveredNodeAncestors = listContext.hoveredNodeMetaRef.current?.ancestorIdentifiers ?? []; if (hasItems && checkIsAncestorItem(props.identifier, hoveredNodeAncestors)) return; if (props.identifier === listContext.draggingNodeMeta.identifier) return; // Skips if the dragging item is an ancestor group of this item. if (checkIsAncestorItem(listContext.draggingNodeMeta.identifier, ancestorIdentifiers)) return; onMove(xy); }, onPointerLeave: onLeave, }); const dragHandlers: React.ContextType<typeof ItemContext>["dragHandlers"] = { onDragStart, onDrag, onDragEnd }; const draggableBinder = useGesture({ onDragStart: (state: any) => { if (isLocked) return; const event: React.SyntheticEvent = state.event; event.persist(); event.stopPropagation(); dragHandlers.onDragStart(); }, onDrag: ({ down, movement }) => { if (isLocked) return; dragHandlers.onDrag(down, movement); }, onDragEnd: () => { if (isLocked) return; dragHandlers.onDragEnd(); }, }); const contentElement = React.useMemo((): JSX.Element => { const draggingNodeMeta = listContext.draggingNodeMeta; const isDragging = draggingNodeMeta != undefined && props.identifier === draggingNodeMeta.identifier; const { renderPlaceholder, renderStackedGroup, itemSpacing, direction } = listContext; const rendererMeta: Omit<PlaceholderRendererMeta<any>, "isGroup"> | StackedGroupRendererMeta<any> = { identifier: props.identifier, groupIdentifier: groupContext.identifier, index: props.index, }; let children = props.children; if (isDragging && renderPlaceholder != undefined) { const style = getPlaceholderElementStyle(draggingNodeMeta, itemSpacing, direction); children = renderPlaceholder({ style }, { ...rendererMeta, isGroup }); } if (listContext.stackedGroupIdentifier === props.identifier && renderStackedGroup != undefined) { const style = getStackedGroupElementStyle(listContext.hoveredNodeMetaRef.current, itemSpacing, direction); children = renderStackedGroup({ style }, rendererMeta); } const padding: [string, string] = ["0", "0"]; if (direction === "vertical") padding[0] = `${itemSpacing / 2}px`; if (direction === "horizontal") padding[1] = `${itemSpacing / 2}px`; return ( <div ref={wrapperElementRef} style={{ boxSizing: "border-box", position: "static", padding: padding.join(" ") }} {...binder()} {...(isUsedCustomDragHandlers ? {} : draggableBinder())} > {children} </div> ); }, [ listContext.draggingNodeMeta, listContext.renderPlaceholder, listContext.renderStackedGroup, listContext.stackedGroupIdentifier, listContext.itemSpacing, listContext.direction, groupContext.identifier, props.identifier, props.children, props.index, isGroup, isUsedCustomDragHandlers, binder, draggableBinder, ]); if (!isGroup) return <ItemContext.Provider value={{ isLocked, dragHandlers }}>{contentElement}</ItemContext.Provider>; return ( <GroupContext.Provider value={{ identifier: props.identifier, ancestorIdentifiers, childIdentifiersRef }}> <ItemContext.Provider value={{ isLocked, dragHandlers }}>{contentElement}</ItemContext.Provider> </GroupContext.Provider> ); }