Adds space panning, tweak css for performance
This commit is contained in:
parent
5a99f5e49c
commit
f8cb7f03b6
17 changed files with 308 additions and 239 deletions
|
@ -10,7 +10,7 @@ interface BoundsBgProps {
|
|||
rotation: number
|
||||
}
|
||||
|
||||
export function BoundsBg({ bounds, rotation }: BoundsBgProps): JSX.Element {
|
||||
export const BoundsBg = React.memo(({ bounds, rotation }: BoundsBgProps): JSX.Element => {
|
||||
const events = useBoundsEvents()
|
||||
|
||||
return (
|
||||
|
@ -20,4 +20,4 @@ export function BoundsBg({ bounds, rotation }: BoundsBgProps): JSX.Element {
|
|||
</SVGContainer>
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
})
|
||||
|
|
|
@ -17,88 +17,83 @@ interface BoundsProps {
|
|||
viewportWidth: number
|
||||
}
|
||||
|
||||
export function Bounds({
|
||||
zoom,
|
||||
bounds,
|
||||
viewportWidth,
|
||||
rotation,
|
||||
isHidden,
|
||||
isLocked,
|
||||
}: BoundsProps): JSX.Element {
|
||||
// Touch target size
|
||||
const targetSize = (viewportWidth < 768 ? 16 : 8) / zoom
|
||||
// Handle size
|
||||
const size = 8 / zoom
|
||||
export const Bounds = React.memo(
|
||||
({ zoom, bounds, viewportWidth, rotation, isHidden, isLocked }: BoundsProps): JSX.Element => {
|
||||
// Touch target size
|
||||
const targetSize = (viewportWidth < 768 ? 16 : 8) / zoom
|
||||
// Handle size
|
||||
const size = 8 / zoom
|
||||
|
||||
const smallDimension = Math.min(bounds.width, bounds.height) * zoom
|
||||
// If the bounds are small, don't show the rotate handle
|
||||
const showRotateHandle = !isHidden && !isLocked && smallDimension > 32
|
||||
// If the bounds are very small, don't show the corner handles
|
||||
const showHandles = !isHidden && !isLocked && smallDimension > 16
|
||||
const smallDimension = Math.min(bounds.width, bounds.height) * zoom
|
||||
// If the bounds are small, don't show the rotate handle
|
||||
const showRotateHandle = !isHidden && !isLocked && smallDimension > 32
|
||||
// If the bounds are very small, don't show the corner handles
|
||||
const showHandles = !isHidden && !isLocked && smallDimension > 16
|
||||
|
||||
return (
|
||||
<Container bounds={bounds} rotation={rotation}>
|
||||
<SVGContainer opacity={isHidden ? 0 : 1}>
|
||||
<CenterHandle bounds={bounds} isLocked={isLocked} />
|
||||
<EdgeHandle
|
||||
targetSize={targetSize}
|
||||
size={size}
|
||||
bounds={bounds}
|
||||
edge={TLBoundsEdge.Top}
|
||||
isHidden={!showHandles}
|
||||
/>
|
||||
<EdgeHandle
|
||||
targetSize={targetSize}
|
||||
size={size}
|
||||
bounds={bounds}
|
||||
edge={TLBoundsEdge.Right}
|
||||
isHidden={!showHandles}
|
||||
/>
|
||||
<EdgeHandle
|
||||
targetSize={targetSize}
|
||||
size={size}
|
||||
bounds={bounds}
|
||||
edge={TLBoundsEdge.Bottom}
|
||||
isHidden={!showHandles}
|
||||
/>
|
||||
<EdgeHandle
|
||||
targetSize={targetSize}
|
||||
size={size}
|
||||
bounds={bounds}
|
||||
edge={TLBoundsEdge.Left}
|
||||
isHidden={!showHandles}
|
||||
/>
|
||||
<CornerHandle
|
||||
targetSize={targetSize}
|
||||
size={size}
|
||||
bounds={bounds}
|
||||
corner={TLBoundsCorner.TopLeft}
|
||||
/>
|
||||
<CornerHandle
|
||||
targetSize={targetSize}
|
||||
size={size}
|
||||
bounds={bounds}
|
||||
corner={TLBoundsCorner.TopRight}
|
||||
/>
|
||||
<CornerHandle
|
||||
targetSize={targetSize}
|
||||
size={size}
|
||||
bounds={bounds}
|
||||
corner={TLBoundsCorner.BottomRight}
|
||||
/>
|
||||
<CornerHandle
|
||||
targetSize={targetSize}
|
||||
size={size}
|
||||
bounds={bounds}
|
||||
corner={TLBoundsCorner.BottomLeft}
|
||||
/>
|
||||
<RotateHandle
|
||||
targetSize={targetSize}
|
||||
size={size}
|
||||
bounds={bounds}
|
||||
isHidden={!showHandles || !showRotateHandle}
|
||||
/>
|
||||
</SVGContainer>
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<Container bounds={bounds} rotation={rotation}>
|
||||
<SVGContainer opacity={isHidden ? 0 : 1}>
|
||||
<CenterHandle bounds={bounds} isLocked={isLocked} />
|
||||
<EdgeHandle
|
||||
targetSize={targetSize}
|
||||
size={size}
|
||||
bounds={bounds}
|
||||
edge={TLBoundsEdge.Top}
|
||||
isHidden={!showHandles}
|
||||
/>
|
||||
<EdgeHandle
|
||||
targetSize={targetSize}
|
||||
size={size}
|
||||
bounds={bounds}
|
||||
edge={TLBoundsEdge.Right}
|
||||
isHidden={!showHandles}
|
||||
/>
|
||||
<EdgeHandle
|
||||
targetSize={targetSize}
|
||||
size={size}
|
||||
bounds={bounds}
|
||||
edge={TLBoundsEdge.Bottom}
|
||||
isHidden={!showHandles}
|
||||
/>
|
||||
<EdgeHandle
|
||||
targetSize={targetSize}
|
||||
size={size}
|
||||
bounds={bounds}
|
||||
edge={TLBoundsEdge.Left}
|
||||
isHidden={!showHandles}
|
||||
/>
|
||||
<CornerHandle
|
||||
targetSize={targetSize}
|
||||
size={size}
|
||||
bounds={bounds}
|
||||
corner={TLBoundsCorner.TopLeft}
|
||||
/>
|
||||
<CornerHandle
|
||||
targetSize={targetSize}
|
||||
size={size}
|
||||
bounds={bounds}
|
||||
corner={TLBoundsCorner.TopRight}
|
||||
/>
|
||||
<CornerHandle
|
||||
targetSize={targetSize}
|
||||
size={size}
|
||||
bounds={bounds}
|
||||
corner={TLBoundsCorner.BottomRight}
|
||||
/>
|
||||
<CornerHandle
|
||||
targetSize={targetSize}
|
||||
size={size}
|
||||
bounds={bounds}
|
||||
corner={TLBoundsCorner.BottomLeft}
|
||||
/>
|
||||
<RotateHandle
|
||||
targetSize={targetSize}
|
||||
size={size}
|
||||
bounds={bounds}
|
||||
isHidden={!showHandles || !showRotateHandle}
|
||||
/>
|
||||
</SVGContainer>
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
)
|
||||
|
|
|
@ -59,14 +59,15 @@ export function Canvas<T extends TLShape, M extends Record<string, unknown>>({
|
|||
|
||||
useSafariFocusOutFix()
|
||||
|
||||
usePreventNavigation(rCanvas)
|
||||
usePreventNavigation(rCanvas, inputs.bounds.width)
|
||||
|
||||
const events = useCanvasEvents()
|
||||
|
||||
useCameraCss(rLayer, rContainer, pageState)
|
||||
|
||||
const preventScrolling = React.useCallback((e: React.UIEvent<HTMLDivElement, UIEvent>) => {
|
||||
e.currentTarget.scrollTo(0, 0)
|
||||
e.preventDefault()
|
||||
// e.currentTarget.scrollTo(0, 0)
|
||||
}, [])
|
||||
|
||||
useKeyEvents()
|
||||
|
|
|
@ -31,7 +31,7 @@ export const ShapeNode = React.memo(
|
|||
meta={meta}
|
||||
/>
|
||||
{children &&
|
||||
children.map((childNode, i) => (
|
||||
children.map((childNode) => (
|
||||
<ShapeNode key={childNode.shape.id} utils={utils} {...childNode} />
|
||||
))}
|
||||
</>
|
||||
|
|
|
@ -9,27 +9,34 @@ export function useCameraCss(
|
|||
) {
|
||||
// Update the tl-zoom CSS variable when the zoom changes
|
||||
const rZoom = React.useRef(pageState.camera.zoom)
|
||||
const rPoint = React.useRef(pageState.camera.point)
|
||||
|
||||
React.useLayoutEffect(() => {
|
||||
const {
|
||||
zoom,
|
||||
point: [x, y],
|
||||
} = pageState.camera
|
||||
const { zoom, point } = pageState.camera
|
||||
|
||||
if (zoom !== rZoom.current) {
|
||||
rZoom.current = zoom
|
||||
const didZoom = zoom !== rZoom.current
|
||||
const didPan = point !== rPoint.current
|
||||
|
||||
const container = containerRef.current
|
||||
rZoom.current = zoom
|
||||
rPoint.current = point
|
||||
|
||||
if (container) {
|
||||
container.style.setProperty('--tl-zoom', zoom.toString())
|
||||
if (didZoom || didPan) {
|
||||
// If we zoomed, set the CSS variable for the zoom
|
||||
if (didZoom) {
|
||||
const container = containerRef.current
|
||||
if (container) {
|
||||
container.style.setProperty('--tl-zoom', zoom.toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const layer = layerRef.current
|
||||
|
||||
if (layer) {
|
||||
layer.style.setProperty('transform', `scale(${zoom}) translate(${x}px, ${y}px)`)
|
||||
// Either way, position the layer
|
||||
const layer = layerRef.current
|
||||
if (layer) {
|
||||
layer.style.setProperty(
|
||||
'transform',
|
||||
`scale(${zoom}) translateX(${point[0]}px) translateY(${point[1]}px)`
|
||||
)
|
||||
}
|
||||
}
|
||||
}, [pageState.camera])
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import Vec from '@tldraw/vec'
|
||||
import * as React from 'react'
|
||||
import { useTLContext } from './useTLContext'
|
||||
|
||||
|
@ -10,8 +11,9 @@ export function useCanvasEvents() {
|
|||
if (!inputs.pointerIsValid(e)) return
|
||||
e.currentTarget.setPointerCapture(e.pointerId)
|
||||
|
||||
const info = inputs.pointerDown(e, 'canvas')
|
||||
|
||||
if (e.button === 0) {
|
||||
const info = inputs.pointerDown(e, 'canvas')
|
||||
callbacks.onPointCanvas?.(info, e)
|
||||
callbacks.onPointerDown?.(info, e)
|
||||
}
|
||||
|
@ -22,11 +24,12 @@ export function useCanvasEvents() {
|
|||
const onPointerMove = React.useCallback(
|
||||
(e: React.PointerEvent) => {
|
||||
if (!inputs.pointerIsValid(e)) return
|
||||
const info = inputs.pointerMove(e, 'canvas')
|
||||
|
||||
if (e.currentTarget.hasPointerCapture(e.pointerId)) {
|
||||
const info = inputs.pointerMove(e, 'canvas')
|
||||
callbacks.onDragCanvas?.(info, e)
|
||||
}
|
||||
const info = inputs.pointerMove(e, 'canvas')
|
||||
|
||||
callbacks.onPointerMove?.(info, e)
|
||||
},
|
||||
[callbacks, inputs]
|
||||
|
|
|
@ -25,7 +25,7 @@ export function usePosition(bounds: TLBounds, rotation = 0, zIndex = 0) {
|
|||
`calc(${Math.floor(bounds.height)}px + (var(--tl-padding) * 2))`
|
||||
)
|
||||
|
||||
elm.style.setProperty('z-index', zIndex + '')
|
||||
// elm.style.setProperty('z-index', zIndex + '')
|
||||
}, [bounds, rotation])
|
||||
|
||||
return rBounds
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
/* eslint-disable @typescript-eslint/ban-ts-comment */
|
||||
import * as React from 'react'
|
||||
|
||||
export function usePreventNavigation(rCanvas: React.RefObject<HTMLDivElement>): void {
|
||||
export function usePreventNavigation(
|
||||
rCanvas: React.RefObject<HTMLDivElement>,
|
||||
width: number
|
||||
): void {
|
||||
React.useEffect(() => {
|
||||
const preventGestureNavigation = (event: TouchEvent) => {
|
||||
event.preventDefault()
|
||||
|
@ -17,10 +20,7 @@ export function usePreventNavigation(rCanvas: React.RefObject<HTMLDivElement>):
|
|||
// if the touch area overlaps with the screen edges
|
||||
// it's likely to trigger the navigation. We prevent the
|
||||
// touchstart event in that case.
|
||||
if (
|
||||
touchXPosition - touchXRadius < 10 ||
|
||||
touchXPosition + touchXRadius > window.innerWidth - 10
|
||||
) {
|
||||
if (touchXPosition - touchXRadius < 10 || touchXPosition + touchXRadius > width - 10) {
|
||||
event.preventDefault()
|
||||
}
|
||||
}
|
||||
|
@ -56,5 +56,5 @@ export function usePreventNavigation(rCanvas: React.RefObject<HTMLDivElement>):
|
|||
elm.removeEventListener('touchstart', preventNavigation)
|
||||
}
|
||||
}
|
||||
}, [rCanvas])
|
||||
}, [rCanvas, width])
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import * as React from 'react'
|
||||
import type { TLPage, TLPageState, TLShape, TLBounds, TLShapeUtils, TLBinding } from '+types'
|
||||
import Utils from '+utils'
|
||||
import { useTLContext } from '+hooks'
|
||||
|
@ -13,6 +14,7 @@ export function useSelection<T extends TLShape, E extends Element>(
|
|||
) {
|
||||
const { rSelectionBounds } = useTLContext()
|
||||
const { selectedIds } = pageState
|
||||
const rPrevBounds = React.useRef<TLBounds>()
|
||||
|
||||
let bounds: TLBounds | undefined = undefined
|
||||
let rotation = 0
|
||||
|
@ -62,5 +64,20 @@ export function useSelection<T extends TLShape, E extends Element>(
|
|||
rSelectionBounds.current = null
|
||||
}
|
||||
|
||||
const prevBounds = rPrevBounds.current
|
||||
|
||||
if (!prevBounds || !bounds) {
|
||||
rPrevBounds.current = bounds
|
||||
} else if (bounds) {
|
||||
if (
|
||||
prevBounds.minX === bounds.minX &&
|
||||
prevBounds.minY === bounds.minY &&
|
||||
prevBounds.maxX === bounds.maxX &&
|
||||
prevBounds.maxY === bounds.maxY
|
||||
) {
|
||||
bounds = rPrevBounds.current
|
||||
}
|
||||
}
|
||||
|
||||
return { bounds, rotation, isLocked }
|
||||
}
|
||||
|
|
|
@ -135,19 +135,20 @@ const tlcss = css`
|
|||
|
||||
.tl-canvas {
|
||||
position: absolute;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
touch-action: none;
|
||||
pointer-events: all;
|
||||
overflow: clip;
|
||||
}
|
||||
|
||||
.tl-layer {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 0;
|
||||
width: 0;
|
||||
position: fixed;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
height: 0px;
|
||||
width: 0px;
|
||||
contain: layout style size;
|
||||
}
|
||||
|
||||
.tl-absolute {
|
||||
|
@ -166,13 +167,14 @@ const tlcss = css`
|
|||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
overflow: clip;
|
||||
overflow: hidden;
|
||||
contain: layout style size;
|
||||
}
|
||||
|
||||
.tl-positioned-svg {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: clip;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.tl-positioned-div {
|
||||
|
@ -181,7 +183,7 @@ const tlcss = css`
|
|||
height: 100%;
|
||||
overflow: hidden;
|
||||
padding: var(--tl-padding);
|
||||
overflow: clip;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.tl-counter-scaled {
|
||||
|
@ -263,6 +265,14 @@ const tlcss = css`
|
|||
|
||||
.tl-bounds {
|
||||
pointer-events: none;
|
||||
contain: layout style size;
|
||||
}
|
||||
|
||||
.tl-bounds-bg {
|
||||
stroke: none;
|
||||
fill: var(--tl-selectFill);
|
||||
pointer-events: all;
|
||||
contain: layout style size;
|
||||
}
|
||||
|
||||
.tl-bounds-center {
|
||||
|
@ -271,17 +281,12 @@ const tlcss = css`
|
|||
stroke-width: calc(1.5px * var(--tl-scale));
|
||||
}
|
||||
|
||||
.tl-bounds-bg {
|
||||
stroke: none;
|
||||
fill: var(--tl-selectFill);
|
||||
pointer-events: all;
|
||||
}
|
||||
|
||||
.tl-brush {
|
||||
fill: var(--tl-brushFill);
|
||||
stroke: var(--tl-brushStroke);
|
||||
stroke-width: calc(1px * var(--tl-scale));
|
||||
pointer-events: none;
|
||||
contain: layout style size;
|
||||
}
|
||||
|
||||
.tl-dot {
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||
import * as React from 'react'
|
||||
import { useTLContext } from './useTLContext'
|
||||
import { useGesture } from '@use-gesture/react'
|
||||
import { useGesture, usePinch, useWheel } from '@use-gesture/react'
|
||||
import { Vec } from '@tldraw/vec'
|
||||
|
||||
// Capture zoom gestures (pinches, wheels and pans)
|
||||
|
@ -31,22 +31,38 @@ export function useZoomEvents<T extends HTMLElement>(zoom: number, ref: React.Re
|
|||
}
|
||||
}, [])
|
||||
|
||||
React.useEffect(() => {
|
||||
const elm = ref.current
|
||||
|
||||
function handleWheel(e: WheelEvent) {
|
||||
if (e.altKey) {
|
||||
const point = inputs.pointer?.point ?? [inputs.bounds.width / 2, inputs.bounds.height / 2]
|
||||
|
||||
const info = inputs.pinch(point, point)
|
||||
|
||||
callbacks.onZoom?.({ ...info, delta: [...point, e.deltaY] }, e)
|
||||
return
|
||||
}
|
||||
|
||||
e.preventDefault()
|
||||
|
||||
if (inputs.isPinching) return
|
||||
|
||||
if (Vec.isEqual([e.deltaX, e.deltaY], [0, 0])) return
|
||||
|
||||
const info = inputs.pan([e.deltaX, e.deltaY], e as WheelEvent)
|
||||
|
||||
callbacks.onPan?.(info, e)
|
||||
}
|
||||
|
||||
elm?.addEventListener('wheel', handleWheel, { passive: false })
|
||||
return () => {
|
||||
elm?.removeEventListener('wheel', handleWheel)
|
||||
}
|
||||
}, [ref, callbacks, inputs])
|
||||
|
||||
useGesture(
|
||||
{
|
||||
onWheel: ({ event: e, delta }) => {
|
||||
const elm = ref.current
|
||||
|
||||
if (!elm || !(e.target === elm || elm.contains(e.target as Node))) return
|
||||
|
||||
e.preventDefault()
|
||||
|
||||
if (inputs.isPinching) return
|
||||
|
||||
if (Vec.isEqual(delta, [0, 0])) return
|
||||
|
||||
const info = inputs.pan(delta, e as WheelEvent)
|
||||
callbacks.onPan?.(info, e)
|
||||
},
|
||||
onPinchStart: ({ origin, event }) => {
|
||||
const elm = ref.current
|
||||
|
||||
|
|
|
@ -61,6 +61,7 @@ export class Inputs {
|
|||
ctrlKey,
|
||||
metaKey: Utils.isDarwin() ? metaKey : ctrlKey,
|
||||
altKey,
|
||||
spaceKey: this.keys[' '],
|
||||
}
|
||||
|
||||
this.pointer = info
|
||||
|
@ -84,6 +85,7 @@ export class Inputs {
|
|||
ctrlKey,
|
||||
metaKey: Utils.isDarwin() ? metaKey : ctrlKey,
|
||||
altKey,
|
||||
spaceKey: this.keys[' '],
|
||||
}
|
||||
|
||||
this.pointer = info
|
||||
|
@ -116,6 +118,7 @@ export class Inputs {
|
|||
ctrlKey,
|
||||
metaKey: Utils.isDarwin() ? metaKey : ctrlKey,
|
||||
altKey,
|
||||
spaceKey: this.keys[' '],
|
||||
}
|
||||
|
||||
this.pointer = info
|
||||
|
@ -141,6 +144,7 @@ export class Inputs {
|
|||
ctrlKey,
|
||||
metaKey: Utils.isDarwin() ? metaKey : ctrlKey,
|
||||
altKey,
|
||||
spaceKey: this.keys[' '],
|
||||
}
|
||||
|
||||
this.pointer = info
|
||||
|
@ -167,6 +171,7 @@ export class Inputs {
|
|||
ctrlKey,
|
||||
metaKey: Utils.isDarwin() ? metaKey : ctrlKey,
|
||||
altKey,
|
||||
spaceKey: this.keys[' '],
|
||||
}
|
||||
|
||||
this.pointer = info
|
||||
|
@ -195,6 +200,7 @@ export class Inputs {
|
|||
ctrlKey,
|
||||
metaKey: Utils.isDarwin() ? metaKey : ctrlKey,
|
||||
altKey,
|
||||
spaceKey: this.keys[' '],
|
||||
}
|
||||
|
||||
this.pointer = info
|
||||
|
@ -225,6 +231,7 @@ export class Inputs {
|
|||
ctrlKey,
|
||||
metaKey: Utils.isDarwin() ? metaKey : ctrlKey,
|
||||
altKey,
|
||||
spaceKey: this.keys[' '],
|
||||
}
|
||||
|
||||
this.pointer = info
|
||||
|
@ -248,6 +255,7 @@ export class Inputs {
|
|||
ctrlKey,
|
||||
metaKey,
|
||||
altKey,
|
||||
spaceKey: this.keys[' '],
|
||||
}
|
||||
|
||||
this.pointer = info
|
||||
|
@ -275,6 +283,7 @@ export class Inputs {
|
|||
ctrlKey,
|
||||
metaKey,
|
||||
altKey,
|
||||
spaceKey: this.keys[' '],
|
||||
}
|
||||
|
||||
this.pointer = info
|
||||
|
@ -348,6 +357,7 @@ export class Inputs {
|
|||
ctrlKey,
|
||||
metaKey: Utils.isDarwin() ? metaKey : ctrlKey,
|
||||
altKey,
|
||||
spaceKey: this.keys[' '],
|
||||
}
|
||||
|
||||
this.pointer = info
|
||||
|
|
|
@ -265,6 +265,7 @@ export interface TLPointerInfo<T extends string = string> {
|
|||
ctrlKey: boolean
|
||||
metaKey: boolean
|
||||
altKey: boolean
|
||||
spaceKey: boolean
|
||||
}
|
||||
|
||||
export interface TLKeyboardInfo {
|
||||
|
|
|
@ -14,7 +14,7 @@ export default function Editor(props: TLDrawProps): JSX.Element {
|
|||
|
||||
return (
|
||||
<div className="tldraw">
|
||||
<TLDraw id="tldraw" {...props} onMount={handleMount} autofocus />
|
||||
<TLDraw id="tldraw1" {...props} onMount={handleMount} autofocus />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -4,6 +4,8 @@ html,
|
|||
}
|
||||
|
||||
body {
|
||||
position: fixed;
|
||||
overflow: hidden;
|
||||
overscroll-behavior: none;
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
|
|
|
@ -176,7 +176,7 @@ function InnerTldraw({
|
|||
|
||||
// Hide indicators when not using the select tool, or when in session
|
||||
const hideIndicators =
|
||||
(isInSession && tlstate.appState.status.current !== TLDrawStatus.Brushing) || !isSelecting
|
||||
(isInSession && tlstate.appState.status !== TLDrawStatus.Brushing) || !isSelecting
|
||||
|
||||
// Custom rendering meta, with dark mode for shapes
|
||||
const meta = React.useMemo(() => ({ isDarkMode }), [isDarkMode])
|
||||
|
@ -215,87 +215,89 @@ function InnerTldraw({
|
|||
<div ref={rWrapper} tabIndex={0}>
|
||||
<div className={layout()}>
|
||||
<OneOff focusableRef={rWrapper} autofocus={autofocus} />
|
||||
<ContextMenu>
|
||||
<Renderer
|
||||
id={id}
|
||||
containerRef={rWrapper}
|
||||
page={page}
|
||||
pageState={pageState}
|
||||
users={users}
|
||||
userId={tlstate.state.room?.userId}
|
||||
shapeUtils={tldrawShapeUtils}
|
||||
theme={theme}
|
||||
meta={meta}
|
||||
hideBounds={hideBounds}
|
||||
hideHandles={hideHandles}
|
||||
hideIndicators={hideIndicators}
|
||||
onPinchStart={tlstate.onPinchStart}
|
||||
onPinchEnd={tlstate.onPinchEnd}
|
||||
onPinch={tlstate.onPinch}
|
||||
onPan={tlstate.onPan}
|
||||
onZoom={tlstate.onZoom}
|
||||
onPointerDown={tlstate.onPointerDown}
|
||||
onPointerMove={tlstate.onPointerMove}
|
||||
onPointerUp={tlstate.onPointerUp}
|
||||
onPointCanvas={tlstate.onPointCanvas}
|
||||
onDoubleClickCanvas={tlstate.onDoubleClickCanvas}
|
||||
onRightPointCanvas={tlstate.onRightPointCanvas}
|
||||
onDragCanvas={tlstate.onDragCanvas}
|
||||
onReleaseCanvas={tlstate.onReleaseCanvas}
|
||||
onPointShape={tlstate.onPointShape}
|
||||
onDoubleClickShape={tlstate.onDoubleClickShape}
|
||||
onRightPointShape={tlstate.onRightPointShape}
|
||||
onDragShape={tlstate.onDragShape}
|
||||
onHoverShape={tlstate.onHoverShape}
|
||||
onUnhoverShape={tlstate.onUnhoverShape}
|
||||
onReleaseShape={tlstate.onReleaseShape}
|
||||
onPointBounds={tlstate.onPointBounds}
|
||||
onDoubleClickBounds={tlstate.onDoubleClickBounds}
|
||||
onRightPointBounds={tlstate.onRightPointBounds}
|
||||
onDragBounds={tlstate.onDragBounds}
|
||||
onHoverBounds={tlstate.onHoverBounds}
|
||||
onUnhoverBounds={tlstate.onUnhoverBounds}
|
||||
onReleaseBounds={tlstate.onReleaseBounds}
|
||||
onPointBoundsHandle={tlstate.onPointBoundsHandle}
|
||||
onDoubleClickBoundsHandle={tlstate.onDoubleClickBoundsHandle}
|
||||
onRightPointBoundsHandle={tlstate.onRightPointBoundsHandle}
|
||||
onDragBoundsHandle={tlstate.onDragBoundsHandle}
|
||||
onHoverBoundsHandle={tlstate.onHoverBoundsHandle}
|
||||
onUnhoverBoundsHandle={tlstate.onUnhoverBoundsHandle}
|
||||
onReleaseBoundsHandle={tlstate.onReleaseBoundsHandle}
|
||||
onPointHandle={tlstate.onPointHandle}
|
||||
onDoubleClickHandle={tlstate.onDoubleClickHandle}
|
||||
onRightPointHandle={tlstate.onRightPointHandle}
|
||||
onDragHandle={tlstate.onDragHandle}
|
||||
onHoverHandle={tlstate.onHoverHandle}
|
||||
onUnhoverHandle={tlstate.onUnhoverHandle}
|
||||
onReleaseHandle={tlstate.onReleaseHandle}
|
||||
onError={tlstate.onError}
|
||||
onRenderCountChange={tlstate.onRenderCountChange}
|
||||
onShapeChange={tlstate.onShapeChange}
|
||||
onShapeBlur={tlstate.onShapeBlur}
|
||||
onBoundsChange={tlstate.updateBounds}
|
||||
onKeyDown={tlstate.onKeyDown}
|
||||
onKeyUp={tlstate.onKeyUp}
|
||||
/>
|
||||
</ContextMenu>
|
||||
{isFocusMode ? (
|
||||
<div className={unfocusButton()}>
|
||||
<button className={iconButton({ bp: breakpoints })} onClick={tlstate.toggleFocusMode}>
|
||||
<DotFilledIcon />
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<div className={menuButtons()}>
|
||||
{showMenu && <Menu />}
|
||||
{showPages && <PagePanel />}
|
||||
{/* <ContextMenu> */}
|
||||
<Renderer
|
||||
id={id}
|
||||
containerRef={rWrapper}
|
||||
page={page}
|
||||
pageState={pageState}
|
||||
users={users}
|
||||
userId={tlstate.state.room?.userId}
|
||||
shapeUtils={tldrawShapeUtils}
|
||||
theme={theme}
|
||||
meta={meta}
|
||||
hideBounds={hideBounds}
|
||||
hideHandles={hideHandles}
|
||||
hideIndicators={hideIndicators}
|
||||
onPinchStart={tlstate.onPinchStart}
|
||||
onPinchEnd={tlstate.onPinchEnd}
|
||||
onPinch={tlstate.onPinch}
|
||||
onPan={tlstate.onPan}
|
||||
onZoom={tlstate.onZoom}
|
||||
onPointerDown={tlstate.onPointerDown}
|
||||
onPointerMove={tlstate.onPointerMove}
|
||||
onPointerUp={tlstate.onPointerUp}
|
||||
onPointCanvas={tlstate.onPointCanvas}
|
||||
onDoubleClickCanvas={tlstate.onDoubleClickCanvas}
|
||||
onRightPointCanvas={tlstate.onRightPointCanvas}
|
||||
onDragCanvas={tlstate.onDragCanvas}
|
||||
onReleaseCanvas={tlstate.onReleaseCanvas}
|
||||
onPointShape={tlstate.onPointShape}
|
||||
onDoubleClickShape={tlstate.onDoubleClickShape}
|
||||
onRightPointShape={tlstate.onRightPointShape}
|
||||
onDragShape={tlstate.onDragShape}
|
||||
onHoverShape={tlstate.onHoverShape}
|
||||
onUnhoverShape={tlstate.onUnhoverShape}
|
||||
onReleaseShape={tlstate.onReleaseShape}
|
||||
onPointBounds={tlstate.onPointBounds}
|
||||
onDoubleClickBounds={tlstate.onDoubleClickBounds}
|
||||
onRightPointBounds={tlstate.onRightPointBounds}
|
||||
onDragBounds={tlstate.onDragBounds}
|
||||
onHoverBounds={tlstate.onHoverBounds}
|
||||
onUnhoverBounds={tlstate.onUnhoverBounds}
|
||||
onReleaseBounds={tlstate.onReleaseBounds}
|
||||
onPointBoundsHandle={tlstate.onPointBoundsHandle}
|
||||
onDoubleClickBoundsHandle={tlstate.onDoubleClickBoundsHandle}
|
||||
onRightPointBoundsHandle={tlstate.onRightPointBoundsHandle}
|
||||
onDragBoundsHandle={tlstate.onDragBoundsHandle}
|
||||
onHoverBoundsHandle={tlstate.onHoverBoundsHandle}
|
||||
onUnhoverBoundsHandle={tlstate.onUnhoverBoundsHandle}
|
||||
onReleaseBoundsHandle={tlstate.onReleaseBoundsHandle}
|
||||
onPointHandle={tlstate.onPointHandle}
|
||||
onDoubleClickHandle={tlstate.onDoubleClickHandle}
|
||||
onRightPointHandle={tlstate.onRightPointHandle}
|
||||
onDragHandle={tlstate.onDragHandle}
|
||||
onHoverHandle={tlstate.onHoverHandle}
|
||||
onUnhoverHandle={tlstate.onUnhoverHandle}
|
||||
onReleaseHandle={tlstate.onReleaseHandle}
|
||||
onError={tlstate.onError}
|
||||
onRenderCountChange={tlstate.onRenderCountChange}
|
||||
onShapeChange={tlstate.onShapeChange}
|
||||
onShapeBlur={tlstate.onShapeBlur}
|
||||
onBoundsChange={tlstate.updateBounds}
|
||||
onKeyDown={tlstate.onKeyDown}
|
||||
onKeyUp={tlstate.onKeyUp}
|
||||
/>
|
||||
{/* </ContextMenu> */}
|
||||
<div className={ui()}>
|
||||
{isFocusMode ? (
|
||||
<div className={unfocusButton()}>
|
||||
<button className={iconButton({ bp: breakpoints })} onClick={tlstate.toggleFocusMode}>
|
||||
<DotFilledIcon />
|
||||
</button>
|
||||
</div>
|
||||
<div className={spacer()} />
|
||||
<StylePanel />
|
||||
<ToolsPanel />
|
||||
</>
|
||||
)}
|
||||
) : (
|
||||
<>
|
||||
<div className={menuButtons()}>
|
||||
{showMenu && <Menu />}
|
||||
{showPages && <PagePanel />}
|
||||
</div>
|
||||
<div className={spacer()} />
|
||||
<StylePanel />
|
||||
<ToolsPanel />
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
@ -331,28 +333,33 @@ const layout = css({
|
|||
maxHeight: '100%',
|
||||
maxWidth: '100%',
|
||||
overflow: 'hidden',
|
||||
padding: '8px 8px 0 8px',
|
||||
display: 'flex',
|
||||
alignItems: 'flex-start',
|
||||
justifyContent: 'flex-start',
|
||||
boxSizing: 'border-box',
|
||||
pointerEvents: 'none',
|
||||
outline: 'none',
|
||||
zIndex: 1,
|
||||
border: '1px solid $blurred',
|
||||
|
||||
'&:focus': {
|
||||
border: '1px solid $focused',
|
||||
},
|
||||
|
||||
'& > *': {
|
||||
pointerEvents: 'all',
|
||||
},
|
||||
|
||||
'& .tl-container': {
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
height: '100%',
|
||||
width: '100%',
|
||||
zIndex: 1,
|
||||
},
|
||||
})
|
||||
|
||||
const ui = css({
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
height: '100%',
|
||||
width: '100%',
|
||||
padding: '8px 8px 0 8px',
|
||||
display: 'flex',
|
||||
alignItems: 'flex-start',
|
||||
justifyContent: 'flex-start',
|
||||
pointerEvents: 'none',
|
||||
zIndex: 2,
|
||||
'& > *': {
|
||||
pointerEvents: 'all',
|
||||
},
|
||||
})
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@ const pointsBoundsCache = new WeakMap<DrawShape['points'], TLBounds>([])
|
|||
const shapeBoundsCache = new Map<string, TLBounds>()
|
||||
const rotatedCache = new WeakMap<DrawShape, number[][]>([])
|
||||
const pointCache: Record<string, number[]> = {}
|
||||
|
||||
export const Draw = new ShapeUtil<DrawShape, SVGSVGElement, TLDrawMeta>(() => ({
|
||||
type: TLDrawShapeType.Draw,
|
||||
|
||||
|
@ -139,6 +140,8 @@ export const Draw = new ShapeUtil<DrawShape, SVGSVGElement, TLDrawMeta>(() => ({
|
|||
return getSolidStrokePathData(shape, false)
|
||||
}, [points])
|
||||
|
||||
if (!shape) return null
|
||||
|
||||
const bounds = this.getBounds(shape)
|
||||
|
||||
const verySmall = bounds.width < 4 && bounds.height < 4
|
||||
|
@ -151,6 +154,8 @@ export const Draw = new ShapeUtil<DrawShape, SVGSVGElement, TLDrawMeta>(() => ({
|
|||
},
|
||||
|
||||
getBounds(shape: DrawShape): TLBounds {
|
||||
// return Utils.translateBounds(Utils.getBoundsFromPoints(shape.points), shape.point)
|
||||
|
||||
// The goal here is to avoid recalculating the bounds from the
|
||||
// points array, which is expensive. However, we still need a
|
||||
// new bounds if the point has changed, but we will reuse the
|
||||
|
|
Loading…
Reference in a new issue