Adjusts small example, makes inputs unique to each instance

This commit is contained in:
Steve Ruiz 2021-09-08 17:18:43 +01:00
parent 2653f396bf
commit 8154ed5a2a
25 changed files with 190 additions and 81 deletions

View file

@ -48,6 +48,7 @@
"lerna": "^3.15.0",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"resize-observer-polyfill": "^1.5.1",
"ts-jest": "^27.0.5",
"tslib": "^2.3.0",
"typedoc": "^0.21.9",
@ -114,4 +115,4 @@
"\\+(.*)": "<rootDir>/packages/core/src/$1"
}
}
}
}

View file

@ -12,6 +12,7 @@ import { ErrorBoundary } from '+components/error-boundary'
import { Brush } from '+components/brush'
import { Defs } from '+components/defs'
import { Page } from '+components/page'
import { useResizeObserver } from '+hooks/useResizeObserver'
function resetError() {
void null
@ -35,10 +36,13 @@ export function Canvas<T extends TLShape>({
hideIndicators = false,
}: CanvasProps<T>): JSX.Element {
const rCanvas = React.useRef<SVGSVGElement>(null)
const rContainer = React.useRef<HTMLDivElement>(null)
const rGroup = useCameraCss(pageState)
useZoomEvents()
useResizeObserver(rCanvas)
useZoomEvents(rCanvas)
useSafariFocusOutFix()
@ -47,7 +51,7 @@ export function Canvas<T extends TLShape>({
const events = useCanvasEvents()
return (
<div className="tl-container">
<div className="tl-container" ref={rContainer}>
<svg id="canvas" className="tl-canvas" ref={rCanvas} {...events}>
<ErrorBoundary FallbackComponent={ErrorFallback} onReset={resetError}>
<Defs zoom={pageState.camera.zoom} />

View file

@ -10,6 +10,7 @@ import type {
TLBinding,
} from '../../types'
import { Canvas } from '../canvas'
import { Inputs } from '../../inputs'
import { useTLTheme, TLContext, TLContextType } from '../../hooks'
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,
rScreenBounds,
rPageState,
inputs: new Inputs(),
}))
return (

View file

@ -1,9 +1,8 @@
import * as React from 'react'
import { inputs } from '+inputs'
import { useTLContext } from './useTLContext'
export function useBoundsEvents() {
const { callbacks } = useTLContext()
const { callbacks, inputs } = useTLContext()
const onPointerDown = React.useCallback(
(e: React.PointerEvent) => {
@ -15,7 +14,7 @@ export function useBoundsEvents() {
callbacks.onPointBounds?.(info, e)
callbacks.onPointerDown?.(info, e)
},
[callbacks]
[callbacks, inputs]
)
const onPointerUp = React.useCallback(
@ -36,7 +35,7 @@ export function useBoundsEvents() {
callbacks.onReleaseBounds?.(info, e)
callbacks.onPointerUp?.(info, e)
},
[callbacks]
[callbacks, inputs]
)
const onPointerMove = React.useCallback(
@ -49,21 +48,21 @@ export function useBoundsEvents() {
const info = inputs.pointerMove(e, 'bounds')
callbacks.onPointerMove?.(info, e)
},
[callbacks]
[callbacks, inputs]
)
const onPointerEnter = React.useCallback(
(e: React.PointerEvent) => {
callbacks.onHoverBounds?.(inputs.pointerEnter(e, 'bounds'), e)
},
[callbacks]
[callbacks, inputs]
)
const onPointerLeave = React.useCallback(
(e: React.PointerEvent) => {
callbacks.onUnhoverBounds?.(inputs.pointerEnter(e, 'bounds'), e)
},
[callbacks]
[callbacks, inputs]
)
const onTouchStart = React.useCallback((e: React.TouchEvent) => {

View file

@ -1,10 +1,9 @@
import * as React from 'react'
import { inputs } from '+inputs'
import type { TLBoundsEdge, TLBoundsCorner } from '+types'
import { useTLContext } from './useTLContext'
export function useBoundsHandleEvents(id: TLBoundsCorner | TLBoundsEdge | 'rotate') {
const { callbacks } = useTLContext()
const { callbacks, inputs } = useTLContext()
const onPointerDown = React.useCallback(
(e: React.PointerEvent) => {
@ -16,7 +15,7 @@ export function useBoundsHandleEvents(id: TLBoundsCorner | TLBoundsEdge | 'rotat
callbacks.onPointBoundsHandle?.(info, e)
callbacks.onPointerDown?.(info, e)
},
[callbacks, id]
[inputs, callbacks, id]
)
const onPointerUp = React.useCallback(
@ -37,7 +36,7 @@ export function useBoundsHandleEvents(id: TLBoundsCorner | TLBoundsEdge | 'rotat
callbacks.onReleaseBoundsHandle?.(info, e)
callbacks.onPointerUp?.(info, e)
},
[callbacks, id]
[inputs, callbacks, id]
)
const onPointerMove = React.useCallback(
@ -48,21 +47,21 @@ export function useBoundsHandleEvents(id: TLBoundsCorner | TLBoundsEdge | 'rotat
const info = inputs.pointerMove(e, id)
callbacks.onPointerMove?.(info, e)
},
[callbacks, id]
[inputs, callbacks, id]
)
const onPointerEnter = React.useCallback(
(e: React.PointerEvent) => {
callbacks.onHoverBoundsHandle?.(inputs.pointerEnter(e, id), e)
},
[callbacks, id]
[inputs, callbacks, id]
)
const onPointerLeave = React.useCallback(
(e: React.PointerEvent) => {
callbacks.onUnhoverBoundsHandle?.(inputs.pointerEnter(e, id), e)
},
[callbacks, id]
[inputs, callbacks, id]
)
const onTouchStart = React.useCallback((e: React.TouchEvent) => {

View file

@ -1,9 +1,8 @@
import * as React from 'react'
import { useTLContext } from './useTLContext'
import { inputs } from '+inputs'
export function useCanvasEvents() {
const { callbacks } = useTLContext()
const { callbacks, inputs } = useTLContext()
const onPointerDown = React.useCallback(
(e: React.PointerEvent) => {
@ -16,7 +15,7 @@ export function useCanvasEvents() {
callbacks.onPointerDown?.(info, e)
}
},
[callbacks]
[callbacks, inputs]
)
const onPointerMove = React.useCallback(
@ -28,7 +27,7 @@ export function useCanvasEvents() {
const info = inputs.pointerMove(e, 'canvas')
callbacks.onPointerMove?.(info, e)
},
[callbacks]
[callbacks, inputs]
)
const onPointerUp = React.useCallback(
@ -47,7 +46,7 @@ export function useCanvasEvents() {
callbacks.onReleaseCanvas?.(info, e)
callbacks.onPointerUp?.(info, e)
},
[callbacks]
[callbacks, inputs]
)
return {

View file

@ -1,9 +1,8 @@
import * as React from 'react'
import { inputs } from '+inputs'
import { useTLContext } from './useTLContext'
export function useHandleEvents(id: string) {
const { callbacks } = useTLContext()
const { inputs, callbacks } = useTLContext()
const onPointerDown = React.useCallback(
(e: React.PointerEvent) => {
@ -15,7 +14,7 @@ export function useHandleEvents(id: string) {
callbacks.onPointHandle?.(info, e)
callbacks.onPointerDown?.(info, e)
},
[callbacks, id]
[inputs, callbacks, id]
)
const onPointerUp = React.useCallback(
@ -36,7 +35,7 @@ export function useHandleEvents(id: string) {
}
callbacks.onPointerUp?.(info, e)
},
[callbacks]
[inputs, callbacks]
)
const onPointerMove = React.useCallback(
@ -48,7 +47,7 @@ export function useHandleEvents(id: string) {
const info = inputs.pointerMove(e, id)
callbacks.onPointerMove?.(info, e)
},
[callbacks, id]
[inputs, callbacks, id]
)
const onPointerEnter = React.useCallback(
@ -56,7 +55,7 @@ export function useHandleEvents(id: string) {
const info = inputs.pointerEnter(e, id)
callbacks.onHoverHandle?.(info, e)
},
[callbacks, id]
[inputs, callbacks, id]
)
const onPointerLeave = React.useCallback(
@ -64,7 +63,7 @@ export function useHandleEvents(id: string) {
const info = inputs.pointerEnter(e, id)
callbacks.onUnhoverHandle?.(info, e)
},
[callbacks, id]
[inputs, callbacks, id]
)
const onTouchStart = React.useCallback((e: React.TouchEvent) => {

View 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])
}

View file

@ -1,10 +1,9 @@
import * as React from 'react'
import { inputs } from '+inputs'
import { Utils } from '+utils'
import { TLContext } from '+hooks'
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(
(e: React.PointerEvent) => {
@ -38,7 +37,7 @@ export function useShapeEvents(id: string, disable = false) {
callbacks.onPointShape?.(info, e)
callbacks.onPointerDown?.(info, e)
},
[callbacks, id, disable]
[inputs, callbacks, id, disable]
)
const onPointerUp = React.useCallback(
@ -60,7 +59,7 @@ export function useShapeEvents(id: string, disable = false) {
callbacks.onReleaseShape?.(info, e)
callbacks.onPointerUp?.(info, e)
},
[callbacks, id, disable]
[inputs, callbacks, id, disable]
)
const onPointerMove = React.useCallback(
@ -77,7 +76,7 @@ export function useShapeEvents(id: string, disable = false) {
callbacks.onPointerMove?.(info, e)
},
[callbacks, id, disable]
[inputs, callbacks, id, disable]
)
const onPointerEnter = React.useCallback(
@ -86,7 +85,7 @@ export function useShapeEvents(id: string, disable = false) {
const info = inputs.pointerEnter(e, id)
callbacks.onHoverShape?.(info, e)
},
[callbacks, id, disable]
[inputs, callbacks, id, disable]
)
const onPointerLeave = React.useCallback(
@ -95,7 +94,7 @@ export function useShapeEvents(id: string, disable = false) {
const info = inputs.pointerEnter(e, id)
callbacks.onUnhoverShape?.(info, e)
},
[callbacks, id, disable]
[inputs, callbacks, id, disable]
)
const onTouchStart = React.useCallback((e: React.TouchEvent) => {

View file

@ -185,14 +185,13 @@ const tlcss = css`
pointer-events: none;
}
.tl-canvas {
position: fixed;
position: absolute;
overflow: hidden;
top: 0px;
left: 0px;
width: 100%;
height: 100%;
touch-action: none;
z-index: 100;
pointer-events: all;
}
.tl-container {

View file

@ -1,4 +1,5 @@
import * as React from 'react'
import type { Inputs } from '+inputs'
import type { TLCallbacks, TLShape, TLBounds, TLPageState, TLShapeUtils } from '+types'
export interface TLContextType {
@ -7,6 +8,7 @@ export interface TLContextType {
shapeUtils: TLShapeUtils<TLShape>
rPageState: React.MutableRefObject<TLPageState>
rScreenBounds: React.MutableRefObject<TLBounds | null>
inputs: Inputs
}
export const TLContext = React.createContext<TLContextType>({} as TLContextType)

View file

@ -1,20 +1,22 @@
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import { useRef } from 'react'
import * as React from 'react'
import { useTLContext } from './useTLContext'
import { Vec } from '+utils'
import { useWheel, usePinch } from 'react-use-gesture'
import { inputs } from '+inputs'
// Capture zoom gestures (pinches, wheels and pans)
export function useZoomEvents() {
const rPinchDa = useRef<number[] | undefined>(undefined)
const rOriginPoint = useRef<number[] | undefined>(undefined)
const rPinchPoint = useRef<number[] | undefined>(undefined)
export function useZoomEvents<T extends HTMLElement | SVGElement>(ref: React.RefObject<T>) {
const rPinchDa = React.useRef<number[] | undefined>(undefined)
const rOriginPoint = React.useRef<number[] | undefined>(undefined)
const rPinchPoint = React.useRef<number[] | undefined>(undefined)
const { callbacks } = useTLContext()
const { inputs, callbacks } = useTLContext()
useWheel(
({ event: e, delta }) => {
const elm = ref.current
if (!(e.target === elm || elm?.contains(e.target as Node))) return
e.preventDefault()
if (Vec.isEqual(delta, [0, 0])) return
@ -24,15 +26,20 @@ export function useZoomEvents() {
callbacks.onPan?.(info, e)
},
{
domTarget: typeof document === 'undefined' ? undefined : document.body,
domTarget: window,
eventOptions: { passive: false },
}
)
usePinch(
({ 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) {
const info = inputs.pinch(origin, origin)
inputs.isPinching = false
callbacks.onPinchEnd?.(
info,
e as React.WheelEvent<Element> | WheelEvent | React.TouchEvent<Element> | TouchEvent
@ -44,14 +51,14 @@ export function useZoomEvents() {
}
if (rPinchPoint.current === undefined) {
const info = inputs.pinch(origin, origin)
inputs.isPinching = true
callbacks.onPinchStart?.(
info,
e as React.WheelEvent<Element> | WheelEvent | React.TouchEvent<Element> | TouchEvent
)
rPinchDa.current = da
rPinchPoint.current = origin
rOriginPoint.current = origin
rPinchPoint.current = info.point
rOriginPoint.current = info.point
}
if (!rPinchDa.current) throw Error('No pinch direction!')
@ -59,8 +66,6 @@ export function useZoomEvents() {
const [distanceDelta] = Vec.sub(rPinchDa.current, da)
const info = inputs.pinch(rPinchPoint.current, origin)
callbacks.onPinch?.(
{
...info,
@ -75,7 +80,7 @@ export function useZoomEvents() {
rPinchPoint.current = origin
},
{
domTarget: typeof document === 'undefined' ? undefined : document.body,
domTarget: window,
eventOptions: { passive: false },
}
)

View file

@ -4,10 +4,13 @@ import { Vec, Utils } from './utils'
const DOUBLE_CLICK_DURATION = 250
class Inputs {
export class Inputs {
pointer?: TLPointerInfo<string>
keyboard?: TLKeyboardInfo
keys: Record<string, boolean> = {}
isPinching = false
offset = [0, 0]
pointerUpTime = 0
@ -69,7 +72,7 @@ class Inputs {
pointerDown<T extends string>(e: PointerEvent | React.PointerEvent, target: T): TLPointerInfo<T> {
const { shiftKey, ctrlKey, metaKey, altKey } = e
const point = Inputs.getPoint(e)
const point = Inputs.getPoint(e, this.offset)
const info: TLPointerInfo<T> = {
target,
@ -95,7 +98,7 @@ class Inputs {
): TLPointerInfo<T> {
const { shiftKey, ctrlKey, metaKey, altKey } = e
const point = Inputs.getPoint(e)
const point = Inputs.getPoint(e, this.offset)
const info: TLPointerInfo<T> = {
target,
@ -120,7 +123,7 @@ class Inputs {
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]
@ -148,7 +151,7 @@ class Inputs {
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]
@ -182,7 +185,7 @@ class Inputs {
origin: this.pointer?.origin || [0, 0],
delta: [0, 0],
pressure: 0.5,
point: Inputs.getPoint(e),
point: Inputs.getPoint(e, this.offset),
shiftKey,
ctrlKey,
metaKey,
@ -203,7 +206,7 @@ class Inputs {
const prev = this.pointer
const point = Inputs.getPoint(e)
const point = Inputs.getPoint(e, this.offset)
const info: TLPointerInfo<'wheel'> = {
...prev,
@ -281,9 +284,9 @@ class Inputs {
const info: TLPointerInfo<'pinch'> = {
pointerId: 0,
target: 'pinch',
origin: prev?.origin || Vec.round(point),
origin: prev?.origin || Vec.sub(Vec.round(point), this.offset),
delta: delta,
point: Vec.round(point),
point: Vec.sub(Vec.round(point), this.offset),
pressure: 0.5,
shiftKey,
ctrlKey,
@ -304,9 +307,13 @@ class Inputs {
}
static getPoint(
e: PointerEvent | React.PointerEvent | Touch | React.Touch | WheelEvent
e: PointerEvent | React.PointerEvent | Touch | React.Touch | WheelEvent,
offset = [0, 0]
): 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) {

View file

@ -3,6 +3,7 @@ import type { TLPageState, TLBounds } from '../types'
import { mockDocument } from './mockDocument'
import { mockUtils } from './mockUtils'
import { useTLTheme, TLContext } from '../hooks'
import { Inputs } from '+inputs'
export const ContextWrapper: React.FC = ({ children }) => {
useTLTheme()
@ -14,6 +15,7 @@ export const ContextWrapper: React.FC = ({ children }) => {
shapeUtils: mockUtils,
rScreenBounds,
rPageState,
inputs: new Inputs(),
}))
return <TLContext.Provider value={context}>{children}</TLContext.Provider>

View file

@ -9,6 +9,10 @@ if (!fs.existsSync('./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) => {
if (err) throw err
})

View file

@ -2,7 +2,8 @@ import * as React from 'react'
import Basic from './basic'
import Controlled from './controlled'
import Imperative from './imperative'
import Small from './small'
export default function App(): JSX.Element {
return <Basic />
return <Small />
}

View file

@ -2,7 +2,8 @@
<html lang="en">
<head>
<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" />
<title>tldraw</title>
</head>

View 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>
)
}

View file

@ -0,0 +1,8 @@
html,
* {
box-sizing: border-box;
}
body {
overscroll-behavior: none;
}

View file

@ -5,7 +5,7 @@ import styled from '~styles'
/* -------------------------------------------------- */
export const DialogContent = styled('div', {
position: 'fixed',
position: 'absolute',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',

View file

@ -227,17 +227,16 @@ const MenuButtons = styled('div', {
gap: 8,
})
const Layout = styled('main', {
position: 'fixed',
const Layout = styled('div', {
overflow: 'hidden',
top: 0,
left: 0,
bottom: 0,
right: 0,
position: 'absolute',
height: '100%',
width: '100%',
top: 0,
right: 0,
bottom: 0,
left: 0,
padding: '8px 8px 0 8px',
zIndex: 200,
display: 'flex',
alignItems: 'flex-start',
justifyContent: 'flex-start',
@ -253,5 +252,7 @@ const Layout = styled('main', {
position: 'absolute',
top: 0,
left: 0,
width: '100%',
height: '100%',
},
})

View file

@ -139,7 +139,7 @@ export const ToolsPanel = React.memo((): JSX.Element => {
})
const ToolsPanelContainer = styled('div', {
position: 'fixed',
position: 'absolute',
bottom: 0,
left: 0,
right: 0,

View file

@ -2231,11 +2231,11 @@ export class TLDrawState extends StateManager<Data> {
}
onPinchEnd: TLPinchEventHandler = () => {
if (this.state.settings.isZoomSnap) {
const i = Math.round((this.pageState.camera.zoom * 100) / 25)
const nextZoom = TLDR.getCameraZoom(i * 0.25)
this.zoomTo(nextZoom, inputs.pointer?.point)
}
// if (this.state.settings.isZoomSnap) {
// const i = Math.round((this.pageState.camera.zoom * 100) / 25)
// const nextZoom = TLDR.getCameraZoom(i * 0.25)
// this.zoomTo(nextZoom, inputs.pointer?.point)
// }
this.setStatus(this.appState.status.previous)
}

View file

@ -1,2 +1,3 @@
import '@testing-library/jest-dom/extend-expect'
import "fake-indexeddb/auto"
import 'fake-indexeddb/auto'
global.ResizeObserver = require('resize-observer-polyfill')

View file

@ -11417,6 +11417,11 @@ require_optional@^1.0.1:
resolve-from "^2.0.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:
version "2.0.0"
resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-2.0.0.tgz#00a9f7387556e27038eae232caa372a6a59b665a"