@tscircuit/core 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/README.md +35 -0
  2. package/dist/index.cjs +5353 -0
  3. package/index.ts +1 -0
  4. package/lib/Project.ts +84 -0
  5. package/lib/components/base-components/NormalComponent.ts +330 -0
  6. package/lib/components/base-components/PrimitiveComponent.ts +299 -0
  7. package/lib/components/base-components/Renderable.ts +113 -0
  8. package/lib/components/index.ts +14 -0
  9. package/lib/components/normal-components/Board.ts +38 -0
  10. package/lib/components/normal-components/Capacitor.ts +23 -0
  11. package/lib/components/normal-components/Diode.ts +27 -0
  12. package/lib/components/normal-components/Led.ts +27 -0
  13. package/lib/components/normal-components/Resistor.ts +19 -0
  14. package/lib/components/primitive-components/Footprint.ts +4 -0
  15. package/lib/components/primitive-components/Group.ts +10 -0
  16. package/lib/components/primitive-components/Net.ts +12 -0
  17. package/lib/components/primitive-components/Port.ts +187 -0
  18. package/lib/components/primitive-components/SmtPad.ts +80 -0
  19. package/lib/components/primitive-components/Trace.ts +219 -0
  20. package/lib/components/primitive-components/TraceHint.ts +4 -0
  21. package/lib/fiber/catalogue.ts +22 -0
  22. package/lib/fiber/create-instance-from-react-element.ts +168 -0
  23. package/lib/fiber/intrinsic-jsx.ts +43 -0
  24. package/lib/index.ts +5 -0
  25. package/lib/register-catalogue.ts +3 -0
  26. package/lib/utils/autorouting/SimpleRouteJson.ts +37 -0
  27. package/lib/utils/autorouting/computeObstacleBounds.ts +10 -0
  28. package/lib/utils/constants.ts +35 -0
  29. package/lib/utils/createComponentsFromSoup.ts +86 -0
  30. package/lib/utils/get-relative-direction.ts +14 -0
  31. package/lib/utils/getPortFromHints.ts +10 -0
  32. package/lib/utils/projectPointInDirection.ts +18 -0
  33. package/lib/utils/selector-matching/index.ts +62 -0
  34. package/package.json +40 -0
package/index.ts ADDED
@@ -0,0 +1 @@
1
+ export * from "./lib/index"
package/lib/Project.ts ADDED
@@ -0,0 +1,84 @@
1
+ import type { AnySoupElement } from "@tscircuit/soup"
2
+ import type { PrimitiveComponent } from "./components/base-components/PrimitiveComponent"
3
+ import type { SoupUtilObjects } from "@tscircuit/soup-util"
4
+ import { su } from "@tscircuit/soup-util"
5
+ import { isValidElement, type ReactElement } from "react"
6
+ import { createInstanceFromReactElement } from "./fiber/create-instance-from-react-element"
7
+ import { identity, type Matrix } from "transformation-matrix"
8
+
9
+ export class Project {
10
+ rootComponent: PrimitiveComponent | null = null
11
+ children: PrimitiveComponent[]
12
+ db: SoupUtilObjects
13
+
14
+ constructor() {
15
+ this.children = []
16
+ this.db = su([])
17
+ }
18
+
19
+ add(componentOrElm: PrimitiveComponent | ReactElement) {
20
+ let component: PrimitiveComponent
21
+ if (isValidElement(componentOrElm)) {
22
+ // TODO store subtree
23
+ component = createInstanceFromReactElement(componentOrElm)
24
+ } else {
25
+ component = componentOrElm as PrimitiveComponent
26
+ }
27
+ this.children.push(component)
28
+ }
29
+
30
+ _guessRootComponent() {
31
+ if (this.rootComponent) return
32
+ if (this.children.length === 1) {
33
+ this.rootComponent = this.children[0]
34
+ return
35
+ }
36
+ if (this.children.length === 0) {
37
+ throw new Error(
38
+ "Not able to guess root component: Project has no children (use project.add(...))",
39
+ )
40
+ }
41
+
42
+ if (this.children.length > 0) {
43
+ const board =
44
+ this.children.find((c) => c.componentName === "Board") ?? null
45
+
46
+ if (board) {
47
+ this.rootComponent = board
48
+ return
49
+ }
50
+ }
51
+ throw new Error(
52
+ "Not able to guess root component: Project has multiple children and no board",
53
+ )
54
+ }
55
+
56
+ render() {
57
+ if (!this.rootComponent) {
58
+ this._guessRootComponent()
59
+ }
60
+ const { rootComponent, db } = this
61
+
62
+ if (!rootComponent) throw new Error("Project has no root component")
63
+
64
+ rootComponent.setProject(this)
65
+
66
+ rootComponent.runRenderCycle()
67
+ }
68
+
69
+ getSoup(): AnySoupElement[] {
70
+ return this.db.toArray()
71
+ }
72
+
73
+ getCircuitJson(): AnySoupElement[] {
74
+ return this.getSoup()
75
+ }
76
+
77
+ computeGlobalSchematicTransform(): Matrix {
78
+ return identity()
79
+ }
80
+
81
+ computeGlobalPcbTransform(): Matrix {
82
+ return identity()
83
+ }
84
+ }
@@ -0,0 +1,330 @@
1
+ import { Footprint } from "../primitive-components/Footprint"
2
+ import { ZodType, z } from "zod"
3
+ import { PrimitiveComponent } from "./PrimitiveComponent"
4
+ import { Port } from "../primitive-components/Port"
5
+ import { symbols, type BaseSymbolName, type SchSymbol } from "schematic-symbols"
6
+ import { fp } from "footprinter"
7
+ import {
8
+ isValidElement as isReactElement,
9
+ isValidElement,
10
+ type ReactElement,
11
+ type ReactNode,
12
+ } from "react"
13
+ import {
14
+ createInstanceFromReactElement,
15
+ type ReactSubtree,
16
+ } from "lib/fiber/create-instance-from-react-element"
17
+ import { getPortFromHints } from "lib/utils/getPortFromHints"
18
+ import { createComponentsFromSoup } from "lib/utils/createComponentsFromSoup"
19
+
20
+ export type PortMap<T extends string> = {
21
+ [K in T]: Port
22
+ }
23
+
24
+ /**
25
+ * A NormalComponent is the base class for most components that a user will
26
+ * interact with. It has the ability to set a footprint and discover ports.
27
+ *
28
+ * When you're extending a NormalComponent, you almost always want to override
29
+ * initPorts() to create ports for the component.
30
+ *
31
+ * class Led extends NormalComponent<typeof resistorProps> {
32
+ * pin1: Port = this.portMap.pin1
33
+ * pin2: Port = this.portMap.pin2
34
+ *
35
+ * initPorts() {
36
+ * this.add(new Port({ pinNumber: 1, aliases: ["anode", "pos"] }))
37
+ * this.add(new Port({ pinNumber: 2, aliases: ["cathode", "neg"] }))
38
+ * }
39
+ * }
40
+ */
41
+ export class NormalComponent<
42
+ ZodProps extends ZodType = any,
43
+ PortNames extends string = never,
44
+ > extends PrimitiveComponent<ZodProps> {
45
+ reactSubtrees: Array<ReactSubtree> = []
46
+
47
+ constructor(props: z.input<ZodProps>) {
48
+ super(props)
49
+ this._addChildrenFromStringFootprint()
50
+ this.initPorts()
51
+ }
52
+
53
+ /**
54
+ * Override this method for better control over the auto-discovery of ports.
55
+ *
56
+ * If you override this method just do something like:
57
+ * initPorts() {
58
+ * this.add(new Port({ pinNumber: 1, aliases: ["anode", "pos"] }))
59
+ * this.add(new Port({ pinNumber: 2, aliases: ["cathode", "neg"] }))
60
+ * }
61
+ *
62
+ * By default, we'll pull the ports from the first place we find them:
63
+ * 1. `config.schematicSymbolName`
64
+ * 2. `props.footprint`
65
+ *
66
+ */
67
+ initPorts() {
68
+ const { config } = this
69
+ if (config.schematicSymbolName) {
70
+ const sym = symbols[
71
+ `${config.schematicSymbolName}_horz` as keyof typeof symbols
72
+ ] as SchSymbol | undefined
73
+ if (!sym) return
74
+
75
+ for (const symPort of sym.ports) {
76
+ const port = getPortFromHints(symPort.labels)
77
+
78
+ if (port) {
79
+ port.schematicSymbolPortDef = symPort
80
+ this.add(port)
81
+ }
82
+ }
83
+
84
+ return
85
+ }
86
+
87
+ const portsFromFootprint = this.getPortsFromFootprint()
88
+
89
+ this.addAll(portsFromFootprint)
90
+ }
91
+
92
+ _addChildrenFromStringFootprint() {
93
+ const { footprint } = this.props
94
+ if (!footprint) return
95
+ if (typeof footprint === "string") {
96
+ const fpSoup = fp.string(footprint).soup()
97
+ const fpComponents = createComponentsFromSoup(fpSoup)
98
+ this.addAll(fpComponents)
99
+ }
100
+ }
101
+
102
+ get portMap(): PortMap<PortNames> {
103
+ return new Proxy(
104
+ {},
105
+ {
106
+ get: (target, prop): Port => {
107
+ const port = this.children.find(
108
+ (c) =>
109
+ c.componentName === "Port" &&
110
+ (c as Port).isMatchingNameOrAlias(prop as string),
111
+ )
112
+ if (!port) {
113
+ throw new Error(
114
+ `There was an issue finding the port "${prop.toString()}" inside of a ${this.componentName} component with name: "${this.props.name}". This is a bug in @tscircuit/core`,
115
+ )
116
+ }
117
+ return port as Port
118
+ },
119
+ },
120
+ ) as any
121
+ }
122
+
123
+ getInstanceForReactElement(element: ReactElement): NormalComponent | null {
124
+ for (const subtree of this.reactSubtrees) {
125
+ if (subtree.element === element) return subtree.component
126
+ }
127
+ return null
128
+ }
129
+
130
+ doInitialSourceRender() {
131
+ const ftype = this.config.sourceFtype
132
+ if (!ftype) return
133
+ const { db } = this.project!
134
+ const { _parsedProps: props } = this
135
+ const source_component = db.source_component.insert({
136
+ ftype,
137
+ name: props.name,
138
+ manufacturer_part_number: props.manufacturerPartNumber ?? props.mfn,
139
+ supplier_part_numbers: props.supplierPartNumbers,
140
+ })
141
+ this.source_component_id = source_component.source_component_id
142
+ }
143
+
144
+ /**
145
+ * Render the schematic component for this NormalComponent using the
146
+ * config.schematicSymbolName if it exists.
147
+ *
148
+ * You can override this method to do more complicated things.
149
+ */
150
+ doInitialSchematicComponentRender() {
151
+ const { db } = this.project!
152
+ const { schematicSymbolName } = this.config
153
+ if (!schematicSymbolName) return
154
+ // TODO switch between horizontal and vertical based on schRotation
155
+ const symbol_name = `${this.config.schematicSymbolName}_horz`
156
+
157
+ const symbol = (symbols as any)[symbol_name] as SchSymbol | undefined
158
+
159
+ if (!symbol) {
160
+ throw new Error(`Could not find schematic-symbol "${symbol_name}"`)
161
+ }
162
+
163
+ const schematic_component = db.schematic_component.insert({
164
+ center: { x: this.props.schX ?? 0, y: this.props.schY ?? 0 },
165
+ rotation: this.props.schRotation ?? 0,
166
+ size: symbol.size,
167
+ source_component_id: this.source_component_id!,
168
+
169
+ // @ts-ignore
170
+ symbol_name,
171
+ })
172
+ this.schematic_component_id = schematic_component.schematic_component_id
173
+ }
174
+
175
+ doInitialPcbComponentRender() {
176
+ const { db } = this.project!
177
+ const { _parsedProps: props } = this
178
+ const pcb_component = db.pcb_component.insert({
179
+ center: { x: this.props.pcbX ?? 0, y: this.props.pcbY ?? 0 },
180
+ // width/height are computed in the PcbAnalysis phase
181
+ width: 0,
182
+ height: 0,
183
+ layer: props.layer ?? "top",
184
+ rotation: props.rotation ?? 0,
185
+ source_component_id: this.source_component_id!,
186
+ })
187
+ this.pcb_component_id = pcb_component.pcb_component_id
188
+ }
189
+
190
+ doInitialPcbAnalysis(): void {
191
+ const { db } = this.project!
192
+ const { _parsedProps: props } = this
193
+
194
+ // TODO Examine children to compute width/height and pcbX/pcbY
195
+ }
196
+
197
+ _renderReactSubtree(element: ReactElement): ReactSubtree {
198
+ return {
199
+ element,
200
+ component: createInstanceFromReactElement(element),
201
+ }
202
+ }
203
+
204
+ doInitialReactSubtreesRender(): void {
205
+ if (isReactElement(this.props.footprint)) {
206
+ if (this.reactSubtrees.some((rs) => rs.element === this.props.footprint))
207
+ return
208
+ const subtree = this._renderReactSubtree(this.props.footprint)
209
+ this.reactSubtrees.push(subtree)
210
+ this.add(subtree.component)
211
+ }
212
+ }
213
+
214
+ add(componentOrElm: PrimitiveComponent | ReactElement) {
215
+ let component: PrimitiveComponent
216
+ if (isReactElement(componentOrElm)) {
217
+ const subtree = this._renderReactSubtree(componentOrElm)
218
+ this.reactSubtrees.push(subtree)
219
+ component = subtree.component
220
+ } else {
221
+ component = componentOrElm as PrimitiveComponent
222
+ }
223
+
224
+ super.add(component)
225
+ }
226
+
227
+ getPortsFromFootprint(): Port[] {
228
+ let { footprint } = this.props
229
+
230
+ if (!footprint || isValidElement(footprint)) {
231
+ footprint = this.children.find((c) => c.componentName === "Footprint")
232
+ }
233
+
234
+ if (typeof footprint === "string") {
235
+ const fpSoup = fp.string(footprint).soup()
236
+
237
+ const newPorts: Port[] = []
238
+ for (const elm of fpSoup) {
239
+ if ("port_hints" in elm && elm.port_hints) {
240
+ const newPort = getPortFromHints(elm.port_hints)
241
+ if (!newPort) continue
242
+ newPorts.push(newPort)
243
+ }
244
+ }
245
+
246
+ return newPorts
247
+ }
248
+ if (
249
+ !isValidElement(footprint) &&
250
+ footprint &&
251
+ footprint.componentName === "Footprint"
252
+ ) {
253
+ const fp = footprint as Footprint
254
+
255
+ const newPorts: Port[] = []
256
+ for (const fpChild of fp.children) {
257
+ const newPort = getPortFromHints(fpChild.props.portHints ?? [])
258
+ if (!newPort) continue
259
+ newPorts.push(newPort)
260
+ }
261
+
262
+ return newPorts
263
+ }
264
+
265
+ // Explore children for possible smtpads etc.
266
+ const newPorts: Port[] = []
267
+ if (!footprint) {
268
+ for (const child of this.children) {
269
+ if (child.props.portHints && child.isPcbPrimitive) {
270
+ const port = getPortFromHints(child.props.portHints)
271
+ if (port) newPorts.push(port)
272
+ }
273
+ }
274
+ }
275
+ return newPorts
276
+ }
277
+
278
+ getPortsFromSchematicSymbol(): Port[] {
279
+ const { config } = this
280
+ if (!config.schematicSymbolName) return []
281
+ const symbol: SchSymbol = (symbols as any)[config.schematicSymbolName]
282
+ if (!symbol) return []
283
+ const newPorts: Port[] = []
284
+ for (const symbolPort of symbol.ports) {
285
+ const port = getPortFromHints(symbolPort.labels)
286
+ if (port) {
287
+ port.schematicSymbolPortDef = symbolPort
288
+ newPorts.push(port)
289
+ }
290
+ }
291
+ return newPorts
292
+ }
293
+
294
+ /**
295
+ * Use data from our props to create ports for this component.
296
+ *
297
+ * Generally, this is done by looking at the schematic and the footprint,
298
+ * reading the pins, making sure there aren't duplicates.
299
+ *
300
+ * Can probably be removed in favor of initPorts()
301
+ *
302
+ */
303
+ doInitialPortDiscovery(): void {
304
+ const newPorts = [
305
+ ...this.getPortsFromFootprint(),
306
+ ...this.getPortsFromSchematicSymbol(),
307
+ ]
308
+
309
+ const existingPorts = this.children.filter(
310
+ (c) => c.componentName === "Port",
311
+ ) as Port[]
312
+
313
+ for (const newPort of newPorts) {
314
+ const existingPort = existingPorts.find((p) =>
315
+ p.isMatchingAnyOf(newPort.getNameAndAliases()),
316
+ )
317
+ if (existingPort) {
318
+ if (
319
+ !existingPort.schematicSymbolPortDef &&
320
+ newPort.schematicSymbolPortDef
321
+ ) {
322
+ existingPort.schematicSymbolPortDef = newPort.schematicSymbolPortDef
323
+ }
324
+ continue
325
+ }
326
+ existingPorts.push(newPort)
327
+ this.add(newPort)
328
+ }
329
+ }
330
+ }
@@ -0,0 +1,299 @@
1
+ import type { AnySoupElement, AnySourceComponent } from "@tscircuit/soup"
2
+ import type { Project } from "../../Project"
3
+ import type { ZodType } from "zod"
4
+ import { z } from "zod"
5
+ import { symbols, type SchSymbol, type BaseSymbolName } from "schematic-symbols"
6
+ import { isValidElement as isReactElement, type ReactElement } from "react"
7
+ import type { Port } from "../primitive-components/Port"
8
+ import { Renderable, type RenderPhase } from "./Renderable"
9
+ import {
10
+ applyToPoint,
11
+ compose,
12
+ identity,
13
+ translate,
14
+ type Matrix,
15
+ } from "transformation-matrix"
16
+ import { isMatchingSelector } from "lib/utils/selector-matching"
17
+
18
+ export interface BaseComponentConfig {
19
+ schematicSymbolName?: BaseSymbolName | null
20
+ zodProps: ZodType
21
+ sourceFtype?: AnySourceComponent["ftype"] | null
22
+ }
23
+
24
+ /**
25
+ * A PrimitiveComponent (SmtPad, Port etc.) doesn't have the ability to contain
26
+ * React subtrees or explicit handling of the "footprint" prop. But otherwise
27
+ * has most of the features of a NormalComponent.
28
+ */
29
+ export abstract class PrimitiveComponent<
30
+ ZodProps extends ZodType = any,
31
+ > extends Renderable {
32
+ parent: PrimitiveComponent | null = null
33
+ children: PrimitiveComponent[]
34
+ childrenPendingRemoval: PrimitiveComponent[]
35
+
36
+ get config(): BaseComponentConfig {
37
+ return {
38
+ zodProps: z.object({}).passthrough(),
39
+ }
40
+ }
41
+
42
+ project: Project | null = null
43
+ props: z.input<ZodProps>
44
+ _parsedProps: z.infer<ZodProps>
45
+
46
+ componentName = ""
47
+ lowercaseComponentName = ""
48
+
49
+ source_group_id: string | null = null
50
+ source_component_id: string | null = null
51
+ schematic_component_id: string | null = null
52
+ pcb_component_id: string | null = null
53
+ cad_component_id: string | null = null
54
+
55
+ constructor(props: z.input<ZodProps>) {
56
+ super(props)
57
+ this.children = []
58
+ this.childrenPendingRemoval = []
59
+ this.props = props ?? {}
60
+ this._parsedProps = this.config.zodProps.parse(
61
+ props ?? {},
62
+ ) as z.infer<ZodProps>
63
+ if (!this.componentName) {
64
+ this.componentName = this.constructor.name
65
+ this.lowercaseComponentName = this.componentName.toLowerCase()
66
+ }
67
+ }
68
+
69
+ setProject(project: Project) {
70
+ this.project = project
71
+ for (const c of this.children) {
72
+ c.setProject(project)
73
+ }
74
+ }
75
+
76
+ setProps(props: Partial<z.input<ZodProps>>) {
77
+ const newProps = this.config.zodProps.parse({
78
+ ...this.props,
79
+ ...props,
80
+ }) as z.infer<ZodProps>
81
+ const oldProps = this.props
82
+ this.props = newProps
83
+ this._parsedProps = this.config.zodProps.parse(props) as z.infer<ZodProps>
84
+ this.onPropsChange({
85
+ oldProps,
86
+ newProps,
87
+ changedProps: Object.keys(props),
88
+ })
89
+ this.parent?.onChildChanged(this)
90
+ }
91
+
92
+ /**
93
+ * Computes a transformation matrix from the props of this component for PCB
94
+ * components
95
+ */
96
+ computePcbPropsTransform(): Matrix {
97
+ // TODO rotations
98
+ return compose(translate(this.props.pcbX, this.props.pcbY))
99
+ }
100
+
101
+ /**
102
+ * Compute a transformation matrix combining all parent transforms for PCB
103
+ * components
104
+ */
105
+ computePcbGlobalTransform(): Matrix {
106
+ return compose(
107
+ this.parent?.computePcbGlobalTransform() ?? identity(),
108
+ this.computePcbPropsTransform(),
109
+ )
110
+ }
111
+
112
+ /**
113
+ * Computes a transformation matrix from the props of this component for
114
+ * schematic components
115
+ */
116
+ computeSchematicPropsTransform(): Matrix {
117
+ return compose(translate(this.props.schX ?? 0, this.props.schY ?? 0))
118
+ }
119
+
120
+ /**
121
+ * Compute a transformation matrix combining all parent transforms for this
122
+ * component
123
+ */
124
+ computeSchematicGlobalTransform(): Matrix {
125
+ return compose(
126
+ this.parent?.computeSchematicGlobalTransform?.() ?? identity(),
127
+ this.computeSchematicPropsTransform(),
128
+ )
129
+ }
130
+
131
+ getSchematicSymbol(variant: "horz" | "vert" | null = null): SchSymbol | null {
132
+ if (variant === null) {
133
+ return this.getSchematicSymbol(
134
+ this.props.schRotation % 90 === 0 ? "vert" : "horz",
135
+ )
136
+ }
137
+ const { config } = this
138
+ if (!config.schematicSymbolName) return null
139
+ return symbols[
140
+ `${config.schematicSymbolName}_${variant}` as keyof typeof symbols
141
+ ]
142
+ }
143
+
144
+ getGlobalPcbPosition(): { x: number; y: number } {
145
+ return applyToPoint(this.computePcbGlobalTransform(), { x: 0, y: 0 })
146
+ }
147
+
148
+ getGlobalSchematicPosition(): { x: number; y: number } {
149
+ return applyToPoint(this.computeSchematicGlobalTransform(), { x: 0, y: 0 })
150
+ }
151
+
152
+ onAddToParent(parent: PrimitiveComponent) {
153
+ this.parent = parent
154
+ this.project = parent.project
155
+ }
156
+
157
+ /**
158
+ * Called whenever the props change
159
+ */
160
+ onPropsChange(params: {
161
+ oldProps: z.infer<ZodProps>
162
+ newProps: z.infer<ZodProps>
163
+ changedProps: string[]
164
+ }) {}
165
+
166
+ onChildChanged(child: PrimitiveComponent) {
167
+ this.parent?.onChildChanged(child)
168
+ }
169
+
170
+ add(component: PrimitiveComponent) {
171
+ component.onAddToParent(this)
172
+ component.parent = this
173
+ this.children.push(component)
174
+ }
175
+
176
+ addAll(components: PrimitiveComponent[]) {
177
+ for (const component of components) {
178
+ this.add(component)
179
+ }
180
+ }
181
+
182
+ remove(component: PrimitiveComponent) {
183
+ this.children = this.children.filter((c) => c !== component)
184
+ this.childrenPendingRemoval.push(component)
185
+ component.shouldBeRemoved = true
186
+ }
187
+
188
+ getNameAndAliases(): string[] {
189
+ return [
190
+ this._parsedProps.name,
191
+ ...(this._parsedProps.portHints ?? []),
192
+ ].filter(Boolean)
193
+ }
194
+ isMatchingNameOrAlias(name: string) {
195
+ return this.getNameAndAliases().includes(name)
196
+ }
197
+ isMatchingAnyOf(aliases: Array<string | number>) {
198
+ return this.getNameAndAliases().some((a) =>
199
+ aliases.map((a) => a.toString()).includes(a),
200
+ )
201
+ }
202
+
203
+ doesSelectorMatch(selector: string): boolean {
204
+ const myTypeNames = [this.componentName, this.lowercaseComponentName]
205
+ const myClassNames = [this._parsedProps.name].filter(Boolean)
206
+
207
+ const parts = selector.trim().split(/\> /)[0]
208
+ const firstPart = parts[0]
209
+
210
+ if (parts.length > 1) return false
211
+ if (selector === "*") return true
212
+ if (selector[0] === "#" && selector.slice(1) === this.props.id) return true
213
+ if (selector[0] === "." && myClassNames.includes(selector.slice(1)))
214
+ return true
215
+ if (/^[a-zA-Z0-9_]/.test(firstPart) && myTypeNames.includes(firstPart))
216
+ return true
217
+
218
+ return false
219
+ }
220
+
221
+ selectAll(selector: string): PrimitiveComponent[] {
222
+ const parts = selector.trim().split(/\s+/)
223
+ let results: PrimitiveComponent[] = [this]
224
+
225
+ let onlyDirectChildren = false
226
+ for (const part of parts) {
227
+ if (part === ">") {
228
+ onlyDirectChildren = true
229
+ } else {
230
+ results = results.flatMap((component) => {
231
+ return (
232
+ onlyDirectChildren ? component.children : component.getDescendants()
233
+ ).filter((descendant) => isMatchingSelector(descendant, part))
234
+ })
235
+ onlyDirectChildren = false
236
+ }
237
+ }
238
+
239
+ return results.filter((component) => component !== this)
240
+ }
241
+
242
+ selectOne(
243
+ selector: string,
244
+ options?: {
245
+ type?: string
246
+ port?: boolean
247
+ pcbPrimitive?: boolean
248
+ schematicPrimitive?: boolean
249
+ },
250
+ ): PrimitiveComponent | null {
251
+ let type = options?.type?.toLowerCase()
252
+ if (options?.port) type = "port"
253
+ if (type) {
254
+ return (
255
+ this.selectAll(selector).find(
256
+ (c) => c.lowercaseComponentName === type,
257
+ ) ?? null
258
+ )
259
+ }
260
+ if (options?.pcbPrimitive) {
261
+ return this.selectAll(selector).find((c) => c.isPcbPrimitive) ?? null
262
+ }
263
+ if (options?.schematicPrimitive) {
264
+ return (
265
+ this.selectAll(selector).find((c) => c.isSchematicPrimitive) ?? null
266
+ )
267
+ }
268
+ return this.selectAll(selector)[0] ?? null
269
+ }
270
+
271
+ getDescendants(): PrimitiveComponent[] {
272
+ const descendants: PrimitiveComponent[] = []
273
+ for (const child of this.children) {
274
+ descendants.push(child)
275
+ descendants.push(...child.getDescendants())
276
+ }
277
+ return descendants
278
+ }
279
+
280
+ getString(): string {
281
+ const { lowercaseComponentName: cname, _parsedProps: props, parent } = this
282
+ if (parent?.props?.name && props?.name) {
283
+ return `<${cname}#${this._renderId}(.${parent?.props.name}>.${props?.name}) />`
284
+ }
285
+ if (props?.name) {
286
+ return `<${cname}#${this._renderId} name=".${props?.name}" />`
287
+ }
288
+ if (props?.portHints) {
289
+ return `<${cname}#${this._renderId}(${props.portHints.map((ph: string) => `.${ph}`).join(", ")}) />`
290
+ }
291
+ return `<${cname}#${this._renderId} />`
292
+ }
293
+ get [Symbol.toStringTag](): string {
294
+ return this.getString()
295
+ }
296
+ [Symbol.for("nodejs.util.inspect.custom")]() {
297
+ return this.getString()
298
+ }
299
+ }