Adjusts small example, makes inputs unique to each instance
This commit is contained in:
parent
2653f396bf
commit
8154ed5a2a
25 changed files with 190 additions and 81 deletions
|
@ -48,6 +48,7 @@
|
||||||
"lerna": "^3.15.0",
|
"lerna": "^3.15.0",
|
||||||
"react": "^17.0.2",
|
"react": "^17.0.2",
|
||||||
"react-dom": "^17.0.2",
|
"react-dom": "^17.0.2",
|
||||||
|
"resize-observer-polyfill": "^1.5.1",
|
||||||
"ts-jest": "^27.0.5",
|
"ts-jest": "^27.0.5",
|
||||||
"tslib": "^2.3.0",
|
"tslib": "^2.3.0",
|
||||||
"typedoc": "^0.21.9",
|
"typedoc": "^0.21.9",
|
||||||
|
@ -114,4 +115,4 @@
|
||||||
"\\+(.*)": "<rootDir>/packages/core/src/$1"
|
"\\+(.*)": "<rootDir>/packages/core/src/$1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,7 @@ import { ErrorBoundary } from '+components/error-boundary'
|
||||||
import { Brush } from '+components/brush'
|
import { Brush } from '+components/brush'
|
||||||
import { Defs } from '+components/defs'
|
import { Defs } from '+components/defs'
|
||||||
import { Page } from '+components/page'
|
import { Page } from '+components/page'
|
||||||
|
import { useResizeObserver } from '+hooks/useResizeObserver'
|
||||||
|
|
||||||
function resetError() {
|
function resetError() {
|
||||||
void null
|
void null
|
||||||
|
@ -35,10 +36,13 @@ export function Canvas<T extends TLShape>({
|
||||||
hideIndicators = false,
|
hideIndicators = false,
|
||||||
}: CanvasProps<T>): JSX.Element {
|
}: CanvasProps<T>): JSX.Element {
|
||||||
const rCanvas = React.useRef<SVGSVGElement>(null)
|
const rCanvas = React.useRef<SVGSVGElement>(null)
|
||||||
|
const rContainer = React.useRef<HTMLDivElement>(null)
|
||||||
|
|
||||||
const rGroup = useCameraCss(pageState)
|
const rGroup = useCameraCss(pageState)
|
||||||
|
|
||||||
useZoomEvents()
|
useResizeObserver(rCanvas)
|
||||||
|
|
||||||
|
useZoomEvents(rCanvas)
|
||||||
|
|
||||||
useSafariFocusOutFix()
|
useSafariFocusOutFix()
|
||||||
|
|
||||||
|
@ -47,7 +51,7 @@ export function Canvas<T extends TLShape>({
|
||||||
const events = useCanvasEvents()
|
const events = useCanvasEvents()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="tl-container">
|
<div className="tl-container" ref={rContainer}>
|
||||||
<svg id="canvas" className="tl-canvas" ref={rCanvas} {...events}>
|
<svg id="canvas" className="tl-canvas" ref={rCanvas} {...events}>
|
||||||
<ErrorBoundary FallbackComponent={ErrorFallback} onReset={resetError}>
|
<ErrorBoundary FallbackComponent={ErrorFallback} onReset={resetError}>
|
||||||
<Defs zoom={pageState.camera.zoom} />
|
<Defs zoom={pageState.camera.zoom} />
|
||||||
|
|
|
@ -10,6 +10,7 @@ import type {
|
||||||
TLBinding,
|
TLBinding,
|
||||||
} from '../../types'
|
} from '../../types'
|
||||||
import { Canvas } from '../canvas'
|
import { Canvas } from '../canvas'
|
||||||
|
import { Inputs } from '../../inputs'
|
||||||
import { useTLTheme, TLContext, TLContextType } from '../../hooks'
|
import { useTLTheme, TLContext, TLContextType } from '../../hooks'
|
||||||
|
|
||||||
export interface RendererProps<T extends TLShape, M extends Record<string, unknown>>
|
export interface RendererProps<T extends TLShape, M extends Record<string, unknown>>
|
||||||
|
@ -87,6 +88,7 @@ export function Renderer<T extends TLShape, M extends Record<string, unknown>>({
|
||||||
shapeUtils,
|
shapeUtils,
|
||||||
rScreenBounds,
|
rScreenBounds,
|
||||||
rPageState,
|
rPageState,
|
||||||
|
inputs: new Inputs(),
|
||||||
}))
|
}))
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import { inputs } from '+inputs'
|
|
||||||
import { useTLContext } from './useTLContext'
|
import { useTLContext } from './useTLContext'
|
||||||
|
|
||||||
export function useBoundsEvents() {
|
export function useBoundsEvents() {
|
||||||
const { callbacks } = useTLContext()
|
const { callbacks, inputs } = useTLContext()
|
||||||
|
|
||||||
const onPointerDown = React.useCallback(
|
const onPointerDown = React.useCallback(
|
||||||
(e: React.PointerEvent) => {
|
(e: React.PointerEvent) => {
|
||||||
|
@ -15,7 +14,7 @@ export function useBoundsEvents() {
|
||||||
callbacks.onPointBounds?.(info, e)
|
callbacks.onPointBounds?.(info, e)
|
||||||
callbacks.onPointerDown?.(info, e)
|
callbacks.onPointerDown?.(info, e)
|
||||||
},
|
},
|
||||||
[callbacks]
|
[callbacks, inputs]
|
||||||
)
|
)
|
||||||
|
|
||||||
const onPointerUp = React.useCallback(
|
const onPointerUp = React.useCallback(
|
||||||
|
@ -36,7 +35,7 @@ export function useBoundsEvents() {
|
||||||
callbacks.onReleaseBounds?.(info, e)
|
callbacks.onReleaseBounds?.(info, e)
|
||||||
callbacks.onPointerUp?.(info, e)
|
callbacks.onPointerUp?.(info, e)
|
||||||
},
|
},
|
||||||
[callbacks]
|
[callbacks, inputs]
|
||||||
)
|
)
|
||||||
|
|
||||||
const onPointerMove = React.useCallback(
|
const onPointerMove = React.useCallback(
|
||||||
|
@ -49,21 +48,21 @@ export function useBoundsEvents() {
|
||||||
const info = inputs.pointerMove(e, 'bounds')
|
const info = inputs.pointerMove(e, 'bounds')
|
||||||
callbacks.onPointerMove?.(info, e)
|
callbacks.onPointerMove?.(info, e)
|
||||||
},
|
},
|
||||||
[callbacks]
|
[callbacks, inputs]
|
||||||
)
|
)
|
||||||
|
|
||||||
const onPointerEnter = React.useCallback(
|
const onPointerEnter = React.useCallback(
|
||||||
(e: React.PointerEvent) => {
|
(e: React.PointerEvent) => {
|
||||||
callbacks.onHoverBounds?.(inputs.pointerEnter(e, 'bounds'), e)
|
callbacks.onHoverBounds?.(inputs.pointerEnter(e, 'bounds'), e)
|
||||||
},
|
},
|
||||||
[callbacks]
|
[callbacks, inputs]
|
||||||
)
|
)
|
||||||
|
|
||||||
const onPointerLeave = React.useCallback(
|
const onPointerLeave = React.useCallback(
|
||||||
(e: React.PointerEvent) => {
|
(e: React.PointerEvent) => {
|
||||||
callbacks.onUnhoverBounds?.(inputs.pointerEnter(e, 'bounds'), e)
|
callbacks.onUnhoverBounds?.(inputs.pointerEnter(e, 'bounds'), e)
|
||||||
},
|
},
|
||||||
[callbacks]
|
[callbacks, inputs]
|
||||||
)
|
)
|
||||||
|
|
||||||
const onTouchStart = React.useCallback((e: React.TouchEvent) => {
|
const onTouchStart = React.useCallback((e: React.TouchEvent) => {
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import { inputs } from '+inputs'
|
|
||||||
import type { TLBoundsEdge, TLBoundsCorner } from '+types'
|
import type { TLBoundsEdge, TLBoundsCorner } from '+types'
|
||||||
import { useTLContext } from './useTLContext'
|
import { useTLContext } from './useTLContext'
|
||||||
|
|
||||||
export function useBoundsHandleEvents(id: TLBoundsCorner | TLBoundsEdge | 'rotate') {
|
export function useBoundsHandleEvents(id: TLBoundsCorner | TLBoundsEdge | 'rotate') {
|
||||||
const { callbacks } = useTLContext()
|
const { callbacks, inputs } = useTLContext()
|
||||||
|
|
||||||
const onPointerDown = React.useCallback(
|
const onPointerDown = React.useCallback(
|
||||||
(e: React.PointerEvent) => {
|
(e: React.PointerEvent) => {
|
||||||
|
@ -16,7 +15,7 @@ export function useBoundsHandleEvents(id: TLBoundsCorner | TLBoundsEdge | 'rotat
|
||||||
callbacks.onPointBoundsHandle?.(info, e)
|
callbacks.onPointBoundsHandle?.(info, e)
|
||||||
callbacks.onPointerDown?.(info, e)
|
callbacks.onPointerDown?.(info, e)
|
||||||
},
|
},
|
||||||
[callbacks, id]
|
[inputs, callbacks, id]
|
||||||
)
|
)
|
||||||
|
|
||||||
const onPointerUp = React.useCallback(
|
const onPointerUp = React.useCallback(
|
||||||
|
@ -37,7 +36,7 @@ export function useBoundsHandleEvents(id: TLBoundsCorner | TLBoundsEdge | 'rotat
|
||||||
callbacks.onReleaseBoundsHandle?.(info, e)
|
callbacks.onReleaseBoundsHandle?.(info, e)
|
||||||
callbacks.onPointerUp?.(info, e)
|
callbacks.onPointerUp?.(info, e)
|
||||||
},
|
},
|
||||||
[callbacks, id]
|
[inputs, callbacks, id]
|
||||||
)
|
)
|
||||||
|
|
||||||
const onPointerMove = React.useCallback(
|
const onPointerMove = React.useCallback(
|
||||||
|
@ -48,21 +47,21 @@ export function useBoundsHandleEvents(id: TLBoundsCorner | TLBoundsEdge | 'rotat
|
||||||
const info = inputs.pointerMove(e, id)
|
const info = inputs.pointerMove(e, id)
|
||||||
callbacks.onPointerMove?.(info, e)
|
callbacks.onPointerMove?.(info, e)
|
||||||
},
|
},
|
||||||
[callbacks, id]
|
[inputs, callbacks, id]
|
||||||
)
|
)
|
||||||
|
|
||||||
const onPointerEnter = React.useCallback(
|
const onPointerEnter = React.useCallback(
|
||||||
(e: React.PointerEvent) => {
|
(e: React.PointerEvent) => {
|
||||||
callbacks.onHoverBoundsHandle?.(inputs.pointerEnter(e, id), e)
|
callbacks.onHoverBoundsHandle?.(inputs.pointerEnter(e, id), e)
|
||||||
},
|
},
|
||||||
[callbacks, id]
|
[inputs, callbacks, id]
|
||||||
)
|
)
|
||||||
|
|
||||||
const onPointerLeave = React.useCallback(
|
const onPointerLeave = React.useCallback(
|
||||||
(e: React.PointerEvent) => {
|
(e: React.PointerEvent) => {
|
||||||
callbacks.onUnhoverBoundsHandle?.(inputs.pointerEnter(e, id), e)
|
callbacks.onUnhoverBoundsHandle?.(inputs.pointerEnter(e, id), e)
|
||||||
},
|
},
|
||||||
[callbacks, id]
|
[inputs, callbacks, id]
|
||||||
)
|
)
|
||||||
|
|
||||||
const onTouchStart = React.useCallback((e: React.TouchEvent) => {
|
const onTouchStart = React.useCallback((e: React.TouchEvent) => {
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import { useTLContext } from './useTLContext'
|
import { useTLContext } from './useTLContext'
|
||||||
import { inputs } from '+inputs'
|
|
||||||
|
|
||||||
export function useCanvasEvents() {
|
export function useCanvasEvents() {
|
||||||
const { callbacks } = useTLContext()
|
const { callbacks, inputs } = useTLContext()
|
||||||
|
|
||||||
const onPointerDown = React.useCallback(
|
const onPointerDown = React.useCallback(
|
||||||
(e: React.PointerEvent) => {
|
(e: React.PointerEvent) => {
|
||||||
|
@ -16,7 +15,7 @@ export function useCanvasEvents() {
|
||||||
callbacks.onPointerDown?.(info, e)
|
callbacks.onPointerDown?.(info, e)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[callbacks]
|
[callbacks, inputs]
|
||||||
)
|
)
|
||||||
|
|
||||||
const onPointerMove = React.useCallback(
|
const onPointerMove = React.useCallback(
|
||||||
|
@ -28,7 +27,7 @@ export function useCanvasEvents() {
|
||||||
const info = inputs.pointerMove(e, 'canvas')
|
const info = inputs.pointerMove(e, 'canvas')
|
||||||
callbacks.onPointerMove?.(info, e)
|
callbacks.onPointerMove?.(info, e)
|
||||||
},
|
},
|
||||||
[callbacks]
|
[callbacks, inputs]
|
||||||
)
|
)
|
||||||
|
|
||||||
const onPointerUp = React.useCallback(
|
const onPointerUp = React.useCallback(
|
||||||
|
@ -47,7 +46,7 @@ export function useCanvasEvents() {
|
||||||
callbacks.onReleaseCanvas?.(info, e)
|
callbacks.onReleaseCanvas?.(info, e)
|
||||||
callbacks.onPointerUp?.(info, e)
|
callbacks.onPointerUp?.(info, e)
|
||||||
},
|
},
|
||||||
[callbacks]
|
[callbacks, inputs]
|
||||||
)
|
)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import { inputs } from '+inputs'
|
|
||||||
import { useTLContext } from './useTLContext'
|
import { useTLContext } from './useTLContext'
|
||||||
|
|
||||||
export function useHandleEvents(id: string) {
|
export function useHandleEvents(id: string) {
|
||||||
const { callbacks } = useTLContext()
|
const { inputs, callbacks } = useTLContext()
|
||||||
|
|
||||||
const onPointerDown = React.useCallback(
|
const onPointerDown = React.useCallback(
|
||||||
(e: React.PointerEvent) => {
|
(e: React.PointerEvent) => {
|
||||||
|
@ -15,7 +14,7 @@ export function useHandleEvents(id: string) {
|
||||||
callbacks.onPointHandle?.(info, e)
|
callbacks.onPointHandle?.(info, e)
|
||||||
callbacks.onPointerDown?.(info, e)
|
callbacks.onPointerDown?.(info, e)
|
||||||
},
|
},
|
||||||
[callbacks, id]
|
[inputs, callbacks, id]
|
||||||
)
|
)
|
||||||
|
|
||||||
const onPointerUp = React.useCallback(
|
const onPointerUp = React.useCallback(
|
||||||
|
@ -36,7 +35,7 @@ export function useHandleEvents(id: string) {
|
||||||
}
|
}
|
||||||
callbacks.onPointerUp?.(info, e)
|
callbacks.onPointerUp?.(info, e)
|
||||||
},
|
},
|
||||||
[callbacks]
|
[inputs, callbacks]
|
||||||
)
|
)
|
||||||
|
|
||||||
const onPointerMove = React.useCallback(
|
const onPointerMove = React.useCallback(
|
||||||
|
@ -48,7 +47,7 @@ export function useHandleEvents(id: string) {
|
||||||
const info = inputs.pointerMove(e, id)
|
const info = inputs.pointerMove(e, id)
|
||||||
callbacks.onPointerMove?.(info, e)
|
callbacks.onPointerMove?.(info, e)
|
||||||
},
|
},
|
||||||
[callbacks, id]
|
[inputs, callbacks, id]
|
||||||
)
|
)
|
||||||
|
|
||||||
const onPointerEnter = React.useCallback(
|
const onPointerEnter = React.useCallback(
|
||||||
|
@ -56,7 +55,7 @@ export function useHandleEvents(id: string) {
|
||||||
const info = inputs.pointerEnter(e, id)
|
const info = inputs.pointerEnter(e, id)
|
||||||
callbacks.onHoverHandle?.(info, e)
|
callbacks.onHoverHandle?.(info, e)
|
||||||
},
|
},
|
||||||
[callbacks, id]
|
[inputs, callbacks, id]
|
||||||
)
|
)
|
||||||
|
|
||||||
const onPointerLeave = React.useCallback(
|
const onPointerLeave = React.useCallback(
|
||||||
|
@ -64,7 +63,7 @@ export function useHandleEvents(id: string) {
|
||||||
const info = inputs.pointerEnter(e, id)
|
const info = inputs.pointerEnter(e, id)
|
||||||
callbacks.onUnhoverHandle?.(info, e)
|
callbacks.onUnhoverHandle?.(info, e)
|
||||||
},
|
},
|
||||||
[callbacks, id]
|
[inputs, callbacks, id]
|
||||||
)
|
)
|
||||||
|
|
||||||
const onTouchStart = React.useCallback((e: React.TouchEvent) => {
|
const onTouchStart = React.useCallback((e: React.TouchEvent) => {
|
||||||
|
|
41
packages/core/src/hooks/useResizeObserver.ts
Normal file
41
packages/core/src/hooks/useResizeObserver.ts
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
import { useTLContext } from '+hooks'
|
||||||
|
import * as React from 'react'
|
||||||
|
|
||||||
|
export function useResizeObserver<T extends HTMLElement | SVGElement>(ref: React.RefObject<T>) {
|
||||||
|
const { inputs } = useTLContext()
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
function handleScroll() {
|
||||||
|
const rect = ref.current?.getBoundingClientRect()
|
||||||
|
if (rect) {
|
||||||
|
inputs.offset = [rect.left, rect.top]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('scroll', handleScroll)
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener('scroll', handleScroll)
|
||||||
|
}
|
||||||
|
}, [inputs])
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
const resizeObserver = new ResizeObserver((entries) => {
|
||||||
|
if (inputs.isPinching) return
|
||||||
|
|
||||||
|
if (entries[0].contentRect) {
|
||||||
|
const rect = ref.current?.getBoundingClientRect()
|
||||||
|
if (rect) {
|
||||||
|
inputs.offset = [rect.left, rect.top]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if (ref.current) {
|
||||||
|
resizeObserver.observe(ref.current)
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
resizeObserver.disconnect()
|
||||||
|
}
|
||||||
|
}, [ref, inputs])
|
||||||
|
}
|
|
@ -1,10 +1,9 @@
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import { inputs } from '+inputs'
|
|
||||||
import { Utils } from '+utils'
|
import { Utils } from '+utils'
|
||||||
import { TLContext } from '+hooks'
|
import { TLContext } from '+hooks'
|
||||||
|
|
||||||
export function useShapeEvents(id: string, disable = false) {
|
export function useShapeEvents(id: string, disable = false) {
|
||||||
const { rPageState, rScreenBounds, callbacks } = React.useContext(TLContext)
|
const { rPageState, rScreenBounds, callbacks, inputs } = React.useContext(TLContext)
|
||||||
|
|
||||||
const onPointerDown = React.useCallback(
|
const onPointerDown = React.useCallback(
|
||||||
(e: React.PointerEvent) => {
|
(e: React.PointerEvent) => {
|
||||||
|
@ -38,7 +37,7 @@ export function useShapeEvents(id: string, disable = false) {
|
||||||
callbacks.onPointShape?.(info, e)
|
callbacks.onPointShape?.(info, e)
|
||||||
callbacks.onPointerDown?.(info, e)
|
callbacks.onPointerDown?.(info, e)
|
||||||
},
|
},
|
||||||
[callbacks, id, disable]
|
[inputs, callbacks, id, disable]
|
||||||
)
|
)
|
||||||
|
|
||||||
const onPointerUp = React.useCallback(
|
const onPointerUp = React.useCallback(
|
||||||
|
@ -60,7 +59,7 @@ export function useShapeEvents(id: string, disable = false) {
|
||||||
callbacks.onReleaseShape?.(info, e)
|
callbacks.onReleaseShape?.(info, e)
|
||||||
callbacks.onPointerUp?.(info, e)
|
callbacks.onPointerUp?.(info, e)
|
||||||
},
|
},
|
||||||
[callbacks, id, disable]
|
[inputs, callbacks, id, disable]
|
||||||
)
|
)
|
||||||
|
|
||||||
const onPointerMove = React.useCallback(
|
const onPointerMove = React.useCallback(
|
||||||
|
@ -77,7 +76,7 @@ export function useShapeEvents(id: string, disable = false) {
|
||||||
|
|
||||||
callbacks.onPointerMove?.(info, e)
|
callbacks.onPointerMove?.(info, e)
|
||||||
},
|
},
|
||||||
[callbacks, id, disable]
|
[inputs, callbacks, id, disable]
|
||||||
)
|
)
|
||||||
|
|
||||||
const onPointerEnter = React.useCallback(
|
const onPointerEnter = React.useCallback(
|
||||||
|
@ -86,7 +85,7 @@ export function useShapeEvents(id: string, disable = false) {
|
||||||
const info = inputs.pointerEnter(e, id)
|
const info = inputs.pointerEnter(e, id)
|
||||||
callbacks.onHoverShape?.(info, e)
|
callbacks.onHoverShape?.(info, e)
|
||||||
},
|
},
|
||||||
[callbacks, id, disable]
|
[inputs, callbacks, id, disable]
|
||||||
)
|
)
|
||||||
|
|
||||||
const onPointerLeave = React.useCallback(
|
const onPointerLeave = React.useCallback(
|
||||||
|
@ -95,7 +94,7 @@ export function useShapeEvents(id: string, disable = false) {
|
||||||
const info = inputs.pointerEnter(e, id)
|
const info = inputs.pointerEnter(e, id)
|
||||||
callbacks.onUnhoverShape?.(info, e)
|
callbacks.onUnhoverShape?.(info, e)
|
||||||
},
|
},
|
||||||
[callbacks, id, disable]
|
[inputs, callbacks, id, disable]
|
||||||
)
|
)
|
||||||
|
|
||||||
const onTouchStart = React.useCallback((e: React.TouchEvent) => {
|
const onTouchStart = React.useCallback((e: React.TouchEvent) => {
|
||||||
|
|
|
@ -185,14 +185,13 @@ const tlcss = css`
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
.tl-canvas {
|
.tl-canvas {
|
||||||
position: fixed;
|
position: absolute;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
top: 0px;
|
top: 0px;
|
||||||
left: 0px;
|
left: 0px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
touch-action: none;
|
touch-action: none;
|
||||||
z-index: 100;
|
|
||||||
pointer-events: all;
|
pointer-events: all;
|
||||||
}
|
}
|
||||||
.tl-container {
|
.tl-container {
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
|
import type { Inputs } from '+inputs'
|
||||||
import type { TLCallbacks, TLShape, TLBounds, TLPageState, TLShapeUtils } from '+types'
|
import type { TLCallbacks, TLShape, TLBounds, TLPageState, TLShapeUtils } from '+types'
|
||||||
|
|
||||||
export interface TLContextType {
|
export interface TLContextType {
|
||||||
|
@ -7,6 +8,7 @@ export interface TLContextType {
|
||||||
shapeUtils: TLShapeUtils<TLShape>
|
shapeUtils: TLShapeUtils<TLShape>
|
||||||
rPageState: React.MutableRefObject<TLPageState>
|
rPageState: React.MutableRefObject<TLPageState>
|
||||||
rScreenBounds: React.MutableRefObject<TLBounds | null>
|
rScreenBounds: React.MutableRefObject<TLBounds | null>
|
||||||
|
inputs: Inputs
|
||||||
}
|
}
|
||||||
|
|
||||||
export const TLContext = React.createContext<TLContextType>({} as TLContextType)
|
export const TLContext = React.createContext<TLContextType>({} as TLContextType)
|
||||||
|
|
|
@ -1,20 +1,22 @@
|
||||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||||
import { useRef } from 'react'
|
import * as React from 'react'
|
||||||
import { useTLContext } from './useTLContext'
|
import { useTLContext } from './useTLContext'
|
||||||
import { Vec } from '+utils'
|
import { Vec } from '+utils'
|
||||||
import { useWheel, usePinch } from 'react-use-gesture'
|
import { useWheel, usePinch } from 'react-use-gesture'
|
||||||
import { inputs } from '+inputs'
|
|
||||||
|
|
||||||
// Capture zoom gestures (pinches, wheels and pans)
|
// Capture zoom gestures (pinches, wheels and pans)
|
||||||
export function useZoomEvents() {
|
export function useZoomEvents<T extends HTMLElement | SVGElement>(ref: React.RefObject<T>) {
|
||||||
const rPinchDa = useRef<number[] | undefined>(undefined)
|
const rPinchDa = React.useRef<number[] | undefined>(undefined)
|
||||||
const rOriginPoint = useRef<number[] | undefined>(undefined)
|
const rOriginPoint = React.useRef<number[] | undefined>(undefined)
|
||||||
const rPinchPoint = useRef<number[] | undefined>(undefined)
|
const rPinchPoint = React.useRef<number[] | undefined>(undefined)
|
||||||
|
|
||||||
const { callbacks } = useTLContext()
|
const { inputs, callbacks } = useTLContext()
|
||||||
|
|
||||||
useWheel(
|
useWheel(
|
||||||
({ event: e, delta }) => {
|
({ event: e, delta }) => {
|
||||||
|
const elm = ref.current
|
||||||
|
if (!(e.target === elm || elm?.contains(e.target as Node))) return
|
||||||
|
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
|
|
||||||
if (Vec.isEqual(delta, [0, 0])) return
|
if (Vec.isEqual(delta, [0, 0])) return
|
||||||
|
@ -24,15 +26,20 @@ export function useZoomEvents() {
|
||||||
callbacks.onPan?.(info, e)
|
callbacks.onPan?.(info, e)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
domTarget: typeof document === 'undefined' ? undefined : document.body,
|
domTarget: window,
|
||||||
eventOptions: { passive: false },
|
eventOptions: { passive: false },
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
usePinch(
|
usePinch(
|
||||||
({ pinching, da, origin, event: e }) => {
|
({ pinching, da, origin, event: e }) => {
|
||||||
|
const elm = ref.current
|
||||||
|
if (!(e.target === elm || elm?.contains(e.target as Node))) return
|
||||||
|
|
||||||
|
const info = inputs.pinch(origin, origin)
|
||||||
|
|
||||||
if (!pinching) {
|
if (!pinching) {
|
||||||
const info = inputs.pinch(origin, origin)
|
inputs.isPinching = false
|
||||||
callbacks.onPinchEnd?.(
|
callbacks.onPinchEnd?.(
|
||||||
info,
|
info,
|
||||||
e as React.WheelEvent<Element> | WheelEvent | React.TouchEvent<Element> | TouchEvent
|
e as React.WheelEvent<Element> | WheelEvent | React.TouchEvent<Element> | TouchEvent
|
||||||
|
@ -44,14 +51,14 @@ export function useZoomEvents() {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rPinchPoint.current === undefined) {
|
if (rPinchPoint.current === undefined) {
|
||||||
const info = inputs.pinch(origin, origin)
|
inputs.isPinching = true
|
||||||
callbacks.onPinchStart?.(
|
callbacks.onPinchStart?.(
|
||||||
info,
|
info,
|
||||||
e as React.WheelEvent<Element> | WheelEvent | React.TouchEvent<Element> | TouchEvent
|
e as React.WheelEvent<Element> | WheelEvent | React.TouchEvent<Element> | TouchEvent
|
||||||
)
|
)
|
||||||
rPinchDa.current = da
|
rPinchDa.current = da
|
||||||
rPinchPoint.current = origin
|
rPinchPoint.current = info.point
|
||||||
rOriginPoint.current = origin
|
rOriginPoint.current = info.point
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!rPinchDa.current) throw Error('No pinch direction!')
|
if (!rPinchDa.current) throw Error('No pinch direction!')
|
||||||
|
@ -59,8 +66,6 @@ export function useZoomEvents() {
|
||||||
|
|
||||||
const [distanceDelta] = Vec.sub(rPinchDa.current, da)
|
const [distanceDelta] = Vec.sub(rPinchDa.current, da)
|
||||||
|
|
||||||
const info = inputs.pinch(rPinchPoint.current, origin)
|
|
||||||
|
|
||||||
callbacks.onPinch?.(
|
callbacks.onPinch?.(
|
||||||
{
|
{
|
||||||
...info,
|
...info,
|
||||||
|
@ -75,7 +80,7 @@ export function useZoomEvents() {
|
||||||
rPinchPoint.current = origin
|
rPinchPoint.current = origin
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
domTarget: typeof document === 'undefined' ? undefined : document.body,
|
domTarget: window,
|
||||||
eventOptions: { passive: false },
|
eventOptions: { passive: false },
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
|
@ -4,10 +4,13 @@ import { Vec, Utils } from './utils'
|
||||||
|
|
||||||
const DOUBLE_CLICK_DURATION = 250
|
const DOUBLE_CLICK_DURATION = 250
|
||||||
|
|
||||||
class Inputs {
|
export class Inputs {
|
||||||
pointer?: TLPointerInfo<string>
|
pointer?: TLPointerInfo<string>
|
||||||
keyboard?: TLKeyboardInfo
|
keyboard?: TLKeyboardInfo
|
||||||
keys: Record<string, boolean> = {}
|
keys: Record<string, boolean> = {}
|
||||||
|
isPinching = false
|
||||||
|
|
||||||
|
offset = [0, 0]
|
||||||
|
|
||||||
pointerUpTime = 0
|
pointerUpTime = 0
|
||||||
|
|
||||||
|
@ -69,7 +72,7 @@ class Inputs {
|
||||||
pointerDown<T extends string>(e: PointerEvent | React.PointerEvent, target: T): TLPointerInfo<T> {
|
pointerDown<T extends string>(e: PointerEvent | React.PointerEvent, target: T): TLPointerInfo<T> {
|
||||||
const { shiftKey, ctrlKey, metaKey, altKey } = e
|
const { shiftKey, ctrlKey, metaKey, altKey } = e
|
||||||
|
|
||||||
const point = Inputs.getPoint(e)
|
const point = Inputs.getPoint(e, this.offset)
|
||||||
|
|
||||||
const info: TLPointerInfo<T> = {
|
const info: TLPointerInfo<T> = {
|
||||||
target,
|
target,
|
||||||
|
@ -95,7 +98,7 @@ class Inputs {
|
||||||
): TLPointerInfo<T> {
|
): TLPointerInfo<T> {
|
||||||
const { shiftKey, ctrlKey, metaKey, altKey } = e
|
const { shiftKey, ctrlKey, metaKey, altKey } = e
|
||||||
|
|
||||||
const point = Inputs.getPoint(e)
|
const point = Inputs.getPoint(e, this.offset)
|
||||||
|
|
||||||
const info: TLPointerInfo<T> = {
|
const info: TLPointerInfo<T> = {
|
||||||
target,
|
target,
|
||||||
|
@ -120,7 +123,7 @@ class Inputs {
|
||||||
|
|
||||||
const prev = this.pointer
|
const prev = this.pointer
|
||||||
|
|
||||||
const point = Inputs.getPoint(e)
|
const point = Inputs.getPoint(e, this.offset)
|
||||||
|
|
||||||
const delta = prev?.point ? Vec.sub(point, prev.point) : [0, 0]
|
const delta = prev?.point ? Vec.sub(point, prev.point) : [0, 0]
|
||||||
|
|
||||||
|
@ -148,7 +151,7 @@ class Inputs {
|
||||||
|
|
||||||
const prev = this.pointer
|
const prev = this.pointer
|
||||||
|
|
||||||
const point = Inputs.getPoint(e)
|
const point = Inputs.getPoint(e, this.offset)
|
||||||
|
|
||||||
const delta = prev?.point ? Vec.sub(point, prev.point) : [0, 0]
|
const delta = prev?.point ? Vec.sub(point, prev.point) : [0, 0]
|
||||||
|
|
||||||
|
@ -182,7 +185,7 @@ class Inputs {
|
||||||
origin: this.pointer?.origin || [0, 0],
|
origin: this.pointer?.origin || [0, 0],
|
||||||
delta: [0, 0],
|
delta: [0, 0],
|
||||||
pressure: 0.5,
|
pressure: 0.5,
|
||||||
point: Inputs.getPoint(e),
|
point: Inputs.getPoint(e, this.offset),
|
||||||
shiftKey,
|
shiftKey,
|
||||||
ctrlKey,
|
ctrlKey,
|
||||||
metaKey,
|
metaKey,
|
||||||
|
@ -203,7 +206,7 @@ class Inputs {
|
||||||
|
|
||||||
const prev = this.pointer
|
const prev = this.pointer
|
||||||
|
|
||||||
const point = Inputs.getPoint(e)
|
const point = Inputs.getPoint(e, this.offset)
|
||||||
|
|
||||||
const info: TLPointerInfo<'wheel'> = {
|
const info: TLPointerInfo<'wheel'> = {
|
||||||
...prev,
|
...prev,
|
||||||
|
@ -281,9 +284,9 @@ class Inputs {
|
||||||
const info: TLPointerInfo<'pinch'> = {
|
const info: TLPointerInfo<'pinch'> = {
|
||||||
pointerId: 0,
|
pointerId: 0,
|
||||||
target: 'pinch',
|
target: 'pinch',
|
||||||
origin: prev?.origin || Vec.round(point),
|
origin: prev?.origin || Vec.sub(Vec.round(point), this.offset),
|
||||||
delta: delta,
|
delta: delta,
|
||||||
point: Vec.round(point),
|
point: Vec.sub(Vec.round(point), this.offset),
|
||||||
pressure: 0.5,
|
pressure: 0.5,
|
||||||
shiftKey,
|
shiftKey,
|
||||||
ctrlKey,
|
ctrlKey,
|
||||||
|
@ -304,9 +307,13 @@ class Inputs {
|
||||||
}
|
}
|
||||||
|
|
||||||
static getPoint(
|
static getPoint(
|
||||||
e: PointerEvent | React.PointerEvent | Touch | React.Touch | WheelEvent
|
e: PointerEvent | React.PointerEvent | Touch | React.Touch | WheelEvent,
|
||||||
|
offset = [0, 0]
|
||||||
): number[] {
|
): number[] {
|
||||||
return [Number(e.clientX.toPrecision(5)), Number(e.clientY.toPrecision(5))]
|
return [
|
||||||
|
Number(e.clientX.toPrecision(5)) - offset[0],
|
||||||
|
Number(e.clientY.toPrecision(5)) - offset[1],
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
static getPressure(e: PointerEvent | React.PointerEvent | Touch | React.Touch | WheelEvent) {
|
static getPressure(e: PointerEvent | React.PointerEvent | Touch | React.Touch | WheelEvent) {
|
||||||
|
|
|
@ -3,6 +3,7 @@ import type { TLPageState, TLBounds } from '../types'
|
||||||
import { mockDocument } from './mockDocument'
|
import { mockDocument } from './mockDocument'
|
||||||
import { mockUtils } from './mockUtils'
|
import { mockUtils } from './mockUtils'
|
||||||
import { useTLTheme, TLContext } from '../hooks'
|
import { useTLTheme, TLContext } from '../hooks'
|
||||||
|
import { Inputs } from '+inputs'
|
||||||
|
|
||||||
export const ContextWrapper: React.FC = ({ children }) => {
|
export const ContextWrapper: React.FC = ({ children }) => {
|
||||||
useTLTheme()
|
useTLTheme()
|
||||||
|
@ -14,6 +15,7 @@ export const ContextWrapper: React.FC = ({ children }) => {
|
||||||
shapeUtils: mockUtils,
|
shapeUtils: mockUtils,
|
||||||
rScreenBounds,
|
rScreenBounds,
|
||||||
rPageState,
|
rPageState,
|
||||||
|
inputs: new Inputs(),
|
||||||
}))
|
}))
|
||||||
|
|
||||||
return <TLContext.Provider value={context}>{children}</TLContext.Provider>
|
return <TLContext.Provider value={context}>{children}</TLContext.Provider>
|
||||||
|
|
|
@ -9,6 +9,10 @@ if (!fs.existsSync('./dist')) {
|
||||||
fs.mkdirSync('./dist')
|
fs.mkdirSync('./dist')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fs.copyFile('./src/styles.css', './dist/styles.css', (err) => {
|
||||||
|
if (err) throw err
|
||||||
|
})
|
||||||
|
|
||||||
fs.copyFile('./src/index.html', './dist/index.html', (err) => {
|
fs.copyFile('./src/index.html', './dist/index.html', (err) => {
|
||||||
if (err) throw err
|
if (err) throw err
|
||||||
})
|
})
|
||||||
|
|
|
@ -2,7 +2,8 @@ import * as React from 'react'
|
||||||
import Basic from './basic'
|
import Basic from './basic'
|
||||||
import Controlled from './controlled'
|
import Controlled from './controlled'
|
||||||
import Imperative from './imperative'
|
import Imperative from './imperative'
|
||||||
|
import Small from './small'
|
||||||
|
|
||||||
export default function App(): JSX.Element {
|
export default function App(): JSX.Element {
|
||||||
return <Basic />
|
return <Small />
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,8 @@
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<link rel="icon" href="/favicon.ico" />
|
<link rel="icon" href="favicon.ico" />
|
||||||
|
<link rel="stylesheet" href="styles.css" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<title>tldraw</title>
|
<title>tldraw</title>
|
||||||
</head>
|
</head>
|
||||||
|
|
30
packages/dev/src/small.tsx
Normal file
30
packages/dev/src/small.tsx
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
import * as React from 'react'
|
||||||
|
import Editor from './components/editor'
|
||||||
|
|
||||||
|
export default function BasicUsage(): JSX.Element {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
position: 'relative',
|
||||||
|
margin: '5%',
|
||||||
|
width: 'calc(100% - 100px)',
|
||||||
|
height: '500px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Editor id="small1" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
position: 'relative',
|
||||||
|
margin: '5%',
|
||||||
|
width: 'calc(100% - 100px)',
|
||||||
|
height: '500px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Editor id="small2" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
8
packages/dev/src/styles.css
Normal file
8
packages/dev/src/styles.css
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
html,
|
||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
overscroll-behavior: none;
|
||||||
|
}
|
|
@ -5,7 +5,7 @@ import styled from '~styles'
|
||||||
/* -------------------------------------------------- */
|
/* -------------------------------------------------- */
|
||||||
|
|
||||||
export const DialogContent = styled('div', {
|
export const DialogContent = styled('div', {
|
||||||
position: 'fixed',
|
position: 'absolute',
|
||||||
top: '50%',
|
top: '50%',
|
||||||
left: '50%',
|
left: '50%',
|
||||||
transform: 'translate(-50%, -50%)',
|
transform: 'translate(-50%, -50%)',
|
||||||
|
|
|
@ -227,17 +227,16 @@ const MenuButtons = styled('div', {
|
||||||
gap: 8,
|
gap: 8,
|
||||||
})
|
})
|
||||||
|
|
||||||
const Layout = styled('main', {
|
const Layout = styled('div', {
|
||||||
position: 'fixed',
|
|
||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
top: 0,
|
position: 'absolute',
|
||||||
left: 0,
|
|
||||||
bottom: 0,
|
|
||||||
right: 0,
|
|
||||||
height: '100%',
|
height: '100%',
|
||||||
width: '100%',
|
width: '100%',
|
||||||
|
top: 0,
|
||||||
|
right: 0,
|
||||||
|
bottom: 0,
|
||||||
|
left: 0,
|
||||||
padding: '8px 8px 0 8px',
|
padding: '8px 8px 0 8px',
|
||||||
zIndex: 200,
|
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'flex-start',
|
alignItems: 'flex-start',
|
||||||
justifyContent: 'flex-start',
|
justifyContent: 'flex-start',
|
||||||
|
@ -253,5 +252,7 @@ const Layout = styled('main', {
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
top: 0,
|
top: 0,
|
||||||
left: 0,
|
left: 0,
|
||||||
|
width: '100%',
|
||||||
|
height: '100%',
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
|
@ -139,7 +139,7 @@ export const ToolsPanel = React.memo((): JSX.Element => {
|
||||||
})
|
})
|
||||||
|
|
||||||
const ToolsPanelContainer = styled('div', {
|
const ToolsPanelContainer = styled('div', {
|
||||||
position: 'fixed',
|
position: 'absolute',
|
||||||
bottom: 0,
|
bottom: 0,
|
||||||
left: 0,
|
left: 0,
|
||||||
right: 0,
|
right: 0,
|
||||||
|
|
|
@ -2231,11 +2231,11 @@ export class TLDrawState extends StateManager<Data> {
|
||||||
}
|
}
|
||||||
|
|
||||||
onPinchEnd: TLPinchEventHandler = () => {
|
onPinchEnd: TLPinchEventHandler = () => {
|
||||||
if (this.state.settings.isZoomSnap) {
|
// if (this.state.settings.isZoomSnap) {
|
||||||
const i = Math.round((this.pageState.camera.zoom * 100) / 25)
|
// const i = Math.round((this.pageState.camera.zoom * 100) / 25)
|
||||||
const nextZoom = TLDR.getCameraZoom(i * 0.25)
|
// const nextZoom = TLDR.getCameraZoom(i * 0.25)
|
||||||
this.zoomTo(nextZoom, inputs.pointer?.point)
|
// this.zoomTo(nextZoom, inputs.pointer?.point)
|
||||||
}
|
// }
|
||||||
this.setStatus(this.appState.status.previous)
|
this.setStatus(this.appState.status.previous)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,2 +1,3 @@
|
||||||
import '@testing-library/jest-dom/extend-expect'
|
import '@testing-library/jest-dom/extend-expect'
|
||||||
import "fake-indexeddb/auto"
|
import 'fake-indexeddb/auto'
|
||||||
|
global.ResizeObserver = require('resize-observer-polyfill')
|
||||||
|
|
|
@ -11417,6 +11417,11 @@ require_optional@^1.0.1:
|
||||||
resolve-from "^2.0.0"
|
resolve-from "^2.0.0"
|
||||||
semver "^5.1.0"
|
semver "^5.1.0"
|
||||||
|
|
||||||
|
resize-observer-polyfill@^1.5.1:
|
||||||
|
version "1.5.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz#0e9020dd3d21024458d4ebd27e23e40269810464"
|
||||||
|
integrity sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==
|
||||||
|
|
||||||
resolve-cwd@^2.0.0:
|
resolve-cwd@^2.0.0:
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-2.0.0.tgz#00a9f7387556e27038eae232caa372a6a59b665a"
|
resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-2.0.0.tgz#00a9f7387556e27038eae232caa372a6a59b665a"
|
||||||
|
|
Loading…
Reference in a new issue