@tscircuit/props 0.0.715 → 0.0.717

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.
@@ -0,0 +1,73 @@
1
+ import { distance, type Distance } from "lib/common/distance"
2
+ import { z } from "zod"
3
+
4
+ /** Signed distances along the target face's local in-plane X/Y axes, in mm. */
5
+ export interface AssemblyMountOffset {
6
+ x?: Distance
7
+ y?: Distance
8
+ }
9
+
10
+ export interface AssemblyFaceMountProps {
11
+ /** Target face reference, e.g. "BRIDGE.rail" or ".BRIDGE > .rail".
12
+ * CSS selectors and compact identity paths are resolved by core.
13
+ * References are resolved in the containing device, independently of order.
14
+ */
15
+ mountedTo?: string
16
+ /** This element's named mating face. Outward normals oppose and in-plane
17
+ * X directions align. A mount determines the complete local rigid pose.
18
+ */
19
+ mountFace?: string
20
+ /** Finite nonnegative clearance, in mm or a distance string.
21
+ * Requires mountedTo. Omission stays absent and means zero in core.
22
+ * Positive gaps separate the faces along the target's outward normal.
23
+ */
24
+ mountGap?: Distance
25
+ /** Signed in-plane offset in the target face frame; requires mountedTo.
26
+ * Supplied axes accept mm numbers or distance strings; omitted axes are zero.
27
+ * Normal separation is expressed by mountGap, not a third offset axis.
28
+ */
29
+ mountOffset?: AssemblyMountOffset
30
+ }
31
+
32
+ export const assemblyMountOffset = z
33
+ .object({
34
+ x: distance.pipe(z.number().finite()).default(0),
35
+ y: distance.pipe(z.number().finite()).default(0),
36
+ })
37
+ .strict()
38
+
39
+ // Shared fields/refinement are internal composition helpers. Consumers use
40
+ // each element's complete validator rather than a partial mounting schema.
41
+ export const assemblyFaceMountShape = {
42
+ mountedTo: z.string().trim().min(1).optional(),
43
+ mountFace: z.string().trim().min(1).optional(),
44
+ mountGap: distance.pipe(z.number().nonnegative().finite()).optional(),
45
+ mountOffset: assemblyMountOffset.optional(),
46
+ }
47
+
48
+ export const refineAssemblyFaceMount = (
49
+ part: AssemblyFaceMountProps,
50
+ context: z.RefinementCtx,
51
+ ) => {
52
+ if ((part.mountedTo === undefined) !== (part.mountFace === undefined)) {
53
+ context.addIssue({
54
+ code: z.ZodIssueCode.custom,
55
+ path: ["mountFace"],
56
+ message: "Provide mountedTo and mountFace together",
57
+ })
58
+ }
59
+ if (part.mountGap !== undefined && part.mountedTo === undefined) {
60
+ context.addIssue({
61
+ code: z.ZodIssueCode.custom,
62
+ path: ["mountGap"],
63
+ message: "mountGap requires mountedTo",
64
+ })
65
+ }
66
+ if (part.mountOffset !== undefined && part.mountedTo === undefined) {
67
+ context.addIssue({
68
+ code: z.ZodIssueCode.custom,
69
+ path: ["mountOffset"],
70
+ message: "mountOffset requires mountedTo",
71
+ })
72
+ }
73
+ }
@@ -1,11 +1,13 @@
1
1
  import { distance, type Distance } from "lib/common/distance"
2
2
  import { expectTypesMatch } from "lib/typecheck"
3
3
  import { z } from "zod"
4
- import { assemblyPartProps, type AssemblyPartProps } from "./part"
4
+ import { assemblyPartBaseProps, type AssemblyPartProps } from "./part"
5
+ import { assemblyMountOffset } from "./face-mounting"
5
6
 
6
7
  /** An assembly part that fastens to its mounting target, even without geometry. */
7
8
  export interface AssemblyFastenerProps extends AssemblyPartProps {
8
- /** Qualified reference surface, e.g. "FRAME.boltHole". Implies fastening.
9
+ /** Reference surface, e.g. "FRAME.boltHole" or ".FRAME > .boltHole".
10
+ * CSS selectors and compact paths are resolved by core. Implies fastening.
9
11
  * Without mountFace, use the fastener's authored local mounting origin.
10
12
  */
11
13
  mountedTo?: string
@@ -23,21 +25,16 @@ export interface AssemblyFastenerProps extends AssemblyPartProps {
23
25
  mountGap?: Distance
24
26
  }
25
27
 
26
- const mountingTarget = z
27
- .string()
28
- .trim()
29
- .regex(
30
- /^[A-Za-z_][A-Za-z0-9_-]*(?:\.[A-Za-z_][A-Za-z0-9_-]*)+$/,
31
- "Expected a qualified reference surface, e.g. part.face",
32
- )
28
+ const mountingTarget = z.string().trim().min(1)
33
29
 
34
- export const assemblyFastenerProps = assemblyPartProps
30
+ export const assemblyFastenerProps = assemblyPartBaseProps
35
31
  .and(
36
32
  z.object({
37
33
  mountedTo: mountingTarget.optional(),
38
34
  fastenedTo: mountingTarget.optional(),
39
35
  mountFace: z.string().trim().min(1).optional(),
40
36
  mountGap: distance.pipe(z.number().nonnegative().finite()).optional(),
37
+ mountOffset: assemblyMountOffset.optional(),
41
38
  }),
42
39
  )
43
40
  .superRefine((fastener, ctx) => {
@@ -53,7 +50,7 @@ export const assemblyFastenerProps = assemblyPartProps
53
50
  })
54
51
  }
55
52
  if (fastener.mountedTo === undefined && fastener.fastenedTo === undefined) {
56
- for (const prop of ["mountFace", "mountGap"] as const) {
53
+ for (const prop of ["mountFace", "mountGap", "mountOffset"] as const) {
57
54
  if (fastener[prop] !== undefined) {
58
55
  ctx.addIssue({
59
56
  code: z.ZodIssueCode.custom,
@@ -19,7 +19,15 @@ export * from "./fastener"
19
19
  export * from "./cable"
20
20
  export * from "./device"
21
21
  export * from "./motor"
22
- export * from "./part"
22
+ export {
23
+ assemblyPartProps,
24
+ type AssemblyPartProps,
25
+ type AssemblyPartPropsInput,
26
+ } from "./part"
27
+ export type {
28
+ AssemblyFaceMountProps,
29
+ AssemblyMountOffset,
30
+ } from "./face-mounting"
23
31
  export * from "./referencesurface"
24
32
  export * from "./printedpart"
25
33
  export * from "./screen"
@@ -2,13 +2,17 @@ import {
2
2
  type CadModelAxisDirection,
3
3
  cadModelAxisDirection,
4
4
  } from "lib/common/cadModel"
5
- import { distance, type Distance } from "lib/common/distance"
6
5
  import { expectTypesMatch } from "lib/typecheck"
7
6
  import { z } from "zod"
7
+ import {
8
+ assemblyFaceMountShape,
9
+ refineAssemblyFaceMount,
10
+ type AssemblyFaceMountProps,
11
+ } from "./face-mounting"
8
12
 
9
13
  export type AssemblyMotorStandard = "nema8" | "nema17" | "nema23"
10
14
 
11
- export interface AssemblyMotorProps {
15
+ export interface AssemblyMotorProps extends AssemblyFaceMountProps {
12
16
  /** Stable assembly identity used by selectors. */
13
17
  name: string
14
18
  /** Human-facing alternate to the stable name. */
@@ -21,29 +25,19 @@ export interface AssemblyMotorProps {
21
25
  * Direction from the motor body toward the shaft tip in the right-handed
22
26
  * circuit frame: +X right, +Y top, +Z above the board. This is a direction,
23
27
  * not a position; it does not specify translation or rotation about the
24
- * shaft. Defaults to "z+", the native shaft axis of the NEMA models.
28
+ * shaft. Defaults to "z+" only when unmounted. Omit it when mounted;
29
+ * the mating faces then determine the complete motor orientation.
25
30
  */
26
31
  shaftFacingDirection?: CadModelAxisDirection
27
32
  /** Cable connection string, e.g. "jst6_ph".
28
33
  * Omit to use the model's default termination.
29
34
  */
30
35
  wireConnection?: string
31
- /** Assembly mounting target, e.g. "FRAME.xMotor"; paired with mountFace.
32
- * Face mating determines orientation, so shaftFacingDirection must be omitted.
33
- */
34
- mountedTo?: string
35
- /** This motor's mating face, e.g. "frontface" or "backface".
36
- * Outward normals oppose and in-plane X directions align with the target.
37
- */
38
- mountFace?: string
39
- /** Nonnegative surface clearance in mm or a unit string; defaults to zero.
40
- * Requires mountedTo. Positive values separate the mating faces.
41
- */
42
- mountGap?: Distance
43
36
  }
44
37
 
45
38
  export const assemblyMotorProps = z
46
39
  .object({
40
+ ...assemblyFaceMountShape,
47
41
  name: z.string().refine((value) => value.trim().length > 0, {
48
42
  message: "name cannot be empty",
49
43
  }),
@@ -52,29 +46,9 @@ export const assemblyMotorProps = z
52
46
  model: z.string().trim().min(1).optional(),
53
47
  shaftFacingDirection: cadModelAxisDirection.optional(),
54
48
  wireConnection: z.string().optional(),
55
- mountedTo: z
56
- .string()
57
- .trim()
58
- .regex(/^.+\.[^.]+$/, "Expected part.face")
59
- .optional(),
60
- mountFace: z.string().trim().min(1).optional(),
61
- mountGap: distance.pipe(z.number().nonnegative().finite()).optional(),
62
49
  })
63
50
  .superRefine((motor, context) => {
64
- if ((motor.mountedTo === undefined) !== (motor.mountFace === undefined)) {
65
- context.addIssue({
66
- code: z.ZodIssueCode.custom,
67
- message: "Provide mountedTo and mountFace together",
68
- path: ["mountFace"],
69
- })
70
- }
71
- if (motor.mountGap !== undefined && motor.mountedTo === undefined) {
72
- context.addIssue({
73
- code: z.ZodIssueCode.custom,
74
- message: "mountGap requires mountedTo",
75
- path: ["mountGap"],
76
- })
77
- }
51
+ refineAssemblyFaceMount(motor, context)
78
52
  if (
79
53
  motor.mountedTo !== undefined &&
80
54
  motor.shaftFacingDirection !== undefined
@@ -3,9 +3,14 @@ import { url } from "lib/common/url"
3
3
  import { expectTypesMatch } from "lib/typecheck"
4
4
  import { z } from "zod"
5
5
  import type { ReactNode } from "react"
6
+ import {
7
+ assemblyFaceMountShape,
8
+ refineAssemblyFaceMount,
9
+ type AssemblyFaceMountProps,
10
+ } from "./face-mounting"
6
11
 
7
12
  /** A generic component of an assembly, with optional CAD geometry. */
8
- export interface AssemblyPartProps {
13
+ export interface AssemblyPartProps extends AssemblyFaceMountProps {
9
14
  /** Stable identity used by assembly selectors. */
10
15
  name: string
11
16
  /** Human-facing alternate to the stable name. */
@@ -20,7 +25,9 @@ export interface AssemblyPartProps {
20
25
  children?: ReactNode
21
26
  }
22
27
 
23
- export const assemblyPartProps = z
28
+ // Internal base for elements with their own mounting semantics, e.g. fasteners.
29
+ // Keep model validation separate from ordinary face-mount pairing.
30
+ export const assemblyPartBaseProps = z
24
31
  .object({
25
32
  name: z.string().trim().min(1),
26
33
  displayName: z.string().optional(),
@@ -42,6 +49,14 @@ export const assemblyPartProps = z
42
49
  },
43
50
  )
44
51
 
45
- export type AssemblyPartPropsInput = z.input<typeof assemblyPartProps>
52
+ export const assemblyPartProps = assemblyPartBaseProps
53
+ .and(z.object(assemblyFaceMountShape))
54
+ .superRefine(refineAssemblyFaceMount)
55
+
56
+ export type AssemblyPartPropsInput = {
57
+ [K in keyof z.input<typeof assemblyPartProps>]: z.input<
58
+ typeof assemblyPartProps
59
+ >[K]
60
+ }
46
61
 
47
62
  expectTypesMatch<AssemblyPartProps, AssemblyPartPropsInput>(true)
@@ -1,12 +1,16 @@
1
1
  import { type CadModelProp, cadModelProp } from "lib/common/cadModel"
2
2
  import { url } from "lib/common/url"
3
- import { distance, type Distance } from "lib/common/distance"
4
3
  import { expectTypesMatch } from "lib/typecheck"
5
4
  import { isValidElement, type ReactElement, type ReactNode } from "react"
6
5
  import { z } from "zod"
6
+ import {
7
+ assemblyFaceMountShape,
8
+ refineAssemblyFaceMount,
9
+ type AssemblyFaceMountProps,
10
+ } from "./face-mounting"
7
11
 
8
12
  /** Provide exactly one of jscad, model, modelUrl, or cadModel. */
9
- export interface AssemblyPrintedPartProps {
13
+ export interface AssemblyPrintedPartProps extends AssemblyFaceMountProps {
10
14
  /** Stable identity used by assembly mounting selectors. */
11
15
  name: string
12
16
  displayName?: string
@@ -25,21 +29,11 @@ export interface AssemblyPrintedPartProps {
25
29
  model?: string
26
30
  modelUrl?: string
27
31
  cadModel?: CadModelProp
28
- /** Assembly mounting target. */
29
- mountedTo?: string
30
- /** Name of this part's reference rectangle to mate with mountedTo.
31
- * Both props must be supplied together; outward normals oppose and local
32
- * in-plane X directions align. Without them the part uses its local origin.
33
- */
34
- mountFace?: string
35
- /** Nonnegative surface clearance in mm or a unit string; defaults to zero.
36
- * Requires mountedTo. Positive values separate the mating faces.
37
- */
38
- mountGap?: Distance
39
32
  }
40
33
 
41
34
  export const assemblyPrintedPartProps = z
42
35
  .object({
36
+ ...assemblyFaceMountShape,
43
37
  name: z.string().trim().min(1),
44
38
  displayName: z.string().optional(),
45
39
  color: z.string().trim().min(1).optional(),
@@ -53,15 +47,9 @@ export const assemblyPrintedPartProps = z
53
47
  .refine((value) => value.trim().length > 0, "modelUrl cannot be empty")
54
48
  .optional(),
55
49
  cadModel: cadModelProp.optional(),
56
- mountedTo: z
57
- .string()
58
- .trim()
59
- .regex(/^.+\.[^.]+$/, "Expected part.face")
60
- .optional(),
61
- mountFace: z.string().trim().min(1).optional(),
62
- mountGap: distance.pipe(z.number().nonnegative().finite()).optional(),
63
50
  })
64
51
  .superRefine((part, ctx) => {
52
+ refineAssemblyFaceMount(part, ctx)
65
53
  if (
66
54
  [part.jscad, part.model, part.modelUrl, part.cadModel].filter(
67
55
  (value) => value !== undefined,
@@ -73,20 +61,6 @@ export const assemblyPrintedPartProps = z
73
61
  message: "Provide exactly one of jscad, model, modelUrl, or cadModel",
74
62
  })
75
63
  }
76
- if ((part.mountedTo === undefined) !== (part.mountFace === undefined)) {
77
- ctx.addIssue({
78
- code: z.ZodIssueCode.custom,
79
- path: ["mountFace"],
80
- message: "Provide mountedTo and mountFace together",
81
- })
82
- }
83
- if (part.mountGap !== undefined && part.mountedTo === undefined) {
84
- ctx.addIssue({
85
- code: z.ZodIssueCode.custom,
86
- path: ["mountGap"],
87
- message: "mountGap requires mountedTo",
88
- })
89
- }
90
64
  })
91
65
 
92
66
  export type AssemblyPrintedPartPropsInput = z.input<
@@ -3,8 +3,13 @@ import { type CadModelProp, cadModelProp } from "lib/common/cadModel"
3
3
  import { expectTypesMatch } from "lib/typecheck"
4
4
  import type { ReactNode } from "react"
5
5
  import { z } from "zod"
6
+ import {
7
+ assemblyFaceMountShape,
8
+ refineAssemblyFaceMount,
9
+ type AssemblyFaceMountProps,
10
+ } from "./face-mounting"
6
11
 
7
- export interface AssemblySubassemblyProps {
12
+ export interface AssemblySubassemblyProps extends AssemblyFaceMountProps {
8
13
  /** Stable identity used by selectors from other assembly elements. */
9
14
  name: string
10
15
  /** Imported CAD model URL. Mutually exclusive with cadModel where supported. */
@@ -21,6 +26,7 @@ export interface AssemblySubassemblyProps {
21
26
 
22
27
  export const assemblySubassemblyProps = z
23
28
  .object({
29
+ ...assemblyFaceMountShape,
24
30
  name: z.string().refine((value) => value.trim().length > 0, {
25
31
  message: "name cannot be empty",
26
32
  }),
@@ -51,6 +57,7 @@ export const assemblySubassemblyProps = z
51
57
  path: ["model"],
52
58
  },
53
59
  )
60
+ .superRefine(refineAssemblyFaceMount)
54
61
 
55
62
  export type AssemblySubassemblyPropsInput = z.input<
56
63
  typeof assemblySubassemblyProps
@@ -0,0 +1,35 @@
1
+ import { expectTypesMatch } from "lib/typecheck"
2
+ import { z } from "zod"
3
+ import {
4
+ type SystemBlockPcbReferenceProps,
5
+ refineSystemBlockPcbReference,
6
+ systemBlockPcbReferenceShape,
7
+ } from "../systemblock/pcb-reference"
8
+
9
+ /** A functional block, optionally associated with one or more PCB components. */
10
+ export interface SystemBlockComponentProps
11
+ extends SystemBlockPcbReferenceProps {
12
+ /** Stable block identity, unique across its page; trimmed and nonempty. */
13
+ name: string
14
+ /** Human-facing title; consumers fall back to name when omitted. */
15
+ displayName?: string
16
+ /** Plain-text explanation of what the block does. */
17
+ description?: string
18
+ }
19
+
20
+ export const systemBlockComponentProps = z
21
+ .object({
22
+ ...systemBlockPcbReferenceShape,
23
+ name: z.string().trim().min(1),
24
+ displayName: z.string().trim().min(1).optional(),
25
+ description: z.string().optional(),
26
+ })
27
+ .superRefine(refineSystemBlockPcbReference)
28
+
29
+ export type SystemBlockComponentPropsInput = z.input<
30
+ typeof systemBlockComponentProps
31
+ >
32
+
33
+ expectTypesMatch<SystemBlockComponentProps, SystemBlockComponentPropsInput>(
34
+ true,
35
+ )
@@ -0,0 +1,35 @@
1
+ import { expectTypesMatch } from "lib/typecheck"
2
+ import { z } from "zod"
3
+
4
+ /** A logical relationship between two blocks; does not create electrical traces. */
5
+ export interface SystemBlockConnectionProps {
6
+ /** Optional stable connection identity, unique within its page when supplied. */
7
+ name?: string
8
+ /** Component name within the containing page, including inside nested groups. */
9
+ from: string
10
+ /** Component name within the same page; group names and pin selectors are not endpoints. */
11
+ to: string
12
+ /** Interface annotation such as I2C, SPI, or 3.3 V; not the connection's identity. */
13
+ label?: string
14
+ /** Relationship category; defaults to signal. */
15
+ kind?: "signal" | "power"
16
+ /** Arrowheads: from → to, both ends, or neither. Defaults to forward. */
17
+ direction?: "forward" | "both" | "none"
18
+ }
19
+
20
+ export const systemBlockConnectionProps = z.object({
21
+ name: z.string().trim().min(1).optional(),
22
+ from: z.string().trim().min(1),
23
+ to: z.string().trim().min(1),
24
+ label: z.string().optional(),
25
+ kind: z.enum(["signal", "power"]).default("signal"),
26
+ direction: z.enum(["forward", "both", "none"]).default("forward"),
27
+ })
28
+
29
+ export type SystemBlockConnectionPropsInput = z.input<
30
+ typeof systemBlockConnectionProps
31
+ >
32
+
33
+ expectTypesMatch<SystemBlockConnectionProps, SystemBlockConnectionPropsInput>(
34
+ true,
35
+ )
@@ -0,0 +1,34 @@
1
+ import { expectTypesMatch } from "lib/typecheck"
2
+ import type { ReactNode } from "react"
3
+ import { z } from "zod"
4
+ import {
5
+ type SystemBlockPcbReferenceProps,
6
+ refineSystemBlockPcbReference,
7
+ systemBlockPcbReferenceShape,
8
+ } from "../systemblock/pcb-reference"
9
+
10
+ /** A subsystem boundary containing blocks, nested groups, and connections. */
11
+ export interface SystemBlockGroupProps extends SystemBlockPcbReferenceProps {
12
+ /** Stable group identity, unique across its page; trimmed and nonempty. */
13
+ name: string
14
+ /** Human-facing title; consumers fall back to name when omitted. */
15
+ displayName?: string
16
+ /** Plain-text explanation of the subsystem's responsibility. */
17
+ description?: string
18
+ /** Preserved unchanged; grouping does not create electrical connectivity. */
19
+ children?: ReactNode
20
+ }
21
+
22
+ export const systemBlockGroupProps = z
23
+ .object({
24
+ ...systemBlockPcbReferenceShape,
25
+ name: z.string().trim().min(1),
26
+ displayName: z.string().trim().min(1).optional(),
27
+ description: z.string().optional(),
28
+ children: z.custom<ReactNode>().optional(),
29
+ })
30
+ .superRefine(refineSystemBlockPcbReference)
31
+
32
+ export type SystemBlockGroupPropsInput = z.input<typeof systemBlockGroupProps>
33
+
34
+ expectTypesMatch<SystemBlockGroupProps, SystemBlockGroupPropsInput>(true)
@@ -0,0 +1,29 @@
1
+ import { expectTypesMatch } from "lib/typecheck"
2
+ import type { ReactNode } from "react"
3
+ import { z } from "zod"
4
+
5
+ /** One high-level system overview, independent of schematic or PCB geometry. */
6
+ export interface SystemBlockPageProps {
7
+ /** Stable page identity, unique within the circuit; trimmed and nonempty. */
8
+ name: string
9
+ /** Human-facing title; consumers fall back to name when omitted. */
10
+ displayName?: string
11
+ /** Plain-text explanation of the system's purpose. */
12
+ description?: string
13
+ /** Preferred automatic layout direction; defaults to right. */
14
+ direction?: "right" | "left" | "down" | "up"
15
+ /** System blocks, nested groups, and connections; preserved unchanged. */
16
+ children?: ReactNode
17
+ }
18
+
19
+ export const systemBlockPageProps = z.object({
20
+ name: z.string().trim().min(1),
21
+ displayName: z.string().trim().min(1).optional(),
22
+ description: z.string().optional(),
23
+ direction: z.enum(["right", "left", "down", "up"]).default("right"),
24
+ children: z.custom<ReactNode>().optional(),
25
+ })
26
+
27
+ export type SystemBlockPagePropsInput = z.input<typeof systemBlockPageProps>
28
+
29
+ expectTypesMatch<SystemBlockPageProps, SystemBlockPagePropsInput>(true)
package/lib/index.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  export * from "./common/direction"
2
2
  export * from "./common/commonShape"
3
3
  export * from "./assembly"
4
+ export * from "./systemblock"
4
5
  export * from "./enclosure"
5
6
  export * from "./common/portHints"
6
7
  export * from "./common/layout"
@@ -0,0 +1,18 @@
1
+ import { systemBlockPageProps } from "../components/system-block-page"
2
+ import { systemBlockGroupProps } from "../components/system-block-group"
3
+ import { systemBlockComponentProps } from "../components/system-block-component"
4
+ import { systemBlockConnectionProps } from "../components/system-block-connection"
5
+
6
+ export * from "../components/system-block-page"
7
+ export * from "../components/system-block-group"
8
+ export * from "../components/system-block-component"
9
+ export * from "../components/system-block-connection"
10
+ export type { SystemBlockPcbReferenceProps } from "./pcb-reference"
11
+
12
+ /** Validators for the systemblock JSX namespace; rendering is supplied by core. */
13
+ export const systemBlockProps = {
14
+ page: systemBlockPageProps,
15
+ group: systemBlockGroupProps,
16
+ component: systemBlockComponentProps,
17
+ connection: systemBlockConnectionProps,
18
+ } as const
@@ -0,0 +1,39 @@
1
+ import { z } from "zod"
2
+
3
+ /** Optional PCB associations shared only by system blocks and their groups. */
4
+ export interface SystemBlockPcbReferenceProps {
5
+ /** One PCB component name; mutually exclusive with pcbComponentNames. */
6
+ pcbComponentName?: string
7
+ /** Nonempty list of PCB component names; mutually exclusive with pcbComponentName. */
8
+ pcbComponentNames?: string[]
9
+ /**
10
+ * Named PCB subcircuit in which component names are resolved. Without component
11
+ * names, associates the entire subcircuit. Omitted uses the owning circuit scope.
12
+ * References are names, not selectors; resolution is performed by the consumer.
13
+ */
14
+ pcbSubcircuitName?: string
15
+ }
16
+
17
+ const pcbName = z.string().trim().min(1)
18
+
19
+ export const systemBlockPcbReferenceShape = {
20
+ pcbComponentName: pcbName.optional(),
21
+ pcbComponentNames: z.array(pcbName).min(1).optional(),
22
+ pcbSubcircuitName: pcbName.optional(),
23
+ }
24
+
25
+ export function refineSystemBlockPcbReference(
26
+ props: SystemBlockPcbReferenceProps,
27
+ ctx: z.RefinementCtx,
28
+ ) {
29
+ if (
30
+ props.pcbComponentName !== undefined &&
31
+ props.pcbComponentNames !== undefined
32
+ ) {
33
+ ctx.addIssue({
34
+ code: "custom",
35
+ path: ["pcbComponentNames"],
36
+ message: "Provide either pcbComponentName or pcbComponentNames, not both",
37
+ })
38
+ }
39
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tscircuit/props",
3
- "version": "0.0.715",
3
+ "version": "0.0.717",
4
4
  "description": "Props for tscircuit builtin component types",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",