@particle-academy/react-fancy 1.5.1 → 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { forwardRef, useId, useRef, useEffect, createContext, Children, useState, isValidElement, useCallback, useMemo, cloneElement, useContext, useLayoutEffect, Fragment as Fragment$1 } from 'react';
1
+ import { forwardRef, useId, useRef, useEffect, useState, useCallback, createContext, Children, isValidElement, useMemo, cloneElement, useLayoutEffect, useContext, Fragment as Fragment$1 } from 'react';
2
2
  import { clsx } from 'clsx';
3
3
  import { twMerge } from 'tailwind-merge';
4
4
  import * as LucideIcons from 'lucide-react';
@@ -1013,13 +1013,6 @@ var avatarSize = {
1013
1013
  lg: "w-6 h-6",
1014
1014
  xl: "w-7 h-7"
1015
1015
  };
1016
- var alertIconSize = {
1017
- xs: "w-2 h-2",
1018
- sm: "w-2.5 h-2.5",
1019
- md: "w-3 h-3",
1020
- lg: "w-4 h-4",
1021
- xl: "w-4 h-4"
1022
- };
1023
1016
  var badgeSize = {
1024
1017
  xs: "text-[10px] px-1 min-w-[14px] h-3.5",
1025
1018
  sm: "text-[10px] px-1.5 min-w-[16px] h-4",
@@ -1131,7 +1124,7 @@ var Action = forwardRef(
1131
1124
  return /* @__PURE__ */ jsx(
1132
1125
  "span",
1133
1126
  {
1134
- className: cn("flex-shrink-0", iconColorCls),
1127
+ className: cn("inline-flex items-center flex-shrink-0", iconColorCls),
1135
1128
  children: /* @__PURE__ */ jsx(Icon, { name: iconSlug, size: iconSizeMap[size] })
1136
1129
  },
1137
1130
  `icon-${trailing ? "t" : "l"}`
@@ -1185,17 +1178,8 @@ var Action = forwardRef(
1185
1178
  className: "relative inline-flex flex-shrink-0",
1186
1179
  "data-action-alert": true,
1187
1180
  children: [
1188
- /* @__PURE__ */ jsx("span", { className: cn(alertIconSize[size], "text-red-500 dark:text-red-400 animate-pulse"), children: alertIconEl }),
1189
- /* @__PURE__ */ jsx(
1190
- "span",
1191
- {
1192
- className: cn(
1193
- alertIconSize[size],
1194
- "absolute inset-0 text-red-400 dark:text-red-300 animate-ping opacity-75"
1195
- ),
1196
- children: alertIconEl
1197
- }
1198
- )
1181
+ /* @__PURE__ */ jsx("span", { className: "text-red-500 dark:text-red-400 animate-pulse", children: alertIconEl }),
1182
+ /* @__PURE__ */ jsx("span", { className: "absolute inset-0 flex items-center justify-center text-red-400 dark:text-red-300 animate-ping opacity-75", children: alertIconEl })
1199
1183
  ]
1200
1184
  },
1201
1185
  "alert-icon"
@@ -1335,7 +1319,7 @@ function dirtyRingClasses(dirty) {
1335
1319
  function errorClasses(error) {
1336
1320
  return error ? "border-red-500 focus:ring-red-500" : "";
1337
1321
  }
1338
- 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";
1322
+ 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";
1339
1323
  function resolveOption(option) {
1340
1324
  if (typeof option === "string") {
1341
1325
  return { value: option, label: option };
@@ -1352,13 +1336,13 @@ function Field({
1352
1336
  children,
1353
1337
  className
1354
1338
  }) {
1355
- return /* @__PURE__ */ jsxs("div", { "data-react-fancy-field": "", className: cn("flex flex-col gap-1.5", className), children: [
1339
+ return /* @__PURE__ */ jsxs("div", { "data-react-fancy-field": "", className: cn("flex flex-col gap-2", className), children: [
1356
1340
  label && /* @__PURE__ */ jsxs(
1357
1341
  "label",
1358
1342
  {
1359
1343
  htmlFor,
1360
1344
  className: cn(
1361
- "font-medium text-zinc-700 dark:text-zinc-300",
1345
+ "font-medium text-zinc-700 dark:text-zinc-100",
1362
1346
  labelSizeClasses[size]
1363
1347
  ),
1364
1348
  children: [
@@ -1386,7 +1370,7 @@ var insidePaddingRight = {
1386
1370
  lg: "pr-10",
1387
1371
  xl: "pr-11"
1388
1372
  };
1389
- 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";
1373
+ 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";
1390
1374
  function InputWrapper({
1391
1375
  children,
1392
1376
  prefix,
@@ -1630,10 +1614,157 @@ var Textarea = forwardRef(
1630
1614
  }
1631
1615
  );
1632
1616
  Textarea.displayName = "Textarea";
1617
+ function Portal({ children, container }) {
1618
+ if (typeof document === "undefined") return null;
1619
+ const target = container ?? document.body;
1620
+ return createPortal(
1621
+ /* @__PURE__ */ jsx(PortalDarkWrapper, { children }),
1622
+ target
1623
+ );
1624
+ }
1625
+ function PortalDarkWrapper({ children }) {
1626
+ const ref = useRef(null);
1627
+ useEffect(() => {
1628
+ const root = document.documentElement;
1629
+ const wrapper = ref.current;
1630
+ if (!wrapper) return;
1631
+ const sync = () => {
1632
+ const isDark = root.classList.contains("dark") || root.getAttribute("data-theme") === "dark";
1633
+ wrapper.classList.toggle("dark", isDark);
1634
+ };
1635
+ sync();
1636
+ const observer = new MutationObserver(sync);
1637
+ observer.observe(root, {
1638
+ attributes: true,
1639
+ attributeFilter: ["class", "data-theme"]
1640
+ });
1641
+ return () => observer.disconnect();
1642
+ }, []);
1643
+ return /* @__PURE__ */ jsx("div", { ref, "data-react-fancy-portal": "", style: { display: "contents" }, children });
1644
+ }
1645
+ function getPosition(anchor, floating, placement, offset) {
1646
+ let x = 0;
1647
+ let y = 0;
1648
+ const base = placement.split("-")[0];
1649
+ const align = placement.split("-")[1];
1650
+ switch (base) {
1651
+ case "top":
1652
+ x = anchor.left + anchor.width / 2 - floating.width / 2;
1653
+ y = anchor.top - floating.height - offset;
1654
+ break;
1655
+ case "bottom":
1656
+ x = anchor.left + anchor.width / 2 - floating.width / 2;
1657
+ y = anchor.bottom + offset;
1658
+ break;
1659
+ case "left":
1660
+ x = anchor.left - floating.width - offset;
1661
+ y = anchor.top + anchor.height / 2 - floating.height / 2;
1662
+ break;
1663
+ case "right":
1664
+ x = anchor.right + offset;
1665
+ y = anchor.top + anchor.height / 2 - floating.height / 2;
1666
+ break;
1667
+ }
1668
+ if (base === "top" || base === "bottom") {
1669
+ if (align === "start") x = anchor.left;
1670
+ else if (align === "end") x = anchor.right - floating.width;
1671
+ }
1672
+ if (base === "left" || base === "right") {
1673
+ if (align === "start") y = anchor.top;
1674
+ else if (align === "end") y = anchor.bottom - floating.height;
1675
+ }
1676
+ let finalPlacement = placement;
1677
+ const vw = window.innerWidth;
1678
+ const vh = window.innerHeight;
1679
+ if (base === "bottom" && y + floating.height > vh) {
1680
+ y = anchor.top - floating.height - offset;
1681
+ finalPlacement = placement.replace("bottom", "top");
1682
+ } else if (base === "top" && y < 0) {
1683
+ y = anchor.bottom + offset;
1684
+ finalPlacement = placement.replace("top", "bottom");
1685
+ }
1686
+ x = Math.max(4, Math.min(x, vw - floating.width - 4));
1687
+ y = Math.max(4, Math.min(y, vh - floating.height - 4));
1688
+ return { x, y, placement: finalPlacement };
1689
+ }
1690
+ function useFloatingPosition(anchorRef, floatingRef, options = {}) {
1691
+ const { placement = "bottom", offset = 8, enabled = true } = options;
1692
+ const [position, setPosition] = useState({
1693
+ x: -9999,
1694
+ y: -9999,
1695
+ placement
1696
+ });
1697
+ const update = useCallback(() => {
1698
+ const anchor = anchorRef.current;
1699
+ const floating = floatingRef.current;
1700
+ if (!anchor || !floating) return;
1701
+ const anchorRect = anchor.getBoundingClientRect();
1702
+ const floatingRect = floating.getBoundingClientRect();
1703
+ setPosition(getPosition(anchorRect, floatingRect, placement, offset));
1704
+ }, [anchorRef, floatingRef, placement, offset]);
1705
+ useLayoutEffect(() => {
1706
+ if (!enabled) return;
1707
+ update();
1708
+ const raf = requestAnimationFrame(() => {
1709
+ update();
1710
+ });
1711
+ return () => cancelAnimationFrame(raf);
1712
+ }, [update, enabled]);
1713
+ useEffect(() => {
1714
+ if (!enabled) return;
1715
+ window.addEventListener("scroll", update, true);
1716
+ window.addEventListener("resize", update);
1717
+ return () => {
1718
+ window.removeEventListener("scroll", update, true);
1719
+ window.removeEventListener("resize", update);
1720
+ };
1721
+ }, [update, enabled]);
1722
+ return position;
1723
+ }
1724
+ function useOutsideClick(ref, handler, enabled = true, ignoreRef) {
1725
+ useEffect(() => {
1726
+ if (!enabled) return;
1727
+ const listener = (event) => {
1728
+ const el = ref.current;
1729
+ if (!el || el.contains(event.target)) return;
1730
+ if (ignoreRef?.current?.contains(event.target)) return;
1731
+ handler(event);
1732
+ };
1733
+ document.addEventListener("mousedown", listener);
1734
+ document.addEventListener("touchstart", listener);
1735
+ return () => {
1736
+ document.removeEventListener("mousedown", listener);
1737
+ document.removeEventListener("touchstart", listener);
1738
+ };
1739
+ }, [ref, handler, enabled, ignoreRef]);
1740
+ }
1741
+ function useEscapeKey(handler, enabled = true) {
1742
+ useEffect(() => {
1743
+ if (!enabled) return;
1744
+ const listener = (event) => {
1745
+ if (event.key === "Escape") {
1746
+ handler();
1747
+ }
1748
+ };
1749
+ document.addEventListener("keydown", listener);
1750
+ return () => document.removeEventListener("keydown", listener);
1751
+ }, [handler, enabled]);
1752
+ }
1633
1753
  function isOptionGroup(item) {
1634
1754
  return typeof item === "object" && "options" in item;
1635
1755
  }
1636
- function renderOption(option, index) {
1756
+ function flattenOptions(list) {
1757
+ const flat = [];
1758
+ for (const item of list) {
1759
+ if (isOptionGroup(item)) {
1760
+ flat.push(...item.options);
1761
+ } else {
1762
+ flat.push(item);
1763
+ }
1764
+ }
1765
+ return flat;
1766
+ }
1767
+ function renderNativeOption(option, index) {
1637
1768
  const resolved = resolveOption(option);
1638
1769
  return /* @__PURE__ */ jsx(
1639
1770
  "option",
@@ -1645,7 +1776,7 @@ function renderOption(option, index) {
1645
1776
  `${resolved.value}-${index}`
1646
1777
  );
1647
1778
  }
1648
- var Select = forwardRef(
1779
+ var NativeSelect = forwardRef(
1649
1780
  ({
1650
1781
  size = "md",
1651
1782
  dirty,
@@ -1703,8 +1834,8 @@ var Select = forwardRef(
1703
1834
  placeholder && /* @__PURE__ */ jsx("option", { value: "", disabled: true, children: placeholder }),
1704
1835
  list.map(
1705
1836
  (item, index) => isOptionGroup(item) ? /* @__PURE__ */ jsx("optgroup", { label: item.label, children: item.options.map(
1706
- (opt, optIndex) => renderOption(opt, optIndex)
1707
- ) }, `group-${index}`) : renderOption(item, index)
1837
+ (opt, optIndex) => renderNativeOption(opt, optIndex)
1838
+ ) }, `group-${index}`) : renderNativeOption(item, index)
1708
1839
  )
1709
1840
  ]
1710
1841
  }
@@ -1728,6 +1859,270 @@ var Select = forwardRef(
1728
1859
  return select;
1729
1860
  }
1730
1861
  );
1862
+ NativeSelect.displayName = "NativeSelect";
1863
+ var ListboxSelect = forwardRef(
1864
+ ({
1865
+ size = "md",
1866
+ dirty,
1867
+ error,
1868
+ label,
1869
+ description,
1870
+ required,
1871
+ disabled,
1872
+ className,
1873
+ id,
1874
+ list,
1875
+ placeholder = "Select...",
1876
+ multiple = false,
1877
+ values: controlledValues,
1878
+ defaultValues,
1879
+ onValueChange,
1880
+ onValuesChange,
1881
+ searchable = false,
1882
+ selectedSuffix = "selected",
1883
+ indicator = "check",
1884
+ value: controlledSingleValue,
1885
+ defaultValue: defaultSingleValue
1886
+ }, _ref) => {
1887
+ const autoId = useId();
1888
+ const selectId = id ?? autoId;
1889
+ const [open, setOpen] = useState(false);
1890
+ const [search2, setSearch] = useState("");
1891
+ const [activeIndex, setActiveIndex] = useState(-1);
1892
+ const [singleValue, setSingleValue] = useState(
1893
+ controlledSingleValue ?? defaultSingleValue ?? ""
1894
+ );
1895
+ const currentSingle = controlledSingleValue ?? singleValue;
1896
+ const [multiValues, setMultiValues] = useState(
1897
+ controlledValues ?? defaultValues ?? []
1898
+ );
1899
+ const currentMulti = controlledValues ?? multiValues;
1900
+ useEffect(() => {
1901
+ if (controlledValues) setMultiValues(controlledValues);
1902
+ }, [controlledValues]);
1903
+ useEffect(() => {
1904
+ if (controlledSingleValue !== void 0) setSingleValue(controlledSingleValue);
1905
+ }, [controlledSingleValue]);
1906
+ const anchorRef = useRef(null);
1907
+ const listRef = useRef(null);
1908
+ const wrapperRef = useRef(null);
1909
+ const searchRef = useRef(null);
1910
+ const position = useFloatingPosition(anchorRef, listRef, {
1911
+ placement: "bottom-start",
1912
+ offset: 4,
1913
+ enabled: open
1914
+ });
1915
+ const close = useCallback(() => {
1916
+ setOpen(false);
1917
+ setSearch("");
1918
+ setActiveIndex(-1);
1919
+ }, []);
1920
+ useOutsideClick(wrapperRef, close, open);
1921
+ useEscapeKey(close, open);
1922
+ useEffect(() => {
1923
+ if (open && searchable) {
1924
+ requestAnimationFrame(() => searchRef.current?.focus());
1925
+ }
1926
+ }, [open, searchable]);
1927
+ const allOptions = flattenOptions(list);
1928
+ const resolvedOptions = allOptions.map(resolveOption);
1929
+ const filtered = search2 ? resolvedOptions.filter(
1930
+ (o) => o.label.toLowerCase().includes(search2.toLowerCase())
1931
+ ) : resolvedOptions;
1932
+ const isSelected = (value) => {
1933
+ if (multiple) return currentMulti.includes(value);
1934
+ return currentSingle === value;
1935
+ };
1936
+ const toggleOption = useCallback(
1937
+ (value) => {
1938
+ if (multiple) {
1939
+ const next = currentMulti.includes(value) ? currentMulti.filter((v) => v !== value) : [...currentMulti, value];
1940
+ setMultiValues(next);
1941
+ onValuesChange?.(next);
1942
+ } else {
1943
+ setSingleValue(value);
1944
+ onValueChange?.(value);
1945
+ close();
1946
+ }
1947
+ },
1948
+ [multiple, currentMulti, onValuesChange, onValueChange, close]
1949
+ );
1950
+ const getDisplayText = () => {
1951
+ if (multiple) {
1952
+ if (currentMulti.length === 0) return placeholder;
1953
+ if (currentMulti.length === 1) {
1954
+ const opt2 = resolvedOptions.find((o) => o.value === currentMulti[0]);
1955
+ return opt2?.label ?? currentMulti[0];
1956
+ }
1957
+ return `${currentMulti.length} ${selectedSuffix}`;
1958
+ }
1959
+ if (!currentSingle) return placeholder;
1960
+ const opt = resolvedOptions.find((o) => o.value === currentSingle);
1961
+ return opt?.label ?? currentSingle;
1962
+ };
1963
+ const hasValue = multiple ? currentMulti.length > 0 : !!currentSingle;
1964
+ const handleKeyDown = (e) => {
1965
+ if (!open) {
1966
+ if (e.key === "ArrowDown" || e.key === "Enter" || e.key === " ") {
1967
+ e.preventDefault();
1968
+ setOpen(true);
1969
+ }
1970
+ return;
1971
+ }
1972
+ if (e.key === "ArrowDown") {
1973
+ e.preventDefault();
1974
+ setActiveIndex((i) => Math.min(i + 1, filtered.length - 1));
1975
+ } else if (e.key === "ArrowUp") {
1976
+ e.preventDefault();
1977
+ setActiveIndex((i) => Math.max(i - 1, 0));
1978
+ } else if (e.key === "Enter" && activeIndex >= 0) {
1979
+ e.preventDefault();
1980
+ const item = filtered[activeIndex];
1981
+ if (item && !item.disabled) toggleOption(item.value);
1982
+ }
1983
+ };
1984
+ const trigger = /* @__PURE__ */ jsxs(
1985
+ "button",
1986
+ {
1987
+ ref: anchorRef,
1988
+ type: "button",
1989
+ id: selectId,
1990
+ disabled,
1991
+ onClick: () => setOpen((o) => !o),
1992
+ onKeyDown: handleKeyDown,
1993
+ role: "combobox",
1994
+ "aria-expanded": open,
1995
+ "aria-haspopup": "listbox",
1996
+ "data-react-fancy-select": "",
1997
+ "data-variant": "listbox",
1998
+ className: cn(
1999
+ inputBaseClasses,
2000
+ inputSizeClasses[size],
2001
+ dirtyClasses(dirty),
2002
+ errorClasses(error),
2003
+ "flex w-full cursor-pointer items-center justify-between gap-2 text-left",
2004
+ !hasValue && "text-zinc-400 dark:text-zinc-500",
2005
+ className
2006
+ ),
2007
+ children: [
2008
+ /* @__PURE__ */ jsx("span", { className: "truncate", children: getDisplayText() }),
2009
+ /* @__PURE__ */ jsx(
2010
+ "svg",
2011
+ {
2012
+ className: cn("h-4 w-4 shrink-0 text-zinc-400 transition-transform", open && "rotate-180"),
2013
+ viewBox: "0 0 20 20",
2014
+ fill: "currentColor",
2015
+ children: /* @__PURE__ */ jsx(
2016
+ "path",
2017
+ {
2018
+ fillRule: "evenodd",
2019
+ d: "M5.23 7.21a.75.75 0 011.06.02L10 11.168l3.71-3.938a.75.75 0 111.08 1.04l-4.25 4.5a.75.75 0 01-1.08 0l-4.25-4.5a.75.75 0 01.02-1.06z",
2020
+ clipRule: "evenodd"
2021
+ }
2022
+ )
2023
+ }
2024
+ )
2025
+ ]
2026
+ }
2027
+ );
2028
+ const dropdown = open && /* @__PURE__ */ jsx(Portal, { children: /* @__PURE__ */ jsxs(
2029
+ "div",
2030
+ {
2031
+ ref: listRef,
2032
+ role: "listbox",
2033
+ "aria-multiselectable": multiple || void 0,
2034
+ className: "fixed z-50 max-h-60 min-w-[8rem] overflow-y-auto rounded-xl border border-zinc-200 bg-white p-1 shadow-lg dark:border-zinc-700 dark:bg-zinc-900 dark:shadow-zinc-950/50 fancy-scale-in",
2035
+ style: {
2036
+ left: position.x,
2037
+ top: position.y,
2038
+ width: anchorRef.current?.offsetWidth
2039
+ },
2040
+ children: [
2041
+ searchable && /* @__PURE__ */ jsx("div", { className: "px-2 pb-1", children: /* @__PURE__ */ jsx(
2042
+ "input",
2043
+ {
2044
+ ref: searchRef,
2045
+ type: "text",
2046
+ value: search2,
2047
+ onChange: (e) => {
2048
+ setSearch(e.target.value);
2049
+ setActiveIndex(-1);
2050
+ },
2051
+ onKeyDown: handleKeyDown,
2052
+ placeholder: "Search...",
2053
+ className: "w-full rounded-md border-0 bg-zinc-100 px-2.5 py-1.5 text-sm text-zinc-900 placeholder:text-zinc-400 outline-none dark:bg-zinc-800 dark:text-zinc-100 dark:placeholder:text-zinc-500"
2054
+ }
2055
+ ) }),
2056
+ filtered.length === 0 ? /* @__PURE__ */ jsx("div", { className: "px-3 py-2 text-sm text-zinc-400", children: "No results found" }) : filtered.map((option, i) => {
2057
+ const selected = isSelected(option.value);
2058
+ return /* @__PURE__ */ jsxs(
2059
+ "button",
2060
+ {
2061
+ type: "button",
2062
+ role: "option",
2063
+ "aria-selected": selected,
2064
+ disabled: option.disabled,
2065
+ onClick: () => toggleOption(option.value),
2066
+ className: cn(
2067
+ "flex w-full items-center gap-2 rounded-lg px-3 py-2 text-left text-sm transition-colors",
2068
+ i === activeIndex ? "bg-zinc-100 dark:bg-zinc-800" : "hover:bg-zinc-50 dark:hover:bg-zinc-800/50",
2069
+ selected ? "text-zinc-900 dark:text-zinc-100" : "text-zinc-700 dark:text-zinc-300",
2070
+ option.disabled && "cursor-not-allowed opacity-50"
2071
+ ),
2072
+ children: [
2073
+ indicator === "checkbox" ? /* @__PURE__ */ jsx(
2074
+ "span",
2075
+ {
2076
+ className: cn(
2077
+ "flex h-4 w-4 shrink-0 items-center justify-center rounded border transition-colors",
2078
+ selected ? "border-blue-500 bg-blue-500 text-white dark:border-blue-400 dark:bg-blue-400" : "border-zinc-300 dark:border-zinc-600"
2079
+ ),
2080
+ children: selected && /* @__PURE__ */ jsx("svg", { className: "h-3 w-3", viewBox: "0 0 12 12", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsx("polyline", { points: "2.5 6 5 8.5 9.5 3.5" }) })
2081
+ }
2082
+ ) : /* @__PURE__ */ jsx("span", { className: "flex h-4 w-4 shrink-0 items-center justify-center", children: selected && /* @__PURE__ */ jsx("svg", { className: "h-4 w-4 text-blue-500 dark:text-blue-400", viewBox: "0 0 20 20", fill: "currentColor", children: /* @__PURE__ */ jsx("path", { fillRule: "evenodd", d: "M16.704 4.153a.75.75 0 01.143 1.052l-8 10.5a.75.75 0 01-1.127.075l-4.5-4.5a.75.75 0 011.06-1.06l3.894 3.893 7.48-9.817a.75.75 0 011.05-.143z", clipRule: "evenodd" }) }) }),
2083
+ /* @__PURE__ */ jsxs("span", { className: "min-w-0 flex-1", children: [
2084
+ /* @__PURE__ */ jsx("span", { className: "block truncate", children: option.label }),
2085
+ option.description && /* @__PURE__ */ jsx("span", { className: "block truncate text-xs text-zinc-400 dark:text-zinc-500", children: option.description })
2086
+ ] })
2087
+ ]
2088
+ },
2089
+ option.value
2090
+ );
2091
+ })
2092
+ ]
2093
+ }
2094
+ ) });
2095
+ const content = /* @__PURE__ */ jsxs("div", { ref: wrapperRef, className: "relative", children: [
2096
+ trigger,
2097
+ dropdown
2098
+ ] });
2099
+ if (label || error || description) {
2100
+ return /* @__PURE__ */ jsx(
2101
+ Field,
2102
+ {
2103
+ label,
2104
+ description,
2105
+ error,
2106
+ required,
2107
+ htmlFor: selectId,
2108
+ size,
2109
+ children: content
2110
+ }
2111
+ );
2112
+ }
2113
+ return content;
2114
+ }
2115
+ );
2116
+ ListboxSelect.displayName = "ListboxSelect";
2117
+ var Select = forwardRef(
2118
+ (props, ref) => {
2119
+ const variant = props.variant ?? (props.multiple ? "listbox" : "native");
2120
+ if (variant === "listbox") {
2121
+ return /* @__PURE__ */ jsx(ListboxSelect, { ...props, ref });
2122
+ }
2123
+ return /* @__PURE__ */ jsx(NativeSelect, { ...props, ref });
2124
+ }
2125
+ );
1731
2126
  Select.displayName = "Select";
1732
2127
  function useControllableState(controlledValue, defaultValue, onChange) {
1733
2128
  const [uncontrolledValue, setUncontrolledValue] = useState(defaultValue);
@@ -1806,7 +2201,7 @@ var Checkbox = forwardRef(
1806
2201
  onChange: (e) => setChecked(e.target.checked),
1807
2202
  className: cn(
1808
2203
  sizeClasses6,
1809
- "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",
2204
+ "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]",
1810
2205
  dirtyRingClasses(dirty),
1811
2206
  error && "border-red-500"
1812
2207
  )
@@ -1818,7 +2213,7 @@ var Checkbox = forwardRef(
1818
2213
  {
1819
2214
  htmlFor: checkboxId,
1820
2215
  className: cn(
1821
- "cursor-pointer text-sm text-zinc-700 dark:text-zinc-300",
2216
+ "cursor-pointer text-sm text-zinc-700 dark:text-zinc-100",
1822
2217
  disabled && "cursor-not-allowed opacity-50"
1823
2218
  ),
1824
2219
  children: [
@@ -1893,7 +2288,7 @@ function CheckboxGroup({
1893
2288
  onChange: () => handleToggle(resolved.value),
1894
2289
  className: cn(
1895
2290
  sizeClasses6,
1896
- "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",
2291
+ "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]",
1897
2292
  dirtyRingClasses(dirty),
1898
2293
  error && "border-red-500"
1899
2294
  )
@@ -1905,7 +2300,7 @@ function CheckboxGroup({
1905
2300
  {
1906
2301
  htmlFor: optionId,
1907
2302
  className: cn(
1908
- "cursor-pointer text-sm text-zinc-700 dark:text-zinc-300",
2303
+ "cursor-pointer text-sm text-zinc-700 dark:text-zinc-100",
1909
2304
  (disabled || resolved.disabled) && "cursor-not-allowed opacity-50"
1910
2305
  ),
1911
2306
  children: resolved.label
@@ -1990,7 +2385,7 @@ function RadioGroup({
1990
2385
  onChange: () => setValue(resolved.value),
1991
2386
  className: cn(
1992
2387
  sizeClasses6,
1993
- "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",
2388
+ "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]",
1994
2389
  dirtyRingClasses(dirty),
1995
2390
  error && "border-red-500"
1996
2391
  )
@@ -2002,7 +2397,7 @@ function RadioGroup({
2002
2397
  {
2003
2398
  htmlFor: optionId,
2004
2399
  className: cn(
2005
- "cursor-pointer text-sm text-zinc-700 dark:text-zinc-300",
2400
+ "cursor-pointer text-sm text-zinc-700 dark:text-zinc-100",
2006
2401
  (disabled || resolved.disabled) && "cursor-not-allowed opacity-50"
2007
2402
  ),
2008
2403
  children: resolved.label
@@ -2109,7 +2504,7 @@ var Switch = forwardRef(
2109
2504
  className: cn(
2110
2505
  "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",
2111
2506
  trackSizes,
2112
- checked ? trackColorMap[color] : "bg-zinc-200 dark:bg-zinc-700",
2507
+ checked ? trackColorMap[color] : "bg-zinc-200 dark:bg-zinc-600",
2113
2508
  dirtyRingClasses(dirty),
2114
2509
  error && "ring-2 ring-red-500/50"
2115
2510
  ),
@@ -2132,7 +2527,7 @@ var Switch = forwardRef(
2132
2527
  {
2133
2528
  htmlFor: switchId,
2134
2529
  className: cn(
2135
- "cursor-pointer text-sm text-zinc-700 dark:text-zinc-300",
2530
+ "cursor-pointer text-sm text-zinc-700 dark:text-zinc-100",
2136
2531
  disabled && "cursor-not-allowed opacity-50"
2137
2532
  ),
2138
2533
  children: [
@@ -2434,7 +2829,7 @@ function MultiSwitch({
2434
2829
  role: "radiogroup",
2435
2830
  id,
2436
2831
  className: cn(
2437
- "relative inline-flex rounded-lg border border-zinc-300 bg-zinc-100 dark:border-zinc-600 dark:bg-zinc-800",
2832
+ "relative inline-flex rounded-lg border border-zinc-300 bg-zinc-100 dark:border-zinc-700 dark:bg-zinc-800",
2438
2833
  dirty && "ring-2 ring-amber-400/50",
2439
2834
  error && "ring-2 ring-red-500/50",
2440
2835
  disabled && "opacity-50 cursor-not-allowed",
@@ -3128,7 +3523,7 @@ function EmojiSelect({
3128
3523
  "button",
3129
3524
  {
3130
3525
  type: "button",
3131
- className: "flex items-center gap-2 rounded-lg border border-zinc-300 px-3 py-2 text-sm dark:border-zinc-600",
3526
+ 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]",
3132
3527
  onClick: () => setOpen(!open),
3133
3528
  children: selected ? /* @__PURE__ */ jsx("span", { className: "text-xl", children: selected }) : /* @__PURE__ */ jsx("span", { className: "text-zinc-400", children: "Pick emoji" })
3134
3529
  }
@@ -3141,7 +3536,7 @@ function EmojiSelect({
3141
3536
  value: query,
3142
3537
  onChange: (e) => setQuery(e.target.value),
3143
3538
  placeholder,
3144
- 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",
3539
+ 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",
3145
3540
  autoFocus: true
3146
3541
  }
3147
3542
  ),
@@ -3396,34 +3791,6 @@ var Table = Object.assign(TableRoot, {
3396
3791
  Tray: TableTray,
3397
3792
  RowTray: TableRowTray
3398
3793
  });
3399
- function Portal({ children, container }) {
3400
- if (typeof document === "undefined") return null;
3401
- const target = container ?? document.body;
3402
- return createPortal(
3403
- /* @__PURE__ */ jsx(PortalDarkWrapper, { children }),
3404
- target
3405
- );
3406
- }
3407
- function PortalDarkWrapper({ children }) {
3408
- const ref = useRef(null);
3409
- useEffect(() => {
3410
- const root = document.documentElement;
3411
- const wrapper = ref.current;
3412
- if (!wrapper) return;
3413
- const sync = () => {
3414
- const isDark = root.classList.contains("dark") || root.getAttribute("data-theme") === "dark";
3415
- wrapper.classList.toggle("dark", isDark);
3416
- };
3417
- sync();
3418
- const observer = new MutationObserver(sync);
3419
- observer.observe(root, {
3420
- attributes: true,
3421
- attributeFilter: ["class", "data-theme"]
3422
- });
3423
- return () => observer.disconnect();
3424
- }, []);
3425
- return /* @__PURE__ */ jsx("div", { ref, "data-react-fancy-portal": "", style: { display: "contents" }, children });
3426
- }
3427
3794
  var sizeClasses3 = {
3428
3795
  xs: "text-xs",
3429
3796
  sm: "text-sm",
@@ -4201,42 +4568,66 @@ var Callout = forwardRef(
4201
4568
  );
4202
4569
  Callout.displayName = "Callout";
4203
4570
  var TimelineContext = createContext({
4204
- orientation: "vertical",
4205
- index: 0
4571
+ variant: "stacked",
4572
+ index: 0,
4573
+ total: 0,
4574
+ animated: true
4206
4575
  });
4207
4576
  function useTimeline() {
4208
4577
  return useContext(TimelineContext);
4209
4578
  }
4210
4579
  var dotColorClasses2 = {
4211
- blue: "bg-blue-500 dark:bg-blue-400",
4212
- green: "bg-green-500 dark:bg-green-400",
4213
- amber: "bg-amber-500 dark:bg-amber-400",
4214
- red: "bg-red-500 dark:bg-red-400",
4215
- zinc: "bg-zinc-400 dark:bg-zinc-500"
4216
- };
4217
- var iconColorClasses2 = {
4218
- blue: "text-blue-500 dark:text-blue-400",
4219
- green: "text-green-500 dark:text-green-400",
4220
- amber: "text-amber-500 dark:text-amber-400",
4221
- red: "text-red-500 dark:text-red-400",
4222
- zinc: "text-zinc-500 dark:text-zinc-400"
4580
+ red: "bg-red-500",
4581
+ orange: "bg-orange-500",
4582
+ amber: "bg-amber-500",
4583
+ yellow: "bg-yellow-500",
4584
+ lime: "bg-lime-500",
4585
+ green: "bg-green-500",
4586
+ emerald: "bg-emerald-500",
4587
+ teal: "bg-teal-500",
4588
+ cyan: "bg-cyan-500",
4589
+ sky: "bg-sky-500",
4590
+ blue: "bg-blue-500",
4591
+ indigo: "bg-indigo-500",
4592
+ violet: "bg-violet-500",
4593
+ purple: "bg-purple-500",
4594
+ fuchsia: "bg-fuchsia-500",
4595
+ pink: "bg-pink-500",
4596
+ rose: "bg-rose-500",
4597
+ zinc: "bg-zinc-300 dark:bg-zinc-600"
4223
4598
  };
4224
4599
  var ringColorClasses = {
4225
- blue: "ring-blue-500/30 dark:ring-blue-400/30",
4226
- green: "ring-green-500/30 dark:ring-green-400/30",
4227
- amber: "ring-amber-500/30 dark:ring-amber-400/30",
4228
- red: "ring-red-500/30 dark:ring-red-400/30",
4600
+ red: "ring-red-500/30",
4601
+ orange: "ring-orange-500/30",
4602
+ amber: "ring-amber-500/30",
4603
+ yellow: "ring-yellow-500/30",
4604
+ lime: "ring-lime-500/30",
4605
+ green: "ring-green-500/30",
4606
+ emerald: "ring-emerald-500/30",
4607
+ teal: "ring-teal-500/30",
4608
+ cyan: "ring-cyan-500/30",
4609
+ sky: "ring-sky-500/30",
4610
+ blue: "ring-blue-500/30",
4611
+ indigo: "ring-indigo-500/30",
4612
+ violet: "ring-violet-500/30",
4613
+ purple: "ring-purple-500/30",
4614
+ fuchsia: "ring-fuchsia-500/30",
4615
+ pink: "ring-pink-500/30",
4616
+ rose: "ring-rose-500/30",
4229
4617
  zinc: "ring-zinc-400/30 dark:ring-zinc-500/30"
4230
4618
  };
4231
- function Dot({ icon, color, active }) {
4619
+ function Dot({ icon, emoji, color, active }) {
4232
4620
  const c = color ?? "zinc";
4621
+ if (emoji) {
4622
+ return /* @__PURE__ */ 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__ */ jsx("span", { className: "text-sm", children: emoji }) });
4623
+ }
4233
4624
  if (icon) {
4234
4625
  return /* @__PURE__ */ jsx(
4235
4626
  "span",
4236
4627
  {
4237
4628
  className: cn(
4238
- "flex h-6 w-6 items-center justify-center rounded-full bg-white dark:bg-zinc-900",
4239
- iconColorClasses2[c],
4629
+ "flex h-8 w-8 shrink-0 items-center justify-center rounded-full text-white",
4630
+ dotColorClasses2[c],
4240
4631
  active && "ring-4",
4241
4632
  active && ringColorClasses[c]
4242
4633
  ),
@@ -4248,7 +4639,7 @@ function Dot({ icon, color, active }) {
4248
4639
  "span",
4249
4640
  {
4250
4641
  className: cn(
4251
- "h-3 w-3 rounded-full",
4642
+ "h-3 w-3 shrink-0 rounded-full",
4252
4643
  dotColorClasses2[c],
4253
4644
  active && "ring-4",
4254
4645
  active && ringColorClasses[c]
@@ -4256,32 +4647,83 @@ function Dot({ icon, color, active }) {
4256
4647
  }
4257
4648
  );
4258
4649
  }
4650
+ function useIntersectionReveal(animated) {
4651
+ const ref = useRef(null);
4652
+ const [visible, setVisible] = useState(!animated);
4653
+ useEffect(() => {
4654
+ if (!animated || !ref.current) return;
4655
+ const el = ref.current;
4656
+ const observer = new IntersectionObserver(
4657
+ ([entry]) => {
4658
+ if (entry.isIntersecting) {
4659
+ setVisible(true);
4660
+ observer.disconnect();
4661
+ }
4662
+ },
4663
+ { threshold: 0.2 }
4664
+ );
4665
+ observer.observe(el);
4666
+ return () => observer.disconnect();
4667
+ }, [animated]);
4668
+ return { ref, visible };
4669
+ }
4259
4670
  var TimelineItem = forwardRef(
4260
- ({
4261
- children,
4262
- icon,
4263
- color = "zinc",
4264
- active = false,
4265
- className
4266
- }, ref) => {
4267
- const { orientation, index } = useTimeline();
4268
- if (orientation === "horizontal") {
4269
- const isTop = index % 2 === 0;
4671
+ ({ children, icon, emoji, date, color = "zinc", active = false, className }, _ref) => {
4672
+ const { variant, index, total, animated } = useTimeline();
4673
+ const { ref, visible } = useIntersectionReveal(animated);
4674
+ const isLast = index === total - 1;
4675
+ const isLargeDot = !!icon || !!emoji;
4676
+ const isEven = index % 2 === 0;
4677
+ if (variant === "horizontal") {
4270
4678
  return /* @__PURE__ */ jsxs(
4271
4679
  "div",
4272
4680
  {
4273
4681
  ref,
4274
4682
  "data-react-fancy-timeline-item": "",
4275
- className: cn("relative flex flex-col items-center", className),
4276
- style: { minWidth: 120 },
4683
+ className: cn(
4684
+ "flex flex-col items-center",
4685
+ !isLast && "min-w-40",
4686
+ animated && "transition duration-500 ease-out",
4687
+ animated && (visible ? "translate-y-0 opacity-100" : "translate-y-4 opacity-0"),
4688
+ className
4689
+ ),
4277
4690
  children: [
4278
- /* @__PURE__ */ jsx("div", { className: cn("flex min-h-[4rem] items-end pb-2", !isTop && "invisible"), children: isTop && /* @__PURE__ */ jsx("div", { className: "min-w-0 text-center", children }) }),
4279
- /* @__PURE__ */ jsxs("div", { className: "relative flex w-full items-center justify-center", children: [
4280
- /* @__PURE__ */ jsx("div", { className: "h-px flex-1 bg-zinc-200 dark:bg-zinc-700" }),
4281
- /* @__PURE__ */ jsx("div", { className: "relative z-10 flex shrink-0 items-center justify-center", children: /* @__PURE__ */ jsx(Dot, { icon, color, active }) }),
4282
- /* @__PURE__ */ jsx("div", { className: "h-px flex-1 bg-zinc-200 dark:bg-zinc-700" })
4691
+ /* @__PURE__ */ jsxs("div", { className: "flex h-8 w-full items-center", children: [
4692
+ index > 0 ? /* @__PURE__ */ jsx("div", { className: "h-px flex-1 bg-zinc-200 dark:bg-zinc-700" }) : /* @__PURE__ */ jsx("div", { className: "flex-1" }),
4693
+ /* @__PURE__ */ jsx(Dot, { icon, emoji, color, active }),
4694
+ !isLast ? /* @__PURE__ */ jsx("div", { className: "h-px flex-1 bg-zinc-200 dark:bg-zinc-700" }) : /* @__PURE__ */ jsx("div", { className: "flex-1" })
4283
4695
  ] }),
4284
- /* @__PURE__ */ jsx("div", { className: cn("flex min-h-[4rem] items-start pt-2", isTop && "invisible"), children: !isTop && /* @__PURE__ */ jsx("div", { className: "min-w-0 text-center", children }) })
4696
+ /* @__PURE__ */ jsxs("div", { className: "mt-3 max-w-40 px-2 text-center", children: [
4697
+ date && /* @__PURE__ */ jsx("time", { className: "text-xs font-medium uppercase tracking-wide text-zinc-500 dark:text-zinc-400", children: date }),
4698
+ children
4699
+ ] })
4700
+ ]
4701
+ }
4702
+ );
4703
+ }
4704
+ if (variant === "alternating") {
4705
+ return /* @__PURE__ */ jsxs(
4706
+ "div",
4707
+ {
4708
+ ref,
4709
+ "data-react-fancy-timeline-item": "",
4710
+ className: cn(
4711
+ "relative flex gap-x-4 md:grid md:grid-cols-[1fr_1.5rem_1fr] md:gap-x-6",
4712
+ animated && "transition duration-500 ease-out",
4713
+ animated && (visible ? "translate-y-0 opacity-100" : "translate-y-4 opacity-0"),
4714
+ className
4715
+ ),
4716
+ children: [
4717
+ /* @__PURE__ */ 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__ */ jsx("div", { className: "mt-1.5", children: /* @__PURE__ */ jsx(Dot, { icon, emoji, color, active }) }) : /* @__PURE__ */ jsx(Dot, { icon, emoji, color, active }) }),
4718
+ /* @__PURE__ */ jsxs("div", { className: cn(
4719
+ "min-w-0 flex-1",
4720
+ !isLast && "pb-8",
4721
+ isLargeDot && "pt-1",
4722
+ isEven ? "md:col-start-1 md:row-start-1 md:text-right" : "md:col-start-3"
4723
+ ), children: [
4724
+ date && /* @__PURE__ */ jsx("time", { className: "text-xs font-medium uppercase tracking-wide text-zinc-500 dark:text-zinc-400", children: date }),
4725
+ children
4726
+ ] })
4285
4727
  ]
4286
4728
  }
4287
4729
  );
@@ -4291,11 +4733,18 @@ var TimelineItem = forwardRef(
4291
4733
  {
4292
4734
  ref,
4293
4735
  "data-react-fancy-timeline-item": "",
4294
- className: cn("relative flex gap-4 pb-8 last:pb-0", className),
4736
+ className: cn(
4737
+ "relative flex gap-x-4",
4738
+ animated && "transition duration-500 ease-out",
4739
+ animated && (visible ? "translate-y-0 opacity-100" : "translate-y-4 opacity-0"),
4740
+ className
4741
+ ),
4295
4742
  children: [
4296
- /* @__PURE__ */ jsx("div", { className: "absolute left-[11px] top-6 bottom-0 w-px bg-zinc-200 last:hidden dark:bg-zinc-700" }),
4297
- /* @__PURE__ */ jsx("div", { className: "relative z-10 flex shrink-0 mt-1.5", children: /* @__PURE__ */ jsx(Dot, { icon, color, active }) }),
4298
- /* @__PURE__ */ jsx("div", { className: "min-w-0 flex-1 pt-0.5", children })
4743
+ /* @__PURE__ */ jsx("div", { className: "relative z-10 flex w-8 shrink-0 justify-center", children: !isLargeDot ? /* @__PURE__ */ jsx("div", { className: "mt-1.5", children: /* @__PURE__ */ jsx(Dot, { icon, emoji, color, active }) }) : /* @__PURE__ */ jsx(Dot, { icon, emoji, color, active }) }),
4744
+ /* @__PURE__ */ jsxs("div", { className: cn("min-w-0 flex-1", !isLast && "pb-8", isLargeDot && "pt-1"), children: [
4745
+ date && /* @__PURE__ */ jsx("time", { className: "text-xs font-medium uppercase tracking-wide text-zinc-500 dark:text-zinc-400", children: date }),
4746
+ children
4747
+ ] })
4299
4748
  ]
4300
4749
  }
4301
4750
  );
@@ -4303,8 +4752,8 @@ var TimelineItem = forwardRef(
4303
4752
  );
4304
4753
  TimelineItem.displayName = "TimelineItem";
4305
4754
  var TimelineBlock = forwardRef(
4306
- ({ heading, children, icon, color = "zinc", active = false, className }, ref) => {
4307
- return /* @__PURE__ */ jsx(TimelineItem, { icon, color, active, children: /* @__PURE__ */ jsxs(
4755
+ ({ heading, children, icon, emoji, color = "zinc", active = false, className }, ref) => {
4756
+ return /* @__PURE__ */ jsx(TimelineItem, { icon, emoji, color, active, children: /* @__PURE__ */ jsxs(
4308
4757
  "div",
4309
4758
  {
4310
4759
  ref,
@@ -4313,118 +4762,76 @@ var TimelineBlock = forwardRef(
4313
4762
  "rounded-lg border border-zinc-200 bg-white p-4 dark:border-zinc-700 dark:bg-zinc-900",
4314
4763
  active && "ring-2 ring-blue-500/20 dark:ring-blue-400/20",
4315
4764
  className
4316
- ),
4317
- children: [
4318
- heading && /* @__PURE__ */ jsx("div", { className: "mb-2 text-sm font-semibold text-zinc-900 dark:text-zinc-100", children: heading }),
4319
- /* @__PURE__ */ jsx("div", { className: "text-sm text-zinc-600 dark:text-zinc-400", children })
4320
- ]
4321
- }
4322
- ) });
4323
- }
4324
- );
4325
- TimelineBlock.displayName = "TimelineBlock";
4326
- var TimelineRoot = forwardRef(
4327
- ({ children, orientation = "vertical", className }, ref) => {
4328
- const items = Children.toArray(children);
4329
- return /* @__PURE__ */ jsx(
4330
- "div",
4331
- {
4332
- ref,
4333
- "data-react-fancy-timeline": "",
4334
- "data-orientation": orientation,
4335
- className: cn(
4336
- orientation === "vertical" ? "flex flex-col" : "flex flex-row items-center overflow-x-auto",
4337
- className
4338
- ),
4339
- children: items.map((child, i) => /* @__PURE__ */ jsx(TimelineContext.Provider, { value: { orientation, index: i }, children: child }, i))
4340
- }
4341
- );
4342
- }
4343
- );
4344
- TimelineRoot.displayName = "Timeline";
4345
- var Timeline = Object.assign(TimelineRoot, {
4346
- Item: TimelineItem,
4347
- Block: TimelineBlock
4348
- });
4349
- function getPosition(anchor, floating, placement, offset) {
4350
- let x = 0;
4351
- let y = 0;
4352
- const base = placement.split("-")[0];
4353
- const align = placement.split("-")[1];
4354
- switch (base) {
4355
- case "top":
4356
- x = anchor.left + anchor.width / 2 - floating.width / 2;
4357
- y = anchor.top - floating.height - offset;
4358
- break;
4359
- case "bottom":
4360
- x = anchor.left + anchor.width / 2 - floating.width / 2;
4361
- y = anchor.bottom + offset;
4362
- break;
4363
- case "left":
4364
- x = anchor.left - floating.width - offset;
4365
- y = anchor.top + anchor.height / 2 - floating.height / 2;
4366
- break;
4367
- case "right":
4368
- x = anchor.right + offset;
4369
- y = anchor.top + anchor.height / 2 - floating.height / 2;
4370
- break;
4371
- }
4372
- if (base === "top" || base === "bottom") {
4373
- if (align === "start") x = anchor.left;
4374
- else if (align === "end") x = anchor.right - floating.width;
4375
- }
4376
- if (base === "left" || base === "right") {
4377
- if (align === "start") y = anchor.top;
4378
- else if (align === "end") y = anchor.bottom - floating.height;
4379
- }
4380
- let finalPlacement = placement;
4381
- const vw = window.innerWidth;
4382
- const vh = window.innerHeight;
4383
- if (base === "bottom" && y + floating.height > vh) {
4384
- y = anchor.top - floating.height - offset;
4385
- finalPlacement = placement.replace("bottom", "top");
4386
- } else if (base === "top" && y < 0) {
4387
- y = anchor.bottom + offset;
4388
- finalPlacement = placement.replace("top", "bottom");
4765
+ ),
4766
+ children: [
4767
+ heading && /* @__PURE__ */ jsx("div", { className: "mb-2 text-sm font-semibold text-zinc-900 dark:text-zinc-100", children: heading }),
4768
+ /* @__PURE__ */ jsx("div", { className: "text-sm text-zinc-600 dark:text-zinc-400", children })
4769
+ ]
4770
+ }
4771
+ ) });
4389
4772
  }
4390
- x = Math.max(4, Math.min(x, vw - floating.width - 4));
4391
- y = Math.max(4, Math.min(y, vh - floating.height - 4));
4392
- return { x, y, placement: finalPlacement };
4393
- }
4394
- function useFloatingPosition(anchorRef, floatingRef, options = {}) {
4395
- const { placement = "bottom", offset = 8, enabled = true } = options;
4396
- const [position, setPosition] = useState({
4397
- x: -9999,
4398
- y: -9999,
4399
- placement
4400
- });
4401
- const update = useCallback(() => {
4402
- const anchor = anchorRef.current;
4403
- const floating = floatingRef.current;
4404
- if (!anchor || !floating) return;
4405
- const anchorRect = anchor.getBoundingClientRect();
4406
- const floatingRect = floating.getBoundingClientRect();
4407
- setPosition(getPosition(anchorRect, floatingRect, placement, offset));
4408
- }, [anchorRef, floatingRef, placement, offset]);
4409
- useLayoutEffect(() => {
4410
- if (!enabled) return;
4411
- update();
4412
- const raf = requestAnimationFrame(() => {
4413
- update();
4414
- });
4415
- return () => cancelAnimationFrame(raf);
4416
- }, [update, enabled]);
4417
- useEffect(() => {
4418
- if (!enabled) return;
4419
- window.addEventListener("scroll", update, true);
4420
- window.addEventListener("resize", update);
4421
- return () => {
4422
- window.removeEventListener("scroll", update, true);
4423
- window.removeEventListener("resize", update);
4424
- };
4425
- }, [update, enabled]);
4426
- return position;
4427
- }
4773
+ );
4774
+ TimelineBlock.displayName = "TimelineBlock";
4775
+ var TimelineRoot = forwardRef(
4776
+ ({
4777
+ children,
4778
+ variant: variantProp,
4779
+ orientation,
4780
+ events,
4781
+ heading,
4782
+ description,
4783
+ animated = true,
4784
+ className
4785
+ }, ref) => {
4786
+ const scrollRef = useRef(null);
4787
+ let variant = variantProp ?? "stacked";
4788
+ if (!variantProp && orientation) {
4789
+ variant = orientation === "horizontal" ? "horizontal" : "stacked";
4790
+ }
4791
+ const isHorizontal = variant === "horizontal";
4792
+ const isAlternating = variant === "alternating";
4793
+ const items = events ? events.map((e, i) => /* @__PURE__ */ jsxs(TimelineItem, { date: e.date, emoji: e.emoji, icon: e.icon, color: e.color, children: [
4794
+ e.title && /* @__PURE__ */ jsx("h3", { className: "font-semibold text-zinc-900 dark:text-white", children: e.title }),
4795
+ e.description && /* @__PURE__ */ jsx("div", { className: "mt-1 text-sm leading-relaxed text-zinc-600 dark:text-zinc-400", children: e.description })
4796
+ ] }, i)) : Children.toArray(children);
4797
+ const handleWheel = useCallback((e) => {
4798
+ if (!scrollRef.current) return;
4799
+ e.preventDefault();
4800
+ scrollRef.current.scrollLeft += e.deltaY;
4801
+ }, []);
4802
+ const content = items.map((child, i) => /* @__PURE__ */ jsx(TimelineContext.Provider, { value: { variant, index: i, total: items.length, animated }, children: child }, i));
4803
+ return /* @__PURE__ */ jsxs("div", { ref, "data-react-fancy-timeline": "", "data-variant": variant, className, children: [
4804
+ (heading || description) && /* @__PURE__ */ jsxs("div", { className: "mb-8", children: [
4805
+ heading && /* @__PURE__ */ jsx("h2", { className: "text-xl font-semibold text-zinc-900 dark:text-white", children: heading }),
4806
+ description && /* @__PURE__ */ jsx("p", { className: "mt-1 text-sm text-zinc-500 dark:text-zinc-400", children: description })
4807
+ ] }),
4808
+ isHorizontal ? /* @__PURE__ */ jsx(
4809
+ "div",
4810
+ {
4811
+ ref: scrollRef,
4812
+ className: "overflow-x-auto pb-4 -mb-4",
4813
+ style: { scrollbarWidth: "thin", scrollbarColor: "rgb(161 161 170) transparent" },
4814
+ onWheel: handleWheel,
4815
+ children: /* @__PURE__ */ jsx("div", { className: "flex min-w-max items-start", children: content })
4816
+ }
4817
+ ) : (
4818
+ /* Vertical variants: continuous background line behind all events */
4819
+ /* @__PURE__ */ jsxs("div", { className: "relative", children: [
4820
+ items.length > 1 && /* @__PURE__ */ jsx("div", { className: cn(
4821
+ "absolute top-0 bottom-0 w-px bg-zinc-200 dark:bg-zinc-700",
4822
+ isAlternating ? "left-4 md:left-1/2 md:-translate-x-px" : "left-4"
4823
+ ) }),
4824
+ content
4825
+ ] })
4826
+ )
4827
+ ] });
4828
+ }
4829
+ );
4830
+ TimelineRoot.displayName = "Timeline";
4831
+ var Timeline = Object.assign(TimelineRoot, {
4832
+ Item: TimelineItem,
4833
+ Block: TimelineBlock
4834
+ });
4428
4835
  var Tooltip = forwardRef(
4429
4836
  function Tooltip2({ children, content, placement = "top", delay = 200, offset = 8, className }, _ref) {
4430
4837
  const [open, setOpen] = useState(false);
@@ -4459,7 +4866,7 @@ var Tooltip = forwardRef(
4459
4866
  "data-react-fancy-tooltip": "",
4460
4867
  role: "tooltip",
4461
4868
  className: cn(
4462
- "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",
4869
+ "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",
4463
4870
  className
4464
4871
  ),
4465
4872
  style: { left: position.x, top: position.y },
@@ -4469,7 +4876,7 @@ var Tooltip = forwardRef(
4469
4876
  "div",
4470
4877
  {
4471
4878
  className: cn(
4472
- "absolute h-2 w-2 rotate-45 bg-zinc-900 dark:bg-zinc-100",
4879
+ "absolute h-2 w-2 rotate-45 bg-zinc-900 dark:bg-zinc-700",
4473
4880
  position.placement.startsWith("top") && "bottom-[-4px] left-1/2 -translate-x-1/2",
4474
4881
  position.placement.startsWith("bottom") && "top-[-4px] left-1/2 -translate-x-1/2",
4475
4882
  position.placement.startsWith("left") && "right-[-4px] top-1/2 -translate-y-1/2",
@@ -4492,78 +4899,26 @@ function usePopover() {
4492
4899
  }
4493
4900
  return ctx;
4494
4901
  }
4495
- function PopoverTrigger({ children }) {
4496
- const { setOpen, open, anchorRef } = usePopover();
4497
- return cloneElement(children, {
4498
- ref: anchorRef,
4499
- onClick: () => setOpen(!open),
4500
- "aria-expanded": open,
4501
- "aria-haspopup": true
4502
- });
4503
- }
4504
- PopoverTrigger.displayName = "PopoverTrigger";
4505
- function useOutsideClick(ref, handler, enabled = true, ignoreRef) {
4506
- useEffect(() => {
4507
- if (!enabled) return;
4508
- const listener = (event) => {
4509
- const el = ref.current;
4510
- if (!el || el.contains(event.target)) return;
4511
- if (ignoreRef?.current?.contains(event.target)) return;
4512
- handler(event);
4513
- };
4514
- document.addEventListener("mousedown", listener);
4515
- document.addEventListener("touchstart", listener);
4516
- return () => {
4517
- document.removeEventListener("mousedown", listener);
4518
- document.removeEventListener("touchstart", listener);
4519
- };
4520
- }, [ref, handler, enabled, ignoreRef]);
4521
- }
4522
- function useEscapeKey(handler, enabled = true) {
4523
- useEffect(() => {
4524
- if (!enabled) return;
4525
- const listener = (event) => {
4526
- if (event.key === "Escape") {
4527
- handler();
4528
- }
4529
- };
4530
- document.addEventListener("keydown", listener);
4531
- return () => document.removeEventListener("keydown", listener);
4532
- }, [handler, enabled]);
4533
- }
4534
- function useAnimation({
4535
- open,
4536
- enterClass,
4537
- exitClass
4538
- }) {
4539
- const [mounted, setMounted] = useState(open);
4540
- const [animClass, setAnimClass] = useState(open ? enterClass : "");
4541
- const ref = useRef(null);
4542
- useEffect(() => {
4543
- if (open) {
4544
- setMounted(true);
4545
- setAnimClass(enterClass);
4546
- } else if (mounted) {
4547
- setAnimClass(exitClass);
4548
- }
4549
- }, [open, enterClass, exitClass, mounted]);
4550
- const handleAnimationEnd = useCallback(() => {
4551
- if (!open) {
4552
- setMounted(false);
4553
- setAnimClass("");
4902
+ function PopoverTrigger({ children, className }) {
4903
+ const { setOpen, open, anchorRef, hover, onHoverEnter, onHoverLeave } = usePopover();
4904
+ return /* @__PURE__ */ jsx(
4905
+ "span",
4906
+ {
4907
+ ref: anchorRef,
4908
+ "data-react-fancy-popover-trigger": "",
4909
+ className: cn("inline-flex", className),
4910
+ onClick: hover ? void 0 : () => setOpen(!open),
4911
+ onMouseEnter: hover ? onHoverEnter : void 0,
4912
+ onMouseLeave: hover ? onHoverLeave : void 0,
4913
+ "aria-expanded": open,
4914
+ "aria-haspopup": true,
4915
+ children
4554
4916
  }
4555
- }, [open]);
4556
- useEffect(() => {
4557
- const el = ref.current;
4558
- if (!el) return;
4559
- el.addEventListener("animationend", handleAnimationEnd);
4560
- return () => el.removeEventListener("animationend", handleAnimationEnd);
4561
- }, [handleAnimationEnd, mounted]);
4562
- return { mounted, className: animClass, ref };
4917
+ );
4563
4918
  }
4919
+ PopoverTrigger.displayName = "PopoverTrigger";
4564
4920
  function PopoverContent({ children, className }) {
4565
- const { open, setOpen, anchorRef, placement, offset } = usePopover();
4566
- const floatingRef = useRef(null);
4921
+ const { open, setOpen, anchorRef, floatingRef, placement, offset, hover, onHoverEnter, onHoverLeave } = usePopover();
4567
4922
  const outsideRef = useRef(null);
4568
4923
  const position = useFloatingPosition(anchorRef, floatingRef, {
4569
4924
  placement,
@@ -4571,29 +4926,26 @@ function PopoverContent({ children, className }) {
4571
4926
  enabled: open
4572
4927
  });
4573
4928
  const close = useCallback(() => setOpen(false), [setOpen]);
4574
- useOutsideClick(outsideRef, close, open, anchorRef);
4929
+ useOutsideClick(outsideRef, close, open && !hover, anchorRef);
4575
4930
  useEscapeKey(close, open);
4576
- const { mounted, className: animClass, ref: animRef } = useAnimation({
4577
- open,
4578
- enterClass: "fancy-scale-in",
4579
- exitClass: "fancy-fade-out"
4580
- });
4581
- if (!mounted) return null;
4931
+ const positioned = position.x !== -9999 && position.y !== -9999;
4932
+ if (!open) return null;
4582
4933
  return /* @__PURE__ */ jsx(Portal, { children: /* @__PURE__ */ jsx(
4583
4934
  "div",
4584
4935
  {
4585
4936
  ref: (node) => {
4586
4937
  outsideRef.current = node;
4587
4938
  floatingRef.current = node;
4588
- animRef.current = node;
4589
4939
  },
4590
4940
  "data-react-fancy-popover": "",
4591
4941
  className: cn(
4592
4942
  "fixed z-50 rounded-xl border border-zinc-200 bg-white p-4 text-zinc-700 shadow-lg dark:border-zinc-700 dark:bg-zinc-900 dark:text-zinc-200 dark:shadow-zinc-950/50",
4593
- animClass,
4943
+ positioned ? "fancy-scale-in" : "invisible",
4594
4944
  className
4595
4945
  ),
4596
4946
  style: { left: position.x, top: position.y },
4947
+ onMouseEnter: hover ? onHoverEnter : void 0,
4948
+ onMouseLeave: hover ? onHoverLeave : void 0,
4597
4949
  children
4598
4950
  }
4599
4951
  ) });
@@ -4605,17 +4957,37 @@ function PopoverRoot({
4605
4957
  defaultOpen = false,
4606
4958
  onOpenChange,
4607
4959
  placement = "bottom",
4608
- offset = 8
4960
+ offset = 8,
4961
+ hover = false,
4962
+ hoverDelay = 200,
4963
+ hoverCloseDelay = 300
4609
4964
  }) {
4610
- const [open, setOpen] = useControllableState(
4611
- controlledOpen,
4612
- defaultOpen,
4613
- onOpenChange
4614
- );
4965
+ const [internalOpen, setInternalOpen] = useState(defaultOpen);
4966
+ const isControlled = controlledOpen !== void 0;
4967
+ const open = isControlled ? controlledOpen : internalOpen;
4615
4968
  const anchorRef = useRef(null);
4969
+ const floatingRef = useRef(null);
4970
+ const hoverTimeoutRef = useRef(void 0);
4971
+ const setOpen = useCallback(
4972
+ (next) => {
4973
+ if (!isControlled) setInternalOpen(next);
4974
+ onOpenChange?.(next);
4975
+ },
4976
+ [isControlled, onOpenChange]
4977
+ );
4978
+ const onHoverEnter = useCallback(() => {
4979
+ if (!hover) return;
4980
+ clearTimeout(hoverTimeoutRef.current);
4981
+ hoverTimeoutRef.current = setTimeout(() => setOpen(true), hoverDelay);
4982
+ }, [hover, hoverDelay, setOpen]);
4983
+ const onHoverLeave = useCallback(() => {
4984
+ if (!hover) return;
4985
+ clearTimeout(hoverTimeoutRef.current);
4986
+ hoverTimeoutRef.current = setTimeout(() => setOpen(false), hoverCloseDelay);
4987
+ }, [hover, hoverCloseDelay, setOpen]);
4616
4988
  const ctx = useMemo(
4617
- () => ({ open, setOpen, anchorRef, placement, offset }),
4618
- [open, setOpen, anchorRef, placement, offset]
4989
+ () => ({ open, setOpen, anchorRef, floatingRef, placement, offset, hover, onHoverEnter, onHoverLeave }),
4990
+ [open, setOpen, placement, offset, hover, onHoverEnter, onHoverLeave]
4619
4991
  );
4620
4992
  return /* @__PURE__ */ jsx(PopoverContext.Provider, { value: ctx, children });
4621
4993
  }
@@ -4643,6 +5015,36 @@ function DropdownTrigger({ children }) {
4643
5015
  });
4644
5016
  }
4645
5017
  DropdownTrigger.displayName = "DropdownTrigger";
5018
+ function useAnimation({
5019
+ open,
5020
+ enterClass,
5021
+ exitClass
5022
+ }) {
5023
+ const [mounted, setMounted] = useState(open);
5024
+ const [animClass, setAnimClass] = useState(open ? enterClass : "");
5025
+ const ref = useRef(null);
5026
+ useEffect(() => {
5027
+ if (open) {
5028
+ setMounted(true);
5029
+ setAnimClass(enterClass);
5030
+ } else if (mounted) {
5031
+ setAnimClass(exitClass);
5032
+ }
5033
+ }, [open, enterClass, exitClass, mounted]);
5034
+ const handleAnimationEnd = useCallback(() => {
5035
+ if (!open) {
5036
+ setMounted(false);
5037
+ setAnimClass("");
5038
+ }
5039
+ }, [open]);
5040
+ useEffect(() => {
5041
+ const el = ref.current;
5042
+ if (!el) return;
5043
+ el.addEventListener("animationend", handleAnimationEnd);
5044
+ return () => el.removeEventListener("animationend", handleAnimationEnd);
5045
+ }, [handleAnimationEnd, mounted]);
5046
+ return { mounted, className: animClass, ref };
5047
+ }
4646
5048
  function DropdownItems({ children, className }) {
4647
5049
  const { open, setOpen, anchorRef, activeIndex, setActiveIndex, placement, offset } = useDropdown();
4648
5050
  const floatingRef = useRef(null);
@@ -5961,7 +6363,7 @@ var Autocomplete = forwardRef(
5961
6363
  role: "combobox",
5962
6364
  "aria-expanded": open,
5963
6365
  "aria-autocomplete": "list",
5964
- 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"
6366
+ 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"
5965
6367
  }
5966
6368
  ),
5967
6369
  open && /* @__PURE__ */ jsx(Portal, { children: /* @__PURE__ */ jsx(
@@ -6055,7 +6457,7 @@ var Pillbox = forwardRef(
6055
6457
  "data-react-fancy-pillbox": "",
6056
6458
  ref,
6057
6459
  className: cn(
6058
- "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",
6460
+ "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",
6059
6461
  disabled && "cursor-not-allowed opacity-50",
6060
6462
  className
6061
6463
  ),
@@ -6186,7 +6588,7 @@ var OtpInput = forwardRef(
6186
6588
  onFocus: (e) => e.target.select(),
6187
6589
  disabled,
6188
6590
  autoFocus: autoFocus && i === 0,
6189
- 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",
6591
+ 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",
6190
6592
  "aria-label": `Digit ${i + 1}`
6191
6593
  },
6192
6594
  i
@@ -6235,7 +6637,7 @@ function FileUploadDropzone({
6235
6637
  onClick: () => !disabled && inputRef.current?.click(),
6236
6638
  className: cn(
6237
6639
  "flex cursor-pointer flex-col items-center justify-center rounded-xl border-2 border-dashed p-8 text-center transition-colors",
6238
- 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",
6640
+ 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",
6239
6641
  disabled && "cursor-not-allowed opacity-50",
6240
6642
  className
6241
6643
  ),
@@ -7595,6 +7997,7 @@ function usePanZoom({
7595
7997
  if (!container) return;
7596
7998
  function handleWheel(e) {
7597
7999
  if (!zoomableRef.current) return;
8000
+ if (!e.ctrlKey && !e.metaKey) return;
7598
8001
  e.preventDefault();
7599
8002
  const rect = container.getBoundingClientRect();
7600
8003
  const mouseX = e.clientX - rect.left;
@@ -8787,9 +9190,11 @@ function useCanvas() {
8787
9190
  if (!ctx) throw new Error("useCanvas must be used within a Canvas component");
8788
9191
  return ctx;
8789
9192
  }
8790
- function CanvasNode({ children, id, x, y, className, style }) {
8791
- const { registerNode, unregisterNode } = useCanvas();
9193
+ function CanvasNode({ children, id, x, y, draggable, onPositionChange, className, style }) {
9194
+ const { registerNode, unregisterNode, viewport } = useCanvas();
8792
9195
  const nodeRef = useRef(null);
9196
+ const isDragging = useRef(false);
9197
+ const dragStart = useRef({ mouseX: 0, mouseY: 0, nodeX: 0, nodeY: 0 });
8793
9198
  useEffect(() => {
8794
9199
  const el = nodeRef.current;
8795
9200
  if (!el) return;
@@ -8804,14 +9209,39 @@ function CanvasNode({ children, id, x, y, className, style }) {
8804
9209
  unregisterNode(id);
8805
9210
  };
8806
9211
  }, [id, x, y, registerNode, unregisterNode]);
9212
+ const handlePointerDown = useCallback(
9213
+ (e) => {
9214
+ if (!draggable || e.button !== 0) return;
9215
+ e.stopPropagation();
9216
+ isDragging.current = true;
9217
+ dragStart.current = { mouseX: e.clientX, mouseY: e.clientY, nodeX: x, nodeY: y };
9218
+ e.target.setPointerCapture(e.pointerId);
9219
+ },
9220
+ [draggable, x, y]
9221
+ );
9222
+ const handlePointerMove = useCallback(
9223
+ (e) => {
9224
+ if (!isDragging.current) return;
9225
+ const dx = (e.clientX - dragStart.current.mouseX) / viewport.zoom;
9226
+ const dy = (e.clientY - dragStart.current.mouseY) / viewport.zoom;
9227
+ onPositionChange?.(dragStart.current.nodeX + dx, dragStart.current.nodeY + dy);
9228
+ },
9229
+ [viewport.zoom, onPositionChange]
9230
+ );
9231
+ const handlePointerUp = useCallback(() => {
9232
+ isDragging.current = false;
9233
+ }, []);
8807
9234
  return /* @__PURE__ */ jsx(
8808
9235
  "div",
8809
9236
  {
8810
9237
  ref: nodeRef,
8811
9238
  "data-react-fancy-canvas-node": "",
8812
9239
  "data-node-id": id,
8813
- className: cn("absolute", className),
9240
+ className: cn("absolute", draggable && "cursor-grab active:cursor-grabbing", className),
8814
9241
  style: { left: x, top: y, ...style },
9242
+ onPointerDown: handlePointerDown,
9243
+ onPointerMove: handlePointerMove,
9244
+ onPointerUp: handlePointerUp,
8815
9245
  children
8816
9246
  }
8817
9247
  );
@@ -8848,10 +9278,18 @@ function getAnchorPoint(rect, anchor, otherRect) {
8848
9278
  }
8849
9279
  }
8850
9280
  function bezierPath(from, to) {
8851
- const dx = Math.abs(to.x - from.x) * 0.5;
8852
- const cp1x = from.x + (to.x > from.x ? dx : -dx);
8853
- const cp2x = to.x + (to.x > from.x ? -dx : dx);
8854
- return `M${from.x},${from.y} C${cp1x},${from.y} ${cp2x},${to.y} ${to.x},${to.y}`;
9281
+ const dx = Math.abs(to.x - from.x);
9282
+ const dy = Math.abs(to.y - from.y);
9283
+ if (dx > dy) {
9284
+ const offset2 = dx * 0.5;
9285
+ const cp1x = from.x + (to.x > from.x ? offset2 : -offset2);
9286
+ const cp2x = to.x + (to.x > from.x ? -offset2 : offset2);
9287
+ return `M${from.x},${from.y} C${cp1x},${from.y} ${cp2x},${to.y} ${to.x},${to.y}`;
9288
+ }
9289
+ const offset = Math.max(dy * 0.5, 30);
9290
+ const cp1y = from.y + (to.y > from.y ? offset : -offset);
9291
+ const cp2y = to.y + (to.y > from.y ? -offset : offset);
9292
+ return `M${from.x},${from.y} C${from.x},${cp1y} ${to.x},${cp2y} ${to.x},${to.y}`;
8855
9293
  }
8856
9294
  function stepPath(from, to) {
8857
9295
  const midX = (from.x + to.x) / 2;
@@ -9036,6 +9474,7 @@ function CanvasRoot({
9036
9474
  pannable = true,
9037
9475
  zoomable = true,
9038
9476
  showGrid = false,
9477
+ fitOnMount = false,
9039
9478
  className,
9040
9479
  style
9041
9480
  }) {
@@ -9055,15 +9494,41 @@ function CanvasRoot({
9055
9494
  () => ({ viewport, setViewport, registerNode, unregisterNode, nodeRects, registryVersion, containerRef }),
9056
9495
  [viewport, setViewport, registerNode, unregisterNode, nodeRects, registryVersion]
9057
9496
  );
9497
+ const hasFitted = useRef(false);
9498
+ useEffect(() => {
9499
+ if (!fitOnMount || hasFitted.current || nodeRects.size === 0) return;
9500
+ const container = containerRef.current;
9501
+ if (!container || container.clientWidth === 0) return;
9502
+ hasFitted.current = true;
9503
+ requestAnimationFrame(() => {
9504
+ let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
9505
+ nodeRects.forEach((r) => {
9506
+ minX = Math.min(minX, r.x);
9507
+ minY = Math.min(minY, r.y);
9508
+ maxX = Math.max(maxX, r.x + r.width);
9509
+ maxY = Math.max(maxY, r.y + r.height);
9510
+ });
9511
+ const padding = 40;
9512
+ const contentW = maxX - minX + padding * 2;
9513
+ const contentH = maxY - minY + padding * 2;
9514
+ const cw = container.clientWidth;
9515
+ const ch = container.clientHeight;
9516
+ const zoom = Math.min(cw / contentW, ch / contentH, 1.5);
9517
+ const panX = (cw - contentW * zoom) / 2 - minX * zoom + padding * zoom;
9518
+ const panY = (ch - contentH * zoom) / 2 - minY * zoom + padding * zoom;
9519
+ setViewport({ panX, panY, zoom });
9520
+ });
9521
+ }, [fitOnMount, nodeRects, registryVersion, setViewport]);
9058
9522
  const edges = [];
9059
9523
  const others = [];
9060
9524
  const overlays = [];
9061
9525
  Children.forEach(children, (child) => {
9062
9526
  const el = child;
9063
9527
  if (!el || !el.type) return;
9064
- if (el.type === CanvasEdge) {
9528
+ const elType = el.type;
9529
+ if (elType === CanvasEdge || elType?._isCanvasEdge) {
9065
9530
  edges.push(el);
9066
- } else if (el.type === CanvasMinimap || el.type === CanvasControls) {
9531
+ } else if (elType === CanvasMinimap || elType === CanvasControls) {
9067
9532
  overlays.push(el);
9068
9533
  } else {
9069
9534
  others.push(el);
@@ -9110,15 +9575,16 @@ function CanvasRoot({
9110
9575
  },
9111
9576
  children: [
9112
9577
  /* @__PURE__ */ jsxs("defs", { children: [
9113
- /* @__PURE__ */ jsx("marker", { id: "canvas-arrow", viewBox: "0 0 10 10", refX: "10", refY: "5", markerWidth: "8", markerHeight: "8", orient: "auto-start-reverse", children: /* @__PURE__ */ jsx("path", { d: "M0,0 L10,5 L0,10 Z", fill: "currentColor", className: "text-zinc-400 dark:text-zinc-500" }) }),
9114
- /* @__PURE__ */ jsx("marker", { id: "canvas-circle", viewBox: "0 0 10 10", refX: "5", refY: "5", markerWidth: "8", markerHeight: "8", orient: "auto-start-reverse", children: /* @__PURE__ */ jsx("circle", { cx: "5", cy: "5", r: "3.5", fill: "currentColor", className: "text-zinc-400 dark:text-zinc-500" }) }),
9115
- /* @__PURE__ */ jsx("marker", { id: "canvas-square", viewBox: "0 0 10 10", refX: "5", refY: "5", markerWidth: "8", markerHeight: "8", orient: "auto-start-reverse", children: /* @__PURE__ */ jsx("rect", { x: "1.5", y: "1.5", width: "7", height: "7", fill: "currentColor", className: "text-zinc-400 dark:text-zinc-500" }) }),
9116
- /* @__PURE__ */ jsxs("marker", { id: "canvas-crow-foot", viewBox: "0 0 12 12", refX: "12", refY: "6", markerWidth: "10", markerHeight: "10", orient: "auto-start-reverse", children: [
9117
- /* @__PURE__ */ jsx("line", { x1: "0", y1: "0", x2: "12", y2: "6", stroke: "currentColor", strokeWidth: "1.5", className: "text-zinc-400 dark:text-zinc-500" }),
9118
- /* @__PURE__ */ jsx("line", { x1: "0", y1: "12", x2: "12", y2: "6", stroke: "currentColor", strokeWidth: "1.5", className: "text-zinc-400 dark:text-zinc-500" }),
9119
- /* @__PURE__ */ jsx("line", { x1: "0", y1: "6", x2: "12", y2: "6", stroke: "currentColor", strokeWidth: "1.5", className: "text-zinc-400 dark:text-zinc-500" })
9120
- ] }),
9121
- /* @__PURE__ */ jsx("marker", { id: "canvas-diamond", viewBox: "0 0 12 12", refX: "6", refY: "6", markerWidth: "10", markerHeight: "10", orient: "auto-start-reverse", children: /* @__PURE__ */ 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" }) })
9578
+ /* @__PURE__ */ jsx("marker", { id: "canvas-arrow", viewBox: "0 0 10 10", refX: "10", refY: "5", markerWidth: "8", markerHeight: "8", orient: "auto", children: /* @__PURE__ */ jsx("path", { d: "M0,0 L10,5 L0,10 Z", fill: "#71717a" }) }),
9579
+ /* @__PURE__ */ jsx("marker", { id: "canvas-circle", viewBox: "0 0 10 10", refX: "5", refY: "5", markerWidth: "8", markerHeight: "8", orient: "auto", children: /* @__PURE__ */ jsx("circle", { cx: "5", cy: "5", r: "3.5", fill: "#71717a" }) }),
9580
+ /* @__PURE__ */ jsx("marker", { id: "canvas-diamond", viewBox: "0 0 12 12", refX: "6", refY: "6", markerWidth: "10", markerHeight: "10", orient: "auto", children: /* @__PURE__ */ jsx("polygon", { points: "6,0 12,6 6,12 0,6", fill: "none", stroke: "#71717a", strokeWidth: "1.5" }) }),
9581
+ /* @__PURE__ */ jsx("marker", { id: "canvas-one", viewBox: "0 0 2 16", refX: "1", refY: "8", markerWidth: "2", markerHeight: "14", orient: "auto", children: /* @__PURE__ */ jsx("line", { x1: "1", y1: "0", x2: "1", y2: "16", stroke: "#71717a", strokeWidth: "2" }) }),
9582
+ /* @__PURE__ */ jsxs("marker", { id: "canvas-crow-foot", viewBox: "0 0 16 16", refX: "16", refY: "8", markerWidth: "14", markerHeight: "14", orient: "auto", children: [
9583
+ /* @__PURE__ */ jsx("line", { x1: "16", y1: "8", x2: "0", y2: "0", stroke: "#71717a", strokeWidth: "2", strokeLinecap: "round" }),
9584
+ /* @__PURE__ */ jsx("line", { x1: "16", y1: "8", x2: "0", y2: "8", stroke: "#71717a", strokeWidth: "2", strokeLinecap: "round" }),
9585
+ /* @__PURE__ */ jsx("line", { x1: "16", y1: "8", x2: "0", y2: "16", stroke: "#71717a", strokeWidth: "2", strokeLinecap: "round" }),
9586
+ /* @__PURE__ */ jsx("line", { x1: "16", y1: "0", x2: "16", y2: "16", stroke: "#71717a", strokeWidth: "2", strokeLinecap: "round" })
9587
+ ] })
9122
9588
  ] }),
9123
9589
  edges
9124
9590
  ]
@@ -9176,13 +9642,16 @@ function DiagramField({
9176
9642
  DiagramField.displayName = "DiagramField";
9177
9643
  function DiagramEntity({
9178
9644
  children,
9179
- id,
9645
+ id: idProp,
9180
9646
  name,
9181
9647
  x = 0,
9182
9648
  y = 0,
9183
9649
  color = "bg-blue-600 dark:bg-blue-500",
9650
+ draggable,
9651
+ onPositionChange,
9184
9652
  className
9185
9653
  }) {
9654
+ const id = idProp ?? name;
9186
9655
  const fields = [];
9187
9656
  const other = [];
9188
9657
  Children.forEach(children, (child) => {
@@ -9194,7 +9663,7 @@ function DiagramEntity({
9194
9663
  other.push(el);
9195
9664
  }
9196
9665
  });
9197
- return /* @__PURE__ */ jsx(Canvas.Node, { id, x, y, children: /* @__PURE__ */ jsxs(
9666
+ return /* @__PURE__ */ jsx(Canvas.Node, { id, x, y, draggable, onPositionChange, children: /* @__PURE__ */ jsxs(
9198
9667
  "div",
9199
9668
  {
9200
9669
  "data-react-fancy-diagram-entity": "",
@@ -9221,37 +9690,193 @@ function DiagramEntity({
9221
9690
  ) });
9222
9691
  }
9223
9692
  DiagramEntity.displayName = "DiagramEntity";
9224
- function getMarkers(type) {
9225
- switch (type) {
9226
- case "one-to-one":
9227
- return { markerStart: "canvas-arrow", markerEnd: "canvas-arrow" };
9228
- case "one-to-many":
9229
- return { markerStart: "canvas-arrow", markerEnd: "canvas-crow-foot" };
9230
- case "many-to-many":
9231
- return { markerStart: "canvas-crow-foot", markerEnd: "canvas-crow-foot" };
9693
+ var HEADER_HEIGHT = 36;
9694
+ var FIELD_HEIGHT = 28;
9695
+ var SYMBOL_SIZE = 12;
9696
+ function oneSymbol(pt, direction) {
9697
+ const s = SYMBOL_SIZE * 0.6;
9698
+ switch (direction) {
9699
+ case "left":
9700
+ case "right":
9701
+ return `M${pt.x},${pt.y - s} L${pt.x},${pt.y + s}`;
9702
+ case "up":
9703
+ case "down":
9704
+ return `M${pt.x - s},${pt.y} L${pt.x + s},${pt.y}`;
9705
+ }
9706
+ }
9707
+ function crowFootSymbol(pt, direction) {
9708
+ const s = SYMBOL_SIZE;
9709
+ const spread = s * 0.8;
9710
+ let tip;
9711
+ switch (direction) {
9712
+ case "right":
9713
+ tip = { x: pt.x - s, y: pt.y };
9714
+ return [
9715
+ `M${pt.x},${pt.y - spread} L${tip.x},${tip.y}`,
9716
+ `M${pt.x},${pt.y} L${tip.x},${tip.y}`,
9717
+ `M${pt.x},${pt.y + spread} L${tip.x},${tip.y}`,
9718
+ // bar at entity edge
9719
+ `M${pt.x},${pt.y - spread} L${pt.x},${pt.y + spread}`
9720
+ ].join(" ");
9721
+ case "left":
9722
+ tip = { x: pt.x + s, y: pt.y };
9723
+ return [
9724
+ `M${pt.x},${pt.y - spread} L${tip.x},${tip.y}`,
9725
+ `M${pt.x},${pt.y} L${tip.x},${tip.y}`,
9726
+ `M${pt.x},${pt.y + spread} L${tip.x},${tip.y}`,
9727
+ `M${pt.x},${pt.y - spread} L${pt.x},${pt.y + spread}`
9728
+ ].join(" ");
9729
+ case "down":
9730
+ tip = { x: pt.x, y: pt.y - s };
9731
+ return [
9732
+ `M${pt.x - spread},${pt.y} L${tip.x},${tip.y}`,
9733
+ `M${pt.x},${pt.y} L${tip.x},${tip.y}`,
9734
+ `M${pt.x + spread},${pt.y} L${tip.x},${tip.y}`,
9735
+ `M${pt.x - spread},${pt.y} L${pt.x + spread},${pt.y}`
9736
+ ].join(" ");
9737
+ case "up":
9738
+ tip = { x: pt.x, y: pt.y + s };
9739
+ return [
9740
+ `M${pt.x - spread},${pt.y} L${tip.x},${tip.y}`,
9741
+ `M${pt.x},${pt.y} L${tip.x},${tip.y}`,
9742
+ `M${pt.x + spread},${pt.y} L${tip.x},${tip.y}`,
9743
+ `M${pt.x - spread},${pt.y} L${pt.x + spread},${pt.y}`
9744
+ ].join(" ");
9232
9745
  }
9233
9746
  }
9747
+ function getSymbolPath(type, end, pt, direction) {
9748
+ const side = end === "start" ? type.split("-to-")[0] : type.split("-to-")[1];
9749
+ if (side === "one") return oneSymbol(pt, direction);
9750
+ if (side === "many") return crowFootSymbol(pt, direction);
9751
+ return null;
9752
+ }
9234
9753
  function DiagramRelation({
9235
9754
  from,
9236
9755
  to,
9756
+ fromField: fromFieldProp,
9757
+ toField: toFieldProp,
9237
9758
  type,
9238
- label,
9239
- className
9759
+ label
9240
9760
  }) {
9241
- const markers = getMarkers(type);
9242
- return /* @__PURE__ */ jsx(
9243
- Canvas.Edge,
9244
- {
9245
- from,
9246
- to,
9247
- curve: "bezier",
9248
- markerStart: markers.markerStart,
9249
- markerEnd: markers.markerEnd,
9250
- label,
9251
- className: cn("text-zinc-300 dark:text-zinc-600", className)
9761
+ const { nodeRects, registryVersion } = useCanvas();
9762
+ const { schema } = useDiagram();
9763
+ const result = useMemo(() => {
9764
+ const fromRect = nodeRects.get(from);
9765
+ const toRect = nodeRects.get(to);
9766
+ if (!fromRect || !toRect) return null;
9767
+ const fromEntity = schema.entities.find((e) => (e.id ?? e.name) === from);
9768
+ const toEntity = schema.entities.find((e) => (e.id ?? e.name) === to);
9769
+ let fromFieldIdx = -1;
9770
+ let toFieldIdx = -1;
9771
+ if (fromFieldProp && fromEntity?.fields) {
9772
+ fromFieldIdx = fromEntity.fields.findIndex((f) => f.name === fromFieldProp);
9773
+ } else if (fromEntity?.fields) {
9774
+ fromFieldIdx = fromEntity.fields.findIndex((f) => f.primary);
9252
9775
  }
9253
- );
9776
+ if (toFieldProp && toEntity?.fields) {
9777
+ toFieldIdx = toEntity.fields.findIndex((f) => f.name === toFieldProp);
9778
+ } else if (toEntity?.fields) {
9779
+ const fromName = (fromEntity?.name ?? from).toLowerCase();
9780
+ toFieldIdx = toEntity.fields.findIndex(
9781
+ (f) => f.foreign && (f.name === `${fromName}_id` || f.name === `${fromName}Id`)
9782
+ );
9783
+ if (toFieldIdx === -1) {
9784
+ toFieldIdx = toEntity.fields.findIndex((f) => f.foreign);
9785
+ }
9786
+ }
9787
+ const fromFieldY = fromFieldIdx >= 0 ? HEADER_HEIGHT + fromFieldIdx * FIELD_HEIGHT + FIELD_HEIGHT / 2 : fromRect.height / 2;
9788
+ const toFieldY = toFieldIdx >= 0 ? HEADER_HEIGHT + toFieldIdx * FIELD_HEIGHT + FIELD_HEIGHT / 2 : toRect.height / 2;
9789
+ const fromCx = fromRect.x + fromRect.width / 2;
9790
+ const toCx = toRect.x + toRect.width / 2;
9791
+ const fromCy = fromRect.y + fromRect.height / 2;
9792
+ const toCy = toRect.y + toRect.height / 2;
9793
+ const dx = Math.abs(fromCx - toCx);
9794
+ const dy = Math.abs(fromCy - toCy);
9795
+ let fromPt, toPt;
9796
+ let fromDir;
9797
+ let toDir;
9798
+ if (dx > dy * 0.5) {
9799
+ if (fromCx < toCx) {
9800
+ fromPt = { x: fromRect.x + fromRect.width, y: fromRect.y + fromFieldY };
9801
+ toPt = { x: toRect.x, y: toRect.y + toFieldY };
9802
+ fromDir = "right";
9803
+ toDir = "left";
9804
+ } else {
9805
+ fromPt = { x: fromRect.x, y: fromRect.y + fromFieldY };
9806
+ toPt = { x: toRect.x + toRect.width, y: toRect.y + toFieldY };
9807
+ fromDir = "left";
9808
+ toDir = "right";
9809
+ }
9810
+ } else {
9811
+ if (fromCy < toCy) {
9812
+ fromPt = { x: fromRect.x + fromRect.width / 2, y: fromRect.y + fromRect.height };
9813
+ toPt = { x: toRect.x + toRect.width / 2, y: toRect.y };
9814
+ fromDir = "down";
9815
+ toDir = "up";
9816
+ } else {
9817
+ fromPt = { x: fromRect.x + fromRect.width / 2, y: fromRect.y };
9818
+ toPt = { x: toRect.x + toRect.width / 2, y: toRect.y + toRect.height };
9819
+ fromDir = "up";
9820
+ toDir = "down";
9821
+ }
9822
+ }
9823
+ const offsetFrom = { ...fromPt };
9824
+ const offsetTo = { ...toPt };
9825
+ switch (fromDir) {
9826
+ case "right":
9827
+ offsetFrom.x += SYMBOL_SIZE;
9828
+ break;
9829
+ case "left":
9830
+ offsetFrom.x -= SYMBOL_SIZE;
9831
+ break;
9832
+ case "down":
9833
+ offsetFrom.y += SYMBOL_SIZE;
9834
+ break;
9835
+ case "up":
9836
+ offsetFrom.y -= SYMBOL_SIZE;
9837
+ break;
9838
+ }
9839
+ switch (toDir) {
9840
+ case "right":
9841
+ offsetTo.x += SYMBOL_SIZE;
9842
+ break;
9843
+ case "left":
9844
+ offsetTo.x -= SYMBOL_SIZE;
9845
+ break;
9846
+ case "down":
9847
+ offsetTo.y += SYMBOL_SIZE;
9848
+ break;
9849
+ case "up":
9850
+ offsetTo.y -= SYMBOL_SIZE;
9851
+ break;
9852
+ }
9853
+ const adx = Math.abs(offsetTo.x - offsetFrom.x);
9854
+ const ady = Math.abs(offsetTo.y - offsetFrom.y);
9855
+ let linePath;
9856
+ if (adx > ady) {
9857
+ const off = adx * 0.4;
9858
+ const cp1x = offsetFrom.x + (offsetTo.x > offsetFrom.x ? off : -off);
9859
+ const cp2x = offsetTo.x + (offsetTo.x > offsetFrom.x ? -off : off);
9860
+ linePath = `M${offsetFrom.x},${offsetFrom.y} C${cp1x},${offsetFrom.y} ${cp2x},${offsetTo.y} ${offsetTo.x},${offsetTo.y}`;
9861
+ } else {
9862
+ const off = Math.max(ady * 0.4, 20);
9863
+ const cp1y = offsetFrom.y + (offsetTo.y > offsetFrom.y ? off : -off);
9864
+ const cp2y = offsetTo.y + (offsetTo.y > offsetFrom.y ? -off : off);
9865
+ linePath = `M${offsetFrom.x},${offsetFrom.y} C${offsetFrom.x},${cp1y} ${offsetTo.x},${cp2y} ${offsetTo.x},${offsetTo.y}`;
9866
+ }
9867
+ const startSymbol = getSymbolPath(type, "start", fromPt, fromDir);
9868
+ const endSymbol = getSymbolPath(type, "end", toPt, toDir);
9869
+ return { linePath, startSymbol, endSymbol, midX: (offsetFrom.x + offsetTo.x) / 2, midY: (offsetFrom.y + offsetTo.y) / 2 };
9870
+ }, [from, to, fromFieldProp, toFieldProp, type, schema, nodeRects, registryVersion]);
9871
+ if (!result) return null;
9872
+ return /* @__PURE__ */ jsxs("g", { "data-react-fancy-diagram-relation": "", children: [
9873
+ /* @__PURE__ */ jsx("path", { d: result.linePath, fill: "none", stroke: "#71717a", strokeWidth: 2 }),
9874
+ result.startSymbol && /* @__PURE__ */ jsx("path", { d: result.startSymbol, fill: "none", stroke: "#71717a", strokeWidth: 2 }),
9875
+ result.endSymbol && /* @__PURE__ */ jsx("path", { d: result.endSymbol, fill: "none", stroke: "#71717a", strokeWidth: 2 }),
9876
+ label && /* @__PURE__ */ jsx("foreignObject", { x: result.midX - 40, y: result.midY - 12, width: 80, height: 24, children: /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center text-xs text-zinc-500", children: label }) })
9877
+ ] });
9254
9878
  }
9879
+ DiagramRelation._isCanvasEdge = true;
9255
9880
  DiagramRelation.displayName = "DiagramRelation";
9256
9881
  var FORMAT_LABELS = {
9257
9882
  erd: "ERD",
@@ -9362,12 +9987,12 @@ DiagramToolbar.displayName = "DiagramToolbar";
9362
9987
 
9363
9988
  // src/components/Diagram/diagram.layout.ts
9364
9989
  var ENTITY_WIDTH = 220;
9365
- var HEADER_HEIGHT = 40;
9366
- var FIELD_HEIGHT = 28;
9990
+ var HEADER_HEIGHT2 = 40;
9991
+ var FIELD_HEIGHT2 = 28;
9367
9992
  var HORIZONTAL_GAP = 80;
9368
9993
  var VERTICAL_GAP = 60;
9369
9994
  function getEntityHeight(fieldCount) {
9370
- return HEADER_HEIGHT + Math.max(fieldCount, 1) * FIELD_HEIGHT;
9995
+ return HEADER_HEIGHT2 + Math.max(fieldCount, 1) * FIELD_HEIGHT2;
9371
9996
  }
9372
9997
  function computeDiagramLayout(schema) {
9373
9998
  const positions = /* @__PURE__ */ new Map();
@@ -9470,58 +10095,103 @@ function DiagramRoot({
9470
10095
  }) {
9471
10096
  const downloadableRef = useRef(downloadable);
9472
10097
  const importableRef = useRef(importable);
9473
- const resolvedSchema = useMemo(() => {
10098
+ const normalizedSchema = useMemo(() => {
9474
10099
  if (!schema) return { entities: [], relations: [] };
9475
- const layout = computeDiagramLayout(schema);
9476
- const entities = schema.entities.map((entity) => {
9477
- if (entity.x !== void 0 && entity.y !== void 0) return entity;
9478
- const pos = layout.get(entity.id);
9479
- return pos ? { ...entity, x: pos.x, y: pos.y } : entity;
9480
- });
9481
- return { entities, relations: schema.relations };
10100
+ const entities = schema.entities.map((e) => ({
10101
+ ...e,
10102
+ id: e.id ?? e.name
10103
+ }));
10104
+ const relations = schema.relations.map((r, i) => ({
10105
+ ...r,
10106
+ id: r.id ?? `rel-${i}`
10107
+ }));
10108
+ return { entities, relations };
9482
10109
  }, [schema]);
10110
+ const initialPositions = useMemo(() => {
10111
+ if (normalizedSchema.entities.length === 0) return /* @__PURE__ */ new Map();
10112
+ const layout = computeDiagramLayout(normalizedSchema);
10113
+ const positions = /* @__PURE__ */ new Map();
10114
+ for (const entity of normalizedSchema.entities) {
10115
+ if (entity.x !== void 0 && entity.y !== void 0) {
10116
+ positions.set(entity.id, { x: entity.x, y: entity.y });
10117
+ } else {
10118
+ const pos = layout.get(entity.id);
10119
+ positions.set(entity.id, pos ?? { x: 0, y: 0 });
10120
+ }
10121
+ }
10122
+ return positions;
10123
+ }, [normalizedSchema]);
10124
+ const computedDefaultViewport = useMemo(() => {
10125
+ if (defaultViewport) return defaultViewport;
10126
+ if (initialPositions.size === 0) return { panX: 0, panY: 0, zoom: 1 };
10127
+ let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
10128
+ initialPositions.forEach((pos) => {
10129
+ minX = Math.min(minX, pos.x);
10130
+ minY = Math.min(minY, pos.y);
10131
+ maxX = Math.max(maxX, pos.x + 220);
10132
+ maxY = Math.max(maxY, pos.y + 200);
10133
+ });
10134
+ const padding = 40;
10135
+ const panX = -minX + padding;
10136
+ const panY = -minY + padding;
10137
+ return { panX, panY, zoom: 1 };
10138
+ }, [defaultViewport, initialPositions]);
10139
+ const [entityPositions, setEntityPositions] = useState(initialPositions);
10140
+ const handleEntityMove = useCallback((entityId, x, y) => {
10141
+ setEntityPositions((prev) => {
10142
+ const next = new Map(prev);
10143
+ next.set(entityId, { x, y });
10144
+ return next;
10145
+ });
10146
+ }, []);
9483
10147
  const ctx = useMemo(
9484
10148
  () => ({
9485
10149
  diagramType: type,
9486
- schema: resolvedSchema,
10150
+ schema: normalizedSchema,
9487
10151
  downloadableRef,
9488
10152
  importableRef,
9489
10153
  exportFormats,
9490
10154
  onImport
9491
10155
  }),
9492
- [type, resolvedSchema, exportFormats, onImport]
10156
+ [type, normalizedSchema, exportFormats, onImport]
9493
10157
  );
9494
10158
  return /* @__PURE__ */ jsx(DiagramContext.Provider, { value: ctx, children: /* @__PURE__ */ jsx("div", { "data-react-fancy-diagram": "", className: "relative h-full w-full", children: /* @__PURE__ */ jsxs(
9495
10159
  Canvas,
9496
10160
  {
9497
10161
  viewport,
9498
- defaultViewport,
10162
+ defaultViewport: computedDefaultViewport,
9499
10163
  onViewportChange,
9500
10164
  showGrid: true,
10165
+ fitOnMount: true,
9501
10166
  className: cn("h-full w-full", className),
9502
10167
  children: [
9503
- resolvedSchema.entities.map((entity) => /* @__PURE__ */ jsx(
9504
- DiagramEntity,
9505
- {
9506
- id: entity.id,
9507
- name: entity.name,
9508
- x: entity.x ?? 0,
9509
- y: entity.y ?? 0,
9510
- children: entity.fields?.map((field) => /* @__PURE__ */ jsx(
9511
- DiagramField,
9512
- {
9513
- name: field.name,
9514
- type: field.type,
9515
- primary: field.primary,
9516
- foreign: field.foreign,
9517
- nullable: field.nullable
9518
- },
9519
- field.name
9520
- ))
9521
- },
9522
- entity.id
9523
- )),
9524
- resolvedSchema.relations.map((rel) => /* @__PURE__ */ jsx(
10168
+ normalizedSchema.entities.map((entity) => {
10169
+ const pos = entityPositions.get(entity.id) ?? { x: 0, y: 0 };
10170
+ return /* @__PURE__ */ jsx(
10171
+ DiagramEntity,
10172
+ {
10173
+ id: entity.id,
10174
+ name: entity.name,
10175
+ x: pos.x,
10176
+ y: pos.y,
10177
+ draggable: true,
10178
+ onPositionChange: (nx, ny) => handleEntityMove(entity.id, nx, ny),
10179
+ children: entity.fields?.map((field) => /* @__PURE__ */ jsx(
10180
+ DiagramField,
10181
+ {
10182
+ name: field.name,
10183
+ type: field.type,
10184
+ primary: field.primary,
10185
+ foreign: field.foreign,
10186
+ nullable: field.nullable
10187
+ },
10188
+ field.name
10189
+ ))
10190
+ },
10191
+ entity.id
10192
+ );
10193
+ }),
10194
+ normalizedSchema.relations.map((rel) => /* @__PURE__ */ jsx(
9525
10195
  DiagramRelation,
9526
10196
  {
9527
10197
  from: rel.from,