Fix stale inputs reference (#92)
* Add `onMount` to Renderer to share inputs * Removes brush updater * Update brush.test.tsx
This commit is contained in:
parent
cdb7c74f8e
commit
8ae625baef
13 changed files with 77 additions and 81 deletions
|
@ -9,7 +9,6 @@ This package contains the core of the [tldraw](https://tldraw.com) library. It i
|
|||
- [`Utils`](#utils)
|
||||
- [`Vec`](#vec)
|
||||
- [`Svg`](#svg)
|
||||
- [`brushUpdater`](#brushupdater)
|
||||
- [`Intersect`](#intersect)
|
||||
|
||||
## Installation
|
||||
|
@ -160,17 +159,6 @@ An object that describes a relationship between two shapes on the page.
|
|||
|
||||
The `TLShapeUtil` is an abstract class that you can extend to create utilities for your custom shapes. See [Guide: Create a Custom Shape](#create-a-custom-shape).
|
||||
|
||||
### `brushUpdater`
|
||||
|
||||
The `brushUpdater` is a special class instance that allows you to quickly update the selection brush rectangle.
|
||||
|
||||
| Method | Description |
|
||||
| ------- | --------------------------------------------------------------- |
|
||||
| `set` | a method that accepts either a `TLBounds` object or `undefined` |
|
||||
| `clear` | a method to hide the brush |
|
||||
|
||||
Normally, the renderer's brush will update in response to changes to `pageState.brush`; however, calling `brushUpdater.set` will produce a faster change in the brush rectangle. Calling `brushUpdater.set` will prevent the brush from any future updates from `pageState.brush`.
|
||||
|
||||
## `inputs`
|
||||
|
||||
A class instance that stores the current pointer position and pressed keys.
|
||||
|
|
|
@ -1,33 +0,0 @@
|
|||
import * as React from 'react'
|
||||
import type { TLBounds } from '+types'
|
||||
|
||||
export class BrushUpdater {
|
||||
ref = React.createRef<SVGSVGElement>()
|
||||
|
||||
isControlled = false
|
||||
|
||||
set(bounds?: TLBounds) {
|
||||
if (!this.isControlled) this.isControlled = true
|
||||
|
||||
if (!bounds) {
|
||||
this.clear()
|
||||
return
|
||||
}
|
||||
|
||||
const elm = this.ref?.current
|
||||
if (!elm) return
|
||||
|
||||
elm.setAttribute('opacity', '1')
|
||||
elm.setAttribute('transform', `translate(${bounds.minX.toString()}, ${bounds.minY.toString()})`)
|
||||
elm.setAttribute('width', bounds.width.toString())
|
||||
elm.setAttribute('height', bounds.height.toString())
|
||||
}
|
||||
|
||||
clear() {
|
||||
const elm = this.ref?.current
|
||||
if (!elm) return
|
||||
elm.setAttribute('opacity', '0')
|
||||
elm.setAttribute('width', '0')
|
||||
elm.setAttribute('height', '0')
|
||||
}
|
||||
}
|
|
@ -4,6 +4,17 @@ import { Brush } from './brush'
|
|||
|
||||
describe('brush', () => {
|
||||
test('mounts component without crashing', () => {
|
||||
renderWithSvg(<Brush />)
|
||||
renderWithSvg(
|
||||
<Brush
|
||||
brush={{
|
||||
minX: 0,
|
||||
maxX: 100,
|
||||
minY: 0,
|
||||
maxY: 100,
|
||||
width: 100,
|
||||
height: 100,
|
||||
}}
|
||||
/>
|
||||
)
|
||||
})
|
||||
})
|
||||
|
|
|
@ -1,12 +1,21 @@
|
|||
import { SVGContainer } from '+components'
|
||||
import { Container } from '+components/container'
|
||||
import type { TLBounds } from '+types'
|
||||
import * as React from 'react'
|
||||
import { BrushUpdater } from './BrushUpdater'
|
||||
|
||||
export const brushUpdater = new BrushUpdater()
|
||||
|
||||
export const Brush = React.memo((): JSX.Element | null => {
|
||||
export const Brush = React.memo(({ brush }: { brush: TLBounds }): JSX.Element | null => {
|
||||
return (
|
||||
<svg ref={brushUpdater.ref} opacity={0} width={0} height={0}>
|
||||
<rect className="tl-brush" x={0} y={0} width="100%" height="100%" />
|
||||
</svg>
|
||||
<Container bounds={brush} rotation={0}>
|
||||
<SVGContainer>
|
||||
<rect
|
||||
className="tl-brush"
|
||||
opacity={1}
|
||||
x={0}
|
||||
y={0}
|
||||
width={brush.width}
|
||||
height={brush.height}
|
||||
/>
|
||||
</SVGContainer>
|
||||
</Container>
|
||||
)
|
||||
})
|
||||
|
|
|
@ -1,2 +1 @@
|
|||
export * from './brush'
|
||||
export * from './BrushUpdater'
|
||||
|
|
|
@ -73,7 +73,7 @@ export function Canvas<T extends TLShape, M extends Record<string, unknown>>({
|
|||
hideHandles={hideHandles}
|
||||
meta={meta}
|
||||
/>
|
||||
<Brush />
|
||||
{pageState.brush && <Brush brush={pageState.brush} />}
|
||||
</div>
|
||||
</ErrorBoundary>
|
||||
</div>
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
export * from './renderer'
|
||||
export { brushUpdater } from './brush'
|
||||
export * from './svg-container'
|
||||
export * from './html-container'
|
||||
|
|
|
@ -55,6 +55,10 @@ export interface RendererProps<T extends TLShape, E extends Element = any, M = a
|
|||
* An object of custom options that should be passed to rendered shapes.
|
||||
*/
|
||||
meta?: M
|
||||
/**
|
||||
* A callback that receives the renderer's inputs manager.
|
||||
*/
|
||||
onMount?: (inputs: Inputs) => void
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -74,6 +78,7 @@ export function Renderer<T extends TLShape, E extends Element, M extends Record<
|
|||
hideHandles = false,
|
||||
hideIndicators = false,
|
||||
hideBounds = false,
|
||||
onMount,
|
||||
...rest
|
||||
}: RendererProps<T, E, M>): JSX.Element {
|
||||
useTLTheme(theme)
|
||||
|
@ -96,6 +101,10 @@ export function Renderer<T extends TLShape, E extends Element, M extends Record<
|
|||
inputs: new Inputs(),
|
||||
}))
|
||||
|
||||
React.useEffect(() => {
|
||||
onMount?.(context.inputs)
|
||||
}, [context])
|
||||
|
||||
return (
|
||||
<TLContext.Provider value={context as unknown as TLContextType<TLShape, Element>}>
|
||||
<Canvas
|
||||
|
|
|
@ -22,7 +22,7 @@ export interface TLPageState {
|
|||
point: number[]
|
||||
zoom: number
|
||||
}
|
||||
brush?: TLBounds
|
||||
brush?: TLBounds | null
|
||||
pointedId?: string | null
|
||||
hoveredId?: string | null
|
||||
editingId?: string | null
|
||||
|
|
|
@ -202,6 +202,7 @@ function InnerTldraw({
|
|||
onRenderCountChange={tlstate.onRenderCountChange}
|
||||
onShapeChange={tlstate.onShapeChange}
|
||||
onShapeBlur={tlstate.onShapeBlur}
|
||||
onMount={tlstate.handleMount}
|
||||
/>
|
||||
</ContextMenu>
|
||||
<MenuButtons>
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import * as React from 'react'
|
||||
import { inputs } from '@tldraw/core'
|
||||
import { useHotkeys } from 'react-hotkeys-hook'
|
||||
import { TLDrawShapeType } from '~types'
|
||||
import { useTLDrawContext } from '~hooks'
|
||||
|
@ -9,13 +8,11 @@ export function useKeyboardShortcuts() {
|
|||
|
||||
React.useEffect(() => {
|
||||
const handleKeyDown = (e: KeyboardEvent) => {
|
||||
const info = inputs.keydown(e)
|
||||
tlstate.onKeyDown(e.key, info)
|
||||
tlstate.onKeyDown(e.key)
|
||||
}
|
||||
|
||||
const handleKeyUp = (e: KeyboardEvent) => {
|
||||
const info = inputs.keyup(e)
|
||||
tlstate.onKeyUp(e.key, info)
|
||||
tlstate.onKeyUp(e.key)
|
||||
}
|
||||
|
||||
window.addEventListener('keydown', handleKeyDown)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { brushUpdater, Utils } from '@tldraw/core'
|
||||
import { Utils } from '@tldraw/core'
|
||||
import { Vec } from '@tldraw/vec'
|
||||
import { Data, Session, TLDrawPatch, TLDrawStatus } from '~types'
|
||||
import { TLDR } from '~state/tldr'
|
||||
|
@ -23,14 +23,11 @@ export class BrushSession implements Session {
|
|||
// Create a bounding box between the origin and the new point
|
||||
const brush = Utils.getBoundsFromPoints([origin, point])
|
||||
|
||||
brushUpdater.set(brush)
|
||||
|
||||
// Find ids of brushed shapes
|
||||
const hits = new Set<string>()
|
||||
const selectedIds = new Set(snapshot.selectedIds)
|
||||
|
||||
const page = TLDR.getPage(data, currentPageId)
|
||||
const pageState = TLDR.getPageState(data, currentPageId)
|
||||
|
||||
snapshot.shapesToTest.forEach(({ id, util, selectId }) => {
|
||||
if (selectedIds.has(id)) return
|
||||
|
@ -55,17 +52,18 @@ export class BrushSession implements Session {
|
|||
}
|
||||
})
|
||||
|
||||
if (
|
||||
selectedIds.size === pageState.selectedIds.length &&
|
||||
pageState.selectedIds.every((id) => selectedIds.has(id))
|
||||
) {
|
||||
return {}
|
||||
}
|
||||
// if (
|
||||
// selectedIds.size === pageState.selectedIds.length &&
|
||||
// pageState.selectedIds.every((id) => selectedIds.has(id))
|
||||
// ) {
|
||||
// return {}
|
||||
// }
|
||||
|
||||
return {
|
||||
document: {
|
||||
pageStates: {
|
||||
[currentPageId]: {
|
||||
brush,
|
||||
selectedIds: Array.from(selectedIds.values()),
|
||||
},
|
||||
},
|
||||
|
@ -79,6 +77,7 @@ export class BrushSession implements Session {
|
|||
document: {
|
||||
pageStates: {
|
||||
[currentPageId]: {
|
||||
brush: null,
|
||||
selectedIds: this.snapshot.selectedIds,
|
||||
},
|
||||
},
|
||||
|
@ -89,10 +88,12 @@ export class BrushSession implements Session {
|
|||
complete(data: Data) {
|
||||
const { currentPageId } = data.appState
|
||||
const pageState = TLDR.getPageState(data, currentPageId)
|
||||
|
||||
return {
|
||||
document: {
|
||||
pageStates: {
|
||||
[currentPageId]: {
|
||||
brush: null,
|
||||
selectedIds: [...pageState.selectedIds],
|
||||
},
|
||||
},
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
||||
import { StateManager } from 'rko'
|
||||
import {
|
||||
TLBoundsCorner,
|
||||
|
@ -5,15 +6,14 @@ import {
|
|||
TLBoundsEventHandler,
|
||||
TLBoundsHandleEventHandler,
|
||||
TLCanvasEventHandler,
|
||||
TLKeyboardInfo,
|
||||
TLPageState,
|
||||
TLPinchEventHandler,
|
||||
TLPointerEventHandler,
|
||||
TLWheelEventHandler,
|
||||
Utils,
|
||||
brushUpdater,
|
||||
TLPointerInfo,
|
||||
TLBounds,
|
||||
Inputs,
|
||||
} from '@tldraw/core'
|
||||
import { Vec } from '@tldraw/vec'
|
||||
import {
|
||||
|
@ -98,6 +98,8 @@ export class TLDrawState extends StateManager<Data> {
|
|||
private _onChange?: (tlstate: TLDrawState, data: Data, reason: string) => void
|
||||
private _onMount?: (tlstate: TLDrawState) => void
|
||||
|
||||
inputs?: Inputs
|
||||
|
||||
selectHistory: SelectHistory = {
|
||||
stack: [[]],
|
||||
pointer: 0,
|
||||
|
@ -271,6 +273,10 @@ export class TLDrawState extends StateManager<Data> {
|
|||
...data.document.pageStates[pageId],
|
||||
}
|
||||
|
||||
if (!nextPageState.brush) {
|
||||
delete nextPageState.brush
|
||||
}
|
||||
|
||||
if (nextPageState.hoveredId && !page.shapes[nextPageState.hoveredId]) {
|
||||
delete nextPageState.hoveredId
|
||||
}
|
||||
|
@ -339,6 +345,10 @@ export class TLDrawState extends StateManager<Data> {
|
|||
)
|
||||
}
|
||||
|
||||
handleMount = (inputs: Inputs): void => {
|
||||
this.inputs = inputs
|
||||
}
|
||||
|
||||
/* -------------------------------------------------- */
|
||||
/* Settings & UI */
|
||||
/* -------------------------------------------------- */
|
||||
|
@ -1357,10 +1367,10 @@ export class TLDrawState extends StateManager<Data> {
|
|||
|
||||
if (!session) return this
|
||||
|
||||
const result = session.complete(this.state, ...args)
|
||||
|
||||
this.session = undefined
|
||||
|
||||
const result = session.complete(this.state, ...args)
|
||||
|
||||
if (result === undefined) {
|
||||
this.isCreating = false
|
||||
|
||||
|
@ -1450,6 +1460,7 @@ export class TLDrawState extends StateManager<Data> {
|
|||
document: {
|
||||
pageStates: {
|
||||
[this.currentPageId]: {
|
||||
...result.document?.pageStates?.[this.currentPageId],
|
||||
editingId: null,
|
||||
},
|
||||
},
|
||||
|
@ -1939,7 +1950,6 @@ export class TLDrawState extends StateManager<Data> {
|
|||
}
|
||||
case TLDrawStatus.Brushing: {
|
||||
this.cancelSession()
|
||||
brushUpdater.clear()
|
||||
break
|
||||
}
|
||||
case TLDrawStatus.Translating:
|
||||
|
@ -2132,9 +2142,13 @@ export class TLDrawState extends StateManager<Data> {
|
|||
|
||||
/* ----------------- Keyboard Events ---------------- */
|
||||
|
||||
onKeyDown = (key: string, info: TLKeyboardInfo) => {
|
||||
onKeyDown = (key: string) => {
|
||||
const info = this.inputs?.pointer
|
||||
if (!info) return
|
||||
|
||||
if (key === 'Escape') {
|
||||
this.cancel()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -2188,7 +2202,10 @@ export class TLDrawState extends StateManager<Data> {
|
|||
}
|
||||
}
|
||||
|
||||
onKeyUp = (key: string, info: TLKeyboardInfo) => {
|
||||
onKeyUp = (key: string) => {
|
||||
const info = this.inputs?.pointer
|
||||
if (!info) return
|
||||
|
||||
switch (this.appState.status.current) {
|
||||
case TLDrawStatus.Brushing: {
|
||||
if (key === 'Meta' || key === 'Control') {
|
||||
|
@ -2360,7 +2377,6 @@ export class TLDrawState extends StateManager<Data> {
|
|||
}
|
||||
case TLDrawStatus.Brushing: {
|
||||
this.completeSession<Sessions.BrushSession>()
|
||||
brushUpdater.clear()
|
||||
break
|
||||
}
|
||||
case TLDrawStatus.Translating: {
|
||||
|
@ -2642,7 +2658,6 @@ export class TLDrawState extends StateManager<Data> {
|
|||
}
|
||||
case TLDrawStatus.Brushing: {
|
||||
this.completeSession<Sessions.BrushSession>()
|
||||
brushUpdater.clear()
|
||||
break
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue