import {
BaseComponentBuilder,
ComponentBuilder,
createFootprintBuilder,
createGroupBuilder,
createProjectBuilder,
createResistorBuilder,
GroupBuilder,
ProjectBuilder,
TraceBuilder,
} from "@tscircuit/builder"
import { ReactNode, isValidElement } from "react"
import ReactReconciler, { Fiber, HostConfig } from "react-reconciler"
import {
getBuilderForType,
builderTypeToInitializer,
BuilderType,
} from "./get-builder-for-type"
import { getSchematicPropertiesFromProps } from "./get-schematic-properties-from-props"
import { removeNils } from "src/utils/removeNils"
import { snakeCasePropsCompat } from "./snake-case-props-compat"
import { AnySoupElement } from "@tscircuit/soup"
export type RootContainer = {}
export type Type = BuilderType | "custom"
export type Props = any
export type Container = GroupBuilder
export type Instance = ComponentBuilder | TraceBuilder | GroupBuilder
export type TextInstance = any
export type SuspenseInstance = any
export type HydratableInstance = any
export type PublicInstance = any
export type HostContext = any
export type UpdatePayload = any
export type ChildSet = any
export type TimeoutHandle = any
export type NoTimeout = any
export const hostConfig: HostConfig<
Type,
Props,
Container,
Instance,
TextInstance,
SuspenseInstance,
HydratableInstance,
PublicInstance,
HostContext,
UpdatePayload,
ChildSet,
TimeoutHandle,
NoTimeout
> = {
supportsMutation: true,
createInstance(type, props, rootContainer, hostContext, internalHandle) {
if (type === "custom") {
if (!props.onAdd) {
throw new Error(` must provide an onAdd prop`)
}
const instance = createGroupBuilder(rootContainer.project_builder)
props.onAdd(instance)
return instance
} else if (typeof type === "string") {
const instance: any = getBuilderForType(
type,
rootContainer.project_builder
)
props = snakeCasePropsCompat(type, props)
let footprint = props.footprint
if (props.footprint && Array.isArray(props.footprint)) {
const fb = createFootprintBuilder(rootContainer.project_builder)
;(fb as any).createBuildContext =
rootContainer.project_builder.createBuildContext
fb.loadFootprintFromSoup(props.footprint)
footprint = fb
} else if (props.footprint && isValidElement(props.footprint)) {
const fb = createFootprintBuilder(rootContainer.project_builder)
;(fb as any).createBuildContext =
rootContainer.project_builder.createBuildContext
createRoot().render(props.footprint, fb as any)
footprint = fb
}
if ("setProps" in instance) {
const propsWithElms = removeNils({
...props,
footprint,
})
;(instance as any).setProps(propsWithElms)
return instance
}
const propNames = Object.keys(props)
for (const propName of propNames) {
let capPropName = propName.charAt(0).toUpperCase() + propName.slice(1)
if (capPropName === "Ftype") {
capPropName = "FType"
}
const setter = (instance as any)[`set${capPropName}`]
if (setter) {
const isPositionProp = capPropName.endsWith("Center")
if (isPositionProp) {
setter.call(instance, ...props[propName])
} else {
setter.call(instance, props[propName])
}
continue
}
}
const setSchPositionFn =
instance.setPosition ?? instance.setSchematicCenter
if (setSchPositionFn && (props.schX || props.schY)) {
if (props.schX === undefined || props.schY === undefined) {
throw new Error(
"if defining schX, must also define schY and vice versa"
)
}
;(instance as any).setSchematicCenter(props.schX, props.schY)
}
// collect all the schematic properties together
const schematic_properties = getSchematicPropertiesFromProps(props)
if ("setSchematicProperties" in instance) {
;(instance as any).setSchematicProperties(schematic_properties)
}
if (schematic_properties.schematic_center) {
if ("setSchematicCenter" in instance) {
;(instance as any).setSchematicCenter(
schematic_properties.schematic_center.x,
schematic_properties.schematic_center.y
)
}
}
if (schematic_properties.schematic_rotation) {
if ("setSchematicRotation" in instance) {
;(instance as any).setSchematicRotation(
schematic_properties.schematic_rotation
)
}
}
if ("footprint" in instance && (props.pcb_x || props.pcb_y)) {
if (props.pcb_x === undefined || props.pcb_y === undefined) {
throw new Error(
"if defining pcb_x, must also define pcb_y and vice versa"
)
}
;(instance as any).footprint.setPosition(props.pcb_x, props.pcb_y)
}
if ("setSize" in instance && (props.width || props.height)) {
if (props.width === undefined || props.height === undefined) {
throw new Error(
"if defining width, must also define height and vice versa"
)
}
// ;(instance as any).setSize
}
return instance
}
throw new Error(`Couldn't handle type: "${type}"`)
},
getRootHostContext() {
return null
},
getChildHostContext(parentHostContext, type, rootContainer) {
return parentHostContext
},
createTextInstance() {
throw new Error("Text is not allowed in TSCircuit React")
},
appendInitialChild(parent: any, child) {
// throw new Error("appendInitialChild not implemented")
parent.appendChild(child)
},
appendChild(parent, child) {
throw new Error("appendChild not implemented")
},
finalizeInitialChildren(instance, type, props) {
// NOTE: return true for commitMount
return false
},
appendChildToContainer(container: any, child) {
if (!("appendChild" in container)) {
throw new Error(
`Container "${container.builder_type}" does not support appending children`
)
}
container.appendChild(child)
},
prepareUpdate(instance, type, oldProps, newProps) {
return true
},
commitUpdate(instance, updatePayload, type, oldProps, newProps) {
throw new Error("commitUpdate not implemented")
// instance.assign(updatePayload)
},
commitTextUpdate(textInstance, oldText, newText) {
throw new Error("Text is not allowed in TSCircuit React")
},
removeChild(parentInstance, child) {
throw new Error("removeChild not implemented")
},
prepareForCommit(containerInfo) {
return null
},
resetAfterCommit(containerInfo) {},
shouldSetTextContent(type, props) {
return false
},
clearContainer(container) {
if (!container.reset) {
console.warn(
`CONTAINER IS MISSING RESET! Add to builder "${container.builder_type}"`
)
return
}
container.reset()
},
supportsPersistence: false,
getPublicInstance: function (instance: any) {
throw new Error("Function not implemented.")
},
preparePortalMount: function (containerInfo: any): void {
throw new Error("Function not implemented.")
},
scheduleTimeout: function (
fn: (...args: unknown[]) => unknown,
delay?: number | undefined
) {
throw new Error("Function not implemented.")
},
cancelTimeout: function (id: any): void {
throw new Error("Function not implemented.")
},
noTimeout: undefined,
isPrimaryRenderer: true,
getCurrentEventPriority: function (): number {
throw new Error("Function not implemented.")
},
getInstanceFromNode: function (node: any): Fiber | null | undefined {
throw new Error("Function not implemented.")
},
beforeActiveInstanceBlur: function (): void {
throw new Error("Function not implemented.")
},
afterActiveInstanceBlur: function (): void {
throw new Error("Function not implemented.")
},
prepareScopeUpdate: function (scopeInstance: any, instance: any): void {
throw new Error("Function not implemented.")
},
getInstanceFromScope: function (scopeInstance: any) {
throw new Error("Function not implemented.")
},
detachDeletedInstance: function (node: any): void {
// throw new Error("Function not implemented.")
},
supportsHydration: false,
}
export const createRoot = () => {
return {
render(
element: ReactNode,
projectBuilder: ProjectBuilder & { _rootContainer?: RootContainer },
callback: null | (() => void) = null
) {
const container = {}
const reconciler = ReactReconciler(hostConfig)
if (!projectBuilder._rootContainer) {
projectBuilder._rootContainer = reconciler.createContainer(
projectBuilder as any,
0,
null,
false,
null,
"tsci",
(error: Error) => {
console.error("got recoverable error from reconciler...", error)
},
null
)
}
reconciler.updateContainer(
element,
projectBuilder._rootContainer,
null,
callback
)
return projectBuilder.build(
projectBuilder.createBuildContext()
) as any as Promise
},
}
}