Browse Source

refactor: convert from arrays to maps for some types

Peng Xiao 3 years ago
parent
commit
811d39285f

File diff suppressed because it is too large
+ 0 - 149
tldraw/apps/tldraw-logseq/src/documents/withEverything.ts


+ 4 - 4
tldraw/apps/tldraw-logseq/src/lib/shapes/LineShape.tsx

@@ -17,10 +17,10 @@ export class LineShape extends TLLineShape<LineShapeProps> {
     parentId: 'page',
     type: 'line',
     point: [0, 0],
-    handles: [
-      { id: 'start', canBind: true, point: [0, 0] },
-      { id: 'end', canBind: true, point: [1, 1] },
-    ],
+    handles: {
+      start: { id: 'start', canBind: true, point: [0, 0] },
+      end: { id: 'end', canBind: true, point: [1, 1] },
+    },
     stroke: '#000000',
     fill: '#ffffff',
     strokeWidth: 2,

+ 1 - 1
tldraw/packages/core/src/lib/TLApp/TLApp.ts

@@ -223,7 +223,7 @@ export class TLApp<
   /* ---------------------- Pages --------------------- */
 
   @observable pages: Map<string, TLPage<S, K>> = new Map([
-    ['page', new TLPage(this, { id: 'page', name: 'page', shapes: [], bindings: [] })],
+    ['page', new TLPage(this, { id: 'page', name: 'page', shapes: [], bindings: {} })],
   ])
 
   @observable currentPageId = 'page'

+ 6 - 6
tldraw/packages/core/src/lib/TLPage/TLPage.ts

@@ -2,13 +2,13 @@
 import { action, observable, makeObservable, computed, observe } from 'mobx'
 import { TLBinding, TLEventMap, TLResizeCorner } from '~types'
 import type { TLApp, TLShape, TLShapeModel } from '~lib'
-import { BoundsUtils } from '~utils'
+import { BoundsUtils, deepCopy } from '~utils'
 
 export interface TLPageModel<S extends TLShape = TLShape> {
   id: string
   name: string
   shapes: TLShapeModel<S['props']>[]
-  bindings: TLBinding[]
+  bindings: Record<string, TLBinding>
   nonce?: number
 }
 
@@ -16,12 +16,12 @@ export interface TLPageProps<S> {
   id: string
   name: string
   shapes: S[]
-  bindings: TLBinding[]
+  bindings: Record<string, TLBinding>
 }
 
 export class TLPage<S extends TLShape = TLShape, E extends TLEventMap = TLEventMap> {
   constructor(app: TLApp<S, E>, props = {} as TLPageProps<S>) {
-    const { id, name, shapes = [], bindings = [] } = props
+    const { id, name, shapes = [], bindings = {} } = props
     this.id = id
     this.name = name
     this.bindings = bindings
@@ -38,14 +38,14 @@ export class TLPage<S extends TLShape = TLShape, E extends TLEventMap = TLEventM
 
   @observable shapes: S[] = []
 
-  @observable bindings: TLBinding[]
+  @observable bindings: Record<string, TLBinding>
 
   @computed get serialized(): TLPageModel<S> {
     return {
       id: this.id,
       name: this.name,
       shapes: this.shapes.map(shape => shape.serialized),
-      bindings: this.bindings.map(binding => ({ ...binding })),
+      bindings: deepCopy(this.bindings),
       nonce: this.nonce,
     }
   }

+ 4 - 4
tldraw/packages/core/src/lib/shapes/TLLineShape/TLLineShape.test.ts

@@ -13,10 +13,10 @@ describe('A minimal test', () => {
         type: 'dot',
         parentId: 'page',
         point: [0, 0],
-        handles: [
-          { id: 'start', point: [0, 0] },
-          { id: 'end', point: [0, 0] },
-        ],
+        handles: {
+          start: { id: 'start', canBind: true, point: [0, 0] },
+          end: { id: 'end', canBind: true, point: [1, 1] },
+        },
         stroke: 'black',
       }
     }

+ 25 - 16
tldraw/packages/core/src/lib/shapes/TLLineShape/TLLineShape.tsx

@@ -5,7 +5,7 @@ import { BoundsUtils, deepMerge } from '~utils'
 import { TLPolylineShape, TLPolylineShapeProps } from '../TLPolylineShape'
 
 export interface TLLineShapeProps extends TLPolylineShapeProps {
-  handles: TLHandle[]
+  handles: Record<'start' | 'end' | string, TLHandle>
 }
 
 export class TLLineShape<
@@ -24,38 +24,45 @@ export class TLLineShape<
     type: 'line',
     parentId: 'page',
     point: [0, 0],
-    handles: [
-      { id: 'start', canBind: true, point: [0, 0] },
-      { id: 'end', canBind: true, point: [1, 1] },
-    ],
+    handles: {
+      start: { id: 'start', canBind: true, point: [0, 0] },
+      end: { id: 'end', canBind: true, point: [1, 1] },
+    },
   }
 
   validateProps = (props: Partial<P>) => {
     if (props.point) props.point = [0, 0]
-    if (props.handles !== undefined && props.handles.length < 1)
-      props.handles = [{ point: [0, 0], id: 'start' }]
+    if (props.handles !== undefined && Object.values(props.handles).length < 1)
+      props.handles = TLLineShape.defaultProps['handles']
     return props
   }
 
-  getHandlesChange = (initialShape: P, handles: Partial<TLHandle>[]): P | void => {
-    let nextHandles = handles.map((h, i) => deepMerge(initialShape.handles[i] ?? {}, h))
-    nextHandles = nextHandles.map(h => ({ ...h, point: Vec.toFixed(h.point) }))
+  getHandlesChange = (shape: P, handles: Partial<P['handles']>): Partial<P> | undefined => {
+    let nextHandles = deepMerge(shape.handles, handles)
 
-    if (nextHandles.length !== 2 || Vec.isEqual(nextHandles[0].point, nextHandles[1].point)) {
-      return
-    }
+    nextHandles = deepMerge(nextHandles, {
+      start: {
+        point: Vec.toFixed(nextHandles.start.point),
+      },
+      end: {
+        point: Vec.toFixed(nextHandles.end.point),
+      },
+    })
+
+    // This will produce NaN values
+    if (Vec.isEqual(nextHandles.start.point, nextHandles.end.point)) return
 
     const nextShape = {
-      ...initialShape,
+      point: shape.point,
       handles: nextHandles,
     }
 
     // Zero out the handles to prevent handles with negative points. If a handle's x or y
     // is below zero, we need to move the shape left or up to make it zero.
-    const topLeft = initialShape.point
+    const topLeft = shape.point
 
     const nextBounds = BoundsUtils.translateBounds(
-      BoundsUtils.getBoundsFromPoints(nextHandles.map(h => h.point)),
+      BoundsUtils.getBoundsFromPoints(Object.values(nextHandles).map(h => h.point)),
       nextShape.point
     )
 
@@ -67,6 +74,8 @@ export class TLLineShape<
       })
       nextShape.point = Vec.toFixed(Vec.add(nextShape.point, offset))
     }
+
+    // @ts-expect-error ???
     return nextShape
   }
 }

+ 4 - 4
tldraw/packages/core/src/lib/shapes/TLPolylineShape/TLPolylineShape.test.ts

@@ -12,10 +12,10 @@ describe('A minimal test', () => {
         type: 'dot',
         parentId: 'page',
         point: [0, 0],
-        handles: [
-          { id: 'start', point: [0, 0] },
-          { id: 'end', point: [0, 0] },
-        ],
+        handles: {
+          start: { id: 'start', canBind: true, point: [0, 0] },
+          end: { id: 'end', canBind: true, point: [1, 1] },
+        },
         stroke: 'black',
       }
     }

+ 8 - 7
tldraw/packages/core/src/lib/shapes/TLPolylineShape/TLPolylineShape.tsx

@@ -11,7 +11,7 @@ import { BoundsUtils, PointUtils, PolygonUtils } from '~utils'
 import { TLShapeProps, TLResizeInfo, TLShape } from '../TLShape'
 
 export interface TLPolylineShapeProps extends TLShapeProps {
-  handles: TLHandle[]
+  handles: Record<string, TLHandle>
 }
 
 export class TLPolylineShape<
@@ -30,11 +30,11 @@ export class TLPolylineShape<
     type: 'polyline',
     parentId: 'page',
     point: [0, 0],
-    handles: [{ id: '0', point: [0, 0] }],
+    handles: {},
   }
 
   @computed get points() {
-    return this.props.handles.map(h => h.point)
+    return Object.values(this.props.handles).map(h => h.point)
   }
 
   @computed get centroid() {
@@ -48,7 +48,7 @@ export class TLPolylineShape<
       props: { handles, rotation },
     } = this
     if (!rotation) return this.points
-    return handles.map(h => Vec.rotWith(h.point, centroid, rotation))
+    return Object.values(handles).map(h => Vec.rotWith(h.point, centroid, rotation))
   }
 
   getBounds = (): TLBounds => {
@@ -76,7 +76,7 @@ export class TLPolylineShape<
     } = this
     this.scale = [...(this.props.scale ?? [1, 1])]
     const size = [bounds.width, bounds.height]
-    this.normalizedHandles = handles.map(h => Vec.divV(h.point, size))
+    this.normalizedHandles = Object.values(handles).map(h => Vec.divV(h.point, size))
     return this
   }
 
@@ -95,7 +95,7 @@ export class TLPolylineShape<
     if (scaleY < 0) nextScale[1] *= -1
     return this.update({
       point: [bounds.minX, bounds.minY],
-      handles: handles.map((handle, i) => ({
+      handles: Object.values(handles).map((handle, i) => ({
         ...handle,
         point: Vec.mulV(normalizedHandles[i], size),
       })),
@@ -146,7 +146,8 @@ export class TLPolylineShape<
 
   validateProps = (props: Partial<P>) => {
     if (props.point) props.point = [0, 0]
-    if (props.handles !== undefined && props.handles.length < 1) props.handles = [{ point: [0, 0] }]
+    if (props.handles !== undefined && Object.values(props.handles).length < 1)
+      props.handles = TLPolylineShape.defaultProps['handles']
     return props
   }
 }

+ 13 - 10
tldraw/packages/core/src/lib/shapes/TLShape/TLShape.ts

@@ -9,7 +9,7 @@ import Vec from '@tldraw/vec'
 import { action, computed, makeObservable, observable, toJS } from 'mobx'
 import { BINDING_DISTANCE } from '~constants'
 import type { TLAsset, TLBounds, TLHandle, TLResizeCorner, TLResizeEdge } from '~types'
-import { BoundsUtils, PointUtils } from '~utils'
+import { BoundsUtils, deepCopy, PointUtils } from '~utils'
 
 export type TLShapeModel<P extends TLShapeProps = TLShapeProps> = {
   nonce?: number
@@ -30,7 +30,7 @@ export interface TLShapeProps {
   point: number[]
   scale?: number[]
   rotation?: number
-  handles?: TLHandle[]
+  handles?: Record<string, TLHandle>
   label?: string
   labelPosition?: number[]
   clipping?: number | number[]
@@ -64,7 +64,7 @@ export interface TLResetBoundsInfo<T extends TLAsset> {
 }
 
 export interface TLHandleChangeInfo {
-  index: number
+  id: string
   delta: number[]
 }
 
@@ -313,17 +313,20 @@ export abstract class TLShape<P extends TLShapeProps = TLShapeProps, M = any> {
     return this
   }
 
-  onHandleChange = (initialShape: any, { index, delta }: TLHandleChangeInfo) => {
+  onHandleChange = (initialShape: any, { id, delta }: TLHandleChangeInfo) => {
     if (initialShape.handles === undefined) return
-    const nextHandles = [...initialShape.handles]
-    nextHandles[index] = {
-      ...nextHandles[index],
-      point: Vec.add(delta, initialShape.handles[index].point),
+    const nextHandles: Record<string, TLHandle> = deepCopy(initialShape.handles)
+    nextHandles[id] = {
+      ...nextHandles[id],
+      point: Vec.add(delta, initialShape.handles[id].point),
     }
-    const topLeft = BoundsUtils.getCommonTopLeft(nextHandles.map(h => h.point))
+    const topLeft = BoundsUtils.getCommonTopLeft(Object.values(nextHandles).map(h => h.point))
+    Object.values(nextHandles).forEach(h => {
+      h.point = Vec.sub(h.point, topLeft)
+    })
     this.update({
       point: Vec.add(initialShape.point, topLeft),
-      handles: nextHandles.map(h => ({ ...h, point: Vec.sub(h.point, topLeft) })),
+      handles: nextHandles
     })
   }
 }

+ 32 - 40
tldraw/packages/core/src/lib/tools/TLLineTool/states/CreatingState.tsx

@@ -1,9 +1,9 @@
-import type { TLLineTool } from '../TLLineTool'
-import { TLShape, TLApp, TLToolState, TLLineShape, TLLineShapeProps } from '~lib'
-import type { TLEventMap, TLLineBinding, TLStateEvents } from '~types'
 import Vec from '@tldraw/vec'
-import { deepCopy, deepMerge, GeomUtils, PointUtils, uniqueId } from '~utils'
 import { toJS } from 'mobx'
+import { TLApp, TLLineShape, TLLineShapeProps, TLShape, TLToolState } from '~lib'
+import type { TLEventMap, TLLineBinding, TLStateEvents } from '~types'
+import { deepMerge, GeomUtils, PointUtils, uniqueId } from '~utils'
+import type { TLLineTool } from '../TLLineTool'
 
 export class CreatingState<
   S extends TLShape,
@@ -24,15 +24,13 @@ export class CreatingState<
   onEnter = () => {
     const { Shape } = this.tool
     const { originPoint } = this.app.inputs
+
     const shape = new Shape({
+      ...Shape.defaultProps,
       id: uniqueId(),
       type: Shape.id,
       parentId: this.app.currentPage.id,
       point: originPoint,
-      handles: [
-        { id: 'start', canBind: true, point: [0, 0] },
-        { id: 'end', canBind: true, point: [1, 1] },
-      ],
     })
     this.initialShape = toJS(shape.props)
     this.creatingShape = shape
@@ -63,26 +61,29 @@ export class CreatingState<
     const shape = this.app.getShapeById<TLLineShape>(this.initialShape.id)
 
     const { handles } = this.initialShape
-    const curIndex = 1
-    const oppIndex = 0
+    const handleId = 'start'
+    const otherHandleId = 'end'
     if (Vec.isEqual(previousPoint, currentPoint)) return
     let delta = Vec.sub(currentPoint, originPoint)
 
     if (shiftKey) {
-      const A = handles[oppIndex].point
-      const B = handles[curIndex].point
+      const A = handles[otherHandleId].point
+      const B = handles[handleId].point
       const C = Vec.add(B, delta)
       const angle = Vec.angle(A, C)
       const adjusted = Vec.rotWith(C, A, GeomUtils.snapAngleToSegments(angle, 24) - angle)
       delta = Vec.add(delta, Vec.sub(adjusted, C))
     }
 
-    const nextPoint = Vec.add(handles[curIndex].point, delta)
+    const nextPoint = Vec.add(handles[handleId].point, delta)
 
-    const handleChanges = deepCopy(handles)
-    handleChanges[curIndex].point = showGrid
-      ? Vec.snap(nextPoint, currentGrid)
-      : Vec.toFixed(nextPoint)
+    const handleChanges = {
+      [handleId]: {
+        ...handles[handleId],
+        point: showGrid ? Vec.snap(nextPoint, currentGrid) : Vec.toFixed(nextPoint),
+        bindingId: undefined,
+      },
+    }
 
     let updated = this.creatingShape.getHandlesChange(this.initialShape, handleChanges)
 
@@ -93,22 +94,15 @@ export class CreatingState<
     // before. If it does change, we'll redefine this later on. And if we've
     // made it this far, the shape should be a new object reference that
     // incorporates the changes we've made due to the handle movement.
-    const next: { props: TLLineShapeProps; bindings: Record<string, TLLineBinding> } = {
-      props: {
-        ...deepCopy(shape.props),
-        ...updated,
-        handles: updated.handles.map((h, idx) => deepMerge(shape.props.handles[idx], h)),
-      },
-      bindings: this.app.currentPage.bindings.reduce(
-        (acc, binding) => ({ ...acc, [binding.id]: binding }),
-        {}
-      ),
+    const next: { shape: TLLineShapeProps; bindings: Record<string, TLLineBinding> } = {
+      shape: deepMerge(shape.props, updated),
+      bindings: {},
     }
 
     let draggedBinding: TLLineBinding | undefined
 
-    const draggingHandle = next.props.handles[curIndex]
-    const oppositeHandle = next.props.handles[oppIndex]
+    const draggingHandle = next.shape.handles[handleId]
+    const oppositeHandle = next.shape.handles[otherHandleId]
 
     // START BINDING
     // If we have a start binding shape id, the recompute the binding
@@ -120,10 +114,10 @@ export class CreatingState<
       const startTarget = this.app.getShapeById(this.startBindingShapeId)
       const center = startTarget.getCenter()
 
-      const startHandle = next.props.handles[0]
-      const endHandle = next.props.handles[1]
+      const startHandle = next.shape.handles.start
+      const endHandle = next.shape.handles.end
 
-      const rayPoint = Vec.add(startHandle.point, next.props.point)
+      const rayPoint = Vec.add(startHandle.point, next.shape.point)
 
       if (Vec.isEqual(rayPoint, center)) rayPoint[1]++ // Fix bug where ray and center are identical
 
@@ -133,12 +127,10 @@ export class CreatingState<
 
       const rayDirection = Vec.uni(Vec.sub(rayPoint, rayOrigin))
 
-      const hasStartBinding = this.app.currentPage.bindings.some(
-        b => b.id === this.newStartBindingId
-      )
+      const hasStartBinding = this.app.currentPage.bindings[this.newStartBindingId] !== undefined
 
       // Don't bind the start handle if both handles are inside of the target shape.
-      if (!modKey && !startTarget.hitTestPoint(Vec.add(next.props.point, endHandle.point))) {
+      if (!modKey && !startTarget.hitTestPoint(Vec.add(next.shape.point, endHandle.point))) {
         nextStartBinding = this.findBindingPoint(
           shape.props,
           startTarget,
@@ -153,18 +145,18 @@ export class CreatingState<
 
       if (nextStartBinding && !hasStartBinding) {
         next.bindings[this.newStartBindingId] = nextStartBinding
-        next.props.handles[0].bindingId = nextStartBinding.id
+        next.shape.handles.start.bindingId = nextStartBinding.id
       } else if (!nextStartBinding && hasStartBinding) {
         delete next.bindings[this.newStartBindingId]
-        next.props.handles[0].bindingId = undefined
+        next.shape.handles.start.bindingId = undefined
       }
     }
 
-    updated = this.creatingShape.getHandlesChange(next.props, next.props.handles)
+    updated = this.creatingShape.getHandlesChange(next.shape, next.shape.handles)
 
     if (updated) {
       this.creatingShape.update(updated)
-      this.app.currentPage.bindings = Object.values(next.bindings)
+      Object.assign(this.app.currentPage.bindings, next.bindings)
     }
   }
 

+ 4 - 5
tldraw/packages/core/src/lib/tools/TLSelectTool/states/TranslatingHandleState.ts

@@ -15,9 +15,8 @@ export class TranslatingHandleState<
 
   private offset = [0, 0]
   private initialTopLeft = [0, 0]
-  private index = 0
+  private handleId = 'start'
   private shape: S = {} as S
-  private handleId: 'start' | 'end' = 'start'
   private initialShape: S['props'] = {} as S['props']
   private handle: TLHandle = {} as TLHandle
   private bindableShapeIds: string[] = []
@@ -29,7 +28,7 @@ export class TranslatingHandleState<
   ) => {
     this.app.history.pause()
     this.offset = [0, 0]
-    this.index = info.index
+    this.handleId = info.id
     this.shape = info.shape
     this.handle = info.handle
     this.initialShape = deepCopy({ ...this.shape.props })
@@ -75,8 +74,8 @@ export class TranslatingHandleState<
         delta[1] = 0
       }
     }
-    const { shape, initialShape, index } = this
-    shape.onHandleChange(initialShape, { index, delta })
+    const { shape, initialShape, handleId: id } = this
+    shape.onHandleChange(initialShape, { id, delta })
   }
 
   onPointerUp: TLEvents<S>['pointer'] = () => {

+ 1 - 1
tldraw/packages/core/src/types/types.ts

@@ -233,7 +233,7 @@ export type TLEventHandleInfo<S extends TLShape = TLShape> = {
   type: TLTargetType.Handle
   shape: S
   handle: TLHandle
-  index: number
+  id: string
   order?: number
 }
 

+ 8 - 7
tldraw/packages/react/src/components/Canvas/Canvas.tsx

@@ -165,13 +165,14 @@ export const Canvas = observer(function Renderer<S extends TLReactShape>({
               {showHandles && onlySelectedShapeWithHandles && components.Handle && (
                 <Container bounds={selectionBounds} zIndex={10003}>
                   <SVGContainer>
-                    {onlySelectedShapeWithHandles.props.handles!.map((handle, i) =>
-                      React.createElement(components.Handle!, {
-                        key: `${handle.id}_handle_${i}`,
-                        shape: onlySelectedShapeWithHandles,
-                        handle,
-                        index: i,
-                      })
+                    {Object.entries(onlySelectedShapeWithHandles.props.handles!).map(
+                      ([id, handle]) =>
+                        React.createElement(components.Handle!, {
+                          key: `${handle.id}_handle_${handle.id}`,
+                          shape: onlySelectedShapeWithHandles,
+                          handle,
+                          id,
+                        })
                     )}
                   </SVGContainer>
                 </Container>

+ 2 - 2
tldraw/packages/react/src/components/ui/Handle/Handle.tsx

@@ -8,9 +8,9 @@ import type { TLHandleComponentProps } from '~types'
 export const Handle = observer(function Handle<S extends TLReactShape, H extends TLHandle>({
   shape,
   handle,
-  index,
+  id,
 }: TLHandleComponentProps<S, H>) {
-  const events = useHandleEvents(shape, index)
+  const events = useHandleEvents(shape, id)
   const [x, y] = handle.point
 
   return (

+ 2 - 2
tldraw/packages/react/src/components/ui/SelectionDetail/SelectionDetail.tsx

@@ -36,8 +36,8 @@ export const SelectionDetail = observer(function SelectionDetail<S extends TLRea
       >
         {isLine
           ? `${Vec.dist(
-              shapes[0].props.handles![0].point,
-              shapes[0].props.handles![1].point
+              shapes[0].props.handles!.start.point,
+              shapes[0].props.handles!.end.point
             ).toFixed()}`
           : detail === 'size'
           ? `${bounds.width.toFixed()} × ${bounds.height.toFixed()}`

+ 15 - 15
tldraw/packages/react/src/hooks/useHandleEvents.ts

@@ -5,55 +5,55 @@ import { useRendererContext } from '~hooks'
 import type { TLReactShape } from '~lib'
 import type { TLReactCustomEvents } from '~types'
 
-export function useHandleEvents<S extends TLReactShape = TLReactShape>(shape: S, index: number) {
+export function useHandleEvents<S extends TLReactShape = TLReactShape>(shape: S, id: string) {
   const { inputs, callbacks } = useRendererContext()
 
   const events = React.useMemo(() => {
     const onPointerMove: TLReactCustomEvents['pointer'] = e => {
       const { order = 0 } = e
-      const handle = shape.props.handles![index]
-      callbacks.onPointerMove?.({ type: TLTargetType.Handle, shape, handle, index, order }, e)
+      const handle = shape.props.handles![id]
+      callbacks.onPointerMove?.({ type: TLTargetType.Handle, shape, handle, id, order }, e)
       e.order = order + 1
     }
 
     const onPointerDown: TLReactCustomEvents['pointer'] = e => {
       const { order = 0 } = e
       if (!order) e.currentTarget?.setPointerCapture(e.pointerId)
-      const handle = shape.props.handles![index]
-      callbacks.onPointerDown?.({ type: TLTargetType.Handle, shape, handle, index, order }, e)
+      const handle = shape.props.handles![id]
+      callbacks.onPointerDown?.({ type: TLTargetType.Handle, shape, handle, id, order }, e)
       e.order = order + 1
     }
 
     const onPointerUp: TLReactCustomEvents['pointer'] = e => {
       const { order = 0 } = e
       if (!order) e.currentTarget?.releasePointerCapture(e.pointerId)
-      const handle = shape.props.handles![index]
-      callbacks.onPointerUp?.({ type: TLTargetType.Handle, shape, handle, index, order }, e)
+      const handle = shape.props.handles![id]
+      callbacks.onPointerUp?.({ type: TLTargetType.Handle, shape, handle, id, order }, e)
       e.order = order + 1
     }
 
     const onPointerEnter: TLReactCustomEvents['pointer'] = e => {
       const { order = 0 } = e
-      const handle = shape.props.handles![index]
-      callbacks.onPointerEnter?.({ type: TLTargetType.Handle, shape, handle, index, order }, e)
+      const handle = shape.props.handles![id]
+      callbacks.onPointerEnter?.({ type: TLTargetType.Handle, shape, handle, id, order }, e)
       e.order = order + 1
     }
 
     const onPointerLeave: TLReactCustomEvents['pointer'] = e => {
       const { order = 0 } = e
-      const handle = shape.props.handles![index]
-      callbacks.onPointerLeave?.({ type: TLTargetType.Handle, shape, handle, index, order }, e)
+      const handle = shape.props.handles![id]
+      callbacks.onPointerLeave?.({ type: TLTargetType.Handle, shape, handle, id, order }, e)
       e.order = order + 1
     }
 
     const onKeyDown: TLReactCustomEvents['keyboard'] = e => {
-      const handle = shape.props.handles![index]
-      callbacks.onKeyDown?.({ type: TLTargetType.Handle, shape, handle, index, order: -1 }, e)
+      const handle = shape.props.handles![id]
+      callbacks.onKeyDown?.({ type: TLTargetType.Handle, shape, handle, id, order: -1 }, e)
     }
 
     const onKeyUp: TLReactCustomEvents['keyboard'] = e => {
-      const handle = shape.props.handles![index]
-      callbacks.onKeyUp?.({ type: TLTargetType.Handle, shape, handle, index, order: -1 }, e)
+      const handle = shape.props.handles![id]
+      callbacks.onKeyUp?.({ type: TLTargetType.Handle, shape, handle, id, order: -1 }, e)
     }
 
     return {

+ 1 - 1
tldraw/packages/react/src/types/component-props.ts

@@ -64,7 +64,7 @@ export interface TLHandleComponentProps<
 > {
   shape: S
   handle: H
-  index: number
+  id: string
 }
 
 export type TLHandleComponent<

Some files were not shown because too many files changed in this diff