@aiready/components 0.1.31 → 0.1.34

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.
Files changed (73) hide show
  1. package/README.md +2 -2
  2. package/dist/charts/ForceDirectedGraph.js +49 -13
  3. package/dist/charts/ForceDirectedGraph.js.map +1 -1
  4. package/dist/components/badge.js.map +1 -1
  5. package/dist/components/button.js.map +1 -1
  6. package/dist/components/card.js.map +1 -1
  7. package/dist/components/checkbox.js.map +1 -1
  8. package/dist/components/container.js.map +1 -1
  9. package/dist/components/grid.js.map +1 -1
  10. package/dist/components/input.d.ts +1 -2
  11. package/dist/components/input.js.map +1 -1
  12. package/dist/components/label.js +1 -8
  13. package/dist/components/label.js.map +1 -1
  14. package/dist/components/radio-group.js.map +1 -1
  15. package/dist/components/select.js.map +1 -1
  16. package/dist/components/separator.js.map +1 -1
  17. package/dist/components/stack.js.map +1 -1
  18. package/dist/components/switch.js +29 -22
  19. package/dist/components/switch.js.map +1 -1
  20. package/dist/components/textarea.d.ts +1 -2
  21. package/dist/components/textarea.js.map +1 -1
  22. package/dist/hooks/useD3.js.map +1 -1
  23. package/dist/hooks/useDebounce.js.map +1 -1
  24. package/dist/hooks/useForceSimulation.d.ts +1 -0
  25. package/dist/hooks/useForceSimulation.js +37 -14
  26. package/dist/hooks/useForceSimulation.js.map +1 -1
  27. package/dist/index.d.ts +5 -5
  28. package/dist/index.js +337 -141
  29. package/dist/index.js.map +1 -1
  30. package/dist/utils/cn.js.map +1 -1
  31. package/dist/utils/colors.js.map +1 -1
  32. package/dist/utils/formatters.js.map +1 -1
  33. package/package.json +3 -2
  34. package/src/__tests__/smoke.test.js +1 -1
  35. package/src/__tests__/smoke.test.ts +3 -3
  36. package/src/charts/ForceDirectedGraph.tsx +583 -517
  37. package/src/charts/GraphControls.tsx +5 -2
  38. package/src/charts/LinkItem.tsx +17 -5
  39. package/src/charts/NodeItem.tsx +17 -2
  40. package/src/code-block/CodeBlock.tsx +53 -16
  41. package/src/code-block/index.ts +1 -1
  42. package/src/components/badge.tsx +3 -2
  43. package/src/components/button.tsx +3 -2
  44. package/src/components/card.tsx +8 -1
  45. package/src/components/checkbox.tsx +6 -4
  46. package/src/components/container.tsx +1 -1
  47. package/src/components/grid.tsx +1 -1
  48. package/src/components/input.tsx +2 -3
  49. package/src/components/label.tsx +4 -7
  50. package/src/components/radio-group.tsx +5 -3
  51. package/src/components/select.tsx +5 -3
  52. package/src/components/separator.tsx +1 -1
  53. package/src/components/stack.tsx +1 -1
  54. package/src/components/switch.tsx +15 -7
  55. package/src/components/textarea.tsx +2 -3
  56. package/src/data-display/ScoreBar.tsx +52 -15
  57. package/src/data-display/index.ts +7 -1
  58. package/src/feedback/ErrorDisplay.tsx +17 -4
  59. package/src/feedback/LoadingSpinner.tsx +8 -3
  60. package/src/feedback/index.ts +12 -2
  61. package/src/hooks/useD3.ts +1 -3
  62. package/src/hooks/useDebounce.ts +1 -1
  63. package/src/hooks/useForceSimulation.ts +142 -44
  64. package/src/index.ts +29 -9
  65. package/src/navigation/Breadcrumb.tsx +17 -8
  66. package/src/navigation/index.ts +5 -1
  67. package/src/theme/ThemeProvider.tsx +11 -3
  68. package/src/theme/index.ts +6 -1
  69. package/src/utils/cn.ts +1 -1
  70. package/src/utils/colors.ts +1 -1
  71. package/src/utils/formatters.ts +1 -1
  72. package/src/utils/score.ts +3 -1
  73. package/tailwind.config.js +1 -1
@@ -15,29 +15,36 @@ var Switch = React.forwardRef(
15
15
  onCheckedChange?.(e.target.checked);
16
16
  };
17
17
  return /* @__PURE__ */ jsxs("div", { className: "flex items-center", children: [
18
- /* @__PURE__ */ jsxs("label", { htmlFor: switchId, className: "relative inline-flex cursor-pointer items-center", children: [
19
- /* @__PURE__ */ jsx(
20
- "input",
21
- {
22
- type: "checkbox",
23
- id: switchId,
24
- ref,
25
- checked,
26
- onChange: handleChange,
27
- className: "peer sr-only",
28
- ...props
29
- }
30
- ),
31
- /* @__PURE__ */ jsx(
32
- "div",
33
- {
34
- className: cn(
35
- 'peer h-6 w-11 rounded-full bg-gray-200 after:absolute after:left-[2px] after:top-[2px] after:h-5 after:w-5 after:rounded-full after:border after:border-gray-300 after:bg-white after:transition-all after:content-[""] peer-checked:bg-primary peer-checked:after:translate-x-full peer-checked:after:border-white peer-focus:outline-none peer-focus:ring-2 peer-focus:ring-ring peer-focus:ring-offset-2 peer-disabled:cursor-not-allowed peer-disabled:opacity-50',
36
- className
18
+ /* @__PURE__ */ jsxs(
19
+ "label",
20
+ {
21
+ htmlFor: switchId,
22
+ className: "relative inline-flex cursor-pointer items-center",
23
+ children: [
24
+ /* @__PURE__ */ jsx(
25
+ "input",
26
+ {
27
+ type: "checkbox",
28
+ id: switchId,
29
+ ref,
30
+ checked,
31
+ onChange: handleChange,
32
+ className: "peer sr-only",
33
+ ...props
34
+ }
35
+ ),
36
+ /* @__PURE__ */ jsx(
37
+ "div",
38
+ {
39
+ className: cn(
40
+ 'peer h-6 w-11 rounded-full bg-gray-200 after:absolute after:left-[2px] after:top-[2px] after:h-5 after:w-5 after:rounded-full after:border after:border-gray-300 after:bg-white after:transition-all after:content-[""] peer-checked:bg-primary peer-checked:after:translate-x-full peer-checked:after:border-white peer-focus:outline-none peer-focus:ring-2 peer-focus:ring-ring peer-focus:ring-offset-2 peer-disabled:cursor-not-allowed peer-disabled:opacity-50',
41
+ className
42
+ )
43
+ }
37
44
  )
38
- }
39
- )
40
- ] }),
45
+ ]
46
+ }
47
+ ),
41
48
  label && /* @__PURE__ */ jsx("span", { className: "ml-3 text-sm font-medium text-foreground", children: label })
42
49
  ] });
43
50
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/utils/cn.ts","../../src/components/switch.tsx"],"names":[],"mappings":";;;;;;AAQO,SAAS,MAAM,MAAA,EAAsB;AAC1C,EAAA,OAAO,OAAA,CAAQ,IAAA,CAAK,MAAM,CAAC,CAAA;AAC7B;ACDA,IAAM,MAAA,GAAe,KAAA,CAAA,UAAA;AAAA,EACnB,CAAC,EAAE,SAAA,EAAW,KAAA,EAAO,EAAA,EAAI,OAAA,EAAS,eAAA,EAAiB,QAAA,EAAU,GAAG,KAAA,EAAM,EAAG,GAAA,KAAQ;AAC/E,IAAA,MAAM,QAAA,GAAW,MAAY,KAAA,CAAA,KAAA,EAAM;AAEnC,IAAA,MAAM,YAAA,GAAe,CAAC,CAAA,KAA2C;AAC/D,MAAA,QAAA,GAAW,CAAC,CAAA;AACZ,MAAA,eAAA,GAAkB,CAAA,CAAE,OAAO,OAAO,CAAA;AAAA,IACpC,CAAA;AAEA,IAAA,uBACE,IAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,mBAAA,EACb,QAAA,EAAA;AAAA,sBAAA,IAAA,CAAC,OAAA,EAAA,EAAM,OAAA,EAAS,QAAA,EAAU,SAAA,EAAU,kDAAA,EAClC,QAAA,EAAA;AAAA,wBAAA,GAAA;AAAA,UAAC,OAAA;AAAA,UAAA;AAAA,YACC,IAAA,EAAK,UAAA;AAAA,YACL,EAAA,EAAI,QAAA;AAAA,YACJ,GAAA;AAAA,YACA,OAAA;AAAA,YACA,QAAA,EAAU,YAAA;AAAA,YACV,SAAA,EAAU,cAAA;AAAA,YACT,GAAG;AAAA;AAAA,SACN;AAAA,wBACA,GAAA;AAAA,UAAC,KAAA;AAAA,UAAA;AAAA,YACC,SAAA,EAAW,EAAA;AAAA,cACT,ucAAA;AAAA,cACA;AAAA;AACF;AAAA;AACF,OAAA,EACF,CAAA;AAAA,MACC,KAAA,oBACC,GAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,4CACb,QAAA,EAAA,KAAA,EACH;AAAA,KAAA,EAEJ,CAAA;AAAA,EAEJ;AACF;AACA,MAAA,CAAO,WAAA,GAAc,QAAA","file":"switch.js","sourcesContent":["import { type ClassValue, clsx } from 'clsx';\nimport { twMerge } from 'tailwind-merge';\n\n/**\n * Merges class names using clsx and tailwind-merge\n * @param inputs - Class values to merge\n * @returns Merged class names\n */\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs));\n}","import * as React from 'react';\nimport { cn } from '../utils/cn';\n\nexport interface SwitchProps\n extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'type'> {\n label?: string;\n onCheckedChange?: (checked: boolean) => void;\n}\n\nconst Switch = React.forwardRef<HTMLInputElement, SwitchProps>(\n ({ className, label, id, checked, onCheckedChange, onChange, ...props }, ref) => {\n const switchId = id || React.useId();\n \n const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {\n onChange?.(e);\n onCheckedChange?.(e.target.checked);\n };\n \n return (\n <div className=\"flex items-center\">\n <label htmlFor={switchId} className=\"relative inline-flex cursor-pointer items-center\">\n <input\n type=\"checkbox\"\n id={switchId}\n ref={ref}\n checked={checked}\n onChange={handleChange}\n className=\"peer sr-only\"\n {...props}\n />\n <div\n className={cn(\n 'peer h-6 w-11 rounded-full bg-gray-200 after:absolute after:left-[2px] after:top-[2px] after:h-5 after:w-5 after:rounded-full after:border after:border-gray-300 after:bg-white after:transition-all after:content-[\"\"] peer-checked:bg-primary peer-checked:after:translate-x-full peer-checked:after:border-white peer-focus:outline-none peer-focus:ring-2 peer-focus:ring-ring peer-focus:ring-offset-2 peer-disabled:cursor-not-allowed peer-disabled:opacity-50',\n className\n )}\n />\n </label>\n {label && (\n <span className=\"ml-3 text-sm font-medium text-foreground\">\n {label}\n </span>\n )}\n </div>\n );\n }\n);\nSwitch.displayName = 'Switch';\n\nexport { Switch };"]}
1
+ {"version":3,"sources":["../../src/utils/cn.ts","../../src/components/switch.tsx"],"names":[],"mappings":";;;;;;AAQO,SAAS,MAAM,MAAA,EAAsB;AAC1C,EAAA,OAAO,OAAA,CAAQ,IAAA,CAAK,MAAM,CAAC,CAAA;AAC7B;ACCA,IAAM,MAAA,GAAe,KAAA,CAAA,UAAA;AAAA,EACnB,CACE,EAAE,SAAA,EAAW,KAAA,EAAO,EAAA,EAAI,OAAA,EAAS,eAAA,EAAiB,QAAA,EAAU,GAAG,KAAA,EAAM,EACrE,GAAA,KACG;AACH,IAAA,MAAM,QAAA,GAAW,MAAY,KAAA,CAAA,KAAA,EAAM;AAEnC,IAAA,MAAM,YAAA,GAAe,CAAC,CAAA,KAA2C;AAC/D,MAAA,QAAA,GAAW,CAAC,CAAA;AACZ,MAAA,eAAA,GAAkB,CAAA,CAAE,OAAO,OAAO,CAAA;AAAA,IACpC,CAAA;AAEA,IAAA,uBACE,IAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,mBAAA,EACb,QAAA,EAAA;AAAA,sBAAA,IAAA;AAAA,QAAC,OAAA;AAAA,QAAA;AAAA,UACC,OAAA,EAAS,QAAA;AAAA,UACT,SAAA,EAAU,kDAAA;AAAA,UAEV,QAAA,EAAA;AAAA,4BAAA,GAAA;AAAA,cAAC,OAAA;AAAA,cAAA;AAAA,gBACC,IAAA,EAAK,UAAA;AAAA,gBACL,EAAA,EAAI,QAAA;AAAA,gBACJ,GAAA;AAAA,gBACA,OAAA;AAAA,gBACA,QAAA,EAAU,YAAA;AAAA,gBACV,SAAA,EAAU,cAAA;AAAA,gBACT,GAAG;AAAA;AAAA,aACN;AAAA,4BACA,GAAA;AAAA,cAAC,KAAA;AAAA,cAAA;AAAA,gBACC,SAAA,EAAW,EAAA;AAAA,kBACT,ucAAA;AAAA,kBACA;AAAA;AACF;AAAA;AACF;AAAA;AAAA,OACF;AAAA,MACC,KAAA,oBACC,GAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,4CACb,QAAA,EAAA,KAAA,EACH;AAAA,KAAA,EAEJ,CAAA;AAAA,EAEJ;AACF;AACA,MAAA,CAAO,WAAA,GAAc,QAAA","file":"switch.js","sourcesContent":["import { type ClassValue, clsx } from 'clsx';\nimport { twMerge } from 'tailwind-merge';\n\n/**\n * Merges class names using clsx and tailwind-merge\n * @param inputs - Class values to merge\n * @returns Merged class names\n */\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs));\n}\n","import * as React from 'react';\nimport { cn } from '../utils/cn';\n\nexport interface SwitchProps extends Omit<\n React.InputHTMLAttributes<HTMLInputElement>,\n 'type'\n> {\n label?: string;\n onCheckedChange?: (checked: boolean) => void;\n}\n\nconst Switch = React.forwardRef<HTMLInputElement, SwitchProps>(\n (\n { className, label, id, checked, onCheckedChange, onChange, ...props },\n ref\n ) => {\n const switchId = id || React.useId();\n\n const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {\n onChange?.(e);\n onCheckedChange?.(e.target.checked);\n };\n\n return (\n <div className=\"flex items-center\">\n <label\n htmlFor={switchId}\n className=\"relative inline-flex cursor-pointer items-center\"\n >\n <input\n type=\"checkbox\"\n id={switchId}\n ref={ref}\n checked={checked}\n onChange={handleChange}\n className=\"peer sr-only\"\n {...props}\n />\n <div\n className={cn(\n 'peer h-6 w-11 rounded-full bg-gray-200 after:absolute after:left-[2px] after:top-[2px] after:h-5 after:w-5 after:rounded-full after:border after:border-gray-300 after:bg-white after:transition-all after:content-[\"\"] peer-checked:bg-primary peer-checked:after:translate-x-full peer-checked:after:border-white peer-focus:outline-none peer-focus:ring-2 peer-focus:ring-ring peer-focus:ring-offset-2 peer-disabled:cursor-not-allowed peer-disabled:opacity-50',\n className\n )}\n />\n </label>\n {label && (\n <span className=\"ml-3 text-sm font-medium text-foreground\">\n {label}\n </span>\n )}\n </div>\n );\n }\n);\nSwitch.displayName = 'Switch';\n\nexport { Switch };\n"]}
@@ -1,7 +1,6 @@
1
1
  import * as React from 'react';
2
2
 
3
- interface TextareaProps extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {
4
- }
3
+ type TextareaProps = React.TextareaHTMLAttributes<HTMLTextAreaElement>;
5
4
  declare const Textarea: React.ForwardRefExoticComponent<TextareaProps & React.RefAttributes<HTMLTextAreaElement>>;
6
5
 
7
6
  export { Textarea, type TextareaProps };
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/utils/cn.ts","../../src/components/textarea.tsx"],"names":[],"mappings":";;;;;;AAQO,SAAS,MAAM,MAAA,EAAsB;AAC1C,EAAA,OAAO,OAAA,CAAQ,IAAA,CAAK,MAAM,CAAC,CAAA;AAC7B;ACJA,IAAM,QAAA,GAAiB,KAAA,CAAA,UAAA;AAAA,EACrB,CAAC,EAAE,SAAA,EAAW,GAAG,KAAA,IAAS,GAAA,KAAQ;AAChC,IAAA,uBACE,GAAA;AAAA,MAAC,UAAA;AAAA,MAAA;AAAA,QACC,SAAA,EAAW,EAAA;AAAA,UACT,sSAAA;AAAA,UACA;AAAA,SACF;AAAA,QACA,GAAA;AAAA,QACC,GAAG;AAAA;AAAA,KACN;AAAA,EAEJ;AACF;AACA,QAAA,CAAS,WAAA,GAAc,UAAA","file":"textarea.js","sourcesContent":["import { type ClassValue, clsx } from 'clsx';\nimport { twMerge } from 'tailwind-merge';\n\n/**\n * Merges class names using clsx and tailwind-merge\n * @param inputs - Class values to merge\n * @returns Merged class names\n */\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs));\n}","import * as React from 'react';\nimport { cn } from '../utils/cn';\n\nexport interface TextareaProps\n extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {}\n\nconst Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(\n ({ className, ...props }, ref) => {\n return (\n <textarea\n className={cn(\n 'flex min-h-[80px] w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50',\n className\n )}\n ref={ref}\n {...props}\n />\n );\n }\n);\nTextarea.displayName = 'Textarea';\n\nexport { Textarea };"]}
1
+ {"version":3,"sources":["../../src/utils/cn.ts","../../src/components/textarea.tsx"],"names":[],"mappings":";;;;;;AAQO,SAAS,MAAM,MAAA,EAAsB;AAC1C,EAAA,OAAO,OAAA,CAAQ,IAAA,CAAK,MAAM,CAAC,CAAA;AAC7B;ACLA,IAAM,QAAA,GAAiB,KAAA,CAAA,UAAA;AAAA,EACrB,CAAC,EAAE,SAAA,EAAW,GAAG,KAAA,IAAS,GAAA,KAAQ;AAChC,IAAA,uBACE,GAAA;AAAA,MAAC,UAAA;AAAA,MAAA;AAAA,QACC,SAAA,EAAW,EAAA;AAAA,UACT,sSAAA;AAAA,UACA;AAAA,SACF;AAAA,QACA,GAAA;AAAA,QACC,GAAG;AAAA;AAAA,KACN;AAAA,EAEJ;AACF;AACA,QAAA,CAAS,WAAA,GAAc,UAAA","file":"textarea.js","sourcesContent":["import { type ClassValue, clsx } from 'clsx';\nimport { twMerge } from 'tailwind-merge';\n\n/**\n * Merges class names using clsx and tailwind-merge\n * @param inputs - Class values to merge\n * @returns Merged class names\n */\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs));\n}\n","import * as React from 'react';\nimport { cn } from '../utils/cn';\n\nexport type TextareaProps = React.TextareaHTMLAttributes<HTMLTextAreaElement>;\n\nconst Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(\n ({ className, ...props }, ref) => {\n return (\n <textarea\n className={cn(\n 'flex min-h-[80px] w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50',\n className\n )}\n ref={ref}\n {...props}\n />\n );\n }\n);\nTextarea.displayName = 'Textarea';\n\nexport { Textarea };\n"]}
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/hooks/useD3.ts"],"names":[],"mappings":";;;;AAkDO,SAAS,KAAA,CACd,QAAA,EACA,YAAA,GAAqC,EAAC,EACX;AAC3B,EAAA,MAAM,GAAA,GAAM,OAAiB,IAAI,CAAA;AAEjC,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,IAAI,IAAI,OAAA,EAAS;AACf,MAAA,MAAM,SAAA,GAAe,EAAA,CAAA,MAAA,CAAO,GAAA,CAAI,OAAO,CAAA;AACvC,MAAA,QAAA,CAAS,SAAS,CAAA;AAAA,IACpB;AAAA,EAEF,GAAG,YAAY,CAAA;AAEf,EAAA,OAAO,GAAA;AACT;AA6BO,SAAS,eAAA,CACd,QAAA,EACA,YAAA,GAAqC,EAAC,EACX;AAC3B,EAAA,MAAM,GAAA,GAAM,OAAiB,IAAI,CAAA;AAEjC,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,IAAI,CAAC,IAAI,OAAA,EAAS;AAElB,IAAA,MAAM,SAAA,GAAe,EAAA,CAAA,MAAA,CAAO,GAAA,CAAI,OAAO,CAAA;AACvC,IAAA,MAAM,MAAA,GAAS,MAAM,QAAA,CAAS,SAAS,CAAA;AAGvC,IAAA,MAAA,EAAO;AAGP,IAAA,MAAM,cAAA,GAAiB,IAAI,cAAA,CAAe,MAAM;AAC9C,MAAA,MAAA,EAAO;AAAA,IACT,CAAC,CAAA;AAED,IAAA,cAAA,CAAe,OAAA,CAAQ,IAAI,OAAO,CAAA;AAGlC,IAAA,OAAO,MAAM;AACX,MAAA,cAAA,CAAe,UAAA,EAAW;AAAA,IAC5B,CAAA;AAAA,EAEF,GAAG,YAAY,CAAA;AAEf,EAAA,OAAO,GAAA;AACT","file":"useD3.js","sourcesContent":["import { useEffect, useRef } from 'react';\nimport * as d3 from 'd3';\n\n/**\n * Hook for managing D3 selections with React lifecycle\n * Provides a ref to the SVG/container element and runs a render function when dependencies change\n *\n * @param renderFn - Function that receives the D3 selection and performs rendering\n * @param dependencies - Array of dependencies that trigger re-render\n * @returns Ref to attach to the SVG/container element\n *\n * @example\n * ```tsx\n * function BarChart({ data }: { data: number[] }) {\n * const ref = useD3(\n * (svg) => {\n * const width = 600;\n * const height = 400;\n * const margin = { top: 20, right: 20, bottom: 30, left: 40 };\n *\n * // Clear previous content\n * svg.selectAll('*').remove();\n *\n * // Set up scales\n * const x = d3.scaleBand()\n * .domain(data.map((_, i) => i.toString()))\n * .range([margin.left, width - margin.right])\n * .padding(0.1);\n *\n * const y = d3.scaleLinear()\n * .domain([0, d3.max(data) || 0])\n * .range([height - margin.bottom, margin.top]);\n *\n * // Draw bars\n * svg.selectAll('rect')\n * .data(data)\n * .join('rect')\n * .attr('x', (_, i) => x(i.toString())!)\n * .attr('y', d => y(d))\n * .attr('width', x.bandwidth())\n * .attr('height', d => y(0) - y(d))\n * .attr('fill', 'steelblue');\n * },\n * [data]\n * );\n *\n * return <svg ref={ref} width={600} height={400} />;\n * }\n * ```\n */\nexport function useD3<T extends SVGSVGElement | HTMLDivElement>(\n renderFn: (selection: d3.Selection<T, unknown, null, undefined>) => void,\n dependencies: React.DependencyList = []\n): React.RefObject<T | null> {\n const ref = useRef<T | null>(null);\n\n useEffect(() => {\n if (ref.current) {\n const selection = d3.select(ref.current);\n renderFn(selection);\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, dependencies);\n\n return ref;\n}\n\n/**\n * Hook for managing D3 selections with automatic resize handling\n * Similar to useD3 but also triggers re-render on window resize\n *\n * @param renderFn - Function that receives the D3 selection and performs rendering\n * @param dependencies - Array of dependencies that trigger re-render\n * @returns Ref to attach to the SVG/container element\n *\n * @example\n * ```tsx\n * function ResponsiveChart({ data }: { data: number[] }) {\n * const ref = useD3WithResize(\n * (svg) => {\n * const container = svg.node();\n * const width = container?.clientWidth || 600;\n * const height = container?.clientHeight || 400;\n *\n * // Render with responsive dimensions\n * // ...\n * },\n * [data]\n * );\n *\n * return <svg ref={ref} style={{ width: '100%', height: '400px' }} />;\n * }\n * ```\n */\nexport function useD3WithResize<T extends SVGSVGElement | HTMLDivElement>(\n renderFn: (selection: d3.Selection<T, unknown, null, undefined>) => void,\n dependencies: React.DependencyList = []\n): React.RefObject<T | null> {\n const ref = useRef<T | null>(null);\n\n useEffect(() => {\n if (!ref.current) return;\n\n const selection = d3.select(ref.current);\n const render = () => renderFn(selection);\n\n // Initial render\n render();\n\n // Set up resize observer\n const resizeObserver = new ResizeObserver(() => {\n render();\n });\n\n resizeObserver.observe(ref.current);\n\n // Cleanup\n return () => {\n resizeObserver.disconnect();\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, dependencies);\n\n return ref;\n}"]}
1
+ {"version":3,"sources":["../../src/hooks/useD3.ts"],"names":[],"mappings":";;;;AAkDO,SAAS,KAAA,CACd,QAAA,EACA,YAAA,GAAqC,EAAC,EACX;AAC3B,EAAA,MAAM,GAAA,GAAM,OAAiB,IAAI,CAAA;AAEjC,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,IAAI,IAAI,OAAA,EAAS;AACf,MAAA,MAAM,SAAA,GAAe,EAAA,CAAA,MAAA,CAAO,GAAA,CAAI,OAAO,CAAA;AACvC,MAAA,QAAA,CAAS,SAAS,CAAA;AAAA,IACpB;AAAA,EACF,GAAG,YAAY,CAAA;AAEf,EAAA,OAAO,GAAA;AACT;AA6BO,SAAS,eAAA,CACd,QAAA,EACA,YAAA,GAAqC,EAAC,EACX;AAC3B,EAAA,MAAM,GAAA,GAAM,OAAiB,IAAI,CAAA;AAEjC,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,IAAI,CAAC,IAAI,OAAA,EAAS;AAElB,IAAA,MAAM,SAAA,GAAe,EAAA,CAAA,MAAA,CAAO,GAAA,CAAI,OAAO,CAAA;AACvC,IAAA,MAAM,MAAA,GAAS,MAAM,QAAA,CAAS,SAAS,CAAA;AAGvC,IAAA,MAAA,EAAO;AAGP,IAAA,MAAM,cAAA,GAAiB,IAAI,cAAA,CAAe,MAAM;AAC9C,MAAA,MAAA,EAAO;AAAA,IACT,CAAC,CAAA;AAED,IAAA,cAAA,CAAe,OAAA,CAAQ,IAAI,OAAO,CAAA;AAGlC,IAAA,OAAO,MAAM;AACX,MAAA,cAAA,CAAe,UAAA,EAAW;AAAA,IAC5B,CAAA;AAAA,EACF,GAAG,YAAY,CAAA;AAEf,EAAA,OAAO,GAAA;AACT","file":"useD3.js","sourcesContent":["import { useEffect, useRef } from 'react';\nimport * as d3 from 'd3';\n\n/**\n * Hook for managing D3 selections with React lifecycle\n * Provides a ref to the SVG/container element and runs a render function when dependencies change\n *\n * @param renderFn - Function that receives the D3 selection and performs rendering\n * @param dependencies - Array of dependencies that trigger re-render\n * @returns Ref to attach to the SVG/container element\n *\n * @example\n * ```tsx\n * function BarChart({ data }: { data: number[] }) {\n * const ref = useD3(\n * (svg) => {\n * const width = 600;\n * const height = 400;\n * const margin = { top: 20, right: 20, bottom: 30, left: 40 };\n *\n * // Clear previous content\n * svg.selectAll('*').remove();\n *\n * // Set up scales\n * const x = d3.scaleBand()\n * .domain(data.map((_, i) => i.toString()))\n * .range([margin.left, width - margin.right])\n * .padding(0.1);\n *\n * const y = d3.scaleLinear()\n * .domain([0, d3.max(data) || 0])\n * .range([height - margin.bottom, margin.top]);\n *\n * // Draw bars\n * svg.selectAll('rect')\n * .data(data)\n * .join('rect')\n * .attr('x', (_, i) => x(i.toString())!)\n * .attr('y', d => y(d))\n * .attr('width', x.bandwidth())\n * .attr('height', d => y(0) - y(d))\n * .attr('fill', 'steelblue');\n * },\n * [data]\n * );\n *\n * return <svg ref={ref} width={600} height={400} />;\n * }\n * ```\n */\nexport function useD3<T extends SVGSVGElement | HTMLDivElement>(\n renderFn: (selection: d3.Selection<T, unknown, null, undefined>) => void,\n dependencies: React.DependencyList = []\n): React.RefObject<T | null> {\n const ref = useRef<T | null>(null);\n\n useEffect(() => {\n if (ref.current) {\n const selection = d3.select(ref.current);\n renderFn(selection);\n }\n }, dependencies);\n\n return ref;\n}\n\n/**\n * Hook for managing D3 selections with automatic resize handling\n * Similar to useD3 but also triggers re-render on window resize\n *\n * @param renderFn - Function that receives the D3 selection and performs rendering\n * @param dependencies - Array of dependencies that trigger re-render\n * @returns Ref to attach to the SVG/container element\n *\n * @example\n * ```tsx\n * function ResponsiveChart({ data }: { data: number[] }) {\n * const ref = useD3WithResize(\n * (svg) => {\n * const container = svg.node();\n * const width = container?.clientWidth || 600;\n * const height = container?.clientHeight || 400;\n *\n * // Render with responsive dimensions\n * // ...\n * },\n * [data]\n * );\n *\n * return <svg ref={ref} style={{ width: '100%', height: '400px' }} />;\n * }\n * ```\n */\nexport function useD3WithResize<T extends SVGSVGElement | HTMLDivElement>(\n renderFn: (selection: d3.Selection<T, unknown, null, undefined>) => void,\n dependencies: React.DependencyList = []\n): React.RefObject<T | null> {\n const ref = useRef<T | null>(null);\n\n useEffect(() => {\n if (!ref.current) return;\n\n const selection = d3.select(ref.current);\n const render = () => renderFn(selection);\n\n // Initial render\n render();\n\n // Set up resize observer\n const resizeObserver = new ResizeObserver(() => {\n render();\n });\n\n resizeObserver.observe(ref.current);\n\n // Cleanup\n return () => {\n resizeObserver.disconnect();\n };\n }, dependencies);\n\n return ref;\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/hooks/useDebounce.ts"],"names":[],"mappings":";;;AA2BO,SAAS,WAAA,CAAe,KAAA,EAAU,KAAA,GAAgB,GAAA,EAAQ;AAC/D,EAAA,MAAM,CAAC,cAAA,EAAgB,iBAAiB,CAAA,GAAI,SAAY,KAAK,CAAA;AAE7D,EAAA,SAAA,CAAU,MAAM;AAEd,IAAA,MAAM,KAAA,GAAQ,WAAW,MAAM;AAC7B,MAAA,iBAAA,CAAkB,KAAK,CAAA;AAAA,IACzB,GAAG,KAAK,CAAA;AAGR,IAAA,OAAO,MAAM;AACX,MAAA,YAAA,CAAa,KAAK,CAAA;AAAA,IACpB,CAAA;AAAA,EACF,CAAA,EAAG,CAAC,KAAA,EAAO,KAAK,CAAC,CAAA;AAEjB,EAAA,OAAO,cAAA;AACT","file":"useDebounce.js","sourcesContent":["import { useEffect, useState } from 'react';\n\n/**\n * Debounce a value with a specified delay\n * Useful for search inputs, filters, and other frequently changing values\n *\n * @param value - The value to debounce\n * @param delay - Delay in milliseconds (default: 300ms)\n * @returns The debounced value\n *\n * @example\n * ```tsx\n * function SearchInput() {\n * const [searchTerm, setSearchTerm] = useState('');\n * const debouncedSearchTerm = useDebounce(searchTerm, 500);\n *\n * useEffect(() => {\n * // This will only run when user stops typing for 500ms\n * if (debouncedSearchTerm) {\n * performSearch(debouncedSearchTerm);\n * }\n * }, [debouncedSearchTerm]);\n *\n * return <input value={searchTerm} onChange={(e) => setSearchTerm(e.target.value)} />;\n * }\n * ```\n */\nexport function useDebounce<T>(value: T, delay: number = 300): T {\n const [debouncedValue, setDebouncedValue] = useState<T>(value);\n\n useEffect(() => {\n // Set up the timeout to update debounced value after delay\n const timer = setTimeout(() => {\n setDebouncedValue(value);\n }, delay);\n\n // Clean up the timeout if value changes or component unmounts\n return () => {\n clearTimeout(timer);\n };\n }, [value, delay]);\n\n return debouncedValue;\n}"]}
1
+ {"version":3,"sources":["../../src/hooks/useDebounce.ts"],"names":[],"mappings":";;;AA2BO,SAAS,WAAA,CAAe,KAAA,EAAU,KAAA,GAAgB,GAAA,EAAQ;AAC/D,EAAA,MAAM,CAAC,cAAA,EAAgB,iBAAiB,CAAA,GAAI,SAAY,KAAK,CAAA;AAE7D,EAAA,SAAA,CAAU,MAAM;AAEd,IAAA,MAAM,KAAA,GAAQ,WAAW,MAAM;AAC7B,MAAA,iBAAA,CAAkB,KAAK,CAAA;AAAA,IACzB,GAAG,KAAK,CAAA;AAGR,IAAA,OAAO,MAAM;AACX,MAAA,YAAA,CAAa,KAAK,CAAA;AAAA,IACpB,CAAA;AAAA,EACF,CAAA,EAAG,CAAC,KAAA,EAAO,KAAK,CAAC,CAAA;AAEjB,EAAA,OAAO,cAAA;AACT","file":"useDebounce.js","sourcesContent":["import { useEffect, useState } from 'react';\n\n/**\n * Debounce a value with a specified delay\n * Useful for search inputs, filters, and other frequently changing values\n *\n * @param value - The value to debounce\n * @param delay - Delay in milliseconds (default: 300ms)\n * @returns The debounced value\n *\n * @example\n * ```tsx\n * function SearchInput() {\n * const [searchTerm, setSearchTerm] = useState('');\n * const debouncedSearchTerm = useDebounce(searchTerm, 500);\n *\n * useEffect(() => {\n * // This will only run when user stops typing for 500ms\n * if (debouncedSearchTerm) {\n * performSearch(debouncedSearchTerm);\n * }\n * }, [debouncedSearchTerm]);\n *\n * return <input value={searchTerm} onChange={(e) => setSearchTerm(e.target.value)} />;\n * }\n * ```\n */\nexport function useDebounce<T>(value: T, delay: number = 300): T {\n const [debouncedValue, setDebouncedValue] = useState<T>(value);\n\n useEffect(() => {\n // Set up the timeout to update debounced value after delay\n const timer = setTimeout(() => {\n setDebouncedValue(value);\n }, delay);\n\n // Clean up the timeout if value changes or component unmounts\n return () => {\n clearTimeout(timer);\n };\n }, [value, delay]);\n\n return debouncedValue;\n}\n"]}
@@ -23,6 +23,7 @@ interface ForceSimulationOptions {
23
23
  warmAlpha?: number;
24
24
  alphaMin?: number;
25
25
  stabilizeOnStop?: boolean;
26
+ tickThrottleMs?: number;
26
27
  maxSimulationTimeMs?: number;
27
28
  velocityDecay?: number;
28
29
  onTick?: (nodes: SimulationNode[], links: SimulationLink[], sim: d3.Simulation<SimulationNode, SimulationLink>) => void;
@@ -17,14 +17,11 @@ function useForceSimulation(initialNodes, initialLinks, options) {
17
17
  alphaTarget = 0,
18
18
  warmAlpha = 0.3,
19
19
  alphaMin = 0.01,
20
- // @ts-ignore allow extra option
21
- stabilizeOnStop = true,
22
20
  onTick,
23
21
  // Optional throttle in milliseconds for tick updates (reduce React re-renders)
24
22
  // Lower values = smoother but more CPU; default ~30ms (~33fps)
25
- // @ts-ignore allow extra option
23
+ stabilizeOnStop = true,
26
24
  tickThrottleMs = 33,
27
- // @ts-ignore allow extra option
28
25
  maxSimulationTimeMs = 3e3
29
26
  } = options;
30
27
  const [nodes, setNodes] = useState(initialNodes);
@@ -59,10 +56,16 @@ function useForceSimulation(initialNodes, initialLinks, options) {
59
56
  n.vy = (Math.random() - 0.5) * 10;
60
57
  });
61
58
  }
62
- const simulation = d3.forceSimulation(nodesCopy);
59
+ const simulation = d3.forceSimulation(
60
+ nodesCopy
61
+ );
63
62
  try {
64
- const linkForce = d3.forceLink(linksCopy);
65
- linkForce.id((d) => d.id).distance((d) => d && d.distance != null ? d.distance : linkDistance).strength(linkStrength);
63
+ const linkForce = d3.forceLink(
64
+ linksCopy
65
+ );
66
+ linkForce.id((d) => d.id).distance(
67
+ (d) => d && d.distance != null ? d.distance : linkDistance
68
+ ).strength(linkStrength);
66
69
  simulation.force("link", linkForce);
67
70
  } catch (e) {
68
71
  try {
@@ -71,25 +74,39 @@ function useForceSimulation(initialNodes, initialLinks, options) {
71
74
  }
72
75
  }
73
76
  try {
74
- simulation.force("charge", d3.forceManyBody().strength(chargeStrength));
75
- simulation.force("center", d3.forceCenter(width / 2, height / 2).strength(centerStrength));
77
+ simulation.force(
78
+ "charge",
79
+ d3.forceManyBody().strength(chargeStrength)
80
+ );
81
+ simulation.force(
82
+ "center",
83
+ d3.forceCenter(width / 2, height / 2).strength(centerStrength)
84
+ );
76
85
  const collide = d3.forceCollide().radius((d) => {
77
86
  const nodeSize = d && d.size ? d.size : 10;
78
87
  return nodeSize + collisionRadius;
79
88
  }).strength(collisionStrength);
80
89
  simulation.force("collision", collide);
81
- simulation.force("x", d3.forceX(width / 2).strength(Math.max(0.02, centerStrength * 0.5)));
82
- simulation.force("y", d3.forceY(height / 2).strength(Math.max(0.02, centerStrength * 0.5)));
90
+ simulation.force(
91
+ "x",
92
+ d3.forceX(width / 2).strength(Math.max(0.02, centerStrength * 0.5))
93
+ );
94
+ simulation.force(
95
+ "y",
96
+ d3.forceY(height / 2).strength(Math.max(0.02, centerStrength * 0.5))
97
+ );
83
98
  simulation.alphaDecay(alphaDecay);
84
99
  simulation.velocityDecay(velocityDecay);
85
100
  simulation.alphaMin(alphaMin);
86
101
  try {
87
102
  simulation.alphaTarget(alphaTarget);
88
103
  } catch (e) {
104
+ void e;
89
105
  }
90
106
  try {
91
107
  simulation.alpha(warmAlpha);
92
108
  } catch (e) {
109
+ void e;
93
110
  }
94
111
  } catch (e) {
95
112
  }
@@ -125,7 +142,8 @@ function useForceSimulation(initialNodes, initialLinks, options) {
125
142
  let lastUpdate = 0;
126
143
  const tickHandler = () => {
127
144
  try {
128
- if (typeof onTick === "function") onTick(nodesCopy, linksCopy, simulation);
145
+ if (typeof onTick === "function")
146
+ onTick(nodesCopy, linksCopy, simulation);
129
147
  } catch (e) {
130
148
  }
131
149
  try {
@@ -141,6 +159,7 @@ function useForceSimulation(initialNodes, initialLinks, options) {
141
159
  }
142
160
  simulation.stop();
143
161
  } catch (e) {
162
+ void e;
144
163
  }
145
164
  setAlpha(simulation.alpha());
146
165
  setIsRunning(false);
@@ -211,7 +230,7 @@ function useForceSimulation(initialNodes, initialLinks, options) {
211
230
  if (simulationRef.current) {
212
231
  try {
213
232
  simulationRef.current.alphaTarget(warmAlpha).restart();
214
- } catch (e) {
233
+ } catch {
215
234
  simulationRef.current.restart();
216
235
  }
217
236
  setIsRunning(true);
@@ -240,7 +259,11 @@ function useForceSimulation(initialNodes, initialLinks, options) {
240
259
  setIsRunning(false);
241
260
  }
242
261
  };
243
- const originalForcesRef = useRef({ charge: chargeStrength, link: linkStrength, collision: collisionStrength });
262
+ const originalForcesRef = useRef({
263
+ charge: chargeStrength,
264
+ link: linkStrength,
265
+ collision: collisionStrength
266
+ });
244
267
  const forcesEnabledRef = useRef(true);
245
268
  const setForcesEnabled = (enabled) => {
246
269
  const sim = simulationRef.current;
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/hooks/useForceSimulation.ts"],"names":["e"],"mappings":"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","file":"useForceSimulation.js","sourcesContent":["import { useEffect, useRef, useState } from 'react';\nimport * as d3 from 'd3';\n\nexport interface SimulationNode extends d3.SimulationNodeDatum {\n id: string;\n [key: string]: any;\n}\n\nexport interface SimulationLink extends d3.SimulationLinkDatum<SimulationNode> {\n source: string | SimulationNode;\n target: string | SimulationNode;\n [key: string]: any;\n}\n\nexport interface ForceSimulationOptions {\n /**\n * Strength of the charge force (repulsion between nodes)\n * @default -300\n */\n chargeStrength?: number;\n\n /**\n * Distance for links between nodes\n * @default 100\n */\n linkDistance?: number;\n\n /**\n * Strength of the link force\n * @default 1\n */\n linkStrength?: number;\n\n /**\n * Strength of collision detection\n * @default 1\n */\n collisionStrength?: number;\n\n /**\n * Radius for collision detection (node size)\n * @default 10\n */\n collisionRadius?: number;\n\n /**\n * Strength of centering force\n * @default 0.1\n */\n centerStrength?: number;\n\n /**\n * Width of the simulation space\n */\n width: number;\n\n /**\n * Height of the simulation space\n */\n height: number;\n\n /**\n * Alpha decay rate (how quickly the simulation cools down)\n * @default 0.0228\n */\n alphaDecay?: number;\n\n /**\n * Alpha target controls the resting energy of the simulation. When set to 0\n * the simulation will cool and stop moving once forces settle. Increase to\n * keep the graph more dynamic.\n * @default 0\n */\n alphaTarget?: number;\n\n /**\n * Warm alpha used when (re)starting the simulation to give it a small amount\n * of energy. This mirrors the Observable example which sets a modest\n * alphaTarget when dragging instead of forcing alpha to 1.\n * @default 0.3\n */\n warmAlpha?: number;\n\n /**\n * Minimum alpha threshold below which the simulation is considered cooled\n * and will stop. Increasing this makes the simulation stop earlier.\n * @default 0.01\n */\n alphaMin?: number;\n\n /**\n * When true, zero node velocities and snap positions when the simulation\n * stops to reduce residual jitter.\n * @default true\n */\n stabilizeOnStop?: boolean;\n\n /**\n * Maximum time (ms) to allow the simulation to run after creation/restart.\n * If the simulation hasn't cooled by this time, it will be force-stopped\n * to prevent indefinite animation. Set to 0 to disable.\n * @default 3000\n */\n maxSimulationTimeMs?: number;\n\n /**\n * Velocity decay (friction)\n * @default 0.4\n */\n velocityDecay?: number;\n\n /**\n * Optional tick callback invoked on each simulation tick with current nodes/links and the simulation instance\n */\n onTick?: (nodes: SimulationNode[], links: SimulationLink[], sim: d3.Simulation<SimulationNode, SimulationLink>) => void;\n}\n\nexport interface UseForceSimulationReturn {\n /**\n * Current nodes with positions\n */\n nodes: SimulationNode[];\n\n /**\n * Current links\n */\n links: SimulationLink[];\n\n /**\n * Restart the simulation\n */\n restart: () => void;\n\n /**\n * Stop the simulation\n */\n stop: () => void;\n\n /**\n * Whether the simulation is currently running\n */\n isRunning: boolean;\n\n /**\n * Current alpha value (simulation heat)\n */\n alpha: number;\n}\n\n/**\n * Hook for managing d3-force simulations\n * Automatically handles simulation lifecycle, tick updates, and cleanup\n *\n * @param initialNodes - Initial nodes for the simulation\n * @param initialLinks - Initial links for the simulation\n * @param options - Configuration options for the force simulation\n * @returns Simulation state and control functions\n *\n * @example\n * ```tsx\n * function NetworkGraph() {\n * const nodes = [\n * { id: 'node1', name: 'Node 1' },\n * { id: 'node2', name: 'Node 2' },\n * { id: 'node3', name: 'Node 3' },\n * ];\n *\n * const links = [\n * { source: 'node1', target: 'node2' },\n * { source: 'node2', target: 'node3' },\n * ];\n *\n * const { nodes: simulatedNodes, links: simulatedLinks, restart } = useForceSimulation(\n * nodes,\n * links,\n * {\n * width: 800,\n * height: 600,\n * chargeStrength: -500,\n * linkDistance: 150,\n * }\n * );\n *\n * return (\n * <svg width={800} height={600}>\n * {simulatedLinks.map((link, i) => (\n * <line\n * key={i}\n * x1={(link.source as SimulationNode).x}\n * y1={(link.source as SimulationNode).y}\n * x2={(link.target as SimulationNode).x}\n * y2={(link.target as SimulationNode).y}\n * stroke=\"#999\"\n * />\n * ))}\n * {simulatedNodes.map((node) => (\n * <circle\n * key={node.id}\n * cx={node.x}\n * cy={node.y}\n * r={10}\n * fill=\"#69b3a2\"\n * />\n * ))}\n * </svg>\n * );\n * }\n * ```\n */\nexport function useForceSimulation(\n initialNodes: SimulationNode[],\n initialLinks: SimulationLink[],\n options: ForceSimulationOptions\n): UseForceSimulationReturn & { setForcesEnabled: (enabled: boolean) => void } {\n const {\n chargeStrength = -300,\n linkDistance = 100,\n linkStrength = 1,\n collisionStrength = 1,\n collisionRadius = 10,\n centerStrength = 0.1,\n width,\n height,\n alphaDecay = 0.0228,\n velocityDecay = 0.4,\n alphaTarget = 0,\n warmAlpha = 0.3,\n alphaMin = 0.01,\n // @ts-ignore allow extra option\n stabilizeOnStop = true,\n onTick,\n // Optional throttle in milliseconds for tick updates (reduce React re-renders)\n // Lower values = smoother but more CPU; default ~30ms (~33fps)\n // @ts-ignore allow extra option\n tickThrottleMs = 33,\n // @ts-ignore allow extra option\n maxSimulationTimeMs = 3000,\n } = options;\n\n const [nodes, setNodes] = useState<SimulationNode[]>(initialNodes);\n const [links, setLinks] = useState<SimulationLink[]>(initialLinks);\n const [isRunning, setIsRunning] = useState(false);\n const [alpha, setAlpha] = useState(1);\n\n const simulationRef = useRef<d3.Simulation<SimulationNode, SimulationLink> | null>(null);\n const stopTimeoutRef = useRef<number | null>(null);\n\n // Create lightweight keys for nodes/links so we only recreate the simulation\n // when the actual identity/content of inputs change (not when parent passes\n // new array references on each render).\n const nodesKey = initialNodes.map((n) => n.id).join('|');\n const linksKey = (initialLinks || []).map((l) => {\n const s = typeof l.source === 'string' ? l.source : (l.source as any)?.id;\n const t = typeof l.target === 'string' ? l.target : (l.target as any)?.id;\n return `${s}->${t}:${(l as any).type || ''}`;\n }).join('|');\n\n useEffect(() => {\n // Create a copy of nodes and links to avoid mutating the original data\n const nodesCopy = initialNodes.map((node) => ({ ...node }));\n const linksCopy = initialLinks.map((link) => ({ ...link }));\n\n // ALWAYS seed initial positions to ensure nodes don't stack at origin\n // This is critical for force-directed graphs to work properly\n try {\n // Always seed positions for all nodes when simulation is created\n // This ensures nodes start spread out even if they have coordinates\n nodesCopy.forEach((n, i) => {\n // Use deterministic but more widely spread positions based on index\n const angle = (i * 2 * Math.PI) / nodesCopy.length;\n // Larger seed radius to encourage an initial spread\n const radius = Math.min(width, height) * 0.45;\n n.x = width / 2 + radius * Math.cos(angle);\n n.y = height / 2 + radius * Math.sin(angle);\n // Add very small random velocity to avoid large initial motion\n (n as any).vx = (Math.random() - 0.5) * 2;\n (n as any).vy = (Math.random() - 0.5) * 2;\n });\n } catch (e) {\n // If error, fall back to random positions\n nodesCopy.forEach((n) => {\n n.x = Math.random() * width;\n n.y = Math.random() * height;\n (n as any).vx = (Math.random() - 0.5) * 10;\n (n as any).vy = (Math.random() - 0.5) * 10;\n });\n }\n\n // Create the simulation\n const simulation = (d3.forceSimulation(nodesCopy as any) as unknown) as d3.Simulation<SimulationNode, SimulationLink>;\n\n // Configure link force separately to avoid using generic type args on d3 helpers\n try {\n const linkForce = (d3.forceLink(linksCopy as any) as unknown) as d3.ForceLink<SimulationNode, SimulationLink>;\n linkForce.id((d: any) => d.id).distance((d: any) => (d && d.distance != null ? d.distance : linkDistance)).strength(linkStrength);\n simulation.force('link', linkForce as any);\n } catch (e) {\n // fallback: attach a plain link force\n try { simulation.force('link', d3.forceLink(linksCopy as any) as any); } catch (e) {}\n }\n ;\n\n try {\n simulation.force('charge', d3.forceManyBody().strength(chargeStrength) as any);\n simulation.force('center', d3.forceCenter(width / 2, height / 2).strength(centerStrength) as any);\n const collide = d3.forceCollide().radius((d: any) => {\n const nodeSize = (d && d.size) ? d.size : 10;\n return nodeSize + collisionRadius;\n }).strength(collisionStrength as any) as any;\n simulation.force('collision', collide);\n simulation.force('x', d3.forceX(width / 2).strength(Math.max(0.02, centerStrength * 0.5)) as any);\n simulation.force('y', d3.forceY(height / 2).strength(Math.max(0.02, centerStrength * 0.5)) as any);\n simulation.alphaDecay(alphaDecay);\n simulation.velocityDecay(velocityDecay);\n simulation.alphaMin(alphaMin);\n try { simulation.alphaTarget(alphaTarget); } catch (e) {}\n try { simulation.alpha(warmAlpha); } catch (e) {}\n } catch (e) {\n // ignore force configuration errors\n }\n\n simulationRef.current = simulation;\n\n // Force-stop timeout to ensure simulation doesn't run forever.\n if (stopTimeoutRef.current != null) {\n try { (globalThis.clearTimeout as any)(stopTimeoutRef.current); } catch (e) {}\n stopTimeoutRef.current = null;\n }\n if (maxSimulationTimeMs && maxSimulationTimeMs > 0) {\n stopTimeoutRef.current = (globalThis.setTimeout as any)(() => {\n try {\n if (stabilizeOnStop) {\n nodesCopy.forEach((n) => {\n (n as any).vx = 0;\n (n as any).vy = 0;\n if (typeof n.x === 'number') n.x = Number(n.x.toFixed(3));\n if (typeof n.y === 'number') n.y = Number(n.y.toFixed(3));\n });\n }\n simulation.alpha(0);\n simulation.stop();\n } catch (e) {}\n setIsRunning(false);\n setNodes([...nodesCopy]);\n setLinks([...linksCopy]);\n }, maxSimulationTimeMs) as unknown as number;\n }\n\n // Update state on each tick. Batch updates via requestAnimationFrame to avoid\n // excessive React re-renders which can cause visual flicker.\n let rafId: number | null = null;\n let lastUpdate = 0;\n const tickHandler = () => {\n try {\n if (typeof onTick === 'function') onTick(nodesCopy, linksCopy, simulation);\n } catch (e) {\n // ignore user tick errors\n }\n\n // If simulation alpha has cooled below the configured minimum, stop it to\n // ensure nodes don't drift indefinitely (acts as a hard-stop safeguard).\n try {\n if (simulation.alpha() <= (alphaMin as number)) {\n try {\n if (stabilizeOnStop) {\n nodesCopy.forEach((n) => {\n (n as any).vx = 0;\n (n as any).vy = 0;\n if (typeof n.x === 'number') n.x = Number(n.x.toFixed(3));\n if (typeof n.y === 'number') n.y = Number(n.y.toFixed(3));\n });\n }\n simulation.stop();\n } catch (e) {}\n setAlpha(simulation.alpha());\n setIsRunning(false);\n setNodes([...nodesCopy]);\n setLinks([...linksCopy]);\n return;\n }\n } catch (e) {\n // ignore\n }\n\n const now = Date.now();\n const shouldUpdate = now - lastUpdate >= (tickThrottleMs as number);\n if (rafId == null && shouldUpdate) {\n rafId = (globalThis.requestAnimationFrame || ((cb: FrameRequestCallback) => setTimeout(cb, 16)))(() => {\n rafId = null;\n lastUpdate = Date.now();\n setNodes([...nodesCopy]);\n setLinks([...linksCopy]);\n setAlpha(simulation.alpha());\n setIsRunning(simulation.alpha() > simulation.alphaMin());\n }) as unknown as number;\n }\n };\n\n simulation.on('tick', tickHandler);\n\n simulation.on('end', () => {\n setIsRunning(false);\n });\n\n // Cleanup on unmount\n return () => {\n try {\n simulation.on('tick', null as any);\n } catch (e) {}\n if (stopTimeoutRef.current != null) {\n try { (globalThis.clearTimeout as any)(stopTimeoutRef.current); } catch (e) {}\n stopTimeoutRef.current = null;\n }\n if (rafId != null) {\n try { (globalThis.cancelAnimationFrame || ((id: number) => clearTimeout(id)))(rafId); } catch (e) {}\n rafId = null;\n }\n simulation.stop();\n };\n }, [\n nodesKey,\n linksKey,\n chargeStrength,\n linkDistance,\n linkStrength,\n collisionStrength,\n collisionRadius,\n centerStrength,\n width,\n height,\n alphaDecay,\n velocityDecay,\n alphaTarget,\n alphaMin,\n stabilizeOnStop,\n tickThrottleMs,\n maxSimulationTimeMs,\n ]);\n\n const restart = () => {\n if (simulationRef.current) {\n // Reheat the simulation to a modest alpha target rather than forcing\n // full heat; this matches the Observable pattern and helps stability.\n try { simulationRef.current.alphaTarget(warmAlpha).restart(); } catch (e) { simulationRef.current.restart(); }\n setIsRunning(true);\n // Reset safety timeout when simulation is manually restarted\n if (stopTimeoutRef.current != null) {\n try { (globalThis.clearTimeout as any)(stopTimeoutRef.current); } catch (e) {}\n stopTimeoutRef.current = null;\n }\n if (maxSimulationTimeMs && maxSimulationTimeMs > 0) {\n stopTimeoutRef.current = (globalThis.setTimeout as any)(() => {\n try { simulationRef.current?.alpha(0); simulationRef.current?.stop(); } catch (e) {}\n setIsRunning(false);\n }, maxSimulationTimeMs) as unknown as number;\n }\n }\n };\n\n const stop = () => {\n if (simulationRef.current) {\n simulationRef.current.stop();\n setIsRunning(false);\n }\n };\n\n const originalForcesRef = useRef({ charge: chargeStrength, link: linkStrength, collision: collisionStrength });\n const forcesEnabledRef = useRef(true);\n\n const setForcesEnabled = (enabled: boolean) => {\n const sim = simulationRef.current;\n if (!sim) return;\n // avoid repeated updates\n if (forcesEnabledRef.current === enabled) return;\n forcesEnabledRef.current = enabled;\n\n try {\n // Only toggle charge and link forces to avoid collapse; keep collision/centering\n const charge: any = sim.force('charge');\n if (charge && typeof charge.strength === 'function') {\n charge.strength(enabled ? originalForcesRef.current.charge : 0);\n }\n\n const link: any = sim.force('link');\n if (link && typeof link.strength === 'function') {\n link.strength(enabled ? originalForcesRef.current.link : 0);\n }\n } catch (e) {\n // ignore\n }\n };\n\n return {\n nodes,\n links,\n restart,\n stop,\n isRunning,\n alpha,\n setForcesEnabled,\n };\n}\n\n/**\n * Hook for creating a draggable force simulation\n * Provides drag handlers that can be attached to node elements\n *\n * @param simulation - The d3 force simulation instance\n * @returns Drag behavior that can be applied to nodes\n *\n * @example\n * ```tsx\n * function DraggableNetworkGraph() {\n * const simulation = useRef<d3.Simulation<SimulationNode, SimulationLink>>();\n * const drag = useDrag(simulation.current);\n *\n * return (\n * <svg>\n * {nodes.map((node) => (\n * <circle\n * key={node.id}\n * {...drag}\n * cx={node.x}\n * cy={node.y}\n * r={10}\n * />\n * ))}\n * </svg>\n * );\n * }\n * ```\n */\nexport function useDrag(simulation: d3.Simulation<SimulationNode, any> | null | undefined) {\n const dragStarted = (event: any, node: SimulationNode) => {\n if (!simulation) return;\n if (!event.active) simulation.alphaTarget(0.3).restart();\n node.fx = node.x;\n node.fy = node.y;\n };\n\n const dragged = (event: any, node: SimulationNode) => {\n node.fx = event.x;\n node.fy = event.y;\n };\n\n const dragEnded = (event: any, node: SimulationNode) => {\n if (!simulation) return;\n if (!event.active) simulation.alphaTarget(0);\n node.fx = null;\n node.fy = null;\n };\n\n return {\n onDragStart: dragStarted,\n onDrag: dragged,\n onDragEnd: dragEnded,\n };\n}"]}
1
+ {"version":3,"sources":["../../src/hooks/useForceSimulation.ts"],"names":["e"],"mappings":"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","file":"useForceSimulation.js","sourcesContent":["import { useEffect, useRef, useState } from 'react';\nimport * as d3 from 'd3';\n\nexport interface SimulationNode extends d3.SimulationNodeDatum {\n id: string;\n [key: string]: any;\n}\n\nexport interface SimulationLink extends d3.SimulationLinkDatum<SimulationNode> {\n source: string | SimulationNode;\n target: string | SimulationNode;\n [key: string]: any;\n}\n\nexport interface ForceSimulationOptions {\n /**\n * Strength of the charge force (repulsion between nodes)\n * @default -300\n */\n chargeStrength?: number;\n\n /**\n * Distance for links between nodes\n * @default 100\n */\n linkDistance?: number;\n\n /**\n * Strength of the link force\n * @default 1\n */\n linkStrength?: number;\n\n /**\n * Strength of collision detection\n * @default 1\n */\n collisionStrength?: number;\n\n /**\n * Radius for collision detection (node size)\n * @default 10\n */\n collisionRadius?: number;\n\n /**\n * Strength of centering force\n * @default 0.1\n */\n centerStrength?: number;\n\n /**\n * Width of the simulation space\n */\n width: number;\n\n /**\n * Height of the simulation space\n */\n height: number;\n\n /**\n * Alpha decay rate (how quickly the simulation cools down)\n * @default 0.0228\n */\n alphaDecay?: number;\n\n /**\n * Alpha target controls the resting energy of the simulation. When set to 0\n * the simulation will cool and stop moving once forces settle. Increase to\n * keep the graph more dynamic.\n * @default 0\n */\n alphaTarget?: number;\n\n /**\n * Warm alpha used when (re)starting the simulation to give it a small amount\n * of energy. This mirrors the Observable example which sets a modest\n * alphaTarget when dragging instead of forcing alpha to 1.\n * @default 0.3\n */\n warmAlpha?: number;\n\n /**\n * Minimum alpha threshold below which the simulation is considered cooled\n * and will stop. Increasing this makes the simulation stop earlier.\n * @default 0.01\n */\n alphaMin?: number;\n\n /**\n * When true, zero node velocities and snap positions when the simulation\n * stops to reduce residual jitter.\n * @default true\n */\n stabilizeOnStop?: boolean;\n\n /**\n * Throttle for tick updates in milliseconds to reduce update frequency\n * (helps avoid excessive React re-renders).\n * @default 33\n */\n tickThrottleMs?: number;\n\n /**\n * Maximum time (ms) to allow the simulation to run after creation/restart.\n * If the simulation hasn't cooled by this time, it will be force-stopped\n * to prevent indefinite animation. Set to 0 to disable.\n * @default 3000\n */\n maxSimulationTimeMs?: number;\n\n /**\n * Velocity decay (friction)\n * @default 0.4\n */\n velocityDecay?: number;\n\n /**\n * Optional tick callback invoked on each simulation tick with current nodes/links and the simulation instance\n */\n onTick?: (\n nodes: SimulationNode[],\n links: SimulationLink[],\n sim: d3.Simulation<SimulationNode, SimulationLink>\n ) => void;\n}\n\nexport interface UseForceSimulationReturn {\n /**\n * Current nodes with positions\n */\n nodes: SimulationNode[];\n\n /**\n * Current links\n */\n links: SimulationLink[];\n\n /**\n * Restart the simulation\n */\n restart: () => void;\n\n /**\n * Stop the simulation\n */\n stop: () => void;\n\n /**\n * Whether the simulation is currently running\n */\n isRunning: boolean;\n\n /**\n * Current alpha value (simulation heat)\n */\n alpha: number;\n}\n\n/**\n * Hook for managing d3-force simulations\n * Automatically handles simulation lifecycle, tick updates, and cleanup\n *\n * @param initialNodes - Initial nodes for the simulation\n * @param initialLinks - Initial links for the simulation\n * @param options - Configuration options for the force simulation\n * @returns Simulation state and control functions\n *\n * @example\n * ```tsx\n * function NetworkGraph() {\n * const nodes = [\n * { id: 'node1', name: 'Node 1' },\n * { id: 'node2', name: 'Node 2' },\n * { id: 'node3', name: 'Node 3' },\n * ];\n *\n * const links = [\n * { source: 'node1', target: 'node2' },\n * { source: 'node2', target: 'node3' },\n * ];\n *\n * const { nodes: simulatedNodes, links: simulatedLinks, restart } = useForceSimulation(\n * nodes,\n * links,\n * {\n * width: 800,\n * height: 600,\n * chargeStrength: -500,\n * linkDistance: 150,\n * }\n * );\n *\n * return (\n * <svg width={800} height={600}>\n * {simulatedLinks.map((link, i) => (\n * <line\n * key={i}\n * x1={(link.source as SimulationNode).x}\n * y1={(link.source as SimulationNode).y}\n * x2={(link.target as SimulationNode).x}\n * y2={(link.target as SimulationNode).y}\n * stroke=\"#999\"\n * />\n * ))}\n * {simulatedNodes.map((node) => (\n * <circle\n * key={node.id}\n * cx={node.x}\n * cy={node.y}\n * r={10}\n * fill=\"#69b3a2\"\n * />\n * ))}\n * </svg>\n * );\n * }\n * ```\n */\nexport function useForceSimulation(\n initialNodes: SimulationNode[],\n initialLinks: SimulationLink[],\n options: ForceSimulationOptions\n): UseForceSimulationReturn & { setForcesEnabled: (enabled: boolean) => void } {\n const {\n chargeStrength = -300,\n linkDistance = 100,\n linkStrength = 1,\n collisionStrength = 1,\n collisionRadius = 10,\n centerStrength = 0.1,\n width,\n height,\n alphaDecay = 0.0228,\n velocityDecay = 0.4,\n alphaTarget = 0,\n warmAlpha = 0.3,\n alphaMin = 0.01,\n onTick,\n // Optional throttle in milliseconds for tick updates (reduce React re-renders)\n // Lower values = smoother but more CPU; default ~30ms (~33fps)\n stabilizeOnStop = true,\n tickThrottleMs = 33,\n maxSimulationTimeMs = 3000,\n } = options;\n\n const [nodes, setNodes] = useState<SimulationNode[]>(initialNodes);\n const [links, setLinks] = useState<SimulationLink[]>(initialLinks);\n const [isRunning, setIsRunning] = useState(false);\n const [alpha, setAlpha] = useState(1);\n\n const simulationRef = useRef<d3.Simulation<\n SimulationNode,\n SimulationLink\n > | null>(null);\n const stopTimeoutRef = useRef<number | null>(null);\n\n // Create lightweight keys for nodes/links so we only recreate the simulation\n // when the actual identity/content of inputs change (not when parent passes\n // new array references on each render).\n const nodesKey = initialNodes.map((n) => n.id).join('|');\n const linksKey = (initialLinks || [])\n .map((l) => {\n const s = typeof l.source === 'string' ? l.source : (l.source as any)?.id;\n const t = typeof l.target === 'string' ? l.target : (l.target as any)?.id;\n return `${s}->${t}:${(l as any).type || ''}`;\n })\n .join('|');\n\n useEffect(() => {\n // Create a copy of nodes and links to avoid mutating the original data\n const nodesCopy = initialNodes.map((node) => ({ ...node }));\n const linksCopy = initialLinks.map((link) => ({ ...link }));\n\n // ALWAYS seed initial positions to ensure nodes don't stack at origin\n // This is critical for force-directed graphs to work properly\n try {\n // Always seed positions for all nodes when simulation is created\n // This ensures nodes start spread out even if they have coordinates\n nodesCopy.forEach((n, i) => {\n // Use deterministic but more widely spread positions based on index\n const angle = (i * 2 * Math.PI) / nodesCopy.length;\n // Larger seed radius to encourage an initial spread\n const radius = Math.min(width, height) * 0.45;\n n.x = width / 2 + radius * Math.cos(angle);\n n.y = height / 2 + radius * Math.sin(angle);\n // Add very small random velocity to avoid large initial motion\n (n as any).vx = (Math.random() - 0.5) * 2;\n (n as any).vy = (Math.random() - 0.5) * 2;\n });\n } catch (e) {\n void e;\n // If error, fall back to random positions\n nodesCopy.forEach((n) => {\n n.x = Math.random() * width;\n n.y = Math.random() * height;\n (n as any).vx = (Math.random() - 0.5) * 10;\n (n as any).vy = (Math.random() - 0.5) * 10;\n });\n }\n\n // Create the simulation\n const simulation = d3.forceSimulation(\n nodesCopy as any\n ) as unknown as d3.Simulation<SimulationNode, SimulationLink>;\n\n // Configure link force separately to avoid using generic type args on d3 helpers\n try {\n const linkForce = d3.forceLink(\n linksCopy as any\n ) as unknown as d3.ForceLink<SimulationNode, SimulationLink>;\n linkForce\n .id((d: any) => d.id)\n .distance((d: any) =>\n d && d.distance != null ? d.distance : linkDistance\n )\n .strength(linkStrength);\n simulation.force('link', linkForce as any);\n } catch (e) {\n void e;\n // fallback: attach a plain link force\n try {\n simulation.force('link', d3.forceLink(linksCopy as any) as any);\n } catch (e) {\n void e;\n }\n }\n try {\n simulation.force(\n 'charge',\n d3.forceManyBody().strength(chargeStrength) as any\n );\n simulation.force(\n 'center',\n d3.forceCenter(width / 2, height / 2).strength(centerStrength) as any\n );\n const collide = d3\n .forceCollide()\n .radius((d: any) => {\n const nodeSize = d && d.size ? d.size : 10;\n return nodeSize + collisionRadius;\n })\n .strength(collisionStrength as any) as any;\n simulation.force('collision', collide);\n simulation.force(\n 'x',\n d3\n .forceX(width / 2)\n .strength(Math.max(0.02, centerStrength * 0.5)) as any\n );\n simulation.force(\n 'y',\n d3\n .forceY(height / 2)\n .strength(Math.max(0.02, centerStrength * 0.5)) as any\n );\n simulation.alphaDecay(alphaDecay);\n simulation.velocityDecay(velocityDecay);\n simulation.alphaMin(alphaMin);\n try {\n simulation.alphaTarget(alphaTarget);\n } catch (e) {\n void e;\n }\n try {\n simulation.alpha(warmAlpha);\n } catch (e) {\n void e;\n }\n } catch (e) {\n void e;\n // ignore force configuration errors\n }\n\n simulationRef.current = simulation;\n\n // Force-stop timeout to ensure simulation doesn't run forever.\n if (stopTimeoutRef.current != null) {\n try {\n (globalThis.clearTimeout as any)(stopTimeoutRef.current);\n } catch (e) {\n void e;\n }\n stopTimeoutRef.current = null;\n }\n if (maxSimulationTimeMs && maxSimulationTimeMs > 0) {\n stopTimeoutRef.current = (globalThis.setTimeout as any)(() => {\n try {\n if (stabilizeOnStop) {\n nodesCopy.forEach((n) => {\n (n as any).vx = 0;\n (n as any).vy = 0;\n if (typeof n.x === 'number') n.x = Number(n.x.toFixed(3));\n if (typeof n.y === 'number') n.y = Number(n.y.toFixed(3));\n });\n }\n simulation.alpha(0);\n simulation.stop();\n } catch (e) {\n void e;\n }\n setIsRunning(false);\n setNodes([...nodesCopy]);\n setLinks([...linksCopy]);\n }, maxSimulationTimeMs) as unknown as number;\n }\n\n // Update state on each tick. Batch updates via requestAnimationFrame to avoid\n // excessive React re-renders which can cause visual flicker.\n let rafId: number | null = null;\n let lastUpdate = 0;\n const tickHandler = () => {\n try {\n if (typeof onTick === 'function')\n onTick(nodesCopy, linksCopy, simulation);\n } catch (e) {\n void e;\n }\n\n // If simulation alpha has cooled below the configured minimum, stop it to\n // ensure nodes don't drift indefinitely (acts as a hard-stop safeguard).\n try {\n if (simulation.alpha() <= (alphaMin as number)) {\n try {\n if (stabilizeOnStop) {\n nodesCopy.forEach((n) => {\n (n as any).vx = 0;\n (n as any).vy = 0;\n if (typeof n.x === 'number') n.x = Number(n.x.toFixed(3));\n if (typeof n.y === 'number') n.y = Number(n.y.toFixed(3));\n });\n }\n simulation.stop();\n } catch (e) {\n void e;\n }\n setAlpha(simulation.alpha());\n setIsRunning(false);\n setNodes([...nodesCopy]);\n setLinks([...linksCopy]);\n return;\n }\n } catch (e) {\n void e;\n }\n\n const now = Date.now();\n const shouldUpdate = now - lastUpdate >= (tickThrottleMs as number);\n if (rafId == null && shouldUpdate) {\n rafId = (\n globalThis.requestAnimationFrame ||\n ((cb: FrameRequestCallback) => setTimeout(cb, 16))\n )(() => {\n rafId = null;\n lastUpdate = Date.now();\n setNodes([...nodesCopy]);\n setLinks([...linksCopy]);\n setAlpha(simulation.alpha());\n setIsRunning(simulation.alpha() > simulation.alphaMin());\n }) as unknown as number;\n }\n };\n\n simulation.on('tick', tickHandler);\n\n simulation.on('end', () => {\n setIsRunning(false);\n });\n\n // Cleanup on unmount\n return () => {\n try {\n simulation.on('tick', null as any);\n } catch (e) {\n void e;\n }\n if (stopTimeoutRef.current != null) {\n try {\n (globalThis.clearTimeout as any)(stopTimeoutRef.current);\n } catch (e) {\n void e;\n }\n stopTimeoutRef.current = null;\n }\n if (rafId != null) {\n try {\n (\n globalThis.cancelAnimationFrame ||\n ((id: number) => clearTimeout(id))\n )(rafId);\n } catch (e) {\n void e;\n }\n rafId = null;\n }\n simulation.stop();\n };\n }, [\n nodesKey,\n linksKey,\n chargeStrength,\n linkDistance,\n linkStrength,\n collisionStrength,\n collisionRadius,\n centerStrength,\n width,\n height,\n alphaDecay,\n velocityDecay,\n alphaTarget,\n alphaMin,\n stabilizeOnStop,\n tickThrottleMs,\n maxSimulationTimeMs,\n ]);\n\n const restart = () => {\n if (simulationRef.current) {\n // Reheat the simulation to a modest alpha target rather than forcing\n // full heat; this matches the Observable pattern and helps stability.\n try {\n simulationRef.current.alphaTarget(warmAlpha).restart();\n } catch {\n simulationRef.current.restart();\n }\n setIsRunning(true);\n // Reset safety timeout when simulation is manually restarted\n if (stopTimeoutRef.current != null) {\n try {\n (globalThis.clearTimeout as any)(stopTimeoutRef.current);\n } catch (e) {\n void e;\n }\n stopTimeoutRef.current = null;\n }\n if (maxSimulationTimeMs && maxSimulationTimeMs > 0) {\n stopTimeoutRef.current = (globalThis.setTimeout as any)(() => {\n try {\n simulationRef.current?.alpha(0);\n simulationRef.current?.stop();\n } catch (e) {\n void e;\n }\n setIsRunning(false);\n }, maxSimulationTimeMs) as unknown as number;\n }\n }\n };\n\n const stop = () => {\n if (simulationRef.current) {\n simulationRef.current.stop();\n setIsRunning(false);\n }\n };\n\n const originalForcesRef = useRef({\n charge: chargeStrength,\n link: linkStrength,\n collision: collisionStrength,\n });\n const forcesEnabledRef = useRef(true);\n\n const setForcesEnabled = (enabled: boolean) => {\n const sim = simulationRef.current;\n if (!sim) return;\n // avoid repeated updates\n if (forcesEnabledRef.current === enabled) return;\n forcesEnabledRef.current = enabled;\n\n try {\n // Only toggle charge and link forces to avoid collapse; keep collision/centering\n const charge: any = sim.force('charge');\n if (charge && typeof charge.strength === 'function') {\n charge.strength(enabled ? originalForcesRef.current.charge : 0);\n }\n\n const link: any = sim.force('link');\n if (link && typeof link.strength === 'function') {\n link.strength(enabled ? originalForcesRef.current.link : 0);\n }\n } catch (e) {\n void e;\n }\n };\n\n return {\n nodes,\n links,\n restart,\n stop,\n isRunning,\n alpha,\n setForcesEnabled,\n };\n}\n\n/**\n * Hook for creating a draggable force simulation\n * Provides drag handlers that can be attached to node elements\n *\n * @param simulation - The d3 force simulation instance\n * @returns Drag behavior that can be applied to nodes\n *\n * @example\n * ```tsx\n * function DraggableNetworkGraph() {\n * const simulation = useRef<d3.Simulation<SimulationNode, SimulationLink>>();\n * const drag = useDrag(simulation.current);\n *\n * return (\n * <svg>\n * {nodes.map((node) => (\n * <circle\n * key={node.id}\n * {...drag}\n * cx={node.x}\n * cy={node.y}\n * r={10}\n * />\n * ))}\n * </svg>\n * );\n * }\n * ```\n */\nexport function useDrag(\n simulation: d3.Simulation<SimulationNode, any> | null | undefined\n) {\n const dragStarted = (event: any, node: SimulationNode) => {\n if (!simulation) return;\n if (!event.active) simulation.alphaTarget(0.3).restart();\n node.fx = node.x;\n node.fy = node.y;\n };\n\n const dragged = (event: any, node: SimulationNode) => {\n node.fx = event.x;\n node.fy = event.y;\n };\n\n const dragEnded = (event: any, node: SimulationNode) => {\n if (!simulation) return;\n if (!event.active) simulation.alphaTarget(0);\n node.fx = null;\n node.fy = null;\n };\n\n return {\n onDragStart: dragStarted,\n onDrag: dragged,\n onDragEnd: dragEnded,\n };\n}\n"]}
package/dist/index.d.ts CHANGED
@@ -34,7 +34,7 @@ interface CodeBlockProps {
34
34
  className?: string;
35
35
  }
36
36
  declare function CodeBlock({ children, language, showCopy, showHeader, className, }: CodeBlockProps): react_jsx_runtime.JSX.Element;
37
- declare function InlineCode({ children, className }: {
37
+ declare function InlineCode({ children, className, }: {
38
38
  children: React__default.ReactNode;
39
39
  className?: string;
40
40
  }): react_jsx_runtime.JSX.Element;
@@ -70,17 +70,17 @@ interface ScoreCardProps {
70
70
  }>;
71
71
  className?: string;
72
72
  }
73
- declare function ScoreCard({ score, title, breakdown, className }: ScoreCardProps): react_jsx_runtime.JSX.Element;
73
+ declare function ScoreCard({ score, title, breakdown, className, }: ScoreCardProps): react_jsx_runtime.JSX.Element;
74
74
 
75
75
  interface LoadingSpinnerProps {
76
76
  size?: 'sm' | 'md' | 'lg';
77
77
  className?: string;
78
78
  }
79
- declare function LoadingSpinner({ size, className }: LoadingSpinnerProps): react_jsx_runtime.JSX.Element;
79
+ declare function LoadingSpinner({ size, className, }: LoadingSpinnerProps): react_jsx_runtime.JSX.Element;
80
80
  interface LoadingOverlayProps {
81
81
  message?: string;
82
82
  }
83
- declare function LoadingOverlay({ message }: LoadingOverlayProps): react_jsx_runtime.JSX.Element;
83
+ declare function LoadingOverlay({ message, }: LoadingOverlayProps): react_jsx_runtime.JSX.Element;
84
84
 
85
85
  interface ErrorDisplayProps {
86
86
  title?: string;
@@ -98,7 +98,7 @@ interface EmptyStateProps {
98
98
  onClick: () => void;
99
99
  };
100
100
  }
101
- declare function EmptyState({ title, description, icon, action }: EmptyStateProps): react_jsx_runtime.JSX.Element;
101
+ declare function EmptyState({ title, description, icon, action, }: EmptyStateProps): react_jsx_runtime.JSX.Element;
102
102
 
103
103
  type Theme = 'dark' | 'light' | 'system';
104
104
  type EffectiveTheme = 'dark' | 'light';