@aiready/components 0.14.1 → 0.14.3

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.
@@ -3,7 +3,7 @@ import * as React from 'react';
3
3
  import { VariantProps } from 'class-variance-authority';
4
4
 
5
5
  declare const buttonVariants: (props?: ({
6
- variant?: "default" | "destructive" | "outline" | "secondary" | "ghost" | "link" | "glow" | "glass" | "accent" | null | undefined;
6
+ variant?: "link" | "default" | "destructive" | "outline" | "secondary" | "ghost" | "glow" | "glass" | "accent" | null | undefined;
7
7
  size?: "default" | "sm" | "lg" | "icon" | "xs" | null | undefined;
8
8
  } & class_variance_authority_types.ClassProp) | undefined) => string;
9
9
  interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement>, VariantProps<typeof buttonVariants> {
@@ -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,19 @@ 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("AIReady: Position seeding failed, using random fallback:", error);
70
121
  seedRandomPositions(nodesCopy, width, height);
71
122
  }
72
- const simulation = d3.forceSimulation(
73
- nodesCopy
74
- );
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
- }
123
+ const simulation = d3.forceSimulation(nodesCopy);
124
+ applySimulationForces(simulation, linksCopy);
125
+ configureSimulationParameters(simulation);
139
126
  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
- };
127
+ const rafState = { rafId: null, lastUpdate: 0 };
128
+ setupTickHandler(simulation, nodesCopy, linksCopy, rafState);
129
+ setupStopTimer(simulation, nodesCopy, linksCopy);
130
+ return () => cleanupSimulation(simulation, rafState);
233
131
  }, [
234
132
  nodesKey,
235
133
  linksKey,
@@ -249,98 +147,155 @@ function useForceSimulation(initialNodes, initialLinks, options) {
249
147
  tickThrottleMs,
250
148
  maxSimulationTimeMs
251
149
  ]);
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) {
150
+ const applySimulationForces = (simulation, linksCopy) => {
151
+ try {
152
+ const linkForce = d3.forceLink(linksCopy).id((d) => d.id).distance((d) => d.distance ?? linkDistance).strength(linkStrength);
153
+ simulation.force(FORCE_NAMES.LINK, linkForce).force(FORCE_NAMES.CHARGE, d3.forceManyBody().strength(chargeStrength)).force(FORCE_NAMES.CENTER, d3.forceCenter(width / 2, height / 2).strength(centerStrength)).force(
154
+ FORCE_NAMES.COLLISION,
155
+ d3.forceCollide().radius((d) => (d.size ?? 10) + collisionRadius).strength(collisionStrength)
156
+ ).force(FORCE_NAMES.X, d3.forceX(width / 2).strength(Math.max(0.02, centerStrength * 0.5))).force(FORCE_NAMES.Y, d3.forceY(height / 2).strength(Math.max(0.02, centerStrength * 0.5)));
157
+ } catch (error) {
158
+ console.warn("AIReady: Failed to configure simulation forces:", error);
159
+ }
160
+ };
161
+ const configureSimulationParameters = (simulation) => {
162
+ simulation.alphaDecay(alphaDecay).velocityDecay(velocityDecay).alphaMin(alphaMin).alphaTarget(alphaTarget).alpha(warmAlpha);
163
+ };
164
+ const setupStopTimer = (simulation, nodesCopy, linksCopy) => {
165
+ if (stopTimeoutRef.current) {
166
+ clearTimeout(stopTimeoutRef.current);
167
+ }
168
+ if (maxSimulationTimeMs > 0) {
169
+ stopTimeoutRef.current = setTimeout(() => {
170
+ safelyStopSimulation(simulation, nodesCopy, { stabilize: stabilizeOnStop });
171
+ updateStateAfterStop(nodesCopy, linksCopy, 0);
172
+ }, maxSimulationTimeMs);
173
+ }
174
+ };
175
+ const updateStateAfterStop = (nodesCopy, linksCopy, currentAlpha) => {
176
+ setIsRunning(false);
177
+ setAlpha(currentAlpha);
178
+ setNodes([...nodesCopy]);
179
+ setLinks([...linksCopy]);
180
+ };
181
+ const setupTickHandler = (simulation, nodesCopy, linksCopy, rafState) => {
182
+ const handleTick = () => {
183
+ if (onTick) {
261
184
  try {
262
- globalThis.clearTimeout(stopTimeoutRef.current);
263
- } catch (e) {
264
- console.warn("Failed to clear simulation timeout:", e);
185
+ onTick(nodesCopy, linksCopy, simulation);
186
+ } catch (error) {
187
+ console.warn("AIReady: Simulation onTick callback failed:", error);
265
188
  }
266
- stopTimeoutRef.current = null;
267
189
  }
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
- }
190
+ const currentAlpha = simulation.alpha();
191
+ if (currentAlpha <= alphaMin) {
192
+ safelyStopSimulation(simulation, nodesCopy, { stabilize: stabilizeOnStop });
193
+ updateStateAfterStop(nodesCopy, linksCopy, currentAlpha);
194
+ return;
195
+ }
196
+ syncStateOnTick(nodesCopy, linksCopy, currentAlpha, rafState);
197
+ };
198
+ simulation.on(EVENT_NAMES.TICK, handleTick);
199
+ simulation.on(EVENT_NAMES.END, () => setIsRunning(false));
200
+ };
201
+ const syncStateOnTick = (nodesCopy, linksCopy, currentAlpha, rafState) => {
202
+ const now = Date.now();
203
+ if (rafState.rafId === null && now - rafState.lastUpdate >= tickThrottleMs) {
204
+ rafState.rafId = requestAnimationFrame(() => {
205
+ rafState.rafId = null;
206
+ rafState.lastUpdate = Date.now();
207
+ setNodes([...nodesCopy]);
208
+ setLinks([...linksCopy]);
209
+ setAlpha(currentAlpha);
210
+ setIsRunning(currentAlpha > alphaMin);
211
+ });
212
+ }
213
+ };
214
+ const cleanupSimulation = (simulation, rafState) => {
215
+ simulation.on(EVENT_NAMES.TICK, null);
216
+ if (stopTimeoutRef.current) clearTimeout(stopTimeoutRef.current);
217
+ if (rafState.rafId !== null) cancelAnimationFrame(rafState.rafId);
218
+ simulation.stop();
219
+ };
220
+ const restartSimulation = useCallback(() => {
221
+ const sim = simulationRef.current;
222
+ if (!sim) return;
223
+ try {
224
+ sim.alphaTarget(warmAlpha).restart();
225
+ setIsRunning(true);
226
+ if (stopTimeoutRef.current) clearTimeout(stopTimeoutRef.current);
227
+ if (maxSimulationTimeMs > 0) {
228
+ stopTimeoutRef.current = setTimeout(() => {
229
+ sim.alpha(0);
230
+ sim.stop();
276
231
  setIsRunning(false);
277
232
  }, maxSimulationTimeMs);
278
233
  }
234
+ } catch (error) {
235
+ console.warn("AIReady: Failed to restart simulation:", error);
279
236
  }
280
- };
281
- const stop = () => {
237
+ }, [warmAlpha, maxSimulationTimeMs]);
238
+ const stopSimulation = useCallback(() => {
282
239
  if (simulationRef.current) {
283
240
  simulationRef.current.stop();
284
241
  setIsRunning(false);
285
242
  }
286
- };
287
- const originalForcesRef = useRef({
288
- charge: chargeStrength,
289
- link: linkStrength,
290
- collision: collisionStrength
291
- });
292
- const forcesEnabledRef = useRef(true);
293
- const setForcesEnabled = (enabled) => {
243
+ }, []);
244
+ const setForcesEnabled = useCallback((enabled) => {
294
245
  const sim = simulationRef.current;
295
- if (!sim) return;
296
- if (forcesEnabledRef.current === enabled) return;
246
+ if (!sim || forcesEnabledRef.current === enabled) return;
297
247
  forcesEnabledRef.current = enabled;
298
248
  try {
299
- const charge = sim.force(
300
- "charge"
301
- );
302
- if (charge && typeof charge.strength === "function") {
249
+ const charge = sim.force(FORCE_NAMES.CHARGE);
250
+ if (charge) {
303
251
  charge.strength(enabled ? originalForcesRef.current.charge : 0);
304
252
  }
305
- const link = sim.force("link");
306
- if (link && typeof link.strength === "function") {
253
+ const link = sim.force(FORCE_NAMES.LINK);
254
+ if (link) {
307
255
  link.strength(enabled ? originalForcesRef.current.link : 0);
308
256
  }
309
- } catch (e) {
310
- console.warn("Failed to toggle simulation forces:", e);
257
+ sim.alpha(warmAlpha).restart();
258
+ } catch (error) {
259
+ console.warn("AIReady: Failed to toggle simulation forces:", error);
311
260
  }
312
- };
261
+ }, [warmAlpha]);
313
262
  return {
314
263
  nodes,
315
264
  links,
316
- restart,
317
- stop,
265
+ restart: restartSimulation,
266
+ stop: stopSimulation,
318
267
  isRunning,
319
268
  alpha,
320
269
  setForcesEnabled
321
270
  };
322
271
  }
323
272
  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) => {
273
+ const handleDragStart = useCallback(
274
+ (event, node) => {
275
+ if (!simulation) return;
276
+ if (!event.active) simulation.alphaTarget(0.3).restart();
277
+ node.fx = node.x;
278
+ node.fy = node.y;
279
+ },
280
+ [simulation]
281
+ );
282
+ const handleDragged = useCallback((event, node) => {
331
283
  node.fx = event.x;
332
284
  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
- };
285
+ }, []);
286
+ const handleDragEnd = useCallback(
287
+ (event, node) => {
288
+ if (!simulation) return;
289
+ if (!event.active) simulation.alphaTarget(0);
290
+ node.fx = null;
291
+ node.fy = null;
292
+ },
293
+ [simulation]
294
+ );
340
295
  return {
341
- onDragStart: dragStarted,
342
- onDrag: dragged,
343
- onDragEnd: dragEnded
296
+ onDragStart: handleDragStart,
297
+ onDrag: handleDragged,
298
+ onDragEnd: handleDragEnd
344
299
  };
345
300
  }
346
301