import { getIndexAbove } from '@tldraw/indices' import { TLDefaultShape, TLShapeId, TLShapePartial, createShapeId } from '@tldraw/tlschema' import { assert, assertExists, omitFromStackTrace } from '@tldraw/utils' const shapeTypeSymbol = Symbol('shapeJsx') const createElement = (tag: string) => { const component = () => { throw new Error(`Cannot render test tag ${tag}`) } ;(component as any)[shapeTypeSymbol] = tag return component } type CommonProps = { x: number y: number id?: TLShapeId rotation?: number isLocked?: number ref?: string children?: JSX.Element | JSX.Element[] opacity?: number } type ShapeByType = Extract type PropsForShape = Type extends TLDefaultShape['type'] ? CommonProps & Partial['props']> : CommonProps & Record /** * TL - jsx helpers for creating tldraw shapes in test cases */ export const TL = new Proxy( {}, { get(target, key) { return createElement(key as string) }, } ) as { [K in TLDefaultShape['type']]: (props: PropsForShape) => null } export function shapesFromJsx(shapes: JSX.Element | Array) { const ids = {} as Record const shapesArray: Array = [] function addChildren(children: JSX.Element | Array, parentId?: TLShapeId) { let nextIndex = 'a0' for (const el of Array.isArray(children) ? children : [children]) { const shapeType = (el.type as any)[shapeTypeSymbol] as string if (!shapeType) { throw new Error( `Cannot use ${el.type} as a shape. Only TL.* tags are allowed in shape jsx.` ) } let id const ref = (el as any).ref as string | undefined if (ref) { assert(!ids[ref], `Duplicate shape ref: ${ref}`) assert(!el.props.id, `Cannot use both ref and id on shape: ${ref}`) id = createShapeId(ref) ids[ref] = id } else if (el.props.id) { id = el.props.id } else { id = createShapeId() } const x: number = assertExists(el.props.x, `Shape ${id} is missing x prop`) const y: number = assertExists(el.props.y, `Shape ${id} is missing y prop`) const shapePartial = { id, type: shapeType, x, y, index: nextIndex, props: {}, } as TLShapePartial nextIndex = getIndexAbove(nextIndex) if (parentId) { shapePartial.parentId = parentId } for (const [key, value] of Object.entries(el.props)) { if (key === 'x' || key === 'y' || key === 'ref' || key === 'id' || key === 'children') { continue } if (key === 'rotation' || key === 'isLocked' || key === 'opacity') { shapePartial[key] = value as any continue } ;(shapePartial.props as Record)[key] = value } shapesArray.push(shapePartial) if (el.props.children) { addChildren(el.props.children, id) } } } addChildren(shapes) return { ids: new Proxy(ids, { get: omitFromStackTrace((target, key) => { if (!(key in target)) { throw new Error( `Cannot access ID '${String( key )}'. No ref with that name was specified.\nAvailable refs: ${Object.keys(ids).join( ', ' )}` ) } return target[key as string] }), }), shapes: shapesArray, } }