@aiready/components 0.14.3 → 0.14.5

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,85 +1,45 @@
1
- import { describe, it, expect, vi } from 'vitest';
2
- import { render, screen, fireEvent } from '@testing-library/react';
1
+ import { describe, it, expect } from 'vitest';
2
+ import { render, screen } from '@testing-library/react';
3
3
  import { Switch } from '../switch';
4
4
 
5
5
  describe('Switch', () => {
6
- it('should render a switch', () => {
7
- render(<Switch />);
8
- expect(screen.getByRole('checkbox')).toBeInTheDocument();
6
+ it('renders switch', () => {
7
+ render(<Switch data-testid="switch" />);
8
+ expect(screen.getByTestId('switch')).toBeInTheDocument();
9
9
  });
10
10
 
11
- it('should render with a label', () => {
12
- render(<Switch label="Toggle me" />);
13
- expect(screen.getByText('Toggle me')).toBeInTheDocument();
11
+ it('applies custom className', () => {
12
+ render(<Switch className="custom-switch" data-testid="switch" />);
13
+ expect(screen.getByTestId('switch')).toHaveClass('custom-switch');
14
14
  });
15
15
 
16
- it('should not render label when not provided', () => {
17
- const { container } = render(<Switch />);
18
- expect(container.querySelector('span')).not.toBeInTheDocument();
16
+ it('renders as button role', () => {
17
+ render(<Switch data-testid="switch" />);
18
+ const switchEl = screen.getByTestId('switch');
19
+ expect(switchEl).toHaveAttribute('role', 'switch');
19
20
  });
20
21
 
21
- it('should apply custom className', () => {
22
- const { container } = render(<Switch className="custom-class" />);
23
- expect(container.querySelector('.custom-class')).toBeInTheDocument();
22
+ it('has unchecked state by default', () => {
23
+ render(<Switch data-testid="switch" />);
24
+ const switchEl = screen.getByTestId('switch');
25
+ expect(switchEl).toHaveAttribute('aria-checked', 'false');
24
26
  });
25
27
 
26
- it('should handle checked state', () => {
27
- render(<Switch checked readOnly />);
28
- expect(screen.getByRole('checkbox')).toBeChecked();
28
+ it('can be checked', () => {
29
+ render(<Switch checked data-testid="switch" />);
30
+ const switchEl = screen.getByTestId('switch');
31
+ expect(switchEl).toHaveAttribute('aria-checked', 'true');
29
32
  });
30
33
 
31
- it('should handle unchecked state', () => {
32
- render(<Switch checked={false} readOnly />);
33
- expect(screen.getByRole('checkbox')).not.toBeChecked();
34
+ it('is disabled when disabled prop is true', () => {
35
+ render(<Switch disabled data-testid="switch" />);
36
+ const switchEl = screen.getByTestId('switch');
37
+ expect(switchEl).toBeDisabled();
34
38
  });
35
39
 
36
- it('should call onChange when clicked', () => {
37
- const handleChange = vi.fn();
38
- render(<Switch onChange={handleChange} />);
39
- fireEvent.click(screen.getByRole('checkbox'));
40
- expect(handleChange).toHaveBeenCalledTimes(1);
41
- });
42
-
43
- it('should call onCheckedChange when clicked', () => {
44
- const handleCheckedChange = vi.fn();
45
- render(<Switch onCheckedChange={handleCheckedChange} />);
46
- fireEvent.click(screen.getByRole('checkbox'));
47
- expect(handleCheckedChange).toHaveBeenCalledTimes(1);
48
- expect(handleCheckedChange).toHaveBeenCalledWith(true);
49
- });
50
-
51
- it('should call onCheckedChange with false when unchecking', () => {
52
- const handleCheckedChange = vi.fn();
53
- render(<Switch checked onCheckedChange={handleCheckedChange} />);
54
- fireEvent.click(screen.getByRole('checkbox'));
55
- expect(handleCheckedChange).toHaveBeenCalledWith(false);
56
- });
57
-
58
- it('should be disabled when disabled prop is set', () => {
59
- render(<Switch disabled />);
60
- expect(screen.getByRole('checkbox')).toBeDisabled();
61
- });
62
-
63
- it('should forward ref', () => {
64
- const ref = { current: null };
65
- render(<Switch ref={ref} />);
66
- expect(ref.current).not.toBeNull();
67
- });
68
-
69
- it('should use provided id', () => {
70
- render(<Switch id="custom-id" label="Test" />);
71
- const checkbox = screen.getByRole('checkbox');
72
- expect(checkbox).toHaveAttribute('id', 'custom-id');
73
- });
74
-
75
- it('should generate unique id when not provided', () => {
76
- render(<Switch label="Test" />);
77
- const checkbox = screen.getByRole('checkbox');
78
- expect(checkbox).toHaveAttribute('id');
79
- });
80
-
81
- it('should have sr-only class on input', () => {
82
- render(<Switch />);
83
- expect(screen.getByRole('checkbox')).toHaveClass('sr-only');
40
+ it('has default styling', () => {
41
+ render(<Switch data-testid="switch" />);
42
+ const switchEl = screen.getByTestId('switch');
43
+ expect(switchEl).toHaveClass('peer', 'inline-flex');
84
44
  });
85
45
  });
@@ -1,96 +1,48 @@
1
- import { describe, it, expect, vi } from 'vitest';
2
- import { render, screen, fireEvent } from '@testing-library/react';
1
+ import { describe, it, expect } from 'vitest';
2
+ import { render, screen } from '@testing-library/react';
3
3
  import { Textarea } from '../textarea';
4
4
 
5
5
  describe('Textarea', () => {
6
- it('should render a textarea element', () => {
7
- render(<Textarea />);
8
- expect(screen.getByRole('textbox')).toBeInTheDocument();
6
+ it('renders textarea', () => {
7
+ render(<Textarea data-testid="textarea" />);
8
+ expect(screen.getByTestId('textarea')).toBeInTheDocument();
9
9
  });
10
10
 
11
- it('should apply custom className', () => {
12
- const { container } = render(<Textarea className="custom-class" />);
13
- expect(container.firstChild).toHaveClass('custom-class');
11
+ it('applies custom className', () => {
12
+ render(<Textarea className="custom-textarea" data-testid="textarea" />);
13
+ expect(screen.getByTestId('textarea')).toHaveClass('custom-textarea');
14
14
  });
15
15
 
16
- it('should handle placeholder text', () => {
17
- render(<Textarea placeholder="Enter text" />);
18
- expect(screen.getByPlaceholderText('Enter text')).toBeInTheDocument();
19
- });
20
-
21
- it('should forward ref', () => {
22
- const ref = { current: null };
23
- render(<Textarea ref={ref} />);
24
- expect(ref.current).not.toBeNull();
25
- });
26
-
27
- it('should be disabled when disabled prop is set', () => {
28
- render(<Textarea disabled />);
29
- expect(screen.getByRole('textbox')).toBeDisabled();
30
- });
31
-
32
- it('should handle value prop', () => {
33
- render(<Textarea value="test value" readOnly />);
34
- expect(screen.getByRole('textbox')).toHaveValue('test value');
35
- });
36
-
37
- it('should spread additional props', () => {
38
- const { container } = render(<Textarea data-testid="textarea" />);
39
- expect(container.firstChild).toHaveAttribute('data-testid', 'textarea');
40
- });
41
-
42
- it('should call onChange when text is entered', () => {
43
- const handleChange = vi.fn();
44
- render(<Textarea onChange={handleChange} />);
45
- fireEvent.change(screen.getByRole('textbox'), {
46
- target: { value: 'new text' },
47
- });
48
- expect(handleChange).toHaveBeenCalledTimes(1);
16
+ it('renders as textarea element', () => {
17
+ render(<Textarea data-testid="textarea" />);
18
+ const textarea = screen.getByTestId('textarea');
19
+ expect(textarea.tagName).toBe('TEXTAREA');
49
20
  });
50
21
 
51
- it('should have default styling classes', () => {
52
- const { container } = render(<Textarea />);
53
- expect(container.firstChild).toHaveClass(
54
- 'flex',
55
- 'min-h-[80px]',
56
- 'w-full',
57
- 'rounded-md'
58
- );
59
- });
60
-
61
- it('should render as a textarea element', () => {
62
- render(<Textarea />);
63
- expect(screen.getByRole('textbox').tagName).toBe('TEXTAREA');
64
- });
65
-
66
- it('should support rows attribute', () => {
67
- render(<Textarea rows={5} />);
68
- expect(screen.getByRole('textbox')).toHaveAttribute('rows', '5');
22
+ it('accepts placeholder', () => {
23
+ render(<Textarea placeholder="Enter text" />);
24
+ expect(screen.getByPlaceholderText('Enter text')).toBeInTheDocument();
69
25
  });
70
26
 
71
- it('should support maxLength attribute', () => {
72
- render(<Textarea maxLength={100} />);
73
- expect(screen.getByRole('textbox')).toHaveAttribute('maxlength', '100');
27
+ it('is disabled when disabled prop is true', () => {
28
+ render(<Textarea disabled data-testid="textarea" />);
29
+ expect(screen.getByTestId('textarea')).toBeDisabled();
74
30
  });
75
31
 
76
- it('should have border-input class', () => {
77
- const { container } = render(<Textarea />);
78
- expect(container.firstChild).toHaveClass('border-input');
32
+ it('forwards ref correctly', () => {
33
+ const ref = { current: null };
34
+ render(<Textarea ref={ref} />);
35
+ expect(ref.current).toBeInstanceOf(HTMLTextAreaElement);
79
36
  });
80
37
 
81
- it('should have focus ring classes', () => {
82
- const { container } = render(<Textarea />);
83
- expect(container.firstChild).toHaveClass(
84
- 'focus-visible:ring-2',
85
- 'focus-visible:ring-ring'
86
- );
38
+ it('has default styling', () => {
39
+ render(<Textarea data-testid="textarea" />);
40
+ const textarea = screen.getByTestId('textarea');
41
+ expect(textarea).toHaveClass('flex', 'min-h-[80px]', 'w-full');
87
42
  });
88
43
 
89
- it('should have disabled styling classes', () => {
90
- const { container } = render(<Textarea disabled />);
91
- expect(container.firstChild).toHaveClass(
92
- 'disabled:cursor-not-allowed',
93
- 'disabled:opacity-50'
94
- );
44
+ it('accepts rows prop', () => {
45
+ render(<Textarea rows={5} data-testid="textarea" />);
46
+ expect(screen.getByTestId('textarea')).toHaveAttribute('rows', '5');
95
47
  });
96
48
  });
@@ -10,7 +10,11 @@ import {
10
10
  seedCircularPositions,
11
11
  safelyStopSimulation,
12
12
  } from './simulation-helpers';
13
- import { SIMULATION_DEFAULTS, FORCE_NAMES, EVENT_NAMES } from './simulation-constants';
13
+ import {
14
+ SIMULATION_DEFAULTS,
15
+ FORCE_NAMES,
16
+ EVENT_NAMES,
17
+ } from './simulation-constants';
14
18
  import type {
15
19
  SimulationNode,
16
20
  SimulationLink,
@@ -62,7 +66,10 @@ export function useForceSimulation(
62
66
  const [isRunning, setIsRunning] = useState(false);
63
67
  const [alpha, setAlpha] = useState(1);
64
68
 
65
- const simulationRef = useRef<d3.Simulation<SimulationNode, SimulationLink> | null>(null);
69
+ const simulationRef = useRef<d3.Simulation<
70
+ SimulationNode,
71
+ SimulationLink
72
+ > | null>(null);
66
73
  const stopTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
67
74
  const forcesEnabledRef = useRef(true);
68
75
  const originalForcesRef = useRef({
@@ -74,9 +81,15 @@ export function useForceSimulation(
74
81
  const nodesKey = initialNodes.map((n) => n.id).join('|');
75
82
  const linksKey = initialLinks
76
83
  .map((l) => {
77
- const sourceId = typeof l.source === 'string' ? l.source : (l.source as SimulationNode)?.id;
78
- const targetId = typeof l.target === 'string' ? l.target : (l.target as SimulationNode)?.id;
79
- const linkType = (l as any).type || '';
84
+ const sourceId =
85
+ typeof l.source === 'string'
86
+ ? l.source
87
+ : (l.source as SimulationNode)?.id;
88
+ const targetId =
89
+ typeof l.target === 'string'
90
+ ? l.target
91
+ : (l.target as SimulationNode)?.id;
92
+ const linkType = (l as { type?: string }).type || '';
80
93
  return `${sourceId}->${targetId}:${linkType}`;
81
94
  })
82
95
  .join('|');
@@ -91,11 +104,16 @@ export function useForceSimulation(
91
104
  try {
92
105
  seedCircularPositions(nodesCopy, width, height);
93
106
  } catch (error) {
94
- console.warn('AIReady: Position seeding failed, using random fallback:', error);
107
+ console.warn(
108
+ 'AIReady: Position seeding failed, using random fallback:',
109
+ error
110
+ );
95
111
  seedRandomPositions(nodesCopy, width, height);
96
112
  }
97
113
 
98
- const simulation = d3.forceSimulation<SimulationNode, SimulationLink>(nodesCopy);
114
+ const simulation = d3.forceSimulation<SimulationNode, SimulationLink>(
115
+ nodesCopy
116
+ );
99
117
  applySimulationForces(simulation, linksCopy);
100
118
  configureSimulationParameters(simulation);
101
119
 
@@ -138,19 +156,31 @@ export function useForceSimulation(
138
156
  const linkForce = d3
139
157
  .forceLink<SimulationNode, SimulationLink>(linksCopy)
140
158
  .id((d) => d.id)
141
- .distance((d) => (d as any).distance ?? linkDistance)
159
+ .distance((d) => (d as { distance?: number }).distance ?? linkDistance)
142
160
  .strength(linkStrength);
143
161
 
144
162
  simulation
145
163
  .force(FORCE_NAMES.LINK, linkForce)
146
164
  .force(FORCE_NAMES.CHARGE, d3.forceManyBody().strength(chargeStrength))
147
- .force(FORCE_NAMES.CENTER, d3.forceCenter(width / 2, height / 2).strength(centerStrength))
165
+ .force(
166
+ FORCE_NAMES.CENTER,
167
+ d3.forceCenter(width / 2, height / 2).strength(centerStrength)
168
+ )
148
169
  .force(
149
170
  FORCE_NAMES.COLLISION,
150
- d3.forceCollide<SimulationNode>().radius((d) => (d.size ?? 10) + collisionRadius).strength(collisionStrength)
171
+ d3
172
+ .forceCollide<SimulationNode>()
173
+ .radius((d) => (d.size ?? 10) + collisionRadius)
174
+ .strength(collisionStrength)
175
+ )
176
+ .force(
177
+ FORCE_NAMES.X,
178
+ d3.forceX(width / 2).strength(Math.max(0.02, centerStrength * 0.5))
151
179
  )
152
- .force(FORCE_NAMES.X, d3.forceX(width / 2).strength(Math.max(0.02, centerStrength * 0.5)))
153
- .force(FORCE_NAMES.Y, d3.forceY(height / 2).strength(Math.max(0.02, centerStrength * 0.5)));
180
+ .force(
181
+ FORCE_NAMES.Y,
182
+ d3.forceY(height / 2).strength(Math.max(0.02, centerStrength * 0.5))
183
+ );
154
184
  } catch (error) {
155
185
  console.warn('AIReady: Failed to configure simulation forces:', error);
156
186
  }
@@ -159,7 +189,9 @@ export function useForceSimulation(
159
189
  /**
160
190
  * Configures simulation decay and heat parameters
161
191
  */
162
- const configureSimulationParameters = (simulation: d3.Simulation<SimulationNode, SimulationLink>) => {
192
+ const configureSimulationParameters = (
193
+ simulation: d3.Simulation<SimulationNode, SimulationLink>
194
+ ) => {
163
195
  simulation
164
196
  .alphaDecay(alphaDecay)
165
197
  .velocityDecay(velocityDecay)
@@ -182,7 +214,9 @@ export function useForceSimulation(
182
214
 
183
215
  if (maxSimulationTimeMs > 0) {
184
216
  stopTimeoutRef.current = setTimeout(() => {
185
- safelyStopSimulation(simulation, nodesCopy, { stabilize: stabilizeOnStop });
217
+ safelyStopSimulation(simulation, nodesCopy, {
218
+ stabilize: stabilizeOnStop,
219
+ });
186
220
  updateStateAfterStop(nodesCopy, linksCopy, 0);
187
221
  }, maxSimulationTimeMs);
188
222
  }
@@ -222,7 +256,9 @@ export function useForceSimulation(
222
256
 
223
257
  const currentAlpha = simulation.alpha();
224
258
  if (currentAlpha <= alphaMin) {
225
- safelyStopSimulation(simulation, nodesCopy, { stabilize: stabilizeOnStop });
259
+ safelyStopSimulation(simulation, nodesCopy, {
260
+ stabilize: stabilizeOnStop,
261
+ });
226
262
  updateStateAfterStop(nodesCopy, linksCopy, currentAlpha);
227
263
  return;
228
264
  }
@@ -244,7 +280,10 @@ export function useForceSimulation(
244
280
  rafState: { rafId: number | null; lastUpdate: number }
245
281
  ) => {
246
282
  const now = Date.now();
247
- if (rafState.rafId === null && now - rafState.lastUpdate >= tickThrottleMs) {
283
+ if (
284
+ rafState.rafId === null &&
285
+ now - rafState.lastUpdate >= tickThrottleMs
286
+ ) {
248
287
  rafState.rafId = requestAnimationFrame(() => {
249
288
  rafState.rafId = null;
250
289
  rafState.lastUpdate = Date.now();
@@ -305,28 +344,36 @@ export function useForceSimulation(
305
344
  /**
306
345
  * Enable or disable simulation forces
307
346
  */
308
- const setForcesEnabled = useCallback((enabled: boolean) => {
309
- const sim = simulationRef.current;
310
- if (!sim || forcesEnabledRef.current === enabled) return;
311
-
312
- forcesEnabledRef.current = enabled;
347
+ const setForcesEnabled = useCallback(
348
+ (enabled: boolean) => {
349
+ const sim = simulationRef.current;
350
+ if (!sim || forcesEnabledRef.current === enabled) return;
351
+
352
+ forcesEnabledRef.current = enabled;
353
+
354
+ try {
355
+ const charge = sim.force(
356
+ FORCE_NAMES.CHARGE
357
+ ) as d3.ForceManyBody<SimulationNode> | null;
358
+ if (charge) {
359
+ charge.strength(enabled ? originalForcesRef.current.charge : 0);
360
+ }
313
361
 
314
- try {
315
- const charge = sim.force(FORCE_NAMES.CHARGE) as d3.ForceManyBody<SimulationNode> | null;
316
- if (charge) {
317
- charge.strength(enabled ? originalForcesRef.current.charge : 0);
318
- }
362
+ const link = sim.force(FORCE_NAMES.LINK) as d3.ForceLink<
363
+ SimulationNode,
364
+ SimulationLink
365
+ > | null;
366
+ if (link) {
367
+ link.strength(enabled ? originalForcesRef.current.link : 0);
368
+ }
319
369
 
320
- const link = sim.force(FORCE_NAMES.LINK) as d3.ForceLink<SimulationNode, SimulationLink> | null;
321
- if (link) {
322
- link.strength(enabled ? originalForcesRef.current.link : 0);
370
+ sim.alpha(warmAlpha).restart();
371
+ } catch (error) {
372
+ console.warn('AIReady: Failed to toggle simulation forces:', error);
323
373
  }
324
-
325
- sim.alpha(warmAlpha).restart();
326
- } catch (error) {
327
- console.warn('AIReady: Failed to toggle simulation forces:', error);
328
- }
329
- }, [warmAlpha]);
374
+ },
375
+ [warmAlpha]
376
+ );
330
377
 
331
378
  return {
332
379
  nodes,
@@ -342,7 +389,9 @@ export function useForceSimulation(
342
389
  /**
343
390
  * Hook for creating a draggable force simulation
344
391
  */
345
- export function useDrag(simulation: d3.Simulation<SimulationNode, any> | null | undefined) {
392
+ export function useDrag(
393
+ simulation: d3.Simulation<SimulationNode, any> | null | undefined
394
+ ) {
346
395
  const handleDragStart = useCallback(
347
396
  (event: any, node: SimulationNode) => {
348
397
  if (!simulation) return;