@tscircuit/core 0.0.132 → 0.0.134

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.
package/dist/index.d.ts CHANGED
@@ -13,6 +13,7 @@ type RenderPhase = (typeof orderedRenderPhases)[number];
13
13
  type RenderPhaseFn<K extends RenderPhase = RenderPhase> = `doInitial${K}` | `update${K}` | `remove${K}`;
14
14
  type RenderPhaseStates = Record<RenderPhase, {
15
15
  initialized: boolean;
16
+ dirty: boolean;
16
17
  }>;
17
18
  type RenderPhaseFunctions = {
18
19
  [T in RenderPhaseFn]?: () => void;
@@ -34,7 +35,12 @@ declare abstract class Renderable implements IRenderable {
34
35
  /** Schematic-only, lines, boxes, indicators etc. */
35
36
  isSchematicPrimitive: boolean;
36
37
  _renderId: string;
38
+ _currentRenderPhase: RenderPhase | null;
39
+ private _asyncEffects;
37
40
  constructor(props: any);
41
+ protected _markDirty(phase: RenderPhase): void;
42
+ protected _queueAsyncEffect(effect: () => Promise<void>): void;
43
+ _hasIncompleteAsyncEffects(): boolean;
38
44
  runRenderCycle(): void;
39
45
  /**
40
46
  * This runs all the render methods for a given phase, calling one of:
@@ -53,6 +59,7 @@ type ReactSubtree = {
53
59
  component: NormalComponent;
54
60
  };
55
61
 
62
+ type RootCircuitEventName = "asyncEffectComplete";
56
63
  declare class Circuit {
57
64
  firstChild: PrimitiveComponent | null;
58
65
  children: PrimitiveComponent[];
@@ -71,6 +78,8 @@ declare class Circuit {
71
78
  };
72
79
  _guessRootComponent(): void;
73
80
  render(): void;
81
+ renderUntilSettled(): Promise<void>;
82
+ private _hasIncompleteAsyncEffects;
74
83
  getSoup(): AnyCircuitElement[];
75
84
  getCircuitJson(): AnyCircuitElement[];
76
85
  toJson(): AnyCircuitElement[];
@@ -88,6 +97,9 @@ declare class Circuit {
88
97
  selectOne(selector: string, opts?: {
89
98
  type?: "component" | "port";
90
99
  }): PrimitiveComponent | null;
100
+ _eventListeners: Record<RootCircuitEventName, Array<(...args: any[]) => void>>;
101
+ emit(event: RootCircuitEventName, ...args: any[]): void;
102
+ on(event: RootCircuitEventName, listener: (...args: any[]) => void): void;
91
103
  }
92
104
  /**
93
105
  * @deprecated
package/dist/index.js CHANGED
@@ -87,14 +87,61 @@ var Renderable = class {
87
87
  /** Schematic-only, lines, boxes, indicators etc. */
88
88
  isSchematicPrimitive = false;
89
89
  _renderId;
90
+ _currentRenderPhase = null;
91
+ _asyncEffects = [];
90
92
  constructor(props) {
91
93
  this._renderId = `${globalRenderCounter++}`;
92
94
  this.children = [];
93
95
  this.renderPhaseStates = {};
94
96
  for (const phase of orderedRenderPhases) {
95
- this.renderPhaseStates[phase] = { initialized: false };
97
+ this.renderPhaseStates[phase] = {
98
+ initialized: false,
99
+ dirty: false
100
+ };
101
+ }
102
+ }
103
+ _markDirty(phase) {
104
+ this.renderPhaseStates[phase].dirty = true;
105
+ const phaseIndex = orderedRenderPhases.indexOf(phase);
106
+ for (let i = phaseIndex + 1; i < orderedRenderPhases.length; i++) {
107
+ this.renderPhaseStates[orderedRenderPhases[i]].dirty = true;
96
108
  }
97
109
  }
110
+ _queueAsyncEffect(effect) {
111
+ const asyncEffect = {
112
+ promise: effect(),
113
+ // TODO don't start effects until end of render cycle
114
+ phase: this._currentRenderPhase,
115
+ complete: false
116
+ };
117
+ this._asyncEffects.push(asyncEffect);
118
+ asyncEffect.promise.then(() => {
119
+ asyncEffect.complete = true;
120
+ if ("root" in this && this.root) {
121
+ ;
122
+ this.root.emit("asyncEffectComplete", {
123
+ component: this,
124
+ asyncEffect
125
+ });
126
+ }
127
+ }).catch((error) => {
128
+ console.error(
129
+ `Async effect error in ${this._currentRenderPhase}:`,
130
+ error
131
+ );
132
+ asyncEffect.complete = true;
133
+ if ("root" in this && this.root) {
134
+ ;
135
+ this.root.emit("asyncEffectComplete", {
136
+ component: this,
137
+ asyncEffect
138
+ });
139
+ }
140
+ });
141
+ }
142
+ _hasIncompleteAsyncEffects() {
143
+ return this._asyncEffects.some((effect) => !effect.complete);
144
+ }
98
145
  runRenderCycle() {
99
146
  for (const renderPhase of orderedRenderPhases) {
100
147
  this.runRenderPhaseForChildren(renderPhase);
@@ -109,22 +156,35 @@ var Renderable = class {
109
156
  * ...depending on the current state of the component.
110
157
  */
111
158
  runRenderPhase(phase) {
112
- const isInitialized = this.renderPhaseStates[phase].initialized;
159
+ this._currentRenderPhase = phase;
160
+ const phaseState = this.renderPhaseStates[phase];
161
+ const isInitialized = phaseState.initialized;
162
+ const isDirty = phaseState.dirty;
113
163
  if (!isInitialized && this.shouldBeRemoved) return;
114
164
  if (this.shouldBeRemoved && isInitialized) {
115
165
  ;
116
166
  this?.[`remove${phase}`]?.();
117
- this.renderPhaseStates[phase].initialized = false;
167
+ phaseState.initialized = false;
168
+ phaseState.dirty = false;
118
169
  return;
119
170
  }
171
+ const prevPhaseIndex = orderedRenderPhases.indexOf(phase) - 1;
172
+ if (prevPhaseIndex >= 0) {
173
+ const prevPhase = orderedRenderPhases[prevPhaseIndex];
174
+ const hasIncompleteEffects = this._asyncEffects.filter((e) => e.phase === prevPhase).some((e) => !e.complete);
175
+ if (hasIncompleteEffects) return;
176
+ }
120
177
  if (isInitialized) {
121
- ;
122
- this?.[`update${phase}`]?.();
178
+ if (isDirty) {
179
+ ;
180
+ this?.[`update${phase}`]?.();
181
+ phaseState.dirty = false;
182
+ }
123
183
  return;
124
184
  }
125
- ;
185
+ phaseState.dirty = false;
126
186
  this?.[`doInitial${phase}`]?.();
127
- this.renderPhaseStates[phase].initialized = true;
187
+ phaseState.initialized = true;
128
188
  }
129
189
  runRenderPhaseForChildren(phase) {
130
190
  for (const child of this.children) {
@@ -1543,9 +1603,10 @@ var Port = class extends PrimitiveComponent {
1543
1603
  console.warn(`Could not find parent symbol for ${this}`);
1544
1604
  return { x: 0, y: 0 };
1545
1605
  }
1606
+ const offsetY = -0.045;
1546
1607
  const transform = compose3(
1547
1608
  this.parent.computeSchematicGlobalTransform(),
1548
- translate3(-symbol.center.x, -symbol.center.y)
1609
+ translate3(-symbol.center.x, -symbol.center.y + offsetY)
1549
1610
  );
1550
1611
  return applyToPoint4(transform, this.schematicSymbolPortDef);
1551
1612
  }
@@ -2554,11 +2615,49 @@ var stringProxy = new Proxy(
2554
2615
  var FTYPE = stringProxy;
2555
2616
 
2556
2617
  // lib/components/primitive-components/Trace.ts
2557
- import { traceProps } from "@tscircuit/props";
2558
2618
  import {
2559
2619
  MultilayerIjump,
2560
2620
  getObstaclesFromSoup
2561
2621
  } from "@tscircuit/infgrid-ijump-astar";
2622
+ import { traceProps } from "@tscircuit/props";
2623
+ import { getFullConnectivityMapFromCircuitJson } from "circuit-json-to-connectivity-map";
2624
+
2625
+ // lib/utils/autorouting/DirectLineRouter.ts
2626
+ var DirectLineRouter = class {
2627
+ input;
2628
+ constructor({ input }) {
2629
+ this.input = input;
2630
+ }
2631
+ solveAndMapToTraces() {
2632
+ const traces = [];
2633
+ for (const connection of this.input.connections) {
2634
+ if (connection.pointsToConnect.length !== 2) continue;
2635
+ const [start, end] = connection.pointsToConnect;
2636
+ const trace = {
2637
+ type: "pcb_trace",
2638
+ pcb_trace_id: "",
2639
+ route: [
2640
+ {
2641
+ route_type: "wire",
2642
+ x: start.x,
2643
+ y: start.y,
2644
+ layer: "top",
2645
+ width: 0.1
2646
+ },
2647
+ {
2648
+ route_type: "wire",
2649
+ x: end.x,
2650
+ y: end.y,
2651
+ layer: "top",
2652
+ width: 0.1
2653
+ }
2654
+ ]
2655
+ };
2656
+ traces.push(trace);
2657
+ }
2658
+ return traces;
2659
+ }
2660
+ };
2562
2661
 
2563
2662
  // lib/utils/autorouting/computeObstacleBounds.ts
2564
2663
  var computeObstacleBounds = (obstacles) => {
@@ -2569,22 +2668,6 @@ var computeObstacleBounds = (obstacles) => {
2569
2668
  return { minX, maxX, minY, maxY };
2570
2669
  };
2571
2670
 
2572
- // lib/utils/projectPointInDirection.ts
2573
- var projectPointInDirection = (point, direction, distance) => {
2574
- switch (direction) {
2575
- case "up":
2576
- return { x: point.x, y: point.y - distance };
2577
- case "down":
2578
- return { x: point.x, y: point.y + distance };
2579
- case "left":
2580
- return { x: point.x - distance, y: point.y };
2581
- case "right":
2582
- return { x: point.x + distance, y: point.y };
2583
- default:
2584
- throw new Error(`Unknown direction "${direction}"`);
2585
- }
2586
- };
2587
-
2588
2671
  // lib/utils/autorouting/findPossibleTraceLayerCombinations.ts
2589
2672
  var LAYER_SELECTION_PREFERENCE = ["top", "bottom", "inner1", "inner2"];
2590
2673
  var findPossibleTraceLayerCombinations = (hints, layer_path = []) => {
@@ -2718,8 +2801,37 @@ function getClosest(point, candidates) {
2718
2801
  return closest;
2719
2802
  }
2720
2803
 
2721
- // lib/components/primitive-components/Trace.ts
2722
- import "zod";
2804
+ // lib/utils/projectPointInDirection.ts
2805
+ var projectPointInDirection = (point, direction, distance) => {
2806
+ switch (direction) {
2807
+ case "up":
2808
+ return { x: point.x, y: point.y + distance };
2809
+ case "down":
2810
+ return { x: point.x, y: point.y - distance };
2811
+ case "left":
2812
+ return { x: point.x + distance, y: point.y };
2813
+ case "right":
2814
+ return { x: point.x - distance, y: point.y };
2815
+ default:
2816
+ throw new Error(`Unknown direction "${direction}"`);
2817
+ }
2818
+ };
2819
+
2820
+ // lib/utils/projectPointInOppositeDirection.ts
2821
+ var projectPointInOppositeDirection = (point, direction, distance) => {
2822
+ switch (direction) {
2823
+ case "up":
2824
+ return { x: point.x, y: point.y + distance };
2825
+ case "down":
2826
+ return { x: point.x, y: point.y - distance };
2827
+ case "left":
2828
+ return { x: point.x + distance, y: point.y };
2829
+ case "right":
2830
+ return { x: point.x - distance, y: point.y };
2831
+ default:
2832
+ throw new Error(`Unknown direction "${direction}"`);
2833
+ }
2834
+ };
2723
2835
 
2724
2836
  // lib/utils/try-now.ts
2725
2837
  function tryNow(fn) {
@@ -2731,46 +2843,7 @@ function tryNow(fn) {
2731
2843
  }
2732
2844
 
2733
2845
  // lib/components/primitive-components/Trace.ts
2734
- import { getFullConnectivityMapFromCircuitJson } from "circuit-json-to-connectivity-map";
2735
-
2736
- // lib/utils/autorouting/DirectLineRouter.ts
2737
- var DirectLineRouter = class {
2738
- input;
2739
- constructor({ input }) {
2740
- this.input = input;
2741
- }
2742
- solveAndMapToTraces() {
2743
- const traces = [];
2744
- for (const connection of this.input.connections) {
2745
- if (connection.pointsToConnect.length !== 2) continue;
2746
- const [start, end] = connection.pointsToConnect;
2747
- const trace = {
2748
- type: "pcb_trace",
2749
- pcb_trace_id: "",
2750
- route: [
2751
- {
2752
- route_type: "wire",
2753
- x: start.x,
2754
- y: start.y,
2755
- layer: "top",
2756
- width: 0.1
2757
- },
2758
- {
2759
- route_type: "wire",
2760
- x: end.x,
2761
- y: end.y,
2762
- layer: "top",
2763
- width: 0.1
2764
- }
2765
- ]
2766
- };
2767
- traces.push(trace);
2768
- }
2769
- return traces;
2770
- }
2771
- };
2772
-
2773
- // lib/components/primitive-components/Trace.ts
2846
+ import "zod";
2774
2847
  var portToObjective = (port) => {
2775
2848
  const portPosition = port._getGlobalPcbPositionAfterLayout();
2776
2849
  return {
@@ -3204,17 +3277,19 @@ searched component ${targetComponent.getString()}, which has ports: ${targetComp
3204
3277
  });
3205
3278
  }
3206
3279
  }
3207
- for (const { port } of ports) {
3208
- connection.pointsToConnect.push({
3209
- ...port._getGlobalSchematicPositionAfterLayout(),
3210
- ...projectPointInDirection(
3211
- port._getGlobalSchematicPositionAfterLayout(),
3212
- port.facingDirection,
3213
- 0.1501
3214
- ),
3215
- layer: "top"
3216
- });
3280
+ const portsWithPosition = ports.map(({ port }) => ({
3281
+ port,
3282
+ position: port._getGlobalSchematicPositionAfterLayout(),
3283
+ schematic_port_id: port.schematic_port_id ?? void 0,
3284
+ facingDirection: port.facingDirection
3285
+ }));
3286
+ if (portsWithPosition.length < 2) {
3287
+ return;
3217
3288
  }
3289
+ connection.pointsToConnect = portsWithPosition.map(({ position }) => ({
3290
+ ...position,
3291
+ layer: "top"
3292
+ }));
3218
3293
  const bounds = computeObstacleBounds(obstacles);
3219
3294
  const simpleRouteJsonInput = {
3220
3295
  minTraceWidth: 0.1,
@@ -3223,19 +3298,6 @@ searched component ${targetComponent.getString()}, which has ports: ${targetComp
3223
3298
  bounds,
3224
3299
  layerCount: 1
3225
3300
  };
3226
- if (this.getSubcircuit().props._schDebugObjectsEnabled) {
3227
- for (const obstacle of obstacles) {
3228
- db.schematic_debug_object.insert({
3229
- shape: "rect",
3230
- center: obstacle.center,
3231
- size: {
3232
- width: obstacle.width,
3233
- height: obstacle.height
3234
- },
3235
- label: "obstacle"
3236
- });
3237
- }
3238
- }
3239
3301
  let Autorouter = MultilayerIjump;
3240
3302
  if (this.getSubcircuit().props._schDirectLineRoutingEnabled) {
3241
3303
  Autorouter = DirectLineRouter;
@@ -3250,14 +3312,34 @@ searched component ${targetComponent.getString()}, which has ports: ${targetComp
3250
3312
  const { route } = result;
3251
3313
  const edges = [];
3252
3314
  for (let i = 0; i < route.length - 1; i++) {
3253
- const from = route[i];
3254
- const to = route[i + 1];
3255
3315
  edges.push({
3256
- from,
3257
- to
3258
- // TODO to_schematic_port_id and from_schematic_port_id
3316
+ from: route[i],
3317
+ to: route[i + 1]
3259
3318
  });
3260
3319
  }
3320
+ const STUB_LENGTH = 0.15;
3321
+ edges.unshift({
3322
+ from: {
3323
+ ...projectPointInDirection(
3324
+ route[0],
3325
+ portsWithPosition[0].facingDirection,
3326
+ STUB_LENGTH
3327
+ )
3328
+ },
3329
+ to: route[0],
3330
+ from_schematic_port_id: portsWithPosition[0].schematic_port_id
3331
+ });
3332
+ edges.push({
3333
+ from: route[route.length - 1],
3334
+ to: {
3335
+ ...projectPointInOppositeDirection(
3336
+ route[route.length - 1],
3337
+ portsWithPosition[1].facingDirection,
3338
+ STUB_LENGTH
3339
+ )
3340
+ },
3341
+ from_schematic_port_id: portsWithPosition[1].schematic_port_id
3342
+ });
3261
3343
  const trace = db.schematic_trace.insert({
3262
3344
  source_trace_id: this.source_trace_id,
3263
3345
  edges
@@ -4374,6 +4456,21 @@ var Circuit = class {
4374
4456
  firstChild.runRenderCycle();
4375
4457
  this._hasRenderedAtleastOnce = true;
4376
4458
  }
4459
+ async renderUntilSettled() {
4460
+ this.render();
4461
+ while (this._hasIncompleteAsyncEffects()) {
4462
+ await new Promise((resolve) => setTimeout(resolve, 100));
4463
+ this.render();
4464
+ }
4465
+ }
4466
+ _hasIncompleteAsyncEffects() {
4467
+ return this.children.some((child) => {
4468
+ if (child._hasIncompleteAsyncEffects()) return true;
4469
+ return child.children.some(
4470
+ (grandchild) => grandchild._hasIncompleteAsyncEffects()
4471
+ );
4472
+ });
4473
+ }
4377
4474
  getSoup() {
4378
4475
  if (!this._hasRenderedAtleastOnce) this.render();
4379
4476
  return this.db.toArray();
@@ -4410,6 +4507,19 @@ var Circuit = class {
4410
4507
  selectOne(selector, opts) {
4411
4508
  return this.firstChild?.selectOne(selector, opts) ?? null;
4412
4509
  }
4510
+ _eventListeners = { asyncEffectComplete: [] };
4511
+ emit(event, ...args) {
4512
+ if (!this._eventListeners[event]) return;
4513
+ for (const listener of this._eventListeners[event]) {
4514
+ listener(...args);
4515
+ }
4516
+ }
4517
+ on(event, listener) {
4518
+ if (!this._eventListeners[event]) {
4519
+ this._eventListeners[event] = [];
4520
+ }
4521
+ this._eventListeners[event].push(listener);
4522
+ }
4413
4523
  };
4414
4524
  var Project = Circuit;
4415
4525
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@tscircuit/core",
3
3
  "type": "module",
4
- "version": "0.0.132",
4
+ "version": "0.0.134",
5
5
  "types": "dist/index.d.ts",
6
6
  "main": "dist/index.js",
7
7
  "module": "dist/index.js",