@aiready/components 0.14.2 → 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.
package/dist/index.js CHANGED
@@ -2060,6 +2060,16 @@ function stabilizeNodes(nodes) {
2060
2060
  if (typeof n.y === "number") n.y = Number(n.y.toFixed(3));
2061
2061
  });
2062
2062
  }
2063
+ function seedCircularPositions(nodes, width, height) {
2064
+ const radius = Math.min(width, height) * 0.45;
2065
+ nodes.forEach((n, i) => {
2066
+ const angle = i * 2 * Math.PI / nodes.length;
2067
+ n.x = width / 2 + radius * Math.cos(angle);
2068
+ n.y = height / 2 + radius * Math.sin(angle);
2069
+ n.vx = (Math.random() - 0.5) * 2;
2070
+ n.vy = (Math.random() - 0.5) * 2;
2071
+ });
2072
+ }
2063
2073
  function seedRandomPositions(nodes, width, height) {
2064
2074
  nodes.forEach((n) => {
2065
2075
  n.x = Math.random() * width;
@@ -2068,27 +2078,68 @@ function seedRandomPositions(nodes, width, height) {
2068
2078
  n.vy = (Math.random() - 0.5) * 10;
2069
2079
  });
2070
2080
  }
2081
+ function safelyStopSimulation(simulation, nodes, options = {}) {
2082
+ try {
2083
+ if (options.stabilize) {
2084
+ stabilizeNodes(nodes);
2085
+ }
2086
+ simulation.stop();
2087
+ } catch (error) {
2088
+ console.warn("AIReady: Failed to stop simulation safely:", error);
2089
+ }
2090
+ }
2091
+
2092
+ // src/hooks/simulation-constants.ts
2093
+ var SIMULATION_DEFAULTS = {
2094
+ CHARGE_STRENGTH: -300,
2095
+ LINK_DISTANCE: 100,
2096
+ LINK_STRENGTH: 1,
2097
+ COLLISION_STRENGTH: 1,
2098
+ COLLISION_RADIUS: 10,
2099
+ CENTER_STRENGTH: 0.1,
2100
+ ALPHA_DECAY: 0.0228,
2101
+ VELOCITY_DECAY: 0.4,
2102
+ ALPHA_TARGET: 0,
2103
+ WARM_ALPHA: 0.3,
2104
+ ALPHA_MIN: 0.01,
2105
+ TICK_THROTTLE_MS: 33,
2106
+ // ~30 fps
2107
+ MAX_SIMULATION_TIME_MS: 3e3,
2108
+ STABILIZE_ON_STOP: true
2109
+ };
2110
+ var FORCE_NAMES = {
2111
+ LINK: "link",
2112
+ CHARGE: "charge",
2113
+ CENTER: "center",
2114
+ COLLISION: "collision",
2115
+ X: "x",
2116
+ Y: "y"
2117
+ };
2118
+ var EVENT_NAMES = {
2119
+ TICK: "tick",
2120
+ END: "end"
2121
+ };
2122
+
2123
+ // src/hooks/useForceSimulation.ts
2071
2124
  function useForceSimulation(initialNodes, initialLinks, options) {
2072
2125
  const {
2073
- chargeStrength = -300,
2074
- linkDistance = 100,
2075
- linkStrength = 1,
2076
- collisionStrength = 1,
2077
- collisionRadius = 10,
2078
- centerStrength = 0.1,
2126
+ chargeStrength = SIMULATION_DEFAULTS.CHARGE_STRENGTH,
2127
+ linkDistance = SIMULATION_DEFAULTS.LINK_DISTANCE,
2128
+ linkStrength = SIMULATION_DEFAULTS.LINK_STRENGTH,
2129
+ collisionStrength = SIMULATION_DEFAULTS.COLLISION_STRENGTH,
2130
+ collisionRadius = SIMULATION_DEFAULTS.COLLISION_RADIUS,
2131
+ centerStrength = SIMULATION_DEFAULTS.CENTER_STRENGTH,
2079
2132
  width,
2080
2133
  height,
2081
- alphaDecay = 0.0228,
2082
- velocityDecay = 0.4,
2083
- alphaTarget = 0,
2084
- warmAlpha = 0.3,
2085
- alphaMin = 0.01,
2134
+ alphaDecay = SIMULATION_DEFAULTS.ALPHA_DECAY,
2135
+ velocityDecay = SIMULATION_DEFAULTS.VELOCITY_DECAY,
2136
+ alphaTarget = SIMULATION_DEFAULTS.ALPHA_TARGET,
2137
+ warmAlpha = SIMULATION_DEFAULTS.WARM_ALPHA,
2138
+ alphaMin = SIMULATION_DEFAULTS.ALPHA_MIN,
2086
2139
  onTick,
2087
- // Optional throttle in milliseconds for tick updates (reduce React re-renders)
2088
- // Lower values = smoother but more CPU; default ~30ms (~33fps)
2089
- stabilizeOnStop = true,
2090
- tickThrottleMs = 33,
2091
- maxSimulationTimeMs = 3e3
2140
+ stabilizeOnStop = SIMULATION_DEFAULTS.STABILIZE_ON_STOP,
2141
+ tickThrottleMs = SIMULATION_DEFAULTS.TICK_THROTTLE_MS,
2142
+ maxSimulationTimeMs = SIMULATION_DEFAULTS.MAX_SIMULATION_TIME_MS
2092
2143
  } = options;
2093
2144
  const [nodes, setNodes] = useState(initialNodes);
2094
2145
  const [links, setLinks] = useState(initialLinks);
@@ -2096,8 +2147,13 @@ function useForceSimulation(initialNodes, initialLinks, options) {
2096
2147
  const [alpha, setAlpha] = useState(1);
2097
2148
  const simulationRef = useRef(null);
2098
2149
  const stopTimeoutRef = useRef(null);
2150
+ const forcesEnabledRef = useRef(true);
2151
+ const originalForcesRef = useRef({
2152
+ charge: chargeStrength,
2153
+ link: linkStrength
2154
+ });
2099
2155
  const nodesKey = initialNodes.map((n) => n.id).join("|");
2100
- const linksKey = (initialLinks || []).map((l) => {
2156
+ const linksKey = initialLinks.map((l) => {
2101
2157
  const sourceId = typeof l.source === "string" ? l.source : l.source?.id;
2102
2158
  const targetId = typeof l.target === "string" ? l.target : l.target?.id;
2103
2159
  const linkType = l.type || "";
@@ -2107,179 +2163,19 @@ function useForceSimulation(initialNodes, initialLinks, options) {
2107
2163
  const nodesCopy = initialNodes.map((node) => ({ ...node }));
2108
2164
  const linksCopy = initialLinks.map((link) => ({ ...link }));
2109
2165
  try {
2110
- nodesCopy.forEach((n, i) => {
2111
- const angle = i * 2 * Math.PI / nodesCopy.length;
2112
- const radius = Math.min(width, height) * 0.45;
2113
- n.x = width / 2 + radius * Math.cos(angle);
2114
- n.y = height / 2 + radius * Math.sin(angle);
2115
- n.vx = (Math.random() - 0.5) * 2;
2116
- n.vy = (Math.random() - 0.5) * 2;
2117
- });
2118
- } catch (e) {
2119
- console.warn("Failed to seed node positions, falling back to random:", e);
2166
+ seedCircularPositions(nodesCopy, width, height);
2167
+ } catch (error) {
2168
+ console.warn("AIReady: Position seeding failed, using random fallback:", error);
2120
2169
  seedRandomPositions(nodesCopy, width, height);
2121
2170
  }
2122
- const simulation = d32.forceSimulation(
2123
- nodesCopy
2124
- );
2125
- try {
2126
- const linkForce = d32.forceLink(
2127
- linksCopy
2128
- );
2129
- linkForce.id((d) => d.id).distance((d) => {
2130
- const link = d;
2131
- return link && link.distance != null ? link.distance : linkDistance;
2132
- }).strength(linkStrength);
2133
- simulation.force(
2134
- "link",
2135
- linkForce
2136
- );
2137
- } catch (e) {
2138
- console.warn("Failed to configure link force, using fallback:", e);
2139
- try {
2140
- simulation.force(
2141
- "link",
2142
- d32.forceLink(
2143
- linksCopy
2144
- )
2145
- );
2146
- } catch (fallbackError) {
2147
- console.warn("Fallback link force also failed:", fallbackError);
2148
- }
2149
- }
2150
- try {
2151
- simulation.force(
2152
- "charge",
2153
- d32.forceManyBody().strength(chargeStrength)
2154
- );
2155
- simulation.force(
2156
- "center",
2157
- d32.forceCenter(width / 2, height / 2).strength(centerStrength)
2158
- );
2159
- const collide = d32.forceCollide().radius((d) => {
2160
- const node = d;
2161
- const nodeSize = node && node.size ? node.size : 10;
2162
- return nodeSize + collisionRadius;
2163
- }).strength(collisionStrength);
2164
- simulation.force("collision", collide);
2165
- simulation.force(
2166
- "x",
2167
- d32.forceX(width / 2).strength(Math.max(0.02, centerStrength * 0.5))
2168
- );
2169
- simulation.force(
2170
- "y",
2171
- d32.forceY(height / 2).strength(Math.max(0.02, centerStrength * 0.5))
2172
- );
2173
- simulation.alphaDecay(alphaDecay);
2174
- simulation.velocityDecay(velocityDecay);
2175
- simulation.alphaMin(alphaMin);
2176
- try {
2177
- simulation.alphaTarget(alphaTarget);
2178
- } catch (e) {
2179
- console.warn("Failed to set alpha target:", e);
2180
- }
2181
- try {
2182
- simulation.alpha(warmAlpha);
2183
- } catch (e) {
2184
- console.warn("Failed to set initial alpha:", e);
2185
- }
2186
- } catch (e) {
2187
- console.warn("Failed to configure simulation forces:", e);
2188
- }
2171
+ const simulation = d32.forceSimulation(nodesCopy);
2172
+ applySimulationForces(simulation, linksCopy);
2173
+ configureSimulationParameters(simulation);
2189
2174
  simulationRef.current = simulation;
2190
- if (stopTimeoutRef.current != null) {
2191
- try {
2192
- globalThis.clearTimeout(stopTimeoutRef.current);
2193
- } catch (e) {
2194
- console.warn("Failed to clear simulation timeout:", e);
2195
- }
2196
- stopTimeoutRef.current = null;
2197
- }
2198
- if (maxSimulationTimeMs && maxSimulationTimeMs > 0) {
2199
- stopTimeoutRef.current = globalThis.setTimeout(() => {
2200
- try {
2201
- if (stabilizeOnStop) {
2202
- stabilizeNodes(nodesCopy);
2203
- }
2204
- simulation.alpha(0);
2205
- simulation.stop();
2206
- } catch (e) {
2207
- console.warn("Failed to stop simulation:", e);
2208
- }
2209
- setIsRunning(false);
2210
- setNodes([...nodesCopy]);
2211
- setLinks([...linksCopy]);
2212
- }, maxSimulationTimeMs);
2213
- }
2214
- let rafId = null;
2215
- let lastUpdate = 0;
2216
- const tickHandler = () => {
2217
- try {
2218
- if (typeof onTick === "function")
2219
- onTick(nodesCopy, linksCopy, simulation);
2220
- } catch (e) {
2221
- console.warn("Tick callback error:", e);
2222
- }
2223
- try {
2224
- if (simulation.alpha() <= alphaMin) {
2225
- try {
2226
- if (stabilizeOnStop) {
2227
- stabilizeNodes(nodesCopy);
2228
- }
2229
- simulation.stop();
2230
- } catch (e) {
2231
- console.warn("Failed to stop simulation:", e);
2232
- }
2233
- setAlpha(simulation.alpha());
2234
- setIsRunning(false);
2235
- setNodes([...nodesCopy]);
2236
- setLinks([...linksCopy]);
2237
- return;
2238
- }
2239
- } catch (e) {
2240
- console.warn("Error checking simulation alpha:", e);
2241
- }
2242
- const now = Date.now();
2243
- const shouldUpdate = now - lastUpdate >= tickThrottleMs;
2244
- if (rafId == null && shouldUpdate) {
2245
- rafId = (globalThis.requestAnimationFrame || ((cb) => setTimeout(cb, 16)))(() => {
2246
- rafId = null;
2247
- lastUpdate = Date.now();
2248
- setNodes([...nodesCopy]);
2249
- setLinks([...linksCopy]);
2250
- setAlpha(simulation.alpha());
2251
- setIsRunning(simulation.alpha() > simulation.alphaMin());
2252
- });
2253
- }
2254
- };
2255
- simulation.on("tick", tickHandler);
2256
- simulation.on("end", () => {
2257
- setIsRunning(false);
2258
- });
2259
- return () => {
2260
- try {
2261
- simulation.on("tick", null);
2262
- } catch (e) {
2263
- console.warn("Failed to clear simulation tick handler:", e);
2264
- }
2265
- if (stopTimeoutRef.current != null) {
2266
- try {
2267
- globalThis.clearTimeout(stopTimeoutRef.current);
2268
- } catch (e) {
2269
- console.warn("Failed to clear timeout on cleanup:", e);
2270
- }
2271
- stopTimeoutRef.current = null;
2272
- }
2273
- if (rafId != null) {
2274
- try {
2275
- (globalThis.cancelAnimationFrame || ((id) => clearTimeout(id)))(rafId);
2276
- } catch (e) {
2277
- console.warn("Failed to cancel animation frame:", e);
2278
- }
2279
- rafId = null;
2280
- }
2281
- simulation.stop();
2282
- };
2175
+ const rafState = { rafId: null, lastUpdate: 0 };
2176
+ setupTickHandler(simulation, nodesCopy, linksCopy, rafState);
2177
+ setupStopTimer(simulation, nodesCopy, linksCopy);
2178
+ return () => cleanupSimulation(simulation, rafState);
2283
2179
  }, [
2284
2180
  nodesKey,
2285
2181
  linksKey,
@@ -2299,98 +2195,155 @@ function useForceSimulation(initialNodes, initialLinks, options) {
2299
2195
  tickThrottleMs,
2300
2196
  maxSimulationTimeMs
2301
2197
  ]);
2302
- const restart = () => {
2303
- if (simulationRef.current) {
2304
- try {
2305
- simulationRef.current.alphaTarget(warmAlpha).restart();
2306
- } catch {
2307
- simulationRef.current.restart();
2308
- }
2309
- setIsRunning(true);
2310
- if (stopTimeoutRef.current != null) {
2198
+ const applySimulationForces = (simulation, linksCopy) => {
2199
+ try {
2200
+ const linkForce = d32.forceLink(linksCopy).id((d) => d.id).distance((d) => d.distance ?? linkDistance).strength(linkStrength);
2201
+ simulation.force(FORCE_NAMES.LINK, linkForce).force(FORCE_NAMES.CHARGE, d32.forceManyBody().strength(chargeStrength)).force(FORCE_NAMES.CENTER, d32.forceCenter(width / 2, height / 2).strength(centerStrength)).force(
2202
+ FORCE_NAMES.COLLISION,
2203
+ d32.forceCollide().radius((d) => (d.size ?? 10) + collisionRadius).strength(collisionStrength)
2204
+ ).force(FORCE_NAMES.X, d32.forceX(width / 2).strength(Math.max(0.02, centerStrength * 0.5))).force(FORCE_NAMES.Y, d32.forceY(height / 2).strength(Math.max(0.02, centerStrength * 0.5)));
2205
+ } catch (error) {
2206
+ console.warn("AIReady: Failed to configure simulation forces:", error);
2207
+ }
2208
+ };
2209
+ const configureSimulationParameters = (simulation) => {
2210
+ simulation.alphaDecay(alphaDecay).velocityDecay(velocityDecay).alphaMin(alphaMin).alphaTarget(alphaTarget).alpha(warmAlpha);
2211
+ };
2212
+ const setupStopTimer = (simulation, nodesCopy, linksCopy) => {
2213
+ if (stopTimeoutRef.current) {
2214
+ clearTimeout(stopTimeoutRef.current);
2215
+ }
2216
+ if (maxSimulationTimeMs > 0) {
2217
+ stopTimeoutRef.current = setTimeout(() => {
2218
+ safelyStopSimulation(simulation, nodesCopy, { stabilize: stabilizeOnStop });
2219
+ updateStateAfterStop(nodesCopy, linksCopy, 0);
2220
+ }, maxSimulationTimeMs);
2221
+ }
2222
+ };
2223
+ const updateStateAfterStop = (nodesCopy, linksCopy, currentAlpha) => {
2224
+ setIsRunning(false);
2225
+ setAlpha(currentAlpha);
2226
+ setNodes([...nodesCopy]);
2227
+ setLinks([...linksCopy]);
2228
+ };
2229
+ const setupTickHandler = (simulation, nodesCopy, linksCopy, rafState) => {
2230
+ const handleTick = () => {
2231
+ if (onTick) {
2311
2232
  try {
2312
- globalThis.clearTimeout(stopTimeoutRef.current);
2313
- } catch (e) {
2314
- console.warn("Failed to clear simulation timeout:", e);
2233
+ onTick(nodesCopy, linksCopy, simulation);
2234
+ } catch (error) {
2235
+ console.warn("AIReady: Simulation onTick callback failed:", error);
2315
2236
  }
2316
- stopTimeoutRef.current = null;
2317
2237
  }
2318
- if (maxSimulationTimeMs && maxSimulationTimeMs > 0) {
2319
- stopTimeoutRef.current = globalThis.setTimeout(() => {
2320
- try {
2321
- simulationRef.current?.alpha(0);
2322
- simulationRef.current?.stop();
2323
- } catch (e) {
2324
- console.warn("Failed to stop simulation:", e);
2325
- }
2238
+ const currentAlpha = simulation.alpha();
2239
+ if (currentAlpha <= alphaMin) {
2240
+ safelyStopSimulation(simulation, nodesCopy, { stabilize: stabilizeOnStop });
2241
+ updateStateAfterStop(nodesCopy, linksCopy, currentAlpha);
2242
+ return;
2243
+ }
2244
+ syncStateOnTick(nodesCopy, linksCopy, currentAlpha, rafState);
2245
+ };
2246
+ simulation.on(EVENT_NAMES.TICK, handleTick);
2247
+ simulation.on(EVENT_NAMES.END, () => setIsRunning(false));
2248
+ };
2249
+ const syncStateOnTick = (nodesCopy, linksCopy, currentAlpha, rafState) => {
2250
+ const now = Date.now();
2251
+ if (rafState.rafId === null && now - rafState.lastUpdate >= tickThrottleMs) {
2252
+ rafState.rafId = requestAnimationFrame(() => {
2253
+ rafState.rafId = null;
2254
+ rafState.lastUpdate = Date.now();
2255
+ setNodes([...nodesCopy]);
2256
+ setLinks([...linksCopy]);
2257
+ setAlpha(currentAlpha);
2258
+ setIsRunning(currentAlpha > alphaMin);
2259
+ });
2260
+ }
2261
+ };
2262
+ const cleanupSimulation = (simulation, rafState) => {
2263
+ simulation.on(EVENT_NAMES.TICK, null);
2264
+ if (stopTimeoutRef.current) clearTimeout(stopTimeoutRef.current);
2265
+ if (rafState.rafId !== null) cancelAnimationFrame(rafState.rafId);
2266
+ simulation.stop();
2267
+ };
2268
+ const restartSimulation = useCallback(() => {
2269
+ const sim = simulationRef.current;
2270
+ if (!sim) return;
2271
+ try {
2272
+ sim.alphaTarget(warmAlpha).restart();
2273
+ setIsRunning(true);
2274
+ if (stopTimeoutRef.current) clearTimeout(stopTimeoutRef.current);
2275
+ if (maxSimulationTimeMs > 0) {
2276
+ stopTimeoutRef.current = setTimeout(() => {
2277
+ sim.alpha(0);
2278
+ sim.stop();
2326
2279
  setIsRunning(false);
2327
2280
  }, maxSimulationTimeMs);
2328
2281
  }
2282
+ } catch (error) {
2283
+ console.warn("AIReady: Failed to restart simulation:", error);
2329
2284
  }
2330
- };
2331
- const stop = () => {
2285
+ }, [warmAlpha, maxSimulationTimeMs]);
2286
+ const stopSimulation = useCallback(() => {
2332
2287
  if (simulationRef.current) {
2333
2288
  simulationRef.current.stop();
2334
2289
  setIsRunning(false);
2335
2290
  }
2336
- };
2337
- const originalForcesRef = useRef({
2338
- charge: chargeStrength,
2339
- link: linkStrength,
2340
- collision: collisionStrength
2341
- });
2342
- const forcesEnabledRef = useRef(true);
2343
- const setForcesEnabled = (enabled) => {
2291
+ }, []);
2292
+ const setForcesEnabled = useCallback((enabled) => {
2344
2293
  const sim = simulationRef.current;
2345
- if (!sim) return;
2346
- if (forcesEnabledRef.current === enabled) return;
2294
+ if (!sim || forcesEnabledRef.current === enabled) return;
2347
2295
  forcesEnabledRef.current = enabled;
2348
2296
  try {
2349
- const charge = sim.force(
2350
- "charge"
2351
- );
2352
- if (charge && typeof charge.strength === "function") {
2297
+ const charge = sim.force(FORCE_NAMES.CHARGE);
2298
+ if (charge) {
2353
2299
  charge.strength(enabled ? originalForcesRef.current.charge : 0);
2354
2300
  }
2355
- const link = sim.force("link");
2356
- if (link && typeof link.strength === "function") {
2301
+ const link = sim.force(FORCE_NAMES.LINK);
2302
+ if (link) {
2357
2303
  link.strength(enabled ? originalForcesRef.current.link : 0);
2358
2304
  }
2359
- } catch (e) {
2360
- console.warn("Failed to toggle simulation forces:", e);
2305
+ sim.alpha(warmAlpha).restart();
2306
+ } catch (error) {
2307
+ console.warn("AIReady: Failed to toggle simulation forces:", error);
2361
2308
  }
2362
- };
2309
+ }, [warmAlpha]);
2363
2310
  return {
2364
2311
  nodes,
2365
2312
  links,
2366
- restart,
2367
- stop,
2313
+ restart: restartSimulation,
2314
+ stop: stopSimulation,
2368
2315
  isRunning,
2369
2316
  alpha,
2370
2317
  setForcesEnabled
2371
2318
  };
2372
2319
  }
2373
2320
  function useDrag(simulation) {
2374
- const dragStarted = (event, node) => {
2375
- if (!simulation) return;
2376
- if (!event.active) simulation.alphaTarget(0.3).restart();
2377
- node.fx = node.x;
2378
- node.fy = node.y;
2379
- };
2380
- const dragged = (event, node) => {
2321
+ const handleDragStart = useCallback(
2322
+ (event, node) => {
2323
+ if (!simulation) return;
2324
+ if (!event.active) simulation.alphaTarget(0.3).restart();
2325
+ node.fx = node.x;
2326
+ node.fy = node.y;
2327
+ },
2328
+ [simulation]
2329
+ );
2330
+ const handleDragged = useCallback((event, node) => {
2381
2331
  node.fx = event.x;
2382
2332
  node.fy = event.y;
2383
- };
2384
- const dragEnded = (event, node) => {
2385
- if (!simulation) return;
2386
- if (!event.active) simulation.alphaTarget(0);
2387
- node.fx = null;
2388
- node.fy = null;
2389
- };
2333
+ }, []);
2334
+ const handleDragEnd = useCallback(
2335
+ (event, node) => {
2336
+ if (!simulation) return;
2337
+ if (!event.active) simulation.alphaTarget(0);
2338
+ node.fx = null;
2339
+ node.fy = null;
2340
+ },
2341
+ [simulation]
2342
+ );
2390
2343
  return {
2391
- onDragStart: dragStarted,
2392
- onDrag: dragged,
2393
- onDragEnd: dragEnded
2344
+ onDragStart: handleDragStart,
2345
+ onDrag: handleDragged,
2346
+ onDragEnd: handleDragEnd
2394
2347
  };
2395
2348
  }
2396
2349
  function pinNode(node) {