@openspecui/web 2.3.4 → 2.3.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/dist/assets/CanvasRenderer-BxSz1S0N.js +1 -0
  2. package/dist/assets/WebGLRenderer-BHhaJ0WJ.js +1 -0
  3. package/dist/assets/WebGPURenderer-DRKX39eh.js +1 -0
  4. package/dist/assets/browserAll-D4RKeA6K.js +1 -0
  5. package/dist/assets/{dist-CyMw4R35.js → dist-7cHS5QWY.js} +1 -1
  6. package/dist/assets/dist-BDHG1KDB.js +1 -0
  7. package/dist/assets/{dist-C4qVWCwa.js → dist-Bmr28z9J.js} +1 -1
  8. package/dist/assets/{dist-Dw21sB44.js → dist-C9Q1D0iI.js} +1 -1
  9. package/dist/assets/{dist-BURffNKn.js → dist-CHCyBtgU.js} +1 -1
  10. package/dist/assets/{dist-BkfyzqOP.js → dist-CbImOnsL.js} +1 -1
  11. package/dist/assets/dist-CdimBSfI.js +1 -0
  12. package/dist/assets/{dist-0r-fEysT.js → dist-DmBfd3XF.js} +1 -1
  13. package/dist/assets/{dist-D5aqbpvI.js → dist-DoTjw0T7.js} +1 -1
  14. package/dist/assets/{dist-C_hvHrdB.js → dist-Pw11Vg0a.js} +1 -1
  15. package/dist/assets/dist-TbE9jx_5.js +1 -0
  16. package/dist/assets/{dist-EecJWaiB.js → dist-o5BKCN_5.js} +1 -1
  17. package/dist/assets/{ghostty-web-Dz4JIhSO.js → ghostty-web-BM-sB-n_.js} +1 -1
  18. package/dist/assets/index-Cf-Bih4u.css +1 -0
  19. package/dist/assets/{index-CEpXtWXm.js → index-DglxXq77.js} +92 -102
  20. package/dist/assets/{init-B8PdfdJA.js → init-CdYLlWcL.js} +1 -1
  21. package/dist/assets/trpc-Zloaz5j7.js +1 -0
  22. package/dist/assets/webworkerAll-B7pVi8gv.js +1 -0
  23. package/dist/index.html +2 -2
  24. package/dist-ssg/client/.vite/ssr-manifest.json +15 -15
  25. package/dist-ssg/client/assets/CanvasRenderer-C1h1JIPm.js +1 -0
  26. package/dist-ssg/client/assets/WebGLRenderer-8qk8lSz6.js +1 -0
  27. package/dist-ssg/client/assets/WebGPURenderer-BevM1O4I.js +1 -0
  28. package/dist-ssg/client/assets/browserAll-BgTDhPwL.js +1 -0
  29. package/dist-ssg/client/assets/{dist-DXnJ5xkX.js → dist-BA9W6ivS.js} +1 -1
  30. package/dist-ssg/client/assets/dist-BHVRVW4G.js +1 -0
  31. package/dist-ssg/client/assets/{dist-BTUS8_Kw.js → dist-BKmYJegx.js} +1 -1
  32. package/dist-ssg/client/assets/{dist-D9LLad4O.js → dist-Bl-Yx_pv.js} +1 -1
  33. package/dist-ssg/client/assets/{dist-Djq8HFE5.js → dist-Bo9PZWYk.js} +1 -1
  34. package/dist-ssg/client/assets/{dist-CXFa4KDE.js → dist-BrVgy81Q.js} +1 -1
  35. package/dist-ssg/client/assets/dist-CI8PS7VF.js +1 -0
  36. package/dist-ssg/client/assets/dist-CM3Rniow.js +1 -0
  37. package/dist-ssg/client/assets/{dist-DTnge_6G.js → dist-COdrA1_m.js} +1 -1
  38. package/dist-ssg/client/assets/{dist-BARr54Cb.js → dist-DcyfplO7.js} +1 -1
  39. package/dist-ssg/client/assets/{dist-C77YNQqE.js → dist-GfVlAkN6.js} +1 -1
  40. package/dist-ssg/client/assets/{dist-B7XHcG34.js → dist-VPeqJD5l.js} +1 -1
  41. package/dist-ssg/client/assets/{ghostty-web-BUrM4LmE.js → ghostty-web-DoANeMT4.js} +1 -1
  42. package/dist-ssg/client/assets/index-DbX4vFFM.css +2 -0
  43. package/dist-ssg/client/assets/{index.ssg-BU1Brmat.js → index.ssg-C2pskB7h.js} +94 -104
  44. package/dist-ssg/client/assets/{init-C5vrHUuy.js → init-DSot5A37.js} +1 -1
  45. package/dist-ssg/client/assets/trpc-T5xQ21pb.js +1 -0
  46. package/dist-ssg/client/assets/webworkerAll-DnUz-KLD.js +1 -0
  47. package/dist-ssg/client/index.ssg.html +2 -2
  48. package/dist-ssg/server/entry-server.js +412 -107
  49. package/package.json +1 -1
  50. package/dist/assets/CanvasRenderer-yznXxqwr.js +0 -1
  51. package/dist/assets/WebGLRenderer-B23oVGNL.js +0 -1
  52. package/dist/assets/WebGPURenderer-Cug1nwkB.js +0 -1
  53. package/dist/assets/browserAll-u3VqlriV.js +0 -1
  54. package/dist/assets/dist-COUYL3BZ.js +0 -1
  55. package/dist/assets/dist-hmhW0TeA.js +0 -1
  56. package/dist/assets/dist-zq2gg3Qj.js +0 -1
  57. package/dist/assets/index-C5SoNGIA.css +0 -1
  58. package/dist/assets/trpc-BYEnVZ4c.js +0 -1
  59. package/dist/assets/webworkerAll-BiDEVq-n.js +0 -1
  60. package/dist-ssg/client/assets/CanvasRenderer-BCXK2PVZ.js +0 -1
  61. package/dist-ssg/client/assets/WebGLRenderer-i-T4B6u4.js +0 -1
  62. package/dist-ssg/client/assets/WebGPURenderer-H88EZaPN.js +0 -1
  63. package/dist-ssg/client/assets/browserAll-Ae_1KSeb.js +0 -1
  64. package/dist-ssg/client/assets/dist-D_3RMJZl.js +0 -1
  65. package/dist-ssg/client/assets/dist-ENBjyU2l.js +0 -1
  66. package/dist-ssg/client/assets/dist-VCVOuFg2.js +0 -1
  67. package/dist-ssg/client/assets/index-BE9cyYzu.css +0 -2
  68. package/dist-ssg/client/assets/trpc-3tg7hkky.js +0 -1
  69. package/dist-ssg/client/assets/webworkerAll-eiudlEPb.js +0 -1
@@ -7433,7 +7433,7 @@ function useSearch(opts) {
7433
7433
  }
7434
7434
  //#endregion
7435
7435
  //#region ../../node_modules/.pnpm/@tanstack+react-router@1.139.3_react-dom@19.2.0_react@19.2.0__react@19.2.0/node_modules/@tanstack/react-router/dist/esm/utils.js
7436
- var useLayoutEffect$5 = typeof window !== "undefined" ? import_react.useLayoutEffect : import_react.useEffect;
7436
+ var useLayoutEffect$7 = typeof window !== "undefined" ? import_react.useLayoutEffect : import_react.useEffect;
7437
7437
  function usePrevious(value) {
7438
7438
  const ref = import_react.useRef({
7439
7439
  value,
@@ -8353,7 +8353,7 @@ function Transitioner() {
8353
8353
  unsub();
8354
8354
  };
8355
8355
  }, [router, router.history]);
8356
- useLayoutEffect$5(() => {
8356
+ useLayoutEffect$7(() => {
8357
8357
  if (typeof window !== "undefined" && router.ssr || mountLoadForRouter.current.router === router && mountLoadForRouter.current.mounted) return;
8358
8358
  mountLoadForRouter.current = {
8359
8359
  router,
@@ -8368,7 +8368,7 @@ function Transitioner() {
8368
8368
  };
8369
8369
  tryLoad();
8370
8370
  }, [router]);
8371
- useLayoutEffect$5(() => {
8371
+ useLayoutEffect$7(() => {
8372
8372
  if (previousIsLoading && !isLoading) router.emit({
8373
8373
  type: "onLoad",
8374
8374
  ...getLocationChangeInfo(router.state)
@@ -8378,7 +8378,7 @@ function Transitioner() {
8378
8378
  router,
8379
8379
  isLoading
8380
8380
  ]);
8381
- useLayoutEffect$5(() => {
8381
+ useLayoutEffect$7(() => {
8382
8382
  if (previousIsPagePending && !isPagePending) router.emit({
8383
8383
  type: "onBeforeRouteMount",
8384
8384
  ...getLocationChangeInfo(router.state)
@@ -8388,7 +8388,7 @@ function Transitioner() {
8388
8388
  previousIsPagePending,
8389
8389
  router
8390
8390
  ]);
8391
- useLayoutEffect$5(() => {
8391
+ useLayoutEffect$7(() => {
8392
8392
  if (previousIsAnyPending && !isAnyPending) {
8393
8393
  const changeInfo = getLocationChangeInfo(router.state);
8394
8394
  router.emit({
@@ -39494,8 +39494,9 @@ function waitForDomCommit() {
39494
39494
  setTimeout(finish, 0);
39495
39495
  });
39496
39496
  }
39497
- function filterDistinctEntries(entries, beforeEntries) {
39497
+ function filterAfterEntries(entries, beforeEntries, intent) {
39498
39498
  if (beforeEntries.length === 0) return entries;
39499
+ if (intent.kind === "tab-carousel") return entries;
39499
39500
  const beforeElements = new Set(beforeEntries.map(([element]) => element));
39500
39501
  return entries.filter(([element]) => !beforeElements.has(element));
39501
39502
  }
@@ -39503,15 +39504,15 @@ async function collectSettledAfterEntries(beforeEntries, collectAfterEntries, in
39503
39504
  if (!collectAfterEntries) return [];
39504
39505
  if (intent.kind === "route-detail" && beforeEntries.length > 0) return waitForNamedEntriesReady({
39505
39506
  expectedNames: beforeEntries.map(([, name]) => name),
39506
- collectEntries: () => filterDistinctEntries(collectAfterEntries(), beforeEntries)
39507
+ collectEntries: () => filterAfterEntries(collectAfterEntries(), beforeEntries, intent)
39507
39508
  });
39508
39509
  for (let attempt = 0; attempt < 4; attempt += 1) {
39509
- const entries = filterDistinctEntries(collectAfterEntries(), beforeEntries);
39510
+ const entries = filterAfterEntries(collectAfterEntries(), beforeEntries, intent);
39510
39511
  if (entries.length > 0 || beforeEntries.length === 0) return entries;
39511
39512
  await Promise.resolve();
39512
39513
  await waitForDomCommit();
39513
39514
  }
39514
- return filterDistinctEntries(collectAfterEntries(), beforeEntries);
39515
+ return filterAfterEntries(collectAfterEntries(), beforeEntries, intent);
39515
39516
  }
39516
39517
  function setIntentDataset(intent) {
39517
39518
  const root = document.documentElement;
@@ -41086,6 +41087,7 @@ function useServerStatus() {
41086
41087
  projectDir: null,
41087
41088
  dirName: null,
41088
41089
  watcherEnabled: false,
41090
+ projectRecovery: { state: "idle" },
41089
41091
  error: null,
41090
41092
  wsState: "idle",
41091
41093
  reconnectCountdown: null
@@ -41150,6 +41152,7 @@ function useServerStatus() {
41150
41152
  projectDir: "Static Export",
41151
41153
  dirName: "Static Export",
41152
41154
  watcherEnabled: false,
41155
+ projectRecovery: { state: "idle" },
41153
41156
  error: null
41154
41157
  }));
41155
41158
  document.title = "OpenSpec UI (Static)";
@@ -41165,6 +41168,7 @@ function useServerStatus() {
41165
41168
  projectDir,
41166
41169
  dirName,
41167
41170
  watcherEnabled: data.watcherEnabled,
41171
+ projectRecovery: data.projectRecovery,
41168
41172
  error: null
41169
41173
  }));
41170
41174
  document.title = `${dirName} - OpenSpec UI`;
@@ -73681,16 +73685,6 @@ var tabsStyleText = (id) => {
73681
73685
  transform: scaleX(0.5);
73682
73686
  }
73683
73687
 
73684
- #${id}[data-tabs-variant='default'] .tabs-button > button.tab-selected {
73685
- background-image: linear-gradient(
73686
- to bottom,
73687
- transparent,
73688
- transparent calc(100% - 2px),
73689
- var(--primary) calc(100% - 2px),
73690
- var(--primary)
73691
- );
73692
- }
73693
-
73694
73688
  #${id} .tabs-strip {
73695
73689
  background-image: linear-gradient(
73696
73690
  to bottom,
@@ -73728,11 +73722,17 @@ function TabsImpl({ tabs, selectedTab: controlled, onTabChange, onTabClose, onTa
73728
73722
  const dropIndicatorRef = (0, import_react.useRef)(null);
73729
73723
  const contentOrderRef = (0, import_react.useRef)(tabs.map((tab) => tab.id));
73730
73724
  const rootRef = (0, import_react.useRef)(null);
73725
+ const headerRef = (0, import_react.useRef)(null);
73726
+ const headerShellRef = (0, import_react.useRef)(null);
73727
+ const headerForegroundRef = (0, import_react.useRef)(null);
73728
+ const selectionIndicatorRef = (0, import_react.useRef)(null);
73729
+ const tabsButtonRef = (0, import_react.useRef)(null);
73731
73730
  const triggerRefs = (0, import_react.useRef)(/* @__PURE__ */ new Map());
73732
73731
  const panelRefs = (0, import_react.useRef)(/* @__PURE__ */ new Map());
73733
73732
  const activeTab = controlled ?? uncontrolled;
73734
73733
  const reorderable = typeof onTabOrderChange === "function" && tabs.length > 1;
73735
73734
  const tabIds = tabs.map((tab) => tab.id);
73735
+ const tabLayoutSignature = tabIds.join("|");
73736
73736
  const tabsById = (0, import_react.useMemo)(() => new Map(tabs.map((tab) => [tab.id, tab])), [tabs]);
73737
73737
  const contentTabs = (0, import_react.useMemo)(() => {
73738
73738
  const nextOrder = buildStableContentTabIds(contentOrderRef.current, tabIds);
@@ -73752,10 +73752,69 @@ function TabsImpl({ tabs, selectedTab: controlled, onTabChange, onTabClose, onTa
73752
73752
  getPanel(tabId) {
73753
73753
  return panelRefs.current.get(tabId) ?? null;
73754
73754
  },
73755
+ getHeaderShell() {
73756
+ return headerShellRef.current;
73757
+ },
73758
+ getHeaderForeground() {
73759
+ return headerForegroundRef.current;
73760
+ },
73761
+ getSelectionIndicator() {
73762
+ return selectionIndicatorRef.current;
73763
+ },
73755
73764
  getActiveTabId() {
73756
73765
  return activeTab || null;
73757
73766
  }
73758
73767
  }), [activeTab]);
73768
+ const syncSelectionIndicator = (0, import_react.useCallback)(() => {
73769
+ const indicator = selectionIndicatorRef.current;
73770
+ const header = headerRef.current;
73771
+ const activeTrigger = activeTab ? triggerRefs.current.get(activeTab) : null;
73772
+ if (!indicator) return;
73773
+ if (variant !== "default" || !header || !activeTrigger) {
73774
+ indicator.style.opacity = "0";
73775
+ indicator.style.width = "0px";
73776
+ indicator.style.height = "0px";
73777
+ indicator.style.transform = "translate(0px, 0px)";
73778
+ return;
73779
+ }
73780
+ const headerRect = header.getBoundingClientRect();
73781
+ const triggerRect = activeTrigger.getBoundingClientRect();
73782
+ indicator.style.opacity = "1";
73783
+ indicator.style.width = `${triggerRect.width}px`;
73784
+ indicator.style.height = `${triggerRect.height}px`;
73785
+ indicator.style.transform = `translate(${triggerRect.left - headerRect.left}px, ${triggerRect.top - headerRect.top}px)`;
73786
+ }, [activeTab, variant]);
73787
+ (0, import_react.useLayoutEffect)(() => {
73788
+ syncSelectionIndicator();
73789
+ }, [syncSelectionIndicator, tabLayoutSignature]);
73790
+ (0, import_react.useLayoutEffect)(() => {
73791
+ if (variant !== "default") return;
73792
+ const tabsButton = tabsButtonRef.current;
73793
+ if (!tabsButton) return;
73794
+ const handleScroll = () => {
73795
+ syncSelectionIndicator();
73796
+ };
73797
+ tabsButton.addEventListener("scroll", handleScroll, { passive: true });
73798
+ if (typeof ResizeObserver === "undefined") return () => {
73799
+ tabsButton.removeEventListener("scroll", handleScroll);
73800
+ };
73801
+ const observer = new ResizeObserver(() => {
73802
+ syncSelectionIndicator();
73803
+ });
73804
+ observer.observe(tabsButton);
73805
+ if (headerRef.current) observer.observe(headerRef.current);
73806
+ const activeTrigger = activeTab ? triggerRefs.current.get(activeTab) : null;
73807
+ if (activeTrigger) observer.observe(activeTrigger);
73808
+ return () => {
73809
+ tabsButton.removeEventListener("scroll", handleScroll);
73810
+ observer.disconnect();
73811
+ };
73812
+ }, [
73813
+ activeTab,
73814
+ syncSelectionIndicator,
73815
+ tabLayoutSignature,
73816
+ variant
73817
+ ]);
73759
73818
  const handleChange = (id) => {
73760
73819
  if (!controlled) setUncontrolled(id);
73761
73820
  onTabChange?.(id);
@@ -73835,77 +73894,115 @@ function TabsImpl({ tabs, selectedTab: controlled, onTabChange, onTabClose, onTa
73835
73894
  event.dataTransfer.dropEffect = "move";
73836
73895
  }, [reorderable]);
73837
73896
  if (tabs.length === 0) return null;
73838
- const headerClassName = variant === "terminal" ? "tabs-header bg-terminal text-terminal-foreground sticky top-0 z-20 flex min-w-0 items-stretch" : "tabs-header bg-card/95 sticky top-0 z-20 flex min-w-0 items-stretch rounded-md border border-zinc-500/15 shadow-[inset_0_-1px_0_color-mix(in_srgb,var(--border)_85%,transparent)] backdrop-blur-sm";
73839
- const stripClassName = variant === "terminal" ? "tabs-strip min-w-0 flex-1 bg-terminal px-4" : "tabs-strip bg-card/95 min-w-0 flex-1 rounded-l-md px-4";
73897
+ const headerClassName = variant === "terminal" ? "tabs-header bg-terminal text-terminal-foreground sticky top-0 z-20 flex min-w-0 items-stretch" : "tabs-header relative sticky top-0 z-20 min-w-0";
73898
+ const stripClassName = variant === "terminal" ? "tabs-strip min-w-0 flex-1 bg-terminal px-4" : "tabs-strip min-w-0 flex-1 rounded-l-md px-4";
73840
73899
  const listClassName = variant === "terminal" ? "tabs-button scrollbar-none flex min-w-0 gap-1 overflow-x-auto pt-2" : "tabs-button scrollbar-none flex min-w-0 gap-1 overflow-x-auto";
73841
- const buttonBaseClassName = `group relative m-0 flex h-full shrink-0 items-center gap-2 px-2 text-sm font-medium transition-colors ${variant === "terminal" ? "rounded-t-[8px] py-1" : "py-2"}`;
73842
- const activeButtonClassName = variant === "terminal" ? "tab-selected bg-background text-foreground" : "tab-selected bg-background/70 text-foreground";
73900
+ const buttonBaseClassName = `group relative z-10 m-0 flex h-full shrink-0 items-center gap-2 px-2 text-sm font-medium transition-colors ${variant === "terminal" ? "rounded-t-[8px] py-1" : "py-2"}`;
73901
+ const activeButtonClassName = variant === "terminal" ? "tab-selected bg-background text-foreground" : "tab-selected text-foreground";
73843
73902
  const inactiveButtonClassName = variant === "terminal" ? "bg-terminal text-terminal-foreground/80 hover:bg-terminal hover:text-terminal-foreground" : "text-muted-foreground hover:bg-background/35 hover:text-foreground";
73844
- const actionsClassName = variant === "terminal" ? "tabs-actions border-border bg-terminal text-terminal-foreground flex shrink-0 items-center border-b px-1" : "tabs-actions bg-card/95 border-zinc-500/15 flex shrink-0 items-center rounded-r-md border-l px-1";
73903
+ const actionsClassName = variant === "terminal" ? "tabs-actions border-border bg-terminal text-terminal-foreground flex shrink-0 items-center border-b px-1" : "tabs-actions border-zinc-500/15 flex shrink-0 items-center rounded-r-md border-l px-1";
73845
73904
  const handleTabBarDoubleClick = (event) => {
73846
73905
  if (!onTabBarDoubleClick) return;
73847
73906
  if (event.target.closest("[data-tab-item=\"true\"]")) return;
73848
73907
  onTabBarDoubleClick();
73849
73908
  };
73909
+ const tabButtons = tabs.map((tab) => {
73910
+ const dragIndicatorStyle = dropIndicator?.tabId === tab.id ? { boxShadow: dropIndicator.position === "before" ? "inset 2px 0 0 var(--border)" : "inset -2px 0 0 var(--border)" } : void 0;
73911
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", {
73912
+ ref: (element) => {
73913
+ triggerRefs.current.set(tab.id, element);
73914
+ },
73915
+ "data-tab-item": "true",
73916
+ "data-tab-id": tab.id,
73917
+ draggable: reorderable,
73918
+ onClick: () => handleChange(tab.id),
73919
+ onDragStart: (event) => handleDragStart(event, tab.id),
73920
+ onDragEnd: handleDragEnd,
73921
+ onDragOver: (event) => handleItemDragOver(event, tab.id),
73922
+ onDrop: (event) => handleItemDrop(event, tab.id),
73923
+ className: `${buttonBaseClassName} ${activeTab === tab.id ? activeButtonClassName : inactiveButtonClassName} ${reorderable ? "cursor-grab active:cursor-grabbing" : ""}`,
73924
+ style: dragIndicatorStyle,
73925
+ children: [
73926
+ tab.icon,
73927
+ tab.label,
73928
+ tab.closable && onTabClose && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
73929
+ role: "button",
73930
+ tabIndex: 0,
73931
+ onClick: (event) => {
73932
+ event.stopPropagation();
73933
+ onTabClose(tab.id);
73934
+ },
73935
+ onKeyDown: (event) => {
73936
+ if (event.key === "Enter" || event.key === " ") {
73937
+ event.stopPropagation();
73938
+ onTabClose(tab.id);
73939
+ }
73940
+ },
73941
+ draggable: false,
73942
+ className: `hover:text-foreground -mr-1 rounded p-0.5 transition ${tab.closeButtonVisibility === "always" ? "opacity-100" : "opacity-0 group-hover:opacity-100 [button:hover>&]:opacity-100"} ${activeTab === tab.id ? "text-current/80" : "text-muted-foreground"}`,
73943
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(X$2, { className: "h-3 w-3" })
73944
+ })
73945
+ ]
73946
+ }, tab.id);
73947
+ });
73850
73948
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
73851
73949
  id,
73852
73950
  ref: rootRef,
73853
73951
  "data-tabs-variant": variant,
73854
73952
  className: `relative isolate flex min-h-0 min-w-0 flex-1 flex-col ${className}`,
73855
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
73953
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
73954
+ ref: headerRef,
73856
73955
  className: headerClassName,
73857
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
73956
+ children: variant === "default" ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
73957
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
73958
+ ref: headerShellRef,
73959
+ "data-tabs-header-shell": "true",
73960
+ className: "tabs-header-shell bg-card/95 pointer-events-none absolute inset-0 z-0 rounded-md border border-zinc-500/15 shadow-[inset_0_-1px_0_color-mix(in_srgb,var(--border)_85%,transparent)] backdrop-blur-sm"
73961
+ }),
73962
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
73963
+ className: "pointer-events-none absolute inset-0 z-10",
73964
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
73965
+ ref: selectionIndicatorRef,
73966
+ "data-tabs-selection-indicator": "true",
73967
+ "aria-hidden": "true",
73968
+ className: "tabs-selection-indicator border-primary bg-background/70 duration-280 absolute left-0 top-0 rounded-md border-b-2 opacity-0 shadow-[inset_0_-1px_0_color-mix(in_srgb,var(--border)_85%,transparent)] transition-[transform,width,height,opacity] ease-[cubic-bezier(0.22,1,0.36,1)]"
73969
+ })
73970
+ }),
73971
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
73972
+ ref: headerForegroundRef,
73973
+ "data-tabs-header-foreground": "true",
73974
+ className: "tabs-header-foreground relative z-20 flex min-w-0 items-stretch",
73975
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
73976
+ className: stripClassName,
73977
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
73978
+ ref: tabsButtonRef,
73979
+ className: listClassName,
73980
+ onDoubleClick: handleTabBarDoubleClick,
73981
+ onDragOver: handleListDragOver,
73982
+ onDrop: handleListDrop,
73983
+ children: tabButtons
73984
+ })
73985
+ }), actions && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
73986
+ "data-tabs-actions": "true",
73987
+ className: actionsClassName,
73988
+ children: actions
73989
+ })]
73990
+ })
73991
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
73858
73992
  className: stripClassName,
73859
73993
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
73994
+ ref: tabsButtonRef,
73860
73995
  className: listClassName,
73861
73996
  onDoubleClick: handleTabBarDoubleClick,
73862
73997
  onDragOver: handleListDragOver,
73863
73998
  onDrop: handleListDrop,
73864
- children: tabs.map((tab) => {
73865
- const dragIndicatorStyle = dropIndicator?.tabId === tab.id ? { boxShadow: dropIndicator.position === "before" ? "inset 2px 0 0 var(--border)" : "inset -2px 0 0 var(--border)" } : void 0;
73866
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", {
73867
- ref: (element) => {
73868
- triggerRefs.current.set(tab.id, element);
73869
- },
73870
- "data-tab-item": "true",
73871
- "data-tab-id": tab.id,
73872
- draggable: reorderable,
73873
- onClick: () => handleChange(tab.id),
73874
- onDragStart: (event) => handleDragStart(event, tab.id),
73875
- onDragEnd: handleDragEnd,
73876
- onDragOver: (event) => handleItemDragOver(event, tab.id),
73877
- onDrop: (event) => handleItemDrop(event, tab.id),
73878
- className: `${buttonBaseClassName} ${activeTab === tab.id ? activeButtonClassName : inactiveButtonClassName} ${reorderable ? "cursor-grab active:cursor-grabbing" : ""}`,
73879
- style: dragIndicatorStyle,
73880
- children: [
73881
- tab.icon,
73882
- tab.label,
73883
- tab.closable && onTabClose && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
73884
- role: "button",
73885
- tabIndex: 0,
73886
- onClick: (event) => {
73887
- event.stopPropagation();
73888
- onTabClose(tab.id);
73889
- },
73890
- onKeyDown: (event) => {
73891
- if (event.key === "Enter" || event.key === " ") {
73892
- event.stopPropagation();
73893
- onTabClose(tab.id);
73894
- }
73895
- },
73896
- draggable: false,
73897
- className: `hover:text-foreground -mr-1 rounded p-0.5 transition ${tab.closeButtonVisibility === "always" ? "opacity-100" : "opacity-0 group-hover:opacity-100 [button:hover>&]:opacity-100"} ${activeTab === tab.id ? "text-current/80" : "text-muted-foreground"}`,
73898
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(X$2, { className: "h-3 w-3" })
73899
- })
73900
- ]
73901
- }, tab.id);
73902
- })
73999
+ children: tabButtons
73903
74000
  })
73904
74001
  }), actions && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
73905
74002
  "data-tabs-actions": "true",
73906
74003
  className: actionsClassName,
73907
74004
  children: actions
73908
- })]
74005
+ })] })
73909
74006
  }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
73910
74007
  className: "relative flex min-h-0 flex-1 overflow-hidden",
73911
74008
  children: contentTabs.map((tab) => tab.unmountOnHide ? activeTab === tab.id && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
@@ -73935,6 +74032,10 @@ var Tabs = (0, import_react.forwardRef)(TabsImpl);
73935
74032
  var DATA_VISIBLE_HEIGHT = "tabVisibleHeight";
73936
74033
  var DATA_TOP_INSET = "tabTopInset";
73937
74034
  var DATA_SCROLL_OFFSET = "tabScrollOffset";
74035
+ var DATA_LAYOUT_BRIDGE = "tabLayoutBridge";
74036
+ var DATA_LAYOUT_BRIDGE_BASE_PADDING = "tabLayoutBridgeBasePadding";
74037
+ var DATA_LAYOUT_BRIDGE_INLINE_PADDING = "tabLayoutBridgeInlinePadding";
74038
+ var TAB_SCROLL_ROOT_SELECTOR = "[data-tab-scroll-root=\"true\"]";
73938
74039
  function clamp$2(value, min, max) {
73939
74040
  return Math.min(Math.max(value, min), max);
73940
74041
  }
@@ -73944,6 +74045,63 @@ function maxViewportScroll(viewport) {
73944
74045
  function maxPanelScroll(panel, visibleHeight) {
73945
74046
  return Math.max(panel.scrollHeight - visibleHeight, 0);
73946
74047
  }
74048
+ function readPanelLayoutBridge(panel) {
74049
+ return Number(panel.dataset[DATA_LAYOUT_BRIDGE] ?? "0") || 0;
74050
+ }
74051
+ function ensurePanelLayoutBridgeState(panel) {
74052
+ panel.dataset[DATA_LAYOUT_BRIDGE_BASE_PADDING] ||= window.getComputedStyle(panel).paddingBottom;
74053
+ panel.dataset[DATA_LAYOUT_BRIDGE_INLINE_PADDING] ||= panel.style.paddingBottom;
74054
+ }
74055
+ function setPanelLayoutBridge(panel, extraHeight) {
74056
+ ensurePanelLayoutBridgeState(panel);
74057
+ const normalizedHeight = Math.max(Math.ceil(extraHeight), 0);
74058
+ panel.dataset[DATA_LAYOUT_BRIDGE] = String(normalizedHeight);
74059
+ if (normalizedHeight <= 0) {
74060
+ panel.style.paddingBottom = panel.dataset[DATA_LAYOUT_BRIDGE_INLINE_PADDING] ?? "";
74061
+ return;
74062
+ }
74063
+ const basePadding = panel.dataset[DATA_LAYOUT_BRIDGE_BASE_PADDING] ?? "0px";
74064
+ panel.style.paddingBottom = `calc(${basePadding} + ${normalizedHeight}px)`;
74065
+ }
74066
+ function restoreViewportScroll(viewport, panel, targetScrollTop) {
74067
+ const applyScrollTop = () => {
74068
+ if (!viewport.isConnected || !panel.isConnected) return true;
74069
+ const existingBridge = readPanelLayoutBridge(panel);
74070
+ const baseMaxViewportScroll = Math.max(maxViewportScroll(viewport) - existingBridge, 0);
74071
+ setPanelLayoutBridge(panel, Math.max(targetScrollTop - baseMaxViewportScroll, 0));
74072
+ const nextScrollTop = clamp$2(targetScrollTop, 0, maxViewportScroll(viewport));
74073
+ viewport.scrollTop = nextScrollTop;
74074
+ return viewport.scrollTop === nextScrollTop;
74075
+ };
74076
+ if (applyScrollTop() || typeof requestAnimationFrame !== "function") return;
74077
+ let retriesRemaining = 10;
74078
+ const retry = () => {
74079
+ if (applyScrollTop() || retriesRemaining <= 0) return;
74080
+ retriesRemaining -= 1;
74081
+ requestAnimationFrame(retry);
74082
+ };
74083
+ requestAnimationFrame(retry);
74084
+ }
74085
+ function hasVerticalScrollBehavior(overflowY) {
74086
+ return overflowY === "auto" || overflowY === "scroll" || overflowY === "overlay";
74087
+ }
74088
+ function findScrollableDescendant(panel) {
74089
+ const walker = document.createTreeWalker(panel, NodeFilter.SHOW_ELEMENT);
74090
+ let candidate = null;
74091
+ while (walker.nextNode()) {
74092
+ const node = walker.currentNode;
74093
+ if (!(node instanceof HTMLElement)) continue;
74094
+ if (!hasVerticalScrollBehavior(window.getComputedStyle(node).overflowY) || node.scrollHeight <= node.clientHeight) continue;
74095
+ if (node.scrollTop > 0) return node;
74096
+ candidate ??= node;
74097
+ }
74098
+ return candidate;
74099
+ }
74100
+ function resolveContentScrollRoot(panel) {
74101
+ const markedRoot = panel.matches(TAB_SCROLL_ROOT_SELECTOR) ? panel : panel.querySelector(TAB_SCROLL_ROOT_SELECTOR);
74102
+ if (markedRoot) return markedRoot;
74103
+ return findScrollableDescendant(panel);
74104
+ }
73947
74105
  function panelDocumentTop(panel, viewport) {
73948
74106
  const panelRect = panel.getBoundingClientRect();
73949
74107
  const viewportRect = viewport.getBoundingClientRect();
@@ -73981,40 +74139,99 @@ function restorePanel(panel, previousStyles) {
73981
74139
  panel.style.overflowY = previousStyles.overflowY;
73982
74140
  clearFrozenMetrics(panel);
73983
74141
  }
74142
+ function restoreContentScrollRoot(element, targetScrollTop) {
74143
+ if (!element) return;
74144
+ const applyScrollTop = () => {
74145
+ if (!element.isConnected) return true;
74146
+ element.scrollTop = targetScrollTop;
74147
+ return element.scrollTop === targetScrollTop;
74148
+ };
74149
+ if (applyScrollTop() || typeof requestAnimationFrame !== "function") return;
74150
+ let retriesRemaining = 10;
74151
+ const retry = () => {
74152
+ if (applyScrollTop() || retriesRemaining <= 0) return;
74153
+ retriesRemaining -= 1;
74154
+ requestAnimationFrame(retry);
74155
+ };
74156
+ requestAnimationFrame(retry);
74157
+ }
74158
+ function normalizeViewportSelectors(viewportSelector) {
74159
+ return typeof viewportSelector === "string" ? viewportSelector.split(",").map((selector) => selector.trim()) : [...viewportSelector];
74160
+ }
74161
+ function resolveViewportBoundary(panel, viewportSelector) {
74162
+ const selectors = normalizeViewportSelectors(viewportSelector);
74163
+ for (const selector of selectors) {
74164
+ if (!selector) continue;
74165
+ try {
74166
+ const match = panel.closest(selector);
74167
+ if (match instanceof HTMLElement) return match;
74168
+ } catch {
74169
+ return null;
74170
+ }
74171
+ }
74172
+ return null;
74173
+ }
74174
+ function resolveScrollViewport(panel, boundary) {
74175
+ let current = panel.parentElement;
74176
+ while (current) {
74177
+ if (hasVerticalScrollBehavior(window.getComputedStyle(current).overflowY) && current.scrollHeight > current.clientHeight) return current;
74178
+ if (boundary && current === boundary) break;
74179
+ current = current.parentElement;
74180
+ }
74181
+ if (!boundary) return null;
74182
+ if (hasVerticalScrollBehavior(window.getComputedStyle(boundary).overflowY)) return boundary;
74183
+ return null;
74184
+ }
73984
74185
  function resolveTabScrollElements(handle, tabId, viewportSelector) {
73985
74186
  if (!viewportSelector) return null;
73986
74187
  const panel = handle?.getPanel(tabId);
73987
74188
  if (!(panel instanceof HTMLElement)) return null;
73988
- let viewport = null;
73989
- try {
73990
- viewport = panel.closest(viewportSelector);
73991
- } catch {
73992
- return null;
73993
- }
74189
+ const viewport = resolveScrollViewport(panel, resolveViewportBoundary(panel, viewportSelector));
73994
74190
  if (!(viewport instanceof HTMLElement)) return null;
73995
74191
  return {
73996
74192
  panel,
74193
+ contentScrollRoot: resolveContentScrollRoot(panel),
73997
74194
  viewport
73998
74195
  };
73999
74196
  }
74197
+ function restorePanelContentScroll(panel, snapshot) {
74198
+ if (!(panel instanceof HTMLElement) || !snapshot) return;
74199
+ const contentScrollRoot = resolveContentScrollRoot(panel);
74200
+ if (contentScrollRoot && contentScrollRoot !== panel) restoreContentScrollRoot(contentScrollRoot, snapshot.contentScrollTop);
74201
+ }
74202
+ function restorePanelViewportScroll(panel, viewport, snapshot) {
74203
+ if (!(panel instanceof HTMLElement) || !(viewport instanceof HTMLElement)) return;
74204
+ if (!snapshot) {
74205
+ setPanelLayoutBridge(panel, 0);
74206
+ return;
74207
+ }
74208
+ restoreViewportScroll(viewport, panel, snapshot.viewportScrollTop);
74209
+ }
74000
74210
  function captureTabScrollMemory(elements) {
74001
- const { panel, viewport } = elements;
74211
+ const { panel, contentScrollRoot, viewport } = elements;
74002
74212
  const panelRect = panel.getBoundingClientRect();
74003
74213
  const viewportRect = viewport.getBoundingClientRect();
74004
74214
  const visibleHeight = clamp$2(Math.min(panelRect.bottom, viewportRect.bottom) - Math.max(panelRect.top, viewportRect.top), 0, viewport.clientHeight);
74005
74215
  if (visibleHeight <= 0) return null;
74006
74216
  const topInset = Math.max(panelRect.top - viewportRect.top, 0);
74217
+ const innerScrollTop = clamp$2(Math.max(viewportRect.top - panelRect.top, 0), 0, maxPanelScroll(panel, visibleHeight));
74007
74218
  return {
74008
- innerScrollTop: clamp$2(Math.max(viewportRect.top - panelRect.top, 0), 0, maxPanelScroll(panel, visibleHeight)),
74219
+ contentScrollTop: contentScrollRoot && contentScrollRoot !== panel ? contentScrollRoot.scrollTop : 0,
74220
+ innerScrollTop,
74009
74221
  topInset,
74010
- visibleHeight
74222
+ visibleHeight,
74223
+ viewportScrollTop: viewport.scrollTop
74011
74224
  };
74012
74225
  }
74013
74226
  function freezeOutgoingTab(elements, snapshot) {
74014
74227
  const previousStyles = applyFrozenStyles(elements.panel, snapshot);
74228
+ if (elements.contentScrollRoot && elements.contentScrollRoot !== elements.panel) restoreContentScrollRoot(elements.contentScrollRoot, snapshot.contentScrollTop);
74015
74229
  elements.panel.scrollTop = snapshot.innerScrollTop;
74016
74230
  if (snapshot.innerScrollTop > 0) elements.viewport.scrollTop = clamp$2(elements.viewport.scrollTop - snapshot.innerScrollTop, 0, maxViewportScroll(elements.viewport));
74017
74231
  return {
74232
+ contentScrollRoot: elements.contentScrollRoot && elements.contentScrollRoot !== elements.panel ? elements.contentScrollRoot : null,
74233
+ contentScrollTop: snapshot.contentScrollTop,
74234
+ finalViewportScrollTop: snapshot.viewportScrollTop,
74018
74235
  panel: elements.panel,
74019
74236
  previousStyles,
74020
74237
  viewport: elements.viewport
@@ -74022,26 +74239,32 @@ function freezeOutgoingTab(elements, snapshot) {
74022
74239
  }
74023
74240
  function freezeIncomingTab(elements, snapshot) {
74024
74241
  const normalizedSnapshot = {
74242
+ contentScrollTop: elements.contentScrollRoot && elements.contentScrollRoot !== elements.panel ? snapshot.contentScrollTop : 0,
74025
74243
  topInset: clamp$2(snapshot.topInset, 0, elements.viewport.clientHeight),
74026
74244
  visibleHeight: clamp$2(snapshot.visibleHeight, 1, elements.viewport.clientHeight),
74027
- innerScrollTop: 0
74245
+ innerScrollTop: 0,
74246
+ viewportScrollTop: snapshot.viewportScrollTop
74028
74247
  };
74029
74248
  normalizedSnapshot.innerScrollTop = clamp$2(snapshot.innerScrollTop, 0, maxPanelScroll(elements.panel, normalizedSnapshot.visibleHeight));
74030
74249
  const nextViewportScrollTop = clamp$2(panelDocumentTop(elements.panel, elements.viewport) - normalizedSnapshot.topInset, 0, maxViewportScroll(elements.viewport));
74031
74250
  const previousStyles = applyFrozenStyles(elements.panel, normalizedSnapshot);
74032
74251
  elements.viewport.scrollTop = nextViewportScrollTop;
74252
+ if (elements.contentScrollRoot && elements.contentScrollRoot !== elements.panel) restoreContentScrollRoot(elements.contentScrollRoot, normalizedSnapshot.contentScrollTop);
74033
74253
  elements.panel.scrollTop = normalizedSnapshot.innerScrollTop;
74034
74254
  return {
74255
+ contentScrollRoot: elements.contentScrollRoot && elements.contentScrollRoot !== elements.panel ? elements.contentScrollRoot : null,
74256
+ contentScrollTop: normalizedSnapshot.contentScrollTop,
74257
+ finalViewportScrollTop: snapshot.viewportScrollTop,
74035
74258
  panel: elements.panel,
74036
74259
  previousStyles,
74037
74260
  viewport: elements.viewport
74038
74261
  };
74039
74262
  }
74040
74263
  function finalizeFrozenIncomingTab(state) {
74041
- const transferScrollTop = state.panel.scrollTop;
74042
74264
  restorePanel(state.panel, state.previousStyles);
74043
- state.viewport.scrollTop = clamp$2(state.viewport.scrollTop + transferScrollTop, 0, maxViewportScroll(state.viewport));
74265
+ restoreViewportScroll(state.viewport, state.panel, state.finalViewportScrollTop);
74044
74266
  state.panel.scrollTop = 0;
74267
+ if (state.contentScrollRoot) restoreContentScrollRoot(state.contentScrollRoot, state.contentScrollTop);
74045
74268
  }
74046
74269
  function cleanupFrozenTab(state) {
74047
74270
  restorePanel(state.panel, state.previousStyles);
@@ -74072,6 +74295,11 @@ function resolveTabArea(pathname, area) {
74072
74295
  if (area) return area;
74073
74296
  return isStaticMode() ? "main" : navController.getAreaForPath(pathname);
74074
74297
  }
74298
+ function normalizeViewportSelectorOption(viewportSelector) {
74299
+ if (!viewportSelector) return;
74300
+ const normalized = (typeof viewportSelector === "string" ? viewportSelector.split(",") : [...viewportSelector]).map((selector) => selector.trim()).filter((selector) => selector.length > 0);
74301
+ return normalized.length > 0 ? normalized : void 0;
74302
+ }
74075
74303
  function readWindowLocation() {
74076
74304
  if (typeof window === "undefined") return SERVER_LOCATION;
74077
74305
  return {
@@ -74126,6 +74354,12 @@ function useRoutedTabsLocation() {
74126
74354
  function collectTabEntries(handle, tabId) {
74127
74355
  if (!handle) return [];
74128
74356
  const entries = [];
74357
+ const headerShell = handle.getHeaderShell();
74358
+ if (headerShell) entries.push([headerShell, "vt-tab-header-shell"]);
74359
+ const selectionIndicator = handle.getSelectionIndicator();
74360
+ if (selectionIndicator) entries.push([selectionIndicator, "vt-tab-edge"]);
74361
+ const headerForeground = handle.getHeaderForeground();
74362
+ if (headerForeground) entries.push([headerForeground, "vt-tab-header-foreground"]);
74129
74363
  const panel = handle.getPanel(tabId);
74130
74364
  if (panel) entries.push([panel, "vt-tab-panel"]);
74131
74365
  return entries;
@@ -74133,8 +74367,11 @@ function collectTabEntries(handle, tabId) {
74133
74367
  function useRoutedCarouselTabs({ queryKey, tabs, initialTab, area, history = "replace", allowUnknownSelection = false, viewportSelector }) {
74134
74368
  const { location, router } = useRoutedTabsLocation();
74135
74369
  const tabsRef = (0, import_react.useRef)(null);
74370
+ const viewportSelectorValue = (0, import_react.useMemo)(() => normalizeViewportSelectorOption(viewportSelector), [typeof viewportSelector === "string" ? viewportSelector : (viewportSelector ?? []).join("\0")]);
74136
74371
  const scrollMemoryByTabRef = (0, import_react.useRef)(/* @__PURE__ */ new Map());
74137
74372
  const frozenTabsRef = (0, import_react.useRef)(/* @__PURE__ */ new Map());
74373
+ const frozenTabTokenRef = (0, import_react.useRef)(0);
74374
+ const skipNextRestoreTabRef = (0, import_react.useRef)(null);
74138
74375
  const selectedFromLocation = (0, import_react.useMemo)(() => resolveSelectedTab({
74139
74376
  tabs,
74140
74377
  queryKey,
@@ -74159,7 +74396,7 @@ function useRoutedCarouselTabs({ queryKey, tabs, initialTab, area, history = "re
74159
74396
  selectedFromLocation,
74160
74397
  selectedTab,
74161
74398
  tabs,
74162
- viewportSelector
74399
+ viewportSelector: viewportSelectorValue
74163
74400
  });
74164
74401
  latestRef.current = {
74165
74402
  allowUnknownSelection,
@@ -74171,45 +74408,89 @@ function useRoutedCarouselTabs({ queryKey, tabs, initialTab, area, history = "re
74171
74408
  selectedFromLocation,
74172
74409
  selectedTab,
74173
74410
  tabs,
74174
- viewportSelector
74411
+ viewportSelector: viewportSelectorValue
74175
74412
  };
74176
- const cleanupFrozenTabById = (0, import_react.useCallback)((tabId) => {
74177
- const frozenState = frozenTabsRef.current.get(tabId);
74178
- if (!frozenState) return;
74179
- cleanupFrozenTab(frozenState);
74413
+ const cleanupFrozenTabById = (0, import_react.useCallback)((tabId, token) => {
74414
+ const frozenEntry = frozenTabsRef.current.get(tabId);
74415
+ if (!frozenEntry || token != null && frozenEntry.token !== token) return;
74416
+ cleanupFrozenTab(frozenEntry.state);
74180
74417
  frozenTabsRef.current.delete(tabId);
74181
74418
  }, []);
74182
74419
  const cleanupAllFrozenTabs = (0, import_react.useCallback)(() => {
74183
- for (const frozenState of frozenTabsRef.current.values()) cleanupFrozenTab(frozenState);
74420
+ for (const frozenEntry of frozenTabsRef.current.values()) cleanupFrozenTab(frozenEntry.state);
74184
74421
  frozenTabsRef.current.clear();
74185
74422
  }, []);
74186
- const captureOutgoingTab = (0, import_react.useCallback)((tabId, nextViewportSelector) => {
74423
+ const captureTabSnapshot = (0, import_react.useCallback)((tabId, nextViewportSelector) => {
74424
+ const elements = resolveTabScrollElements(tabsRef.current, tabId, nextViewportSelector);
74425
+ if (!elements) return null;
74426
+ return captureTabScrollMemory(elements);
74427
+ }, []);
74428
+ const captureOutgoingTab = (0, import_react.useCallback)((tabId, nextViewportSelector, snapshotOverride) => {
74187
74429
  const elements = resolveTabScrollElements(tabsRef.current, tabId, nextViewportSelector);
74188
74430
  if (!elements) return;
74189
- const snapshot = captureTabScrollMemory(elements);
74431
+ const snapshot = snapshotOverride ?? captureTabScrollMemory(elements);
74190
74432
  if (!snapshot) return;
74191
74433
  scrollMemoryByTabRef.current.set(tabId, snapshot);
74192
74434
  cleanupFrozenTabById(tabId);
74193
- frozenTabsRef.current.set(tabId, freezeOutgoingTab(elements, snapshot));
74435
+ const token = ++frozenTabTokenRef.current;
74436
+ frozenTabsRef.current.set(tabId, {
74437
+ token,
74438
+ state: freezeOutgoingTab(elements, snapshot)
74439
+ });
74440
+ return token;
74194
74441
  }, [cleanupFrozenTabById]);
74195
- const prepareIncomingTab = (0, import_react.useCallback)((tabId, nextViewportSelector) => {
74442
+ const prepareIncomingTab = (0, import_react.useCallback)((tabId, nextViewportSelector, fallbackSnapshot) => {
74196
74443
  const elements = resolveTabScrollElements(tabsRef.current, tabId, nextViewportSelector);
74197
- if (!elements) return false;
74198
- const snapshot = scrollMemoryByTabRef.current.get(tabId) ?? captureTabScrollMemory(elements);
74199
- if (!snapshot) return false;
74444
+ if (!elements) return null;
74445
+ const snapshot = scrollMemoryByTabRef.current.get(tabId) ?? fallbackSnapshot ?? captureTabScrollMemory(elements);
74446
+ if (!snapshot) return null;
74447
+ if (!scrollMemoryByTabRef.current.has(tabId)) scrollMemoryByTabRef.current.set(tabId, snapshot);
74200
74448
  cleanupFrozenTabById(tabId);
74201
- frozenTabsRef.current.set(tabId, freezeIncomingTab(elements, snapshot));
74202
- return true;
74449
+ const token = ++frozenTabTokenRef.current;
74450
+ frozenTabsRef.current.set(tabId, {
74451
+ token,
74452
+ state: freezeIncomingTab(elements, snapshot)
74453
+ });
74454
+ return token;
74203
74455
  }, [cleanupFrozenTabById]);
74204
- const finalizeIncomingTab = (0, import_react.useCallback)((tabId) => {
74205
- const frozenState = frozenTabsRef.current.get(tabId);
74206
- if (!frozenState) return;
74207
- finalizeFrozenIncomingTab(frozenState);
74456
+ const finalizeIncomingTab = (0, import_react.useCallback)((tabId, token) => {
74457
+ const frozenEntry = frozenTabsRef.current.get(tabId);
74458
+ if (!frozenEntry || token != null && frozenEntry.token !== token) return;
74459
+ finalizeFrozenIncomingTab(frozenEntry.state);
74208
74460
  frozenTabsRef.current.delete(tabId);
74209
74461
  }, []);
74210
74462
  (0, import_react.useEffect)(() => {
74211
74463
  setSelectedTabState((current) => current === selectedFromLocation ? current : selectedFromLocation);
74212
74464
  }, [selectedFromLocation]);
74465
+ (0, import_react.useLayoutEffect)(() => {
74466
+ if (skipNextRestoreTabRef.current === selectedTab) {
74467
+ skipNextRestoreTabRef.current = null;
74468
+ return;
74469
+ }
74470
+ const snapshot = scrollMemoryByTabRef.current.get(selectedTab);
74471
+ const elements = resolveTabScrollElements(tabsRef.current, selectedTab, viewportSelectorValue);
74472
+ const panel = elements?.panel ?? tabsRef.current?.getPanel(selectedTab) ?? null;
74473
+ restorePanelContentScroll(panel, snapshot);
74474
+ restorePanelViewportScroll(panel, elements?.viewport ?? null, snapshot);
74475
+ }, [selectedTab, viewportSelectorValue]);
74476
+ (0, import_react.useEffect)(() => {
74477
+ const elements = resolveTabScrollElements(tabsRef.current, selectedTab, viewportSelectorValue);
74478
+ const contentScrollRoot = elements?.contentScrollRoot;
74479
+ if (!elements || !contentScrollRoot || contentScrollRoot === elements.panel) return;
74480
+ const rememberContentScroll = () => {
74481
+ const existingSnapshot = scrollMemoryByTabRef.current.get(selectedTab);
74482
+ if (!existingSnapshot) return;
74483
+ scrollMemoryByTabRef.current.set(selectedTab, {
74484
+ ...existingSnapshot,
74485
+ contentScrollTop: contentScrollRoot.scrollTop
74486
+ });
74487
+ };
74488
+ rememberContentScroll();
74489
+ contentScrollRoot.addEventListener("scroll", rememberContentScroll, { passive: true });
74490
+ return () => {
74491
+ contentScrollRoot.removeEventListener("scroll", rememberContentScroll);
74492
+ };
74493
+ }, [selectedTab, viewportSelectorValue]);
74213
74494
  (0, import_react.useEffect)(() => {
74214
74495
  const validIds = new Set(tabs.map((tab) => tab.id));
74215
74496
  for (const tabId of scrollMemoryByTabRef.current.keys()) if (!validIds.has(tabId)) scrollMemoryByTabRef.current.delete(tabId);
@@ -74226,6 +74507,7 @@ function useRoutedCarouselTabs({ queryKey, tabs, initialTab, area, history = "re
74226
74507
  const { allowUnknownSelection: allowUnknown, area: latestArea, history: defaultHistory, location: latestLocation, queryKey: latestQueryKey, router: latestRouter, selectedFromLocation: latestSelectedFromLocation, selectedTab: currentTab, tabs: latestTabs, viewportSelector: latestViewportSelector } = latestRef.current;
74227
74508
  if (!new Set(latestTabs.map((tab) => tab.id)).has(nextTabId) && !allowUnknown) return;
74228
74509
  const nextHistory = options?.history ?? defaultHistory;
74510
+ const transferScroll = options?.transferScroll ?? true;
74229
74511
  if (currentTab === nextTabId && latestSelectedFromLocation === nextTabId) return;
74230
74512
  const commitSelection = () => {
74231
74513
  setSelectedTabState(nextTabId);
@@ -74249,18 +74531,40 @@ function useRoutedCarouselTabs({ queryKey, tabs, initialTab, area, history = "re
74249
74531
  }
74250
74532
  navController.push(nextArea, href, latestLocation.state);
74251
74533
  };
74534
+ if (!transferScroll) {
74535
+ const outgoingSnapshot = captureTabSnapshot(currentTab, latestViewportSelector);
74536
+ if (outgoingSnapshot) scrollMemoryByTabRef.current.set(currentTab, outgoingSnapshot);
74537
+ skipNextRestoreTabRef.current = nextTabId;
74538
+ if (!options?.animate || currentTab === nextTabId) {
74539
+ commitSelection();
74540
+ return;
74541
+ }
74542
+ runViewTransition({
74543
+ intent: {
74544
+ area: resolveTabArea(latestLocation.pathname, latestArea),
74545
+ kind: "tab-carousel",
74546
+ direction: "forward"
74547
+ },
74548
+ collectBeforeEntries: () => collectTabEntries(tabsRef.current, currentTab),
74549
+ collectAfterEntries: () => collectTabEntries(tabsRef.current, nextTabId),
74550
+ update: commitSelection
74551
+ });
74552
+ return;
74553
+ }
74252
74554
  const runSelectionWithScrollTransfer = (animated) => {
74253
- captureOutgoingTab(currentTab, latestViewportSelector);
74555
+ const outgoingSnapshot = captureTabSnapshot(currentTab, latestViewportSelector);
74556
+ const incomingSeedSnapshot = scrollMemoryByTabRef.current.get(nextTabId) ?? outgoingSnapshot ?? captureTabSnapshot(nextTabId, latestViewportSelector);
74557
+ const outgoingToken = captureOutgoingTab(currentTab, latestViewportSelector, outgoingSnapshot);
74254
74558
  if (!animated) {
74255
74559
  (0, import_react_dom.flushSync)(() => {
74256
74560
  commitSelection();
74257
74561
  });
74258
- prepareIncomingTab(nextTabId, latestViewportSelector);
74259
- finalizeIncomingTab(nextTabId);
74260
- cleanupFrozenTabById(currentTab);
74562
+ const incomingToken = prepareIncomingTab(nextTabId, latestViewportSelector, incomingSeedSnapshot);
74563
+ if (incomingToken != null) finalizeIncomingTab(nextTabId, incomingToken);
74564
+ if (outgoingToken != null) cleanupFrozenTabById(currentTab, outgoingToken);
74261
74565
  return;
74262
74566
  }
74263
- let hasPreparedIncoming = false;
74567
+ let incomingToken = null;
74264
74568
  runViewTransition({
74265
74569
  intent: {
74266
74570
  area: resolveTabArea(latestLocation.pathname, latestArea),
@@ -74269,14 +74573,14 @@ function useRoutedCarouselTabs({ queryKey, tabs, initialTab, area, history = "re
74269
74573
  },
74270
74574
  collectBeforeEntries: () => collectTabEntries(tabsRef.current, currentTab),
74271
74575
  collectAfterEntries: () => {
74272
- if (!hasPreparedIncoming) hasPreparedIncoming = prepareIncomingTab(nextTabId, latestViewportSelector);
74576
+ if (incomingToken == null) incomingToken = prepareIncomingTab(nextTabId, latestViewportSelector, incomingSeedSnapshot);
74273
74577
  return collectTabEntries(tabsRef.current, nextTabId);
74274
74578
  },
74275
74579
  update: commitSelection
74276
74580
  }).finally(() => {
74277
- if (!hasPreparedIncoming) prepareIncomingTab(nextTabId, latestViewportSelector);
74278
- finalizeIncomingTab(nextTabId);
74279
- cleanupFrozenTabById(currentTab);
74581
+ if (incomingToken == null) incomingToken = prepareIncomingTab(nextTabId, latestViewportSelector, incomingSeedSnapshot);
74582
+ if (incomingToken != null) finalizeIncomingTab(nextTabId, incomingToken);
74583
+ if (outgoingToken != null) cleanupFrozenTabById(currentTab, outgoingToken);
74280
74584
  });
74281
74585
  };
74282
74586
  if (!options?.animate || currentTab === nextTabId) {
@@ -74293,6 +74597,7 @@ function useRoutedCarouselTabs({ queryKey, tabs, initialTab, area, history = "re
74293
74597
  runSelectionWithScrollTransfer(true);
74294
74598
  }, [
74295
74599
  captureOutgoingTab,
74600
+ captureTabSnapshot,
74296
74601
  cleanupFrozenTabById,
74297
74602
  finalizeIncomingTab,
74298
74603
  prepareIncomingTab