@aiready/components 0.14.0 → 0.14.1
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/README.md +6 -6
- package/dist/components/button.d.ts +1 -1
- package/dist/hooks/useForceSimulation.js +41 -14
- package/dist/hooks/useForceSimulation.js.map +1 -1
- package/dist/index.js +41 -14
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/hooks/useForceSimulation.ts +96 -54
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aiready/components",
|
|
3
|
-
"version": "0.14.
|
|
3
|
+
"version": "0.14.1",
|
|
4
4
|
"description": "Unified shared components library (UI, charts, hooks, utilities) for AIReady",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -128,7 +128,7 @@
|
|
|
128
128
|
"framer-motion": "^12.35.0",
|
|
129
129
|
"lucide-react": "^0.577.0",
|
|
130
130
|
"tailwind-merge": "^3.0.0",
|
|
131
|
-
"@aiready/core": "0.24.
|
|
131
|
+
"@aiready/core": "0.24.2"
|
|
132
132
|
},
|
|
133
133
|
"devDependencies": {
|
|
134
134
|
"@testing-library/jest-dom": "^6.6.5",
|
|
@@ -111,7 +111,7 @@ export function useForceSimulation(
|
|
|
111
111
|
SimulationNode,
|
|
112
112
|
SimulationLink
|
|
113
113
|
> | null>(null);
|
|
114
|
-
const stopTimeoutRef = useRef<
|
|
114
|
+
const stopTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
115
115
|
|
|
116
116
|
// Create lightweight keys for nodes/links so we only recreate the simulation
|
|
117
117
|
// when the actual identity/content of inputs change (not when parent passes
|
|
@@ -119,9 +119,16 @@ export function useForceSimulation(
|
|
|
119
119
|
const nodesKey = initialNodes.map((n) => n.id).join('|');
|
|
120
120
|
const linksKey = (initialLinks || [])
|
|
121
121
|
.map((l) => {
|
|
122
|
-
const
|
|
123
|
-
|
|
124
|
-
|
|
122
|
+
const sourceId =
|
|
123
|
+
typeof l.source === 'string'
|
|
124
|
+
? l.source
|
|
125
|
+
: (l.source as SimulationNode)?.id;
|
|
126
|
+
const targetId =
|
|
127
|
+
typeof l.target === 'string'
|
|
128
|
+
? l.target
|
|
129
|
+
: (l.target as SimulationNode)?.id;
|
|
130
|
+
const linkType = (l as SimulationLink & { type?: string }).type || '';
|
|
131
|
+
return `${sourceId}->${targetId}:${linkType}`;
|
|
125
132
|
})
|
|
126
133
|
.join('|');
|
|
127
134
|
|
|
@@ -143,69 +150,99 @@ export function useForceSimulation(
|
|
|
143
150
|
n.x = width / 2 + radius * Math.cos(angle);
|
|
144
151
|
n.y = height / 2 + radius * Math.sin(angle);
|
|
145
152
|
// Add very small random velocity to avoid large initial motion
|
|
146
|
-
|
|
147
|
-
|
|
153
|
+
n.vx = (Math.random() - 0.5) * 2;
|
|
154
|
+
n.vy = (Math.random() - 0.5) * 2;
|
|
148
155
|
});
|
|
149
156
|
} catch (e) {
|
|
150
|
-
|
|
157
|
+
console.warn('Failed to seed node positions, falling back to random:', e);
|
|
151
158
|
// If error, fall back to random positions
|
|
152
159
|
seedRandomPositions(nodesCopy, width, height);
|
|
153
160
|
}
|
|
154
161
|
|
|
155
162
|
// Create the simulation
|
|
156
163
|
const simulation = d3.forceSimulation(
|
|
157
|
-
nodesCopy as
|
|
158
|
-
) as
|
|
164
|
+
nodesCopy as SimulationNode[]
|
|
165
|
+
) as d3.Simulation<SimulationNode, SimulationLink>;
|
|
159
166
|
|
|
160
167
|
// Configure link force separately to avoid using generic type args on d3 helpers
|
|
161
168
|
try {
|
|
162
169
|
const linkForce = d3.forceLink(
|
|
163
|
-
linksCopy as
|
|
164
|
-
) as
|
|
170
|
+
linksCopy as d3.SimulationLinkDatum<SimulationNode>[]
|
|
171
|
+
) as d3.ForceLink<SimulationNode, d3.SimulationLinkDatum<SimulationNode>>;
|
|
165
172
|
linkForce
|
|
166
|
-
.id((d:
|
|
167
|
-
.distance((d:
|
|
168
|
-
|
|
169
|
-
|
|
173
|
+
.id((d: SimulationNode) => d.id)
|
|
174
|
+
.distance((d: d3.SimulationLinkDatum<SimulationNode>) => {
|
|
175
|
+
const link = d as SimulationLink & { distance?: number };
|
|
176
|
+
return link && link.distance != null ? link.distance : linkDistance;
|
|
177
|
+
})
|
|
170
178
|
.strength(linkStrength);
|
|
171
|
-
simulation.force(
|
|
179
|
+
simulation.force(
|
|
180
|
+
'link',
|
|
181
|
+
linkForce as d3.Force<
|
|
182
|
+
SimulationNode,
|
|
183
|
+
d3.SimulationLinkDatum<SimulationNode>
|
|
184
|
+
>
|
|
185
|
+
);
|
|
172
186
|
} catch (e) {
|
|
173
|
-
|
|
187
|
+
console.warn('Failed to configure link force, using fallback:', e);
|
|
174
188
|
// fallback: attach a plain link force
|
|
175
189
|
try {
|
|
176
|
-
simulation.force(
|
|
177
|
-
|
|
178
|
-
|
|
190
|
+
simulation.force(
|
|
191
|
+
'link',
|
|
192
|
+
d3.forceLink(
|
|
193
|
+
linksCopy as d3.SimulationLinkDatum<SimulationNode>[]
|
|
194
|
+
) as d3.Force<SimulationNode, d3.SimulationLinkDatum<SimulationNode>>
|
|
195
|
+
);
|
|
196
|
+
} catch (fallbackError) {
|
|
197
|
+
console.warn('Fallback link force also failed:', fallbackError);
|
|
179
198
|
}
|
|
180
199
|
}
|
|
181
200
|
try {
|
|
182
201
|
simulation.force(
|
|
183
202
|
'charge',
|
|
184
|
-
d3.forceManyBody().strength(chargeStrength) as
|
|
203
|
+
d3.forceManyBody().strength(chargeStrength) as d3.Force<
|
|
204
|
+
SimulationNode,
|
|
205
|
+
d3.SimulationLinkDatum<SimulationNode>
|
|
206
|
+
>
|
|
185
207
|
);
|
|
186
208
|
simulation.force(
|
|
187
209
|
'center',
|
|
188
|
-
d3
|
|
210
|
+
d3
|
|
211
|
+
.forceCenter(width / 2, height / 2)
|
|
212
|
+
.strength(centerStrength) as d3.Force<
|
|
213
|
+
SimulationNode,
|
|
214
|
+
d3.SimulationLinkDatum<SimulationNode>
|
|
215
|
+
>
|
|
189
216
|
);
|
|
190
217
|
const collide = d3
|
|
191
218
|
.forceCollide()
|
|
192
|
-
.radius((d:
|
|
193
|
-
const
|
|
219
|
+
.radius((d: d3.SimulationNodeDatum) => {
|
|
220
|
+
const node = d as SimulationNode;
|
|
221
|
+
const nodeSize = node && node.size ? (node.size as number) : 10;
|
|
194
222
|
return nodeSize + collisionRadius;
|
|
195
223
|
})
|
|
196
|
-
.strength(collisionStrength
|
|
224
|
+
.strength(collisionStrength) as d3.Force<
|
|
225
|
+
SimulationNode,
|
|
226
|
+
d3.SimulationLinkDatum<SimulationNode>
|
|
227
|
+
>;
|
|
197
228
|
simulation.force('collision', collide);
|
|
198
229
|
simulation.force(
|
|
199
230
|
'x',
|
|
200
231
|
d3
|
|
201
232
|
.forceX(width / 2)
|
|
202
|
-
.strength(Math.max(0.02, centerStrength * 0.5)) as
|
|
233
|
+
.strength(Math.max(0.02, centerStrength * 0.5)) as d3.Force<
|
|
234
|
+
SimulationNode,
|
|
235
|
+
d3.SimulationLinkDatum<SimulationNode>
|
|
236
|
+
>
|
|
203
237
|
);
|
|
204
238
|
simulation.force(
|
|
205
239
|
'y',
|
|
206
240
|
d3
|
|
207
241
|
.forceY(height / 2)
|
|
208
|
-
.strength(Math.max(0.02, centerStrength * 0.5)) as
|
|
242
|
+
.strength(Math.max(0.02, centerStrength * 0.5)) as d3.Force<
|
|
243
|
+
SimulationNode,
|
|
244
|
+
d3.SimulationLinkDatum<SimulationNode>
|
|
245
|
+
>
|
|
209
246
|
);
|
|
210
247
|
simulation.alphaDecay(alphaDecay);
|
|
211
248
|
simulation.velocityDecay(velocityDecay);
|
|
@@ -213,15 +250,15 @@ export function useForceSimulation(
|
|
|
213
250
|
try {
|
|
214
251
|
simulation.alphaTarget(alphaTarget);
|
|
215
252
|
} catch (e) {
|
|
216
|
-
|
|
253
|
+
console.warn('Failed to set alpha target:', e);
|
|
217
254
|
}
|
|
218
255
|
try {
|
|
219
256
|
simulation.alpha(warmAlpha);
|
|
220
257
|
} catch (e) {
|
|
221
|
-
|
|
258
|
+
console.warn('Failed to set initial alpha:', e);
|
|
222
259
|
}
|
|
223
260
|
} catch (e) {
|
|
224
|
-
|
|
261
|
+
console.warn('Failed to configure simulation forces:', e);
|
|
225
262
|
// ignore force configuration errors
|
|
226
263
|
}
|
|
227
264
|
|
|
@@ -230,14 +267,14 @@ export function useForceSimulation(
|
|
|
230
267
|
// Force-stop timeout to ensure simulation doesn't run forever.
|
|
231
268
|
if (stopTimeoutRef.current != null) {
|
|
232
269
|
try {
|
|
233
|
-
|
|
270
|
+
globalThis.clearTimeout(stopTimeoutRef.current);
|
|
234
271
|
} catch (e) {
|
|
235
|
-
|
|
272
|
+
console.warn('Failed to clear simulation timeout:', e);
|
|
236
273
|
}
|
|
237
274
|
stopTimeoutRef.current = null;
|
|
238
275
|
}
|
|
239
276
|
if (maxSimulationTimeMs && maxSimulationTimeMs > 0) {
|
|
240
|
-
stopTimeoutRef.current =
|
|
277
|
+
stopTimeoutRef.current = globalThis.setTimeout(() => {
|
|
241
278
|
try {
|
|
242
279
|
if (stabilizeOnStop) {
|
|
243
280
|
stabilizeNodes(nodesCopy);
|
|
@@ -245,12 +282,12 @@ export function useForceSimulation(
|
|
|
245
282
|
simulation.alpha(0);
|
|
246
283
|
simulation.stop();
|
|
247
284
|
} catch (e) {
|
|
248
|
-
|
|
285
|
+
console.warn('Failed to stop simulation:', e);
|
|
249
286
|
}
|
|
250
287
|
setIsRunning(false);
|
|
251
288
|
setNodes([...nodesCopy]);
|
|
252
289
|
setLinks([...linksCopy]);
|
|
253
|
-
}, maxSimulationTimeMs)
|
|
290
|
+
}, maxSimulationTimeMs);
|
|
254
291
|
}
|
|
255
292
|
|
|
256
293
|
// Update state on each tick. Batch updates via requestAnimationFrame to avoid
|
|
@@ -262,20 +299,20 @@ export function useForceSimulation(
|
|
|
262
299
|
if (typeof onTick === 'function')
|
|
263
300
|
onTick(nodesCopy, linksCopy, simulation);
|
|
264
301
|
} catch (e) {
|
|
265
|
-
|
|
302
|
+
console.warn('Tick callback error:', e);
|
|
266
303
|
}
|
|
267
304
|
|
|
268
305
|
// If simulation alpha has cooled below the configured minimum, stop it to
|
|
269
306
|
// ensure nodes don't drift indefinitely (acts as a hard-stop safeguard).
|
|
270
307
|
try {
|
|
271
|
-
if (simulation.alpha() <=
|
|
308
|
+
if (simulation.alpha() <= alphaMin) {
|
|
272
309
|
try {
|
|
273
310
|
if (stabilizeOnStop) {
|
|
274
311
|
stabilizeNodes(nodesCopy);
|
|
275
312
|
}
|
|
276
313
|
simulation.stop();
|
|
277
314
|
} catch (e) {
|
|
278
|
-
|
|
315
|
+
console.warn('Failed to stop simulation:', e);
|
|
279
316
|
}
|
|
280
317
|
setAlpha(simulation.alpha());
|
|
281
318
|
setIsRunning(false);
|
|
@@ -284,11 +321,11 @@ export function useForceSimulation(
|
|
|
284
321
|
return;
|
|
285
322
|
}
|
|
286
323
|
} catch (e) {
|
|
287
|
-
|
|
324
|
+
console.warn('Error checking simulation alpha:', e);
|
|
288
325
|
}
|
|
289
326
|
|
|
290
327
|
const now = Date.now();
|
|
291
|
-
const shouldUpdate = now - lastUpdate >=
|
|
328
|
+
const shouldUpdate = now - lastUpdate >= tickThrottleMs;
|
|
292
329
|
if (rafId == null && shouldUpdate) {
|
|
293
330
|
rafId = (
|
|
294
331
|
globalThis.requestAnimationFrame ||
|
|
@@ -300,7 +337,7 @@ export function useForceSimulation(
|
|
|
300
337
|
setLinks([...linksCopy]);
|
|
301
338
|
setAlpha(simulation.alpha());
|
|
302
339
|
setIsRunning(simulation.alpha() > simulation.alphaMin());
|
|
303
|
-
})
|
|
340
|
+
});
|
|
304
341
|
}
|
|
305
342
|
};
|
|
306
343
|
|
|
@@ -313,15 +350,15 @@ export function useForceSimulation(
|
|
|
313
350
|
// Cleanup on unmount
|
|
314
351
|
return () => {
|
|
315
352
|
try {
|
|
316
|
-
simulation.on('tick', null
|
|
353
|
+
simulation.on('tick', null);
|
|
317
354
|
} catch (e) {
|
|
318
|
-
|
|
355
|
+
console.warn('Failed to clear simulation tick handler:', e);
|
|
319
356
|
}
|
|
320
357
|
if (stopTimeoutRef.current != null) {
|
|
321
358
|
try {
|
|
322
|
-
|
|
359
|
+
globalThis.clearTimeout(stopTimeoutRef.current);
|
|
323
360
|
} catch (e) {
|
|
324
|
-
|
|
361
|
+
console.warn('Failed to clear timeout on cleanup:', e);
|
|
325
362
|
}
|
|
326
363
|
stopTimeoutRef.current = null;
|
|
327
364
|
}
|
|
@@ -332,7 +369,7 @@ export function useForceSimulation(
|
|
|
332
369
|
((id: number) => clearTimeout(id))
|
|
333
370
|
)(rafId);
|
|
334
371
|
} catch (e) {
|
|
335
|
-
|
|
372
|
+
console.warn('Failed to cancel animation frame:', e);
|
|
336
373
|
}
|
|
337
374
|
rafId = null;
|
|
338
375
|
}
|
|
@@ -371,22 +408,22 @@ export function useForceSimulation(
|
|
|
371
408
|
// Reset safety timeout when simulation is manually restarted
|
|
372
409
|
if (stopTimeoutRef.current != null) {
|
|
373
410
|
try {
|
|
374
|
-
|
|
411
|
+
globalThis.clearTimeout(stopTimeoutRef.current);
|
|
375
412
|
} catch (e) {
|
|
376
|
-
|
|
413
|
+
console.warn('Failed to clear simulation timeout:', e);
|
|
377
414
|
}
|
|
378
415
|
stopTimeoutRef.current = null;
|
|
379
416
|
}
|
|
380
417
|
if (maxSimulationTimeMs && maxSimulationTimeMs > 0) {
|
|
381
|
-
stopTimeoutRef.current =
|
|
418
|
+
stopTimeoutRef.current = globalThis.setTimeout(() => {
|
|
382
419
|
try {
|
|
383
420
|
simulationRef.current?.alpha(0);
|
|
384
421
|
simulationRef.current?.stop();
|
|
385
422
|
} catch (e) {
|
|
386
|
-
|
|
423
|
+
console.warn('Failed to stop simulation:', e);
|
|
387
424
|
}
|
|
388
425
|
setIsRunning(false);
|
|
389
|
-
}, maxSimulationTimeMs)
|
|
426
|
+
}, maxSimulationTimeMs);
|
|
390
427
|
}
|
|
391
428
|
}
|
|
392
429
|
};
|
|
@@ -414,17 +451,22 @@ export function useForceSimulation(
|
|
|
414
451
|
|
|
415
452
|
try {
|
|
416
453
|
// Only toggle charge and link forces to avoid collapse; keep collision/centering
|
|
417
|
-
const charge
|
|
454
|
+
const charge = sim.force(
|
|
455
|
+
'charge'
|
|
456
|
+
) as d3.ForceManyBody<SimulationNode> | null;
|
|
418
457
|
if (charge && typeof charge.strength === 'function') {
|
|
419
458
|
charge.strength(enabled ? originalForcesRef.current.charge : 0);
|
|
420
459
|
}
|
|
421
460
|
|
|
422
|
-
const link
|
|
461
|
+
const link = sim.force('link') as d3.ForceLink<
|
|
462
|
+
SimulationNode,
|
|
463
|
+
d3.SimulationLinkDatum<SimulationNode>
|
|
464
|
+
> | null;
|
|
423
465
|
if (link && typeof link.strength === 'function') {
|
|
424
466
|
link.strength(enabled ? originalForcesRef.current.link : 0);
|
|
425
467
|
}
|
|
426
468
|
} catch (e) {
|
|
427
|
-
|
|
469
|
+
console.warn('Failed to toggle simulation forces:', e);
|
|
428
470
|
}
|
|
429
471
|
};
|
|
430
472
|
|