@particle-academy/react-fancy 1.5.0 → 1.6.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/dist/index.cjs CHANGED
@@ -1323,13 +1323,6 @@ var avatarSize = {
1323
1323
  lg: "w-6 h-6",
1324
1324
  xl: "w-7 h-7"
1325
1325
  };
1326
- var alertIconSize = {
1327
- xs: "w-2 h-2",
1328
- sm: "w-2.5 h-2.5",
1329
- md: "w-3 h-3",
1330
- lg: "w-4 h-4",
1331
- xl: "w-4 h-4"
1332
- };
1333
1326
  var badgeSize = {
1334
1327
  xs: "text-[10px] px-1 min-w-[14px] h-3.5",
1335
1328
  sm: "text-[10px] px-1.5 min-w-[16px] h-4",
@@ -1441,7 +1434,7 @@ var Action = react.forwardRef(
1441
1434
  return /* @__PURE__ */ jsxRuntime.jsx(
1442
1435
  "span",
1443
1436
  {
1444
- className: cn("flex-shrink-0", iconColorCls),
1437
+ className: cn("inline-flex items-center flex-shrink-0", iconColorCls),
1445
1438
  children: /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: iconSlug, size: iconSizeMap[size] })
1446
1439
  },
1447
1440
  `icon-${trailing ? "t" : "l"}`
@@ -1495,17 +1488,8 @@ var Action = react.forwardRef(
1495
1488
  className: "relative inline-flex flex-shrink-0",
1496
1489
  "data-action-alert": true,
1497
1490
  children: [
1498
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: cn(alertIconSize[size], "text-red-500 dark:text-red-400 animate-pulse"), children: alertIconEl }),
1499
- /* @__PURE__ */ jsxRuntime.jsx(
1500
- "span",
1501
- {
1502
- className: cn(
1503
- alertIconSize[size],
1504
- "absolute inset-0 text-red-400 dark:text-red-300 animate-ping opacity-75"
1505
- ),
1506
- children: alertIconEl
1507
- }
1508
- )
1491
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-red-500 dark:text-red-400 animate-pulse", children: alertIconEl }),
1492
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "absolute inset-0 flex items-center justify-center text-red-400 dark:text-red-300 animate-ping opacity-75", children: alertIconEl })
1509
1493
  ]
1510
1494
  },
1511
1495
  "alert-icon"
@@ -1645,7 +1629,7 @@ function dirtyRingClasses(dirty) {
1645
1629
  function errorClasses(error) {
1646
1630
  return error ? "border-red-500 focus:ring-red-500" : "";
1647
1631
  }
1648
- var inputBaseClasses = "border border-zinc-300 bg-white text-zinc-900 placeholder:text-zinc-400 focus:outline-none focus:ring-2 focus:ring-blue-500/40 focus:border-blue-500 disabled:opacity-50 disabled:cursor-not-allowed dark:border-zinc-600 dark:bg-zinc-800 dark:text-zinc-100 dark:placeholder:text-zinc-500";
1632
+ var inputBaseClasses = "border border-zinc-300 bg-white text-zinc-900 placeholder:text-zinc-400 transition-[border-color,box-shadow] duration-150 focus:outline-none focus:ring-2 focus:ring-blue-500/40 focus:border-blue-500 disabled:opacity-50 disabled:cursor-not-allowed dark:border-zinc-700 dark:bg-[#1e1e24] dark:text-zinc-100 dark:placeholder:text-zinc-500 dark:focus:border-blue-400 dark:focus:ring-blue-400/20";
1649
1633
  function resolveOption(option) {
1650
1634
  if (typeof option === "string") {
1651
1635
  return { value: option, label: option };
@@ -1662,13 +1646,13 @@ function Field({
1662
1646
  children,
1663
1647
  className
1664
1648
  }) {
1665
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { "data-react-fancy-field": "", className: cn("flex flex-col gap-1.5", className), children: [
1649
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { "data-react-fancy-field": "", className: cn("flex flex-col gap-2", className), children: [
1666
1650
  label && /* @__PURE__ */ jsxRuntime.jsxs(
1667
1651
  "label",
1668
1652
  {
1669
1653
  htmlFor,
1670
1654
  className: cn(
1671
- "font-medium text-zinc-700 dark:text-zinc-300",
1655
+ "font-medium text-zinc-700 dark:text-zinc-100",
1672
1656
  labelSizeClasses[size]
1673
1657
  ),
1674
1658
  children: [
@@ -1696,7 +1680,7 @@ var insidePaddingRight = {
1696
1680
  lg: "pr-10",
1697
1681
  xl: "pr-11"
1698
1682
  };
1699
- var affixOutsideClasses = "inline-flex items-center border border-zinc-300 bg-zinc-50 px-3 text-sm text-zinc-500 dark:border-zinc-600 dark:bg-zinc-800 dark:text-zinc-400";
1683
+ var affixOutsideClasses = "inline-flex items-center border border-zinc-300 bg-zinc-50 px-3 text-sm text-zinc-500 dark:border-zinc-700 dark:bg-zinc-800 dark:text-zinc-400";
1700
1684
  function InputWrapper({
1701
1685
  children,
1702
1686
  prefix,
@@ -2116,7 +2100,7 @@ var Checkbox = react.forwardRef(
2116
2100
  onChange: (e) => setChecked(e.target.checked),
2117
2101
  className: cn(
2118
2102
  sizeClasses6,
2119
- "cursor-pointer rounded border border-zinc-300 bg-white text-blue-600 focus:ring-2 focus:ring-blue-500/40 focus:ring-offset-0 disabled:cursor-not-allowed disabled:opacity-50 dark:border-zinc-600 dark:bg-zinc-800",
2103
+ "cursor-pointer rounded border border-zinc-300 bg-white text-blue-600 transition-[border-color,box-shadow] duration-150 focus:ring-2 focus:ring-blue-500/40 focus:ring-offset-0 disabled:cursor-not-allowed disabled:opacity-50 dark:border-zinc-700 dark:bg-[#1e1e24]",
2120
2104
  dirtyRingClasses(dirty),
2121
2105
  error && "border-red-500"
2122
2106
  )
@@ -2128,7 +2112,7 @@ var Checkbox = react.forwardRef(
2128
2112
  {
2129
2113
  htmlFor: checkboxId,
2130
2114
  className: cn(
2131
- "cursor-pointer text-sm text-zinc-700 dark:text-zinc-300",
2115
+ "cursor-pointer text-sm text-zinc-700 dark:text-zinc-100",
2132
2116
  disabled && "cursor-not-allowed opacity-50"
2133
2117
  ),
2134
2118
  children: [
@@ -2203,7 +2187,7 @@ function CheckboxGroup({
2203
2187
  onChange: () => handleToggle(resolved.value),
2204
2188
  className: cn(
2205
2189
  sizeClasses6,
2206
- "cursor-pointer rounded border border-zinc-300 bg-white text-blue-600 focus:ring-2 focus:ring-blue-500/40 focus:ring-offset-0 disabled:cursor-not-allowed disabled:opacity-50 dark:border-zinc-600 dark:bg-zinc-800",
2190
+ "cursor-pointer rounded border border-zinc-300 bg-white text-blue-600 transition-[border-color,box-shadow] duration-150 focus:ring-2 focus:ring-blue-500/40 focus:ring-offset-0 disabled:cursor-not-allowed disabled:opacity-50 dark:border-zinc-700 dark:bg-[#1e1e24]",
2207
2191
  dirtyRingClasses(dirty),
2208
2192
  error && "border-red-500"
2209
2193
  )
@@ -2215,7 +2199,7 @@ function CheckboxGroup({
2215
2199
  {
2216
2200
  htmlFor: optionId,
2217
2201
  className: cn(
2218
- "cursor-pointer text-sm text-zinc-700 dark:text-zinc-300",
2202
+ "cursor-pointer text-sm text-zinc-700 dark:text-zinc-100",
2219
2203
  (disabled || resolved.disabled) && "cursor-not-allowed opacity-50"
2220
2204
  ),
2221
2205
  children: resolved.label
@@ -2300,7 +2284,7 @@ function RadioGroup({
2300
2284
  onChange: () => setValue(resolved.value),
2301
2285
  className: cn(
2302
2286
  sizeClasses6,
2303
- "cursor-pointer border border-zinc-300 bg-white text-blue-600 focus:ring-2 focus:ring-blue-500/40 focus:ring-offset-0 disabled:cursor-not-allowed disabled:opacity-50 dark:border-zinc-600 dark:bg-zinc-800",
2287
+ "cursor-pointer border border-zinc-300 bg-white text-blue-600 transition-[border-color,box-shadow] duration-150 focus:ring-2 focus:ring-blue-500/40 focus:ring-offset-0 disabled:cursor-not-allowed disabled:opacity-50 dark:border-zinc-700 dark:bg-[#1e1e24]",
2304
2288
  dirtyRingClasses(dirty),
2305
2289
  error && "border-red-500"
2306
2290
  )
@@ -2312,7 +2296,7 @@ function RadioGroup({
2312
2296
  {
2313
2297
  htmlFor: optionId,
2314
2298
  className: cn(
2315
- "cursor-pointer text-sm text-zinc-700 dark:text-zinc-300",
2299
+ "cursor-pointer text-sm text-zinc-700 dark:text-zinc-100",
2316
2300
  (disabled || resolved.disabled) && "cursor-not-allowed opacity-50"
2317
2301
  ),
2318
2302
  children: resolved.label
@@ -2419,7 +2403,7 @@ var Switch = react.forwardRef(
2419
2403
  className: cn(
2420
2404
  "relative inline-flex shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-500/40 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
2421
2405
  trackSizes,
2422
- checked ? trackColorMap[color] : "bg-zinc-200 dark:bg-zinc-700",
2406
+ checked ? trackColorMap[color] : "bg-zinc-200 dark:bg-zinc-600",
2423
2407
  dirtyRingClasses(dirty),
2424
2408
  error && "ring-2 ring-red-500/50"
2425
2409
  ),
@@ -2442,7 +2426,7 @@ var Switch = react.forwardRef(
2442
2426
  {
2443
2427
  htmlFor: switchId,
2444
2428
  className: cn(
2445
- "cursor-pointer text-sm text-zinc-700 dark:text-zinc-300",
2429
+ "cursor-pointer text-sm text-zinc-700 dark:text-zinc-100",
2446
2430
  disabled && "cursor-not-allowed opacity-50"
2447
2431
  ),
2448
2432
  children: [
@@ -2744,7 +2728,7 @@ function MultiSwitch({
2744
2728
  role: "radiogroup",
2745
2729
  id,
2746
2730
  className: cn(
2747
- "relative inline-flex rounded-lg border border-zinc-300 bg-zinc-100 dark:border-zinc-600 dark:bg-zinc-800",
2731
+ "relative inline-flex rounded-lg border border-zinc-300 bg-zinc-100 dark:border-zinc-700 dark:bg-zinc-800",
2748
2732
  dirty && "ring-2 ring-amber-400/50",
2749
2733
  error && "ring-2 ring-red-500/50",
2750
2734
  disabled && "opacity-50 cursor-not-allowed",
@@ -3438,7 +3422,7 @@ function EmojiSelect({
3438
3422
  "button",
3439
3423
  {
3440
3424
  type: "button",
3441
- className: "flex items-center gap-2 rounded-lg border border-zinc-300 px-3 py-2 text-sm dark:border-zinc-600",
3425
+ className: "flex items-center gap-2 rounded-lg border border-zinc-300 px-3 py-2 text-sm transition-[border-color,box-shadow] duration-150 dark:border-zinc-700 dark:bg-[#1e1e24]",
3442
3426
  onClick: () => setOpen(!open),
3443
3427
  children: selected ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xl", children: selected }) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-zinc-400", children: "Pick emoji" })
3444
3428
  }
@@ -3451,7 +3435,7 @@ function EmojiSelect({
3451
3435
  value: query,
3452
3436
  onChange: (e) => setQuery(e.target.value),
3453
3437
  placeholder,
3454
- className: "mb-2 w-full rounded-md border border-zinc-200 px-2 py-1 text-sm dark:border-zinc-600 dark:bg-zinc-700",
3438
+ className: "mb-2 w-full rounded-md border border-zinc-200 px-2 py-1 text-sm transition-[border-color,box-shadow] duration-150 dark:border-zinc-700 dark:bg-[#1e1e24] dark:text-zinc-100",
3455
3439
  autoFocus: true
3456
3440
  }
3457
3441
  ),
@@ -4511,42 +4495,66 @@ var Callout = react.forwardRef(
4511
4495
  );
4512
4496
  Callout.displayName = "Callout";
4513
4497
  var TimelineContext = react.createContext({
4514
- orientation: "vertical",
4515
- index: 0
4498
+ variant: "stacked",
4499
+ index: 0,
4500
+ total: 0,
4501
+ animated: true
4516
4502
  });
4517
4503
  function useTimeline() {
4518
4504
  return react.useContext(TimelineContext);
4519
4505
  }
4520
4506
  var dotColorClasses2 = {
4521
- blue: "bg-blue-500 dark:bg-blue-400",
4522
- green: "bg-green-500 dark:bg-green-400",
4523
- amber: "bg-amber-500 dark:bg-amber-400",
4524
- red: "bg-red-500 dark:bg-red-400",
4525
- zinc: "bg-zinc-400 dark:bg-zinc-500"
4526
- };
4527
- var iconColorClasses2 = {
4528
- blue: "text-blue-500 dark:text-blue-400",
4529
- green: "text-green-500 dark:text-green-400",
4530
- amber: "text-amber-500 dark:text-amber-400",
4531
- red: "text-red-500 dark:text-red-400",
4532
- zinc: "text-zinc-500 dark:text-zinc-400"
4507
+ red: "bg-red-500",
4508
+ orange: "bg-orange-500",
4509
+ amber: "bg-amber-500",
4510
+ yellow: "bg-yellow-500",
4511
+ lime: "bg-lime-500",
4512
+ green: "bg-green-500",
4513
+ emerald: "bg-emerald-500",
4514
+ teal: "bg-teal-500",
4515
+ cyan: "bg-cyan-500",
4516
+ sky: "bg-sky-500",
4517
+ blue: "bg-blue-500",
4518
+ indigo: "bg-indigo-500",
4519
+ violet: "bg-violet-500",
4520
+ purple: "bg-purple-500",
4521
+ fuchsia: "bg-fuchsia-500",
4522
+ pink: "bg-pink-500",
4523
+ rose: "bg-rose-500",
4524
+ zinc: "bg-zinc-300 dark:bg-zinc-600"
4533
4525
  };
4534
4526
  var ringColorClasses = {
4535
- blue: "ring-blue-500/30 dark:ring-blue-400/30",
4536
- green: "ring-green-500/30 dark:ring-green-400/30",
4537
- amber: "ring-amber-500/30 dark:ring-amber-400/30",
4538
- red: "ring-red-500/30 dark:ring-red-400/30",
4527
+ red: "ring-red-500/30",
4528
+ orange: "ring-orange-500/30",
4529
+ amber: "ring-amber-500/30",
4530
+ yellow: "ring-yellow-500/30",
4531
+ lime: "ring-lime-500/30",
4532
+ green: "ring-green-500/30",
4533
+ emerald: "ring-emerald-500/30",
4534
+ teal: "ring-teal-500/30",
4535
+ cyan: "ring-cyan-500/30",
4536
+ sky: "ring-sky-500/30",
4537
+ blue: "ring-blue-500/30",
4538
+ indigo: "ring-indigo-500/30",
4539
+ violet: "ring-violet-500/30",
4540
+ purple: "ring-purple-500/30",
4541
+ fuchsia: "ring-fuchsia-500/30",
4542
+ pink: "ring-pink-500/30",
4543
+ rose: "ring-rose-500/30",
4539
4544
  zinc: "ring-zinc-400/30 dark:ring-zinc-500/30"
4540
4545
  };
4541
- function Dot({ icon, color, active }) {
4546
+ function Dot({ icon, emoji, color, active }) {
4542
4547
  const c = color ?? "zinc";
4548
+ if (emoji) {
4549
+ return /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex h-8 w-8 shrink-0 items-center justify-center rounded-full border border-zinc-200 bg-zinc-100 dark:border-zinc-700 dark:bg-zinc-800", children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm", children: emoji }) });
4550
+ }
4543
4551
  if (icon) {
4544
4552
  return /* @__PURE__ */ jsxRuntime.jsx(
4545
4553
  "span",
4546
4554
  {
4547
4555
  className: cn(
4548
- "flex h-6 w-6 items-center justify-center rounded-full bg-white dark:bg-zinc-900",
4549
- iconColorClasses2[c],
4556
+ "flex h-8 w-8 shrink-0 items-center justify-center rounded-full text-white",
4557
+ dotColorClasses2[c],
4550
4558
  active && "ring-4",
4551
4559
  active && ringColorClasses[c]
4552
4560
  ),
@@ -4558,7 +4566,7 @@ function Dot({ icon, color, active }) {
4558
4566
  "span",
4559
4567
  {
4560
4568
  className: cn(
4561
- "h-3 w-3 rounded-full",
4569
+ "h-3 w-3 shrink-0 rounded-full",
4562
4570
  dotColorClasses2[c],
4563
4571
  active && "ring-4",
4564
4572
  active && ringColorClasses[c]
@@ -4566,32 +4574,83 @@ function Dot({ icon, color, active }) {
4566
4574
  }
4567
4575
  );
4568
4576
  }
4577
+ function useIntersectionReveal(animated) {
4578
+ const ref = react.useRef(null);
4579
+ const [visible, setVisible] = react.useState(!animated);
4580
+ react.useEffect(() => {
4581
+ if (!animated || !ref.current) return;
4582
+ const el = ref.current;
4583
+ const observer = new IntersectionObserver(
4584
+ ([entry]) => {
4585
+ if (entry.isIntersecting) {
4586
+ setVisible(true);
4587
+ observer.disconnect();
4588
+ }
4589
+ },
4590
+ { threshold: 0.2 }
4591
+ );
4592
+ observer.observe(el);
4593
+ return () => observer.disconnect();
4594
+ }, [animated]);
4595
+ return { ref, visible };
4596
+ }
4569
4597
  var TimelineItem = react.forwardRef(
4570
- ({
4571
- children,
4572
- icon,
4573
- color = "zinc",
4574
- active = false,
4575
- className
4576
- }, ref) => {
4577
- const { orientation, index } = useTimeline();
4578
- if (orientation === "horizontal") {
4579
- const isTop = index % 2 === 0;
4598
+ ({ children, icon, emoji, date, color = "zinc", active = false, className }, _ref) => {
4599
+ const { variant, index, total, animated } = useTimeline();
4600
+ const { ref, visible } = useIntersectionReveal(animated);
4601
+ const isLast = index === total - 1;
4602
+ const isLargeDot = !!icon || !!emoji;
4603
+ const isEven = index % 2 === 0;
4604
+ if (variant === "horizontal") {
4580
4605
  return /* @__PURE__ */ jsxRuntime.jsxs(
4581
4606
  "div",
4582
4607
  {
4583
4608
  ref,
4584
4609
  "data-react-fancy-timeline-item": "",
4585
- className: cn("relative flex flex-col items-center", className),
4586
- style: { minWidth: 120 },
4610
+ className: cn(
4611
+ "flex flex-col items-center",
4612
+ !isLast && "min-w-40",
4613
+ animated && "transition duration-500 ease-out",
4614
+ animated && (visible ? "translate-y-0 opacity-100" : "translate-y-4 opacity-0"),
4615
+ className
4616
+ ),
4587
4617
  children: [
4588
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: cn("flex min-h-[4rem] items-end pb-2", !isTop && "invisible"), children: isTop && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "min-w-0 text-center", children }) }),
4589
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative flex w-full items-center justify-center", children: [
4590
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-px flex-1 bg-zinc-200 dark:bg-zinc-700" }),
4591
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "relative z-10 flex shrink-0 items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx(Dot, { icon, color, active }) }),
4592
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-px flex-1 bg-zinc-200 dark:bg-zinc-700" })
4618
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex h-8 w-full items-center", children: [
4619
+ index > 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-px flex-1 bg-zinc-200 dark:bg-zinc-700" }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1" }),
4620
+ /* @__PURE__ */ jsxRuntime.jsx(Dot, { icon, emoji, color, active }),
4621
+ !isLast ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-px flex-1 bg-zinc-200 dark:bg-zinc-700" }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1" })
4593
4622
  ] }),
4594
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: cn("flex min-h-[4rem] items-start pt-2", isTop && "invisible"), children: !isTop && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "min-w-0 text-center", children }) })
4623
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-3 max-w-40 px-2 text-center", children: [
4624
+ date && /* @__PURE__ */ jsxRuntime.jsx("time", { className: "text-xs font-medium uppercase tracking-wide text-zinc-500 dark:text-zinc-400", children: date }),
4625
+ children
4626
+ ] })
4627
+ ]
4628
+ }
4629
+ );
4630
+ }
4631
+ if (variant === "alternating") {
4632
+ return /* @__PURE__ */ jsxRuntime.jsxs(
4633
+ "div",
4634
+ {
4635
+ ref,
4636
+ "data-react-fancy-timeline-item": "",
4637
+ className: cn(
4638
+ "relative flex gap-x-4 md:grid md:grid-cols-[1fr_1.5rem_1fr] md:gap-x-6",
4639
+ animated && "transition duration-500 ease-out",
4640
+ animated && (visible ? "translate-y-0 opacity-100" : "translate-y-4 opacity-0"),
4641
+ className
4642
+ ),
4643
+ children: [
4644
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "relative z-10 flex w-8 shrink-0 justify-center md:col-start-2 md:row-start-1 md:w-auto md:justify-center", children: !isLargeDot ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-1.5", children: /* @__PURE__ */ jsxRuntime.jsx(Dot, { icon, emoji, color, active }) }) : /* @__PURE__ */ jsxRuntime.jsx(Dot, { icon, emoji, color, active }) }),
4645
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn(
4646
+ "min-w-0 flex-1",
4647
+ !isLast && "pb-8",
4648
+ isLargeDot && "pt-1",
4649
+ isEven ? "md:col-start-1 md:row-start-1 md:text-right" : "md:col-start-3"
4650
+ ), children: [
4651
+ date && /* @__PURE__ */ jsxRuntime.jsx("time", { className: "text-xs font-medium uppercase tracking-wide text-zinc-500 dark:text-zinc-400", children: date }),
4652
+ children
4653
+ ] })
4595
4654
  ]
4596
4655
  }
4597
4656
  );
@@ -4601,11 +4660,18 @@ var TimelineItem = react.forwardRef(
4601
4660
  {
4602
4661
  ref,
4603
4662
  "data-react-fancy-timeline-item": "",
4604
- className: cn("relative flex gap-4 pb-8 last:pb-0", className),
4663
+ className: cn(
4664
+ "relative flex gap-x-4",
4665
+ animated && "transition duration-500 ease-out",
4666
+ animated && (visible ? "translate-y-0 opacity-100" : "translate-y-4 opacity-0"),
4667
+ className
4668
+ ),
4605
4669
  children: [
4606
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute left-[11px] top-6 bottom-0 w-px bg-zinc-200 last:hidden dark:bg-zinc-700" }),
4607
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "relative z-10 flex shrink-0 mt-1.5", children: /* @__PURE__ */ jsxRuntime.jsx(Dot, { icon, color, active }) }),
4608
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "min-w-0 flex-1 pt-0.5", children })
4670
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "relative z-10 flex w-8 shrink-0 justify-center", children: !isLargeDot ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-1.5", children: /* @__PURE__ */ jsxRuntime.jsx(Dot, { icon, emoji, color, active }) }) : /* @__PURE__ */ jsxRuntime.jsx(Dot, { icon, emoji, color, active }) }),
4671
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("min-w-0 flex-1", !isLast && "pb-8", isLargeDot && "pt-1"), children: [
4672
+ date && /* @__PURE__ */ jsxRuntime.jsx("time", { className: "text-xs font-medium uppercase tracking-wide text-zinc-500 dark:text-zinc-400", children: date }),
4673
+ children
4674
+ ] })
4609
4675
  ]
4610
4676
  }
4611
4677
  );
@@ -4613,8 +4679,8 @@ var TimelineItem = react.forwardRef(
4613
4679
  );
4614
4680
  TimelineItem.displayName = "TimelineItem";
4615
4681
  var TimelineBlock = react.forwardRef(
4616
- ({ heading, children, icon, color = "zinc", active = false, className }, ref) => {
4617
- return /* @__PURE__ */ jsxRuntime.jsx(TimelineItem, { icon, color, active, children: /* @__PURE__ */ jsxRuntime.jsxs(
4682
+ ({ heading, children, icon, emoji, color = "zinc", active = false, className }, ref) => {
4683
+ return /* @__PURE__ */ jsxRuntime.jsx(TimelineItem, { icon, emoji, color, active, children: /* @__PURE__ */ jsxRuntime.jsxs(
4618
4684
  "div",
4619
4685
  {
4620
4686
  ref,
@@ -4634,21 +4700,58 @@ var TimelineBlock = react.forwardRef(
4634
4700
  );
4635
4701
  TimelineBlock.displayName = "TimelineBlock";
4636
4702
  var TimelineRoot = react.forwardRef(
4637
- ({ children, orientation = "vertical", className }, ref) => {
4638
- const items = react.Children.toArray(children);
4639
- return /* @__PURE__ */ jsxRuntime.jsx(
4640
- "div",
4641
- {
4642
- ref,
4643
- "data-react-fancy-timeline": "",
4644
- "data-orientation": orientation,
4645
- className: cn(
4646
- orientation === "vertical" ? "flex flex-col" : "flex flex-row items-center overflow-x-auto",
4647
- className
4648
- ),
4649
- children: items.map((child, i) => /* @__PURE__ */ jsxRuntime.jsx(TimelineContext.Provider, { value: { orientation, index: i }, children: child }, i))
4650
- }
4651
- );
4703
+ ({
4704
+ children,
4705
+ variant: variantProp,
4706
+ orientation,
4707
+ events,
4708
+ heading,
4709
+ description,
4710
+ animated = true,
4711
+ className
4712
+ }, ref) => {
4713
+ const scrollRef = react.useRef(null);
4714
+ let variant = variantProp ?? "stacked";
4715
+ if (!variantProp && orientation) {
4716
+ variant = orientation === "horizontal" ? "horizontal" : "stacked";
4717
+ }
4718
+ const isHorizontal = variant === "horizontal";
4719
+ const isAlternating = variant === "alternating";
4720
+ const items = events ? events.map((e, i) => /* @__PURE__ */ jsxRuntime.jsxs(TimelineItem, { date: e.date, emoji: e.emoji, icon: e.icon, color: e.color, children: [
4721
+ e.title && /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "font-semibold text-zinc-900 dark:text-white", children: e.title }),
4722
+ e.description && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-1 text-sm leading-relaxed text-zinc-600 dark:text-zinc-400", children: e.description })
4723
+ ] }, i)) : react.Children.toArray(children);
4724
+ const handleWheel = react.useCallback((e) => {
4725
+ if (!scrollRef.current) return;
4726
+ e.preventDefault();
4727
+ scrollRef.current.scrollLeft += e.deltaY;
4728
+ }, []);
4729
+ const content = items.map((child, i) => /* @__PURE__ */ jsxRuntime.jsx(TimelineContext.Provider, { value: { variant, index: i, total: items.length, animated }, children: child }, i));
4730
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { ref, "data-react-fancy-timeline": "", "data-variant": variant, className, children: [
4731
+ (heading || description) && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mb-8", children: [
4732
+ heading && /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "text-xl font-semibold text-zinc-900 dark:text-white", children: heading }),
4733
+ description && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-1 text-sm text-zinc-500 dark:text-zinc-400", children: description })
4734
+ ] }),
4735
+ isHorizontal ? /* @__PURE__ */ jsxRuntime.jsx(
4736
+ "div",
4737
+ {
4738
+ ref: scrollRef,
4739
+ className: "overflow-x-auto pb-4 -mb-4",
4740
+ style: { scrollbarWidth: "thin", scrollbarColor: "rgb(161 161 170) transparent" },
4741
+ onWheel: handleWheel,
4742
+ children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex min-w-max items-start", children: content })
4743
+ }
4744
+ ) : (
4745
+ /* Vertical variants: continuous background line behind all events */
4746
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative", children: [
4747
+ items.length > 1 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: cn(
4748
+ "absolute top-0 bottom-0 w-px bg-zinc-200 dark:bg-zinc-700",
4749
+ isAlternating ? "left-4 md:left-1/2 md:-translate-x-px" : "left-4"
4750
+ ) }),
4751
+ content
4752
+ ] })
4753
+ )
4754
+ ] });
4652
4755
  }
4653
4756
  );
4654
4757
  TimelineRoot.displayName = "Timeline";
@@ -4769,7 +4872,7 @@ var Tooltip = react.forwardRef(
4769
4872
  "data-react-fancy-tooltip": "",
4770
4873
  role: "tooltip",
4771
4874
  className: cn(
4772
- "fancy-fade-in pointer-events-none fixed z-50 max-w-xs rounded-lg bg-zinc-900 px-3 py-1.5 text-sm text-white shadow-lg dark:bg-zinc-100 dark:text-zinc-900",
4875
+ "fancy-fade-in pointer-events-none fixed z-50 max-w-xs rounded-lg bg-zinc-900 px-3 py-1.5 text-sm text-white shadow-lg dark:bg-zinc-700 dark:text-zinc-100",
4773
4876
  className
4774
4877
  ),
4775
4878
  style: { left: position.x, top: position.y },
@@ -4779,7 +4882,7 @@ var Tooltip = react.forwardRef(
4779
4882
  "div",
4780
4883
  {
4781
4884
  className: cn(
4782
- "absolute h-2 w-2 rotate-45 bg-zinc-900 dark:bg-zinc-100",
4885
+ "absolute h-2 w-2 rotate-45 bg-zinc-900 dark:bg-zinc-700",
4783
4886
  position.placement.startsWith("top") && "bottom-[-4px] left-1/2 -translate-x-1/2",
4784
4887
  position.placement.startsWith("bottom") && "top-[-4px] left-1/2 -translate-x-1/2",
4785
4888
  position.placement.startsWith("left") && "right-[-4px] top-1/2 -translate-y-1/2",
@@ -6271,7 +6374,7 @@ var Autocomplete = react.forwardRef(
6271
6374
  role: "combobox",
6272
6375
  "aria-expanded": open,
6273
6376
  "aria-autocomplete": "list",
6274
- className: "w-full rounded-lg border border-zinc-200 bg-white px-3 py-2 text-sm text-zinc-900 placeholder:text-zinc-400 outline-none transition-colors focus:border-zinc-400 dark:border-zinc-700 dark:bg-zinc-900 dark:text-zinc-100 dark:placeholder:text-zinc-500 dark:focus:border-zinc-500"
6377
+ className: "w-full rounded-lg border border-zinc-200 bg-white px-3 py-2 text-sm text-zinc-900 placeholder:text-zinc-400 outline-none transition-[border-color,box-shadow] duration-150 focus:border-blue-500 focus:ring-2 focus:ring-blue-500/40 dark:border-zinc-700 dark:bg-[#1e1e24] dark:text-zinc-100 dark:placeholder:text-zinc-500 dark:focus:border-blue-400 dark:focus:ring-blue-400/20"
6275
6378
  }
6276
6379
  ),
6277
6380
  open && /* @__PURE__ */ jsxRuntime.jsx(Portal, { children: /* @__PURE__ */ jsxRuntime.jsx(
@@ -6365,7 +6468,7 @@ var Pillbox = react.forwardRef(
6365
6468
  "data-react-fancy-pillbox": "",
6366
6469
  ref,
6367
6470
  className: cn(
6368
- "flex flex-wrap items-center gap-1.5 rounded-lg border border-zinc-200 bg-white px-3 py-2 transition-colors focus-within:border-zinc-400 dark:border-zinc-700 dark:bg-zinc-900 dark:focus-within:border-zinc-500",
6471
+ "flex flex-wrap items-center gap-1.5 rounded-lg border border-zinc-200 bg-white px-3 py-2 transition-[border-color,box-shadow] duration-150 focus-within:border-blue-500 focus-within:ring-2 focus-within:ring-blue-500/40 dark:border-zinc-700 dark:bg-[#1e1e24] dark:text-zinc-100 dark:focus-within:border-blue-400 dark:focus-within:ring-blue-400/20",
6369
6472
  disabled && "cursor-not-allowed opacity-50",
6370
6473
  className
6371
6474
  ),
@@ -6496,7 +6599,7 @@ var OtpInput = react.forwardRef(
6496
6599
  onFocus: (e) => e.target.select(),
6497
6600
  disabled,
6498
6601
  autoFocus: autoFocus && i === 0,
6499
- className: "h-12 w-10 rounded-lg border border-zinc-200 bg-white text-center text-lg font-medium outline-none transition-colors focus:border-zinc-400 dark:border-zinc-700 dark:bg-zinc-900 dark:focus:border-zinc-500",
6602
+ className: "h-12 w-10 rounded-lg border border-zinc-200 bg-white text-center text-lg font-medium text-zinc-900 outline-none transition-[border-color,box-shadow] duration-150 focus:border-blue-500 focus:ring-2 focus:ring-blue-500/40 dark:border-zinc-700 dark:bg-[#1e1e24] dark:text-zinc-100 dark:focus:border-blue-400 dark:focus:ring-blue-400/20",
6500
6603
  "aria-label": `Digit ${i + 1}`
6501
6604
  },
6502
6605
  i
@@ -6545,7 +6648,7 @@ function FileUploadDropzone({
6545
6648
  onClick: () => !disabled && inputRef.current?.click(),
6546
6649
  className: cn(
6547
6650
  "flex cursor-pointer flex-col items-center justify-center rounded-xl border-2 border-dashed p-8 text-center transition-colors",
6548
- dragOver ? "border-blue-400 bg-blue-50 dark:border-blue-500 dark:bg-blue-950" : "border-zinc-300 hover:border-zinc-400 dark:border-zinc-600 dark:hover:border-zinc-500",
6651
+ dragOver ? "border-blue-400 bg-blue-50 dark:border-blue-500 dark:bg-blue-950" : "border-zinc-300 hover:border-zinc-400 dark:border-zinc-700 dark:hover:border-zinc-500",
6549
6652
  disabled && "cursor-not-allowed opacity-50",
6550
6653
  className
6551
6654
  ),
@@ -7905,6 +8008,7 @@ function usePanZoom({
7905
8008
  if (!container) return;
7906
8009
  function handleWheel(e) {
7907
8010
  if (!zoomableRef.current) return;
8011
+ if (!e.ctrlKey && !e.metaKey) return;
7908
8012
  e.preventDefault();
7909
8013
  const rect = container.getBoundingClientRect();
7910
8014
  const mouseX = e.clientX - rect.left;
@@ -9097,9 +9201,11 @@ function useCanvas() {
9097
9201
  if (!ctx) throw new Error("useCanvas must be used within a Canvas component");
9098
9202
  return ctx;
9099
9203
  }
9100
- function CanvasNode({ children, id, x, y, className, style }) {
9101
- const { registerNode, unregisterNode } = useCanvas();
9204
+ function CanvasNode({ children, id, x, y, draggable, onPositionChange, className, style }) {
9205
+ const { registerNode, unregisterNode, viewport } = useCanvas();
9102
9206
  const nodeRef = react.useRef(null);
9207
+ const isDragging = react.useRef(false);
9208
+ const dragStart = react.useRef({ mouseX: 0, mouseY: 0, nodeX: 0, nodeY: 0 });
9103
9209
  react.useEffect(() => {
9104
9210
  const el = nodeRef.current;
9105
9211
  if (!el) return;
@@ -9114,14 +9220,39 @@ function CanvasNode({ children, id, x, y, className, style }) {
9114
9220
  unregisterNode(id);
9115
9221
  };
9116
9222
  }, [id, x, y, registerNode, unregisterNode]);
9223
+ const handlePointerDown = react.useCallback(
9224
+ (e) => {
9225
+ if (!draggable || e.button !== 0) return;
9226
+ e.stopPropagation();
9227
+ isDragging.current = true;
9228
+ dragStart.current = { mouseX: e.clientX, mouseY: e.clientY, nodeX: x, nodeY: y };
9229
+ e.target.setPointerCapture(e.pointerId);
9230
+ },
9231
+ [draggable, x, y]
9232
+ );
9233
+ const handlePointerMove = react.useCallback(
9234
+ (e) => {
9235
+ if (!isDragging.current) return;
9236
+ const dx = (e.clientX - dragStart.current.mouseX) / viewport.zoom;
9237
+ const dy = (e.clientY - dragStart.current.mouseY) / viewport.zoom;
9238
+ onPositionChange?.(dragStart.current.nodeX + dx, dragStart.current.nodeY + dy);
9239
+ },
9240
+ [viewport.zoom, onPositionChange]
9241
+ );
9242
+ const handlePointerUp = react.useCallback(() => {
9243
+ isDragging.current = false;
9244
+ }, []);
9117
9245
  return /* @__PURE__ */ jsxRuntime.jsx(
9118
9246
  "div",
9119
9247
  {
9120
9248
  ref: nodeRef,
9121
9249
  "data-react-fancy-canvas-node": "",
9122
9250
  "data-node-id": id,
9123
- className: cn("absolute", className),
9251
+ className: cn("absolute", draggable && "cursor-grab active:cursor-grabbing", className),
9124
9252
  style: { left: x, top: y, ...style },
9253
+ onPointerDown: handlePointerDown,
9254
+ onPointerMove: handlePointerMove,
9255
+ onPointerUp: handlePointerUp,
9125
9256
  children
9126
9257
  }
9127
9258
  );
@@ -9158,10 +9289,18 @@ function getAnchorPoint(rect, anchor, otherRect) {
9158
9289
  }
9159
9290
  }
9160
9291
  function bezierPath(from, to) {
9161
- const dx = Math.abs(to.x - from.x) * 0.5;
9162
- const cp1x = from.x + (to.x > from.x ? dx : -dx);
9163
- const cp2x = to.x + (to.x > from.x ? -dx : dx);
9164
- return `M${from.x},${from.y} C${cp1x},${from.y} ${cp2x},${to.y} ${to.x},${to.y}`;
9292
+ const dx = Math.abs(to.x - from.x);
9293
+ const dy = Math.abs(to.y - from.y);
9294
+ if (dx > dy) {
9295
+ const offset2 = dx * 0.5;
9296
+ const cp1x = from.x + (to.x > from.x ? offset2 : -offset2);
9297
+ const cp2x = to.x + (to.x > from.x ? -offset2 : offset2);
9298
+ return `M${from.x},${from.y} C${cp1x},${from.y} ${cp2x},${to.y} ${to.x},${to.y}`;
9299
+ }
9300
+ const offset = Math.max(dy * 0.5, 30);
9301
+ const cp1y = from.y + (to.y > from.y ? offset : -offset);
9302
+ const cp2y = to.y + (to.y > from.y ? -offset : offset);
9303
+ return `M${from.x},${from.y} C${from.x},${cp1y} ${to.x},${cp2y} ${to.x},${to.y}`;
9165
9304
  }
9166
9305
  function stepPath(from, to) {
9167
9306
  const midX = (from.x + to.x) / 2;
@@ -9346,6 +9485,7 @@ function CanvasRoot({
9346
9485
  pannable = true,
9347
9486
  zoomable = true,
9348
9487
  showGrid = false,
9488
+ fitOnMount = false,
9349
9489
  className,
9350
9490
  style
9351
9491
  }) {
@@ -9365,15 +9505,41 @@ function CanvasRoot({
9365
9505
  () => ({ viewport, setViewport, registerNode, unregisterNode, nodeRects, registryVersion, containerRef }),
9366
9506
  [viewport, setViewport, registerNode, unregisterNode, nodeRects, registryVersion]
9367
9507
  );
9508
+ const hasFitted = react.useRef(false);
9509
+ react.useEffect(() => {
9510
+ if (!fitOnMount || hasFitted.current || nodeRects.size === 0) return;
9511
+ const container = containerRef.current;
9512
+ if (!container || container.clientWidth === 0) return;
9513
+ hasFitted.current = true;
9514
+ requestAnimationFrame(() => {
9515
+ let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
9516
+ nodeRects.forEach((r) => {
9517
+ minX = Math.min(minX, r.x);
9518
+ minY = Math.min(minY, r.y);
9519
+ maxX = Math.max(maxX, r.x + r.width);
9520
+ maxY = Math.max(maxY, r.y + r.height);
9521
+ });
9522
+ const padding = 40;
9523
+ const contentW = maxX - minX + padding * 2;
9524
+ const contentH = maxY - minY + padding * 2;
9525
+ const cw = container.clientWidth;
9526
+ const ch = container.clientHeight;
9527
+ const zoom = Math.min(cw / contentW, ch / contentH, 1.5);
9528
+ const panX = (cw - contentW * zoom) / 2 - minX * zoom + padding * zoom;
9529
+ const panY = (ch - contentH * zoom) / 2 - minY * zoom + padding * zoom;
9530
+ setViewport({ panX, panY, zoom });
9531
+ });
9532
+ }, [fitOnMount, nodeRects, registryVersion, setViewport]);
9368
9533
  const edges = [];
9369
9534
  const others = [];
9370
9535
  const overlays = [];
9371
9536
  react.Children.forEach(children, (child) => {
9372
9537
  const el = child;
9373
9538
  if (!el || !el.type) return;
9374
- if (el.type === CanvasEdge) {
9539
+ const elType = el.type;
9540
+ if (elType === CanvasEdge || elType?._isCanvasEdge) {
9375
9541
  edges.push(el);
9376
- } else if (el.type === CanvasMinimap || el.type === CanvasControls) {
9542
+ } else if (elType === CanvasMinimap || elType === CanvasControls) {
9377
9543
  overlays.push(el);
9378
9544
  } else {
9379
9545
  others.push(el);
@@ -9420,15 +9586,16 @@ function CanvasRoot({
9420
9586
  },
9421
9587
  children: [
9422
9588
  /* @__PURE__ */ jsxRuntime.jsxs("defs", { children: [
9423
- /* @__PURE__ */ jsxRuntime.jsx("marker", { id: "canvas-arrow", viewBox: "0 0 10 10", refX: "10", refY: "5", markerWidth: "8", markerHeight: "8", orient: "auto-start-reverse", children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M0,0 L10,5 L0,10 Z", fill: "currentColor", className: "text-zinc-400 dark:text-zinc-500" }) }),
9424
- /* @__PURE__ */ jsxRuntime.jsx("marker", { id: "canvas-circle", viewBox: "0 0 10 10", refX: "5", refY: "5", markerWidth: "8", markerHeight: "8", orient: "auto-start-reverse", children: /* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "5", cy: "5", r: "3.5", fill: "currentColor", className: "text-zinc-400 dark:text-zinc-500" }) }),
9425
- /* @__PURE__ */ jsxRuntime.jsx("marker", { id: "canvas-square", viewBox: "0 0 10 10", refX: "5", refY: "5", markerWidth: "8", markerHeight: "8", orient: "auto-start-reverse", children: /* @__PURE__ */ jsxRuntime.jsx("rect", { x: "1.5", y: "1.5", width: "7", height: "7", fill: "currentColor", className: "text-zinc-400 dark:text-zinc-500" }) }),
9426
- /* @__PURE__ */ jsxRuntime.jsxs("marker", { id: "canvas-crow-foot", viewBox: "0 0 12 12", refX: "12", refY: "6", markerWidth: "10", markerHeight: "10", orient: "auto-start-reverse", children: [
9427
- /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "0", y1: "0", x2: "12", y2: "6", stroke: "currentColor", strokeWidth: "1.5", className: "text-zinc-400 dark:text-zinc-500" }),
9428
- /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "0", y1: "12", x2: "12", y2: "6", stroke: "currentColor", strokeWidth: "1.5", className: "text-zinc-400 dark:text-zinc-500" }),
9429
- /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "0", y1: "6", x2: "12", y2: "6", stroke: "currentColor", strokeWidth: "1.5", className: "text-zinc-400 dark:text-zinc-500" })
9430
- ] }),
9431
- /* @__PURE__ */ jsxRuntime.jsx("marker", { id: "canvas-diamond", viewBox: "0 0 12 12", refX: "6", refY: "6", markerWidth: "10", markerHeight: "10", orient: "auto-start-reverse", children: /* @__PURE__ */ jsxRuntime.jsx("polygon", { points: "6,0 12,6 6,12 0,6", fill: "none", stroke: "currentColor", strokeWidth: "1.5", className: "text-zinc-400 dark:text-zinc-500" }) })
9589
+ /* @__PURE__ */ jsxRuntime.jsx("marker", { id: "canvas-arrow", viewBox: "0 0 10 10", refX: "10", refY: "5", markerWidth: "8", markerHeight: "8", orient: "auto", children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M0,0 L10,5 L0,10 Z", fill: "#71717a" }) }),
9590
+ /* @__PURE__ */ jsxRuntime.jsx("marker", { id: "canvas-circle", viewBox: "0 0 10 10", refX: "5", refY: "5", markerWidth: "8", markerHeight: "8", orient: "auto", children: /* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "5", cy: "5", r: "3.5", fill: "#71717a" }) }),
9591
+ /* @__PURE__ */ jsxRuntime.jsx("marker", { id: "canvas-diamond", viewBox: "0 0 12 12", refX: "6", refY: "6", markerWidth: "10", markerHeight: "10", orient: "auto", children: /* @__PURE__ */ jsxRuntime.jsx("polygon", { points: "6,0 12,6 6,12 0,6", fill: "none", stroke: "#71717a", strokeWidth: "1.5" }) }),
9592
+ /* @__PURE__ */ jsxRuntime.jsx("marker", { id: "canvas-one", viewBox: "0 0 2 16", refX: "1", refY: "8", markerWidth: "2", markerHeight: "14", orient: "auto", children: /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "1", y1: "0", x2: "1", y2: "16", stroke: "#71717a", strokeWidth: "2" }) }),
9593
+ /* @__PURE__ */ jsxRuntime.jsxs("marker", { id: "canvas-crow-foot", viewBox: "0 0 16 16", refX: "16", refY: "8", markerWidth: "14", markerHeight: "14", orient: "auto", children: [
9594
+ /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "16", y1: "8", x2: "0", y2: "0", stroke: "#71717a", strokeWidth: "2", strokeLinecap: "round" }),
9595
+ /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "16", y1: "8", x2: "0", y2: "8", stroke: "#71717a", strokeWidth: "2", strokeLinecap: "round" }),
9596
+ /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "16", y1: "8", x2: "0", y2: "16", stroke: "#71717a", strokeWidth: "2", strokeLinecap: "round" }),
9597
+ /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "16", y1: "0", x2: "16", y2: "16", stroke: "#71717a", strokeWidth: "2", strokeLinecap: "round" })
9598
+ ] })
9432
9599
  ] }),
9433
9600
  edges
9434
9601
  ]
@@ -9486,13 +9653,16 @@ function DiagramField({
9486
9653
  DiagramField.displayName = "DiagramField";
9487
9654
  function DiagramEntity({
9488
9655
  children,
9489
- id,
9656
+ id: idProp,
9490
9657
  name,
9491
9658
  x = 0,
9492
9659
  y = 0,
9493
9660
  color = "bg-blue-600 dark:bg-blue-500",
9661
+ draggable,
9662
+ onPositionChange,
9494
9663
  className
9495
9664
  }) {
9665
+ const id = idProp ?? name;
9496
9666
  const fields = [];
9497
9667
  const other = [];
9498
9668
  react.Children.forEach(children, (child) => {
@@ -9504,7 +9674,7 @@ function DiagramEntity({
9504
9674
  other.push(el);
9505
9675
  }
9506
9676
  });
9507
- return /* @__PURE__ */ jsxRuntime.jsx(Canvas.Node, { id, x, y, children: /* @__PURE__ */ jsxRuntime.jsxs(
9677
+ return /* @__PURE__ */ jsxRuntime.jsx(Canvas.Node, { id, x, y, draggable, onPositionChange, children: /* @__PURE__ */ jsxRuntime.jsxs(
9508
9678
  "div",
9509
9679
  {
9510
9680
  "data-react-fancy-diagram-entity": "",
@@ -9531,37 +9701,193 @@ function DiagramEntity({
9531
9701
  ) });
9532
9702
  }
9533
9703
  DiagramEntity.displayName = "DiagramEntity";
9534
- function getMarkers(type) {
9535
- switch (type) {
9536
- case "one-to-one":
9537
- return { markerStart: "canvas-arrow", markerEnd: "canvas-arrow" };
9538
- case "one-to-many":
9539
- return { markerStart: "canvas-arrow", markerEnd: "canvas-crow-foot" };
9540
- case "many-to-many":
9541
- return { markerStart: "canvas-crow-foot", markerEnd: "canvas-crow-foot" };
9704
+ var HEADER_HEIGHT = 36;
9705
+ var FIELD_HEIGHT = 28;
9706
+ var SYMBOL_SIZE = 12;
9707
+ function oneSymbol(pt, direction) {
9708
+ const s = SYMBOL_SIZE * 0.6;
9709
+ switch (direction) {
9710
+ case "left":
9711
+ case "right":
9712
+ return `M${pt.x},${pt.y - s} L${pt.x},${pt.y + s}`;
9713
+ case "up":
9714
+ case "down":
9715
+ return `M${pt.x - s},${pt.y} L${pt.x + s},${pt.y}`;
9542
9716
  }
9543
9717
  }
9718
+ function crowFootSymbol(pt, direction) {
9719
+ const s = SYMBOL_SIZE;
9720
+ const spread = s * 0.8;
9721
+ let tip;
9722
+ switch (direction) {
9723
+ case "right":
9724
+ tip = { x: pt.x - s, y: pt.y };
9725
+ return [
9726
+ `M${pt.x},${pt.y - spread} L${tip.x},${tip.y}`,
9727
+ `M${pt.x},${pt.y} L${tip.x},${tip.y}`,
9728
+ `M${pt.x},${pt.y + spread} L${tip.x},${tip.y}`,
9729
+ // bar at entity edge
9730
+ `M${pt.x},${pt.y - spread} L${pt.x},${pt.y + spread}`
9731
+ ].join(" ");
9732
+ case "left":
9733
+ tip = { x: pt.x + s, y: pt.y };
9734
+ return [
9735
+ `M${pt.x},${pt.y - spread} L${tip.x},${tip.y}`,
9736
+ `M${pt.x},${pt.y} L${tip.x},${tip.y}`,
9737
+ `M${pt.x},${pt.y + spread} L${tip.x},${tip.y}`,
9738
+ `M${pt.x},${pt.y - spread} L${pt.x},${pt.y + spread}`
9739
+ ].join(" ");
9740
+ case "down":
9741
+ tip = { x: pt.x, y: pt.y - s };
9742
+ return [
9743
+ `M${pt.x - spread},${pt.y} L${tip.x},${tip.y}`,
9744
+ `M${pt.x},${pt.y} L${tip.x},${tip.y}`,
9745
+ `M${pt.x + spread},${pt.y} L${tip.x},${tip.y}`,
9746
+ `M${pt.x - spread},${pt.y} L${pt.x + spread},${pt.y}`
9747
+ ].join(" ");
9748
+ case "up":
9749
+ tip = { x: pt.x, y: pt.y + s };
9750
+ return [
9751
+ `M${pt.x - spread},${pt.y} L${tip.x},${tip.y}`,
9752
+ `M${pt.x},${pt.y} L${tip.x},${tip.y}`,
9753
+ `M${pt.x + spread},${pt.y} L${tip.x},${tip.y}`,
9754
+ `M${pt.x - spread},${pt.y} L${pt.x + spread},${pt.y}`
9755
+ ].join(" ");
9756
+ }
9757
+ }
9758
+ function getSymbolPath(type, end, pt, direction) {
9759
+ const side = end === "start" ? type.split("-to-")[0] : type.split("-to-")[1];
9760
+ if (side === "one") return oneSymbol(pt, direction);
9761
+ if (side === "many") return crowFootSymbol(pt, direction);
9762
+ return null;
9763
+ }
9544
9764
  function DiagramRelation({
9545
9765
  from,
9546
9766
  to,
9767
+ fromField: fromFieldProp,
9768
+ toField: toFieldProp,
9547
9769
  type,
9548
- label,
9549
- className
9770
+ label
9550
9771
  }) {
9551
- const markers = getMarkers(type);
9552
- return /* @__PURE__ */ jsxRuntime.jsx(
9553
- Canvas.Edge,
9554
- {
9555
- from,
9556
- to,
9557
- curve: "bezier",
9558
- markerStart: markers.markerStart,
9559
- markerEnd: markers.markerEnd,
9560
- label,
9561
- className: cn("text-zinc-300 dark:text-zinc-600", className)
9772
+ const { nodeRects, registryVersion } = useCanvas();
9773
+ const { schema } = useDiagram();
9774
+ const result = react.useMemo(() => {
9775
+ const fromRect = nodeRects.get(from);
9776
+ const toRect = nodeRects.get(to);
9777
+ if (!fromRect || !toRect) return null;
9778
+ const fromEntity = schema.entities.find((e) => (e.id ?? e.name) === from);
9779
+ const toEntity = schema.entities.find((e) => (e.id ?? e.name) === to);
9780
+ let fromFieldIdx = -1;
9781
+ let toFieldIdx = -1;
9782
+ if (fromFieldProp && fromEntity?.fields) {
9783
+ fromFieldIdx = fromEntity.fields.findIndex((f) => f.name === fromFieldProp);
9784
+ } else if (fromEntity?.fields) {
9785
+ fromFieldIdx = fromEntity.fields.findIndex((f) => f.primary);
9562
9786
  }
9563
- );
9787
+ if (toFieldProp && toEntity?.fields) {
9788
+ toFieldIdx = toEntity.fields.findIndex((f) => f.name === toFieldProp);
9789
+ } else if (toEntity?.fields) {
9790
+ const fromName = (fromEntity?.name ?? from).toLowerCase();
9791
+ toFieldIdx = toEntity.fields.findIndex(
9792
+ (f) => f.foreign && (f.name === `${fromName}_id` || f.name === `${fromName}Id`)
9793
+ );
9794
+ if (toFieldIdx === -1) {
9795
+ toFieldIdx = toEntity.fields.findIndex((f) => f.foreign);
9796
+ }
9797
+ }
9798
+ const fromFieldY = fromFieldIdx >= 0 ? HEADER_HEIGHT + fromFieldIdx * FIELD_HEIGHT + FIELD_HEIGHT / 2 : fromRect.height / 2;
9799
+ const toFieldY = toFieldIdx >= 0 ? HEADER_HEIGHT + toFieldIdx * FIELD_HEIGHT + FIELD_HEIGHT / 2 : toRect.height / 2;
9800
+ const fromCx = fromRect.x + fromRect.width / 2;
9801
+ const toCx = toRect.x + toRect.width / 2;
9802
+ const fromCy = fromRect.y + fromRect.height / 2;
9803
+ const toCy = toRect.y + toRect.height / 2;
9804
+ const dx = Math.abs(fromCx - toCx);
9805
+ const dy = Math.abs(fromCy - toCy);
9806
+ let fromPt, toPt;
9807
+ let fromDir;
9808
+ let toDir;
9809
+ if (dx > dy * 0.5) {
9810
+ if (fromCx < toCx) {
9811
+ fromPt = { x: fromRect.x + fromRect.width, y: fromRect.y + fromFieldY };
9812
+ toPt = { x: toRect.x, y: toRect.y + toFieldY };
9813
+ fromDir = "right";
9814
+ toDir = "left";
9815
+ } else {
9816
+ fromPt = { x: fromRect.x, y: fromRect.y + fromFieldY };
9817
+ toPt = { x: toRect.x + toRect.width, y: toRect.y + toFieldY };
9818
+ fromDir = "left";
9819
+ toDir = "right";
9820
+ }
9821
+ } else {
9822
+ if (fromCy < toCy) {
9823
+ fromPt = { x: fromRect.x + fromRect.width / 2, y: fromRect.y + fromRect.height };
9824
+ toPt = { x: toRect.x + toRect.width / 2, y: toRect.y };
9825
+ fromDir = "down";
9826
+ toDir = "up";
9827
+ } else {
9828
+ fromPt = { x: fromRect.x + fromRect.width / 2, y: fromRect.y };
9829
+ toPt = { x: toRect.x + toRect.width / 2, y: toRect.y + toRect.height };
9830
+ fromDir = "up";
9831
+ toDir = "down";
9832
+ }
9833
+ }
9834
+ const offsetFrom = { ...fromPt };
9835
+ const offsetTo = { ...toPt };
9836
+ switch (fromDir) {
9837
+ case "right":
9838
+ offsetFrom.x += SYMBOL_SIZE;
9839
+ break;
9840
+ case "left":
9841
+ offsetFrom.x -= SYMBOL_SIZE;
9842
+ break;
9843
+ case "down":
9844
+ offsetFrom.y += SYMBOL_SIZE;
9845
+ break;
9846
+ case "up":
9847
+ offsetFrom.y -= SYMBOL_SIZE;
9848
+ break;
9849
+ }
9850
+ switch (toDir) {
9851
+ case "right":
9852
+ offsetTo.x += SYMBOL_SIZE;
9853
+ break;
9854
+ case "left":
9855
+ offsetTo.x -= SYMBOL_SIZE;
9856
+ break;
9857
+ case "down":
9858
+ offsetTo.y += SYMBOL_SIZE;
9859
+ break;
9860
+ case "up":
9861
+ offsetTo.y -= SYMBOL_SIZE;
9862
+ break;
9863
+ }
9864
+ const adx = Math.abs(offsetTo.x - offsetFrom.x);
9865
+ const ady = Math.abs(offsetTo.y - offsetFrom.y);
9866
+ let linePath;
9867
+ if (adx > ady) {
9868
+ const off = adx * 0.4;
9869
+ const cp1x = offsetFrom.x + (offsetTo.x > offsetFrom.x ? off : -off);
9870
+ const cp2x = offsetTo.x + (offsetTo.x > offsetFrom.x ? -off : off);
9871
+ linePath = `M${offsetFrom.x},${offsetFrom.y} C${cp1x},${offsetFrom.y} ${cp2x},${offsetTo.y} ${offsetTo.x},${offsetTo.y}`;
9872
+ } else {
9873
+ const off = Math.max(ady * 0.4, 20);
9874
+ const cp1y = offsetFrom.y + (offsetTo.y > offsetFrom.y ? off : -off);
9875
+ const cp2y = offsetTo.y + (offsetTo.y > offsetFrom.y ? -off : off);
9876
+ linePath = `M${offsetFrom.x},${offsetFrom.y} C${offsetFrom.x},${cp1y} ${offsetTo.x},${cp2y} ${offsetTo.x},${offsetTo.y}`;
9877
+ }
9878
+ const startSymbol = getSymbolPath(type, "start", fromPt, fromDir);
9879
+ const endSymbol = getSymbolPath(type, "end", toPt, toDir);
9880
+ return { linePath, startSymbol, endSymbol, midX: (offsetFrom.x + offsetTo.x) / 2, midY: (offsetFrom.y + offsetTo.y) / 2 };
9881
+ }, [from, to, fromFieldProp, toFieldProp, type, schema, nodeRects, registryVersion]);
9882
+ if (!result) return null;
9883
+ return /* @__PURE__ */ jsxRuntime.jsxs("g", { "data-react-fancy-diagram-relation": "", children: [
9884
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: result.linePath, fill: "none", stroke: "#71717a", strokeWidth: 2 }),
9885
+ result.startSymbol && /* @__PURE__ */ jsxRuntime.jsx("path", { d: result.startSymbol, fill: "none", stroke: "#71717a", strokeWidth: 2 }),
9886
+ result.endSymbol && /* @__PURE__ */ jsxRuntime.jsx("path", { d: result.endSymbol, fill: "none", stroke: "#71717a", strokeWidth: 2 }),
9887
+ label && /* @__PURE__ */ jsxRuntime.jsx("foreignObject", { x: result.midX - 40, y: result.midY - 12, width: 80, height: 24, children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-center text-xs text-zinc-500", children: label }) })
9888
+ ] });
9564
9889
  }
9890
+ DiagramRelation._isCanvasEdge = true;
9565
9891
  DiagramRelation.displayName = "DiagramRelation";
9566
9892
  var FORMAT_LABELS = {
9567
9893
  erd: "ERD",
@@ -9672,12 +9998,12 @@ DiagramToolbar.displayName = "DiagramToolbar";
9672
9998
 
9673
9999
  // src/components/Diagram/diagram.layout.ts
9674
10000
  var ENTITY_WIDTH = 220;
9675
- var HEADER_HEIGHT = 40;
9676
- var FIELD_HEIGHT = 28;
10001
+ var HEADER_HEIGHT2 = 40;
10002
+ var FIELD_HEIGHT2 = 28;
9677
10003
  var HORIZONTAL_GAP = 80;
9678
10004
  var VERTICAL_GAP = 60;
9679
10005
  function getEntityHeight(fieldCount) {
9680
- return HEADER_HEIGHT + Math.max(fieldCount, 1) * FIELD_HEIGHT;
10006
+ return HEADER_HEIGHT2 + Math.max(fieldCount, 1) * FIELD_HEIGHT2;
9681
10007
  }
9682
10008
  function computeDiagramLayout(schema) {
9683
10009
  const positions = /* @__PURE__ */ new Map();
@@ -9780,58 +10106,103 @@ function DiagramRoot({
9780
10106
  }) {
9781
10107
  const downloadableRef = react.useRef(downloadable);
9782
10108
  const importableRef = react.useRef(importable);
9783
- const resolvedSchema = react.useMemo(() => {
10109
+ const normalizedSchema = react.useMemo(() => {
9784
10110
  if (!schema) return { entities: [], relations: [] };
9785
- const layout = computeDiagramLayout(schema);
9786
- const entities = schema.entities.map((entity) => {
9787
- if (entity.x !== void 0 && entity.y !== void 0) return entity;
9788
- const pos = layout.get(entity.id);
9789
- return pos ? { ...entity, x: pos.x, y: pos.y } : entity;
9790
- });
9791
- return { entities, relations: schema.relations };
10111
+ const entities = schema.entities.map((e) => ({
10112
+ ...e,
10113
+ id: e.id ?? e.name
10114
+ }));
10115
+ const relations = schema.relations.map((r, i) => ({
10116
+ ...r,
10117
+ id: r.id ?? `rel-${i}`
10118
+ }));
10119
+ return { entities, relations };
9792
10120
  }, [schema]);
10121
+ const initialPositions = react.useMemo(() => {
10122
+ if (normalizedSchema.entities.length === 0) return /* @__PURE__ */ new Map();
10123
+ const layout = computeDiagramLayout(normalizedSchema);
10124
+ const positions = /* @__PURE__ */ new Map();
10125
+ for (const entity of normalizedSchema.entities) {
10126
+ if (entity.x !== void 0 && entity.y !== void 0) {
10127
+ positions.set(entity.id, { x: entity.x, y: entity.y });
10128
+ } else {
10129
+ const pos = layout.get(entity.id);
10130
+ positions.set(entity.id, pos ?? { x: 0, y: 0 });
10131
+ }
10132
+ }
10133
+ return positions;
10134
+ }, [normalizedSchema]);
10135
+ const computedDefaultViewport = react.useMemo(() => {
10136
+ if (defaultViewport) return defaultViewport;
10137
+ if (initialPositions.size === 0) return { panX: 0, panY: 0, zoom: 1 };
10138
+ let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
10139
+ initialPositions.forEach((pos) => {
10140
+ minX = Math.min(minX, pos.x);
10141
+ minY = Math.min(minY, pos.y);
10142
+ maxX = Math.max(maxX, pos.x + 220);
10143
+ maxY = Math.max(maxY, pos.y + 200);
10144
+ });
10145
+ const padding = 40;
10146
+ const panX = -minX + padding;
10147
+ const panY = -minY + padding;
10148
+ return { panX, panY, zoom: 1 };
10149
+ }, [defaultViewport, initialPositions]);
10150
+ const [entityPositions, setEntityPositions] = react.useState(initialPositions);
10151
+ const handleEntityMove = react.useCallback((entityId, x, y) => {
10152
+ setEntityPositions((prev) => {
10153
+ const next = new Map(prev);
10154
+ next.set(entityId, { x, y });
10155
+ return next;
10156
+ });
10157
+ }, []);
9793
10158
  const ctx = react.useMemo(
9794
10159
  () => ({
9795
10160
  diagramType: type,
9796
- schema: resolvedSchema,
10161
+ schema: normalizedSchema,
9797
10162
  downloadableRef,
9798
10163
  importableRef,
9799
10164
  exportFormats,
9800
10165
  onImport
9801
10166
  }),
9802
- [type, resolvedSchema, exportFormats, onImport]
10167
+ [type, normalizedSchema, exportFormats, onImport]
9803
10168
  );
9804
10169
  return /* @__PURE__ */ jsxRuntime.jsx(DiagramContext.Provider, { value: ctx, children: /* @__PURE__ */ jsxRuntime.jsx("div", { "data-react-fancy-diagram": "", className: "relative h-full w-full", children: /* @__PURE__ */ jsxRuntime.jsxs(
9805
10170
  Canvas,
9806
10171
  {
9807
10172
  viewport,
9808
- defaultViewport,
10173
+ defaultViewport: computedDefaultViewport,
9809
10174
  onViewportChange,
9810
10175
  showGrid: true,
10176
+ fitOnMount: true,
9811
10177
  className: cn("h-full w-full", className),
9812
10178
  children: [
9813
- resolvedSchema.entities.map((entity) => /* @__PURE__ */ jsxRuntime.jsx(
9814
- DiagramEntity,
9815
- {
9816
- id: entity.id,
9817
- name: entity.name,
9818
- x: entity.x ?? 0,
9819
- y: entity.y ?? 0,
9820
- children: entity.fields?.map((field) => /* @__PURE__ */ jsxRuntime.jsx(
9821
- DiagramField,
9822
- {
9823
- name: field.name,
9824
- type: field.type,
9825
- primary: field.primary,
9826
- foreign: field.foreign,
9827
- nullable: field.nullable
9828
- },
9829
- field.name
9830
- ))
9831
- },
9832
- entity.id
9833
- )),
9834
- resolvedSchema.relations.map((rel) => /* @__PURE__ */ jsxRuntime.jsx(
10179
+ normalizedSchema.entities.map((entity) => {
10180
+ const pos = entityPositions.get(entity.id) ?? { x: 0, y: 0 };
10181
+ return /* @__PURE__ */ jsxRuntime.jsx(
10182
+ DiagramEntity,
10183
+ {
10184
+ id: entity.id,
10185
+ name: entity.name,
10186
+ x: pos.x,
10187
+ y: pos.y,
10188
+ draggable: true,
10189
+ onPositionChange: (nx, ny) => handleEntityMove(entity.id, nx, ny),
10190
+ children: entity.fields?.map((field) => /* @__PURE__ */ jsxRuntime.jsx(
10191
+ DiagramField,
10192
+ {
10193
+ name: field.name,
10194
+ type: field.type,
10195
+ primary: field.primary,
10196
+ foreign: field.foreign,
10197
+ nullable: field.nullable
10198
+ },
10199
+ field.name
10200
+ ))
10201
+ },
10202
+ entity.id
10203
+ );
10204
+ }),
10205
+ normalizedSchema.relations.map((rel) => /* @__PURE__ */ jsxRuntime.jsx(
9835
10206
  DiagramRelation,
9836
10207
  {
9837
10208
  from: rel.from,