@tscircuit/core 0.0.133 → 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) {
@@ -4396,6 +4456,21 @@ var Circuit = class {
4396
4456
  firstChild.runRenderCycle();
4397
4457
  this._hasRenderedAtleastOnce = true;
4398
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
+ }
4399
4474
  getSoup() {
4400
4475
  if (!this._hasRenderedAtleastOnce) this.render();
4401
4476
  return this.db.toArray();
@@ -4432,6 +4507,19 @@ var Circuit = class {
4432
4507
  selectOne(selector, opts) {
4433
4508
  return this.firstChild?.selectOne(selector, opts) ?? null;
4434
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
+ }
4435
4523
  };
4436
4524
  var Project = Circuit;
4437
4525
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@tscircuit/core",
3
3
  "type": "module",
4
- "version": "0.0.133",
4
+ "version": "0.0.134",
5
5
  "types": "dist/index.d.ts",
6
6
  "main": "dist/index.js",
7
7
  "module": "dist/index.js",