@aiready/components 0.14.2 → 0.14.4

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.
@@ -1,2 +1,2 @@
1
- export { u as useDrag, b as useForceSimulation } from '../useForceSimulation-BNhxX4gH.js';
2
1
  import 'd3';
2
+ export { c as UseForceSimulationReturnExt, u as useDrag, b as useForceSimulation } from '../useForceSimulation-CjLhYevG.js';
@@ -1,6 +1,8 @@
1
- import { useState, useRef, useEffect } from 'react';
1
+ import { useState, useRef, useEffect, useCallback } from 'react';
2
2
  import * as d3 from 'd3';
3
3
 
4
+ // src/hooks/useForceSimulation.ts
5
+
4
6
  // src/hooks/simulation-helpers.ts
5
7
  function stabilizeNodes(nodes) {
6
8
  nodes.forEach((n) => {
@@ -10,6 +12,16 @@ function stabilizeNodes(nodes) {
10
12
  if (typeof n.y === "number") n.y = Number(n.y.toFixed(3));
11
13
  });
12
14
  }
15
+ function seedCircularPositions(nodes, width, height) {
16
+ const radius = Math.min(width, height) * 0.45;
17
+ nodes.forEach((n, i) => {
18
+ const angle = i * 2 * Math.PI / nodes.length;
19
+ n.x = width / 2 + radius * Math.cos(angle);
20
+ n.y = height / 2 + radius * Math.sin(angle);
21
+ n.vx = (Math.random() - 0.5) * 2;
22
+ n.vy = (Math.random() - 0.5) * 2;
23
+ });
24
+ }
13
25
  function seedRandomPositions(nodes, width, height) {
14
26
  nodes.forEach((n) => {
15
27
  n.x = Math.random() * width;
@@ -18,27 +30,68 @@ function seedRandomPositions(nodes, width, height) {
18
30
  n.vy = (Math.random() - 0.5) * 10;
19
31
  });
20
32
  }
33
+ function safelyStopSimulation(simulation, nodes, options = {}) {
34
+ try {
35
+ if (options.stabilize) {
36
+ stabilizeNodes(nodes);
37
+ }
38
+ simulation.stop();
39
+ } catch (error) {
40
+ console.warn("AIReady: Failed to stop simulation safely:", error);
41
+ }
42
+ }
43
+
44
+ // src/hooks/simulation-constants.ts
45
+ var SIMULATION_DEFAULTS = {
46
+ CHARGE_STRENGTH: -300,
47
+ LINK_DISTANCE: 100,
48
+ LINK_STRENGTH: 1,
49
+ COLLISION_STRENGTH: 1,
50
+ COLLISION_RADIUS: 10,
51
+ CENTER_STRENGTH: 0.1,
52
+ ALPHA_DECAY: 0.0228,
53
+ VELOCITY_DECAY: 0.4,
54
+ ALPHA_TARGET: 0,
55
+ WARM_ALPHA: 0.3,
56
+ ALPHA_MIN: 0.01,
57
+ TICK_THROTTLE_MS: 33,
58
+ // ~30 fps
59
+ MAX_SIMULATION_TIME_MS: 3e3,
60
+ STABILIZE_ON_STOP: true
61
+ };
62
+ var FORCE_NAMES = {
63
+ LINK: "link",
64
+ CHARGE: "charge",
65
+ CENTER: "center",
66
+ COLLISION: "collision",
67
+ X: "x",
68
+ Y: "y"
69
+ };
70
+ var EVENT_NAMES = {
71
+ TICK: "tick",
72
+ END: "end"
73
+ };
74
+
75
+ // src/hooks/useForceSimulation.ts
21
76
  function useForceSimulation(initialNodes, initialLinks, options) {
22
77
  const {
23
- chargeStrength = -300,
24
- linkDistance = 100,
25
- linkStrength = 1,
26
- collisionStrength = 1,
27
- collisionRadius = 10,
28
- centerStrength = 0.1,
78
+ chargeStrength = SIMULATION_DEFAULTS.CHARGE_STRENGTH,
79
+ linkDistance = SIMULATION_DEFAULTS.LINK_DISTANCE,
80
+ linkStrength = SIMULATION_DEFAULTS.LINK_STRENGTH,
81
+ collisionStrength = SIMULATION_DEFAULTS.COLLISION_STRENGTH,
82
+ collisionRadius = SIMULATION_DEFAULTS.COLLISION_RADIUS,
83
+ centerStrength = SIMULATION_DEFAULTS.CENTER_STRENGTH,
29
84
  width,
30
85
  height,
31
- alphaDecay = 0.0228,
32
- velocityDecay = 0.4,
33
- alphaTarget = 0,
34
- warmAlpha = 0.3,
35
- alphaMin = 0.01,
86
+ alphaDecay = SIMULATION_DEFAULTS.ALPHA_DECAY,
87
+ velocityDecay = SIMULATION_DEFAULTS.VELOCITY_DECAY,
88
+ alphaTarget = SIMULATION_DEFAULTS.ALPHA_TARGET,
89
+ warmAlpha = SIMULATION_DEFAULTS.WARM_ALPHA,
90
+ alphaMin = SIMULATION_DEFAULTS.ALPHA_MIN,
36
91
  onTick,
37
- // Optional throttle in milliseconds for tick updates (reduce React re-renders)
38
- // Lower values = smoother but more CPU; default ~30ms (~33fps)
39
- stabilizeOnStop = true,
40
- tickThrottleMs = 33,
41
- maxSimulationTimeMs = 3e3
92
+ stabilizeOnStop = SIMULATION_DEFAULTS.STABILIZE_ON_STOP,
93
+ tickThrottleMs = SIMULATION_DEFAULTS.TICK_THROTTLE_MS,
94
+ maxSimulationTimeMs = SIMULATION_DEFAULTS.MAX_SIMULATION_TIME_MS
42
95
  } = options;
43
96
  const [nodes, setNodes] = useState(initialNodes);
44
97
  const [links, setLinks] = useState(initialLinks);
@@ -46,8 +99,13 @@ function useForceSimulation(initialNodes, initialLinks, options) {
46
99
  const [alpha, setAlpha] = useState(1);
47
100
  const simulationRef = useRef(null);
48
101
  const stopTimeoutRef = useRef(null);
102
+ const forcesEnabledRef = useRef(true);
103
+ const originalForcesRef = useRef({
104
+ charge: chargeStrength,
105
+ link: linkStrength
106
+ });
49
107
  const nodesKey = initialNodes.map((n) => n.id).join("|");
50
- const linksKey = (initialLinks || []).map((l) => {
108
+ const linksKey = initialLinks.map((l) => {
51
109
  const sourceId = typeof l.source === "string" ? l.source : l.source?.id;
52
110
  const targetId = typeof l.target === "string" ? l.target : l.target?.id;
53
111
  const linkType = l.type || "";
@@ -57,179 +115,24 @@ function useForceSimulation(initialNodes, initialLinks, options) {
57
115
  const nodesCopy = initialNodes.map((node) => ({ ...node }));
58
116
  const linksCopy = initialLinks.map((link) => ({ ...link }));
59
117
  try {
60
- nodesCopy.forEach((n, i) => {
61
- const angle = i * 2 * Math.PI / nodesCopy.length;
62
- const radius = Math.min(width, height) * 0.45;
63
- n.x = width / 2 + radius * Math.cos(angle);
64
- n.y = height / 2 + radius * Math.sin(angle);
65
- n.vx = (Math.random() - 0.5) * 2;
66
- n.vy = (Math.random() - 0.5) * 2;
67
- });
68
- } catch (e) {
69
- console.warn("Failed to seed node positions, falling back to random:", e);
118
+ seedCircularPositions(nodesCopy, width, height);
119
+ } catch (error) {
120
+ console.warn(
121
+ "AIReady: Position seeding failed, using random fallback:",
122
+ error
123
+ );
70
124
  seedRandomPositions(nodesCopy, width, height);
71
125
  }
72
126
  const simulation = d3.forceSimulation(
73
127
  nodesCopy
74
128
  );
75
- try {
76
- const linkForce = d3.forceLink(
77
- linksCopy
78
- );
79
- linkForce.id((d) => d.id).distance((d) => {
80
- const link = d;
81
- return link && link.distance != null ? link.distance : linkDistance;
82
- }).strength(linkStrength);
83
- simulation.force(
84
- "link",
85
- linkForce
86
- );
87
- } catch (e) {
88
- console.warn("Failed to configure link force, using fallback:", e);
89
- try {
90
- simulation.force(
91
- "link",
92
- d3.forceLink(
93
- linksCopy
94
- )
95
- );
96
- } catch (fallbackError) {
97
- console.warn("Fallback link force also failed:", fallbackError);
98
- }
99
- }
100
- try {
101
- simulation.force(
102
- "charge",
103
- d3.forceManyBody().strength(chargeStrength)
104
- );
105
- simulation.force(
106
- "center",
107
- d3.forceCenter(width / 2, height / 2).strength(centerStrength)
108
- );
109
- const collide = d3.forceCollide().radius((d) => {
110
- const node = d;
111
- const nodeSize = node && node.size ? node.size : 10;
112
- return nodeSize + collisionRadius;
113
- }).strength(collisionStrength);
114
- simulation.force("collision", collide);
115
- simulation.force(
116
- "x",
117
- d3.forceX(width / 2).strength(Math.max(0.02, centerStrength * 0.5))
118
- );
119
- simulation.force(
120
- "y",
121
- d3.forceY(height / 2).strength(Math.max(0.02, centerStrength * 0.5))
122
- );
123
- simulation.alphaDecay(alphaDecay);
124
- simulation.velocityDecay(velocityDecay);
125
- simulation.alphaMin(alphaMin);
126
- try {
127
- simulation.alphaTarget(alphaTarget);
128
- } catch (e) {
129
- console.warn("Failed to set alpha target:", e);
130
- }
131
- try {
132
- simulation.alpha(warmAlpha);
133
- } catch (e) {
134
- console.warn("Failed to set initial alpha:", e);
135
- }
136
- } catch (e) {
137
- console.warn("Failed to configure simulation forces:", e);
138
- }
129
+ applySimulationForces(simulation, linksCopy);
130
+ configureSimulationParameters(simulation);
139
131
  simulationRef.current = simulation;
140
- if (stopTimeoutRef.current != null) {
141
- try {
142
- globalThis.clearTimeout(stopTimeoutRef.current);
143
- } catch (e) {
144
- console.warn("Failed to clear simulation timeout:", e);
145
- }
146
- stopTimeoutRef.current = null;
147
- }
148
- if (maxSimulationTimeMs && maxSimulationTimeMs > 0) {
149
- stopTimeoutRef.current = globalThis.setTimeout(() => {
150
- try {
151
- if (stabilizeOnStop) {
152
- stabilizeNodes(nodesCopy);
153
- }
154
- simulation.alpha(0);
155
- simulation.stop();
156
- } catch (e) {
157
- console.warn("Failed to stop simulation:", e);
158
- }
159
- setIsRunning(false);
160
- setNodes([...nodesCopy]);
161
- setLinks([...linksCopy]);
162
- }, maxSimulationTimeMs);
163
- }
164
- let rafId = null;
165
- let lastUpdate = 0;
166
- const tickHandler = () => {
167
- try {
168
- if (typeof onTick === "function")
169
- onTick(nodesCopy, linksCopy, simulation);
170
- } catch (e) {
171
- console.warn("Tick callback error:", e);
172
- }
173
- try {
174
- if (simulation.alpha() <= alphaMin) {
175
- try {
176
- if (stabilizeOnStop) {
177
- stabilizeNodes(nodesCopy);
178
- }
179
- simulation.stop();
180
- } catch (e) {
181
- console.warn("Failed to stop simulation:", e);
182
- }
183
- setAlpha(simulation.alpha());
184
- setIsRunning(false);
185
- setNodes([...nodesCopy]);
186
- setLinks([...linksCopy]);
187
- return;
188
- }
189
- } catch (e) {
190
- console.warn("Error checking simulation alpha:", e);
191
- }
192
- const now = Date.now();
193
- const shouldUpdate = now - lastUpdate >= tickThrottleMs;
194
- if (rafId == null && shouldUpdate) {
195
- rafId = (globalThis.requestAnimationFrame || ((cb) => setTimeout(cb, 16)))(() => {
196
- rafId = null;
197
- lastUpdate = Date.now();
198
- setNodes([...nodesCopy]);
199
- setLinks([...linksCopy]);
200
- setAlpha(simulation.alpha());
201
- setIsRunning(simulation.alpha() > simulation.alphaMin());
202
- });
203
- }
204
- };
205
- simulation.on("tick", tickHandler);
206
- simulation.on("end", () => {
207
- setIsRunning(false);
208
- });
209
- return () => {
210
- try {
211
- simulation.on("tick", null);
212
- } catch (e) {
213
- console.warn("Failed to clear simulation tick handler:", e);
214
- }
215
- if (stopTimeoutRef.current != null) {
216
- try {
217
- globalThis.clearTimeout(stopTimeoutRef.current);
218
- } catch (e) {
219
- console.warn("Failed to clear timeout on cleanup:", e);
220
- }
221
- stopTimeoutRef.current = null;
222
- }
223
- if (rafId != null) {
224
- try {
225
- (globalThis.cancelAnimationFrame || ((id) => clearTimeout(id)))(rafId);
226
- } catch (e) {
227
- console.warn("Failed to cancel animation frame:", e);
228
- }
229
- rafId = null;
230
- }
231
- simulation.stop();
232
- };
132
+ const rafState = { rafId: null, lastUpdate: 0 };
133
+ setupTickHandler(simulation, nodesCopy, linksCopy, rafState);
134
+ setupStopTimer(simulation, nodesCopy, linksCopy);
135
+ return () => cleanupSimulation(simulation, rafState);
233
136
  }, [
234
137
  nodesKey,
235
138
  linksKey,
@@ -249,98 +152,173 @@ function useForceSimulation(initialNodes, initialLinks, options) {
249
152
  tickThrottleMs,
250
153
  maxSimulationTimeMs
251
154
  ]);
252
- const restart = () => {
253
- if (simulationRef.current) {
254
- try {
255
- simulationRef.current.alphaTarget(warmAlpha).restart();
256
- } catch {
257
- simulationRef.current.restart();
258
- }
259
- setIsRunning(true);
260
- if (stopTimeoutRef.current != null) {
155
+ const applySimulationForces = (simulation, linksCopy) => {
156
+ try {
157
+ const linkForce = d3.forceLink(linksCopy).id((d) => d.id).distance((d) => d.distance ?? linkDistance).strength(linkStrength);
158
+ simulation.force(FORCE_NAMES.LINK, linkForce).force(FORCE_NAMES.CHARGE, d3.forceManyBody().strength(chargeStrength)).force(
159
+ FORCE_NAMES.CENTER,
160
+ d3.forceCenter(width / 2, height / 2).strength(centerStrength)
161
+ ).force(
162
+ FORCE_NAMES.COLLISION,
163
+ d3.forceCollide().radius((d) => (d.size ?? 10) + collisionRadius).strength(collisionStrength)
164
+ ).force(
165
+ FORCE_NAMES.X,
166
+ d3.forceX(width / 2).strength(Math.max(0.02, centerStrength * 0.5))
167
+ ).force(
168
+ FORCE_NAMES.Y,
169
+ d3.forceY(height / 2).strength(Math.max(0.02, centerStrength * 0.5))
170
+ );
171
+ } catch (error) {
172
+ console.warn("AIReady: Failed to configure simulation forces:", error);
173
+ }
174
+ };
175
+ const configureSimulationParameters = (simulation) => {
176
+ simulation.alphaDecay(alphaDecay).velocityDecay(velocityDecay).alphaMin(alphaMin).alphaTarget(alphaTarget).alpha(warmAlpha);
177
+ };
178
+ const setupStopTimer = (simulation, nodesCopy, linksCopy) => {
179
+ if (stopTimeoutRef.current) {
180
+ clearTimeout(stopTimeoutRef.current);
181
+ }
182
+ if (maxSimulationTimeMs > 0) {
183
+ stopTimeoutRef.current = setTimeout(() => {
184
+ safelyStopSimulation(simulation, nodesCopy, {
185
+ stabilize: stabilizeOnStop
186
+ });
187
+ updateStateAfterStop(nodesCopy, linksCopy, 0);
188
+ }, maxSimulationTimeMs);
189
+ }
190
+ };
191
+ const updateStateAfterStop = (nodesCopy, linksCopy, currentAlpha) => {
192
+ setIsRunning(false);
193
+ setAlpha(currentAlpha);
194
+ setNodes([...nodesCopy]);
195
+ setLinks([...linksCopy]);
196
+ };
197
+ const setupTickHandler = (simulation, nodesCopy, linksCopy, rafState) => {
198
+ const handleTick = () => {
199
+ if (onTick) {
261
200
  try {
262
- globalThis.clearTimeout(stopTimeoutRef.current);
263
- } catch (e) {
264
- console.warn("Failed to clear simulation timeout:", e);
201
+ onTick(nodesCopy, linksCopy, simulation);
202
+ } catch (error) {
203
+ console.warn("AIReady: Simulation onTick callback failed:", error);
265
204
  }
266
- stopTimeoutRef.current = null;
267
205
  }
268
- if (maxSimulationTimeMs && maxSimulationTimeMs > 0) {
269
- stopTimeoutRef.current = globalThis.setTimeout(() => {
270
- try {
271
- simulationRef.current?.alpha(0);
272
- simulationRef.current?.stop();
273
- } catch (e) {
274
- console.warn("Failed to stop simulation:", e);
275
- }
276
- setIsRunning(false);
277
- }, maxSimulationTimeMs);
206
+ const currentAlpha = simulation.alpha();
207
+ if (currentAlpha <= alphaMin) {
208
+ safelyStopSimulation(simulation, nodesCopy, {
209
+ stabilize: stabilizeOnStop
210
+ });
211
+ updateStateAfterStop(nodesCopy, linksCopy, currentAlpha);
212
+ return;
278
213
  }
279
- }
214
+ syncStateOnTick(nodesCopy, linksCopy, currentAlpha, rafState);
215
+ };
216
+ simulation.on(EVENT_NAMES.TICK, handleTick);
217
+ simulation.on(EVENT_NAMES.END, () => setIsRunning(false));
280
218
  };
281
- const stop = () => {
282
- if (simulationRef.current) {
283
- simulationRef.current.stop();
284
- setIsRunning(false);
219
+ const syncStateOnTick = (nodesCopy, linksCopy, currentAlpha, rafState) => {
220
+ const now = Date.now();
221
+ if (rafState.rafId === null && now - rafState.lastUpdate >= tickThrottleMs) {
222
+ rafState.rafId = requestAnimationFrame(() => {
223
+ rafState.rafId = null;
224
+ rafState.lastUpdate = Date.now();
225
+ setNodes([...nodesCopy]);
226
+ setLinks([...linksCopy]);
227
+ setAlpha(currentAlpha);
228
+ setIsRunning(currentAlpha > alphaMin);
229
+ });
285
230
  }
286
231
  };
287
- const originalForcesRef = useRef({
288
- charge: chargeStrength,
289
- link: linkStrength,
290
- collision: collisionStrength
291
- });
292
- const forcesEnabledRef = useRef(true);
293
- const setForcesEnabled = (enabled) => {
232
+ const cleanupSimulation = (simulation, rafState) => {
233
+ simulation.on(EVENT_NAMES.TICK, null);
234
+ if (stopTimeoutRef.current) clearTimeout(stopTimeoutRef.current);
235
+ if (rafState.rafId !== null) cancelAnimationFrame(rafState.rafId);
236
+ simulation.stop();
237
+ };
238
+ const restartSimulation = useCallback(() => {
294
239
  const sim = simulationRef.current;
295
240
  if (!sim) return;
296
- if (forcesEnabledRef.current === enabled) return;
297
- forcesEnabledRef.current = enabled;
298
241
  try {
299
- const charge = sim.force(
300
- "charge"
301
- );
302
- if (charge && typeof charge.strength === "function") {
303
- charge.strength(enabled ? originalForcesRef.current.charge : 0);
304
- }
305
- const link = sim.force("link");
306
- if (link && typeof link.strength === "function") {
307
- link.strength(enabled ? originalForcesRef.current.link : 0);
242
+ sim.alphaTarget(warmAlpha).restart();
243
+ setIsRunning(true);
244
+ if (stopTimeoutRef.current) clearTimeout(stopTimeoutRef.current);
245
+ if (maxSimulationTimeMs > 0) {
246
+ stopTimeoutRef.current = setTimeout(() => {
247
+ sim.alpha(0);
248
+ sim.stop();
249
+ setIsRunning(false);
250
+ }, maxSimulationTimeMs);
308
251
  }
309
- } catch (e) {
310
- console.warn("Failed to toggle simulation forces:", e);
252
+ } catch (error) {
253
+ console.warn("AIReady: Failed to restart simulation:", error);
311
254
  }
312
- };
255
+ }, [warmAlpha, maxSimulationTimeMs]);
256
+ const stopSimulation = useCallback(() => {
257
+ if (simulationRef.current) {
258
+ simulationRef.current.stop();
259
+ setIsRunning(false);
260
+ }
261
+ }, []);
262
+ const setForcesEnabled = useCallback(
263
+ (enabled) => {
264
+ const sim = simulationRef.current;
265
+ if (!sim || forcesEnabledRef.current === enabled) return;
266
+ forcesEnabledRef.current = enabled;
267
+ try {
268
+ const charge = sim.force(
269
+ FORCE_NAMES.CHARGE
270
+ );
271
+ if (charge) {
272
+ charge.strength(enabled ? originalForcesRef.current.charge : 0);
273
+ }
274
+ const link = sim.force(FORCE_NAMES.LINK);
275
+ if (link) {
276
+ link.strength(enabled ? originalForcesRef.current.link : 0);
277
+ }
278
+ sim.alpha(warmAlpha).restart();
279
+ } catch (error) {
280
+ console.warn("AIReady: Failed to toggle simulation forces:", error);
281
+ }
282
+ },
283
+ [warmAlpha]
284
+ );
313
285
  return {
314
286
  nodes,
315
287
  links,
316
- restart,
317
- stop,
288
+ restart: restartSimulation,
289
+ stop: stopSimulation,
318
290
  isRunning,
319
291
  alpha,
320
292
  setForcesEnabled
321
293
  };
322
294
  }
323
295
  function useDrag(simulation) {
324
- const dragStarted = (event, node) => {
325
- if (!simulation) return;
326
- if (!event.active) simulation.alphaTarget(0.3).restart();
327
- node.fx = node.x;
328
- node.fy = node.y;
329
- };
330
- const dragged = (event, node) => {
296
+ const handleDragStart = useCallback(
297
+ (event, node) => {
298
+ if (!simulation) return;
299
+ if (!event.active) simulation.alphaTarget(0.3).restart();
300
+ node.fx = node.x;
301
+ node.fy = node.y;
302
+ },
303
+ [simulation]
304
+ );
305
+ const handleDragged = useCallback((event, node) => {
331
306
  node.fx = event.x;
332
307
  node.fy = event.y;
333
- };
334
- const dragEnded = (event, node) => {
335
- if (!simulation) return;
336
- if (!event.active) simulation.alphaTarget(0);
337
- node.fx = null;
338
- node.fy = null;
339
- };
308
+ }, []);
309
+ const handleDragEnd = useCallback(
310
+ (event, node) => {
311
+ if (!simulation) return;
312
+ if (!event.active) simulation.alphaTarget(0);
313
+ node.fx = null;
314
+ node.fy = null;
315
+ },
316
+ [simulation]
317
+ );
340
318
  return {
341
- onDragStart: dragStarted,
342
- onDrag: dragged,
343
- onDragEnd: dragEnded
319
+ onDragStart: handleDragStart,
320
+ onDrag: handleDragged,
321
+ onDragEnd: handleDragEnd
344
322
  };
345
323
  }
346
324