@openspecui/web 2.3.2 → 2.3.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/dist/assets/CanvasRenderer-DLCesp1A.js +1 -0
  2. package/dist/assets/WebGLRenderer-DYT9IbsA.js +1 -0
  3. package/dist/assets/WebGPURenderer-CYfC6MR9.js +1 -0
  4. package/dist/assets/browserAll-CmR4zgGr.js +1 -0
  5. package/dist/assets/{dist-D3tpHX6Y.js → dist-5fi0H1tv.js} +1 -1
  6. package/dist/assets/{dist-DjPJlCHP.js → dist-9w-ZAZ0x.js} +1 -1
  7. package/dist/assets/{dist-NxlD47Et.js → dist-AstaMS2N.js} +1 -1
  8. package/dist/assets/{dist-AzZzg9Or.js → dist-BY07yBep.js} +1 -1
  9. package/dist/assets/dist-BkrPETxD.js +1 -0
  10. package/dist/assets/dist-DABwUepA.js +1 -0
  11. package/dist/assets/{dist-BLkBtxRp.js → dist-DDChZc3A.js} +1 -1
  12. package/dist/assets/{dist-704BMcqW.js → dist-DQMVt2X6.js} +1 -1
  13. package/dist/assets/{dist-CHbFRWhr.js → dist-DTgAMGJk.js} +1 -1
  14. package/dist/assets/{dist-BYskKa8I.js → dist-DtkkWh_t.js} +1 -1
  15. package/dist/assets/dist-hgHsmBgz.js +1 -0
  16. package/dist/assets/{dist-DICpu_MT.js → dist-j40sCais.js} +1 -1
  17. package/dist/assets/{ghostty-web-C70d-U4t.js → ghostty-web-BRCaoYqS.js} +1 -1
  18. package/dist/assets/index-C0tlID-0.css +1 -0
  19. package/dist/assets/index-CUgehjti.js +1578 -0
  20. package/dist/assets/{init-BWnHltxK.js → init-CIIgfU1P.js} +1 -1
  21. package/dist/assets/trpc-2zCd4YGO.js +1 -0
  22. package/dist/assets/webworkerAll-CwuSdyaU.js +1 -0
  23. package/dist/index.html +2 -2
  24. package/dist-ssg/client/.vite/ssr-manifest.json +18 -15
  25. package/dist-ssg/client/assets/CanvasRenderer-vRzYl9FZ.js +1 -0
  26. package/dist-ssg/client/assets/WebGLRenderer-BrfpRfdk.js +1 -0
  27. package/dist-ssg/client/assets/WebGPURenderer-r7TRWUkD.js +1 -0
  28. package/dist-ssg/client/assets/browserAll-D7igzN7y.js +1 -0
  29. package/dist-ssg/client/assets/{dist-C887n21g.js → dist-BFqpuvkT.js} +1 -1
  30. package/dist-ssg/client/assets/{dist-Dsknr6vA.js → dist-BNNep2NV.js} +1 -1
  31. package/dist-ssg/client/assets/{dist-hHeeK4R0.js → dist-B_u-l1Yw.js} +1 -1
  32. package/dist-ssg/client/assets/{dist-Bl--njTk.js → dist-CRvuTuq9.js} +1 -1
  33. package/dist-ssg/client/assets/{dist-C65ij9xh.js → dist-CXGBVAg6.js} +1 -1
  34. package/dist-ssg/client/assets/{dist-X2a3RXr4.js → dist-ClTBYSyP.js} +1 -1
  35. package/dist-ssg/client/assets/{dist-TuIRzaj8.js → dist-CmH6Ad9e.js} +1 -1
  36. package/dist-ssg/client/assets/{dist-Dv6uiUoE.js → dist-DBjExBIn.js} +1 -1
  37. package/dist-ssg/client/assets/{dist-eTaUIlfT.js → dist-DKZbBcYn.js} +1 -1
  38. package/dist-ssg/client/assets/dist-Ky-fb63H.js +1 -0
  39. package/dist-ssg/client/assets/dist-tjvftgmy.js +1 -0
  40. package/dist-ssg/client/assets/dist-y9aZTMms.js +1 -0
  41. package/dist-ssg/client/assets/{ghostty-web-CHBQyH7_.js → ghostty-web-PY5w9Rgq.js} +1 -1
  42. package/dist-ssg/client/assets/index-DvpLdKST.css +2 -0
  43. package/dist-ssg/client/assets/{index.ssg-DkyqyrXf.js → index.ssg-yS5IzoBe.js} +136 -146
  44. package/dist-ssg/client/assets/{init-BRd1iFqg.js → init-zJql3EoS.js} +1 -1
  45. package/dist-ssg/client/assets/trpc-Z2U-lHEj.js +1 -0
  46. package/dist-ssg/client/assets/webworkerAll-_OODBDte.js +1 -0
  47. package/dist-ssg/client/index.ssg.html +2 -2
  48. package/dist-ssg/server/entry-server.js +578 -83
  49. package/package.json +1 -1
  50. package/dist/assets/CanvasRenderer-XYe2KglB.js +0 -1
  51. package/dist/assets/WebGLRenderer-DCMbi58z.js +0 -1
  52. package/dist/assets/WebGPURenderer-D8qkuqdr.js +0 -1
  53. package/dist/assets/browserAll-haNDWq66.js +0 -1
  54. package/dist/assets/dist-BXHMHEwl.js +0 -1
  55. package/dist/assets/dist-D0pwQ8q9.js +0 -1
  56. package/dist/assets/dist-D_Ftxw8J.js +0 -1
  57. package/dist/assets/index-C5SoNGIA.css +0 -1
  58. package/dist/assets/index-Djyctzdp.js +0 -1588
  59. package/dist/assets/trpc-C7hf8_bp.js +0 -1
  60. package/dist/assets/webworkerAll-BXCHpnO9.js +0 -1
  61. package/dist-ssg/client/assets/CanvasRenderer-BVt34cwv.js +0 -1
  62. package/dist-ssg/client/assets/WebGLRenderer-DTGXjggz.js +0 -1
  63. package/dist-ssg/client/assets/WebGPURenderer-9B_42fBK.js +0 -1
  64. package/dist-ssg/client/assets/browserAll-DNt0Yfet.js +0 -1
  65. package/dist-ssg/client/assets/dist-CkOzcD72.js +0 -1
  66. package/dist-ssg/client/assets/dist-DuL-Iyhd.js +0 -1
  67. package/dist-ssg/client/assets/dist-_NPWTu8A.js +0 -1
  68. package/dist-ssg/client/assets/index-BE9cyYzu.css +0 -2
  69. package/dist-ssg/client/assets/trpc-BX7m8KYM.js +0 -1
  70. package/dist-ssg/client/assets/webworkerAll-C_Psgpip.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;
@@ -73681,16 +73682,6 @@ var tabsStyleText = (id) => {
73681
73682
  transform: scaleX(0.5);
73682
73683
  }
73683
73684
 
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
73685
  #${id} .tabs-strip {
73695
73686
  background-image: linear-gradient(
73696
73687
  to bottom,
@@ -73728,11 +73719,17 @@ function TabsImpl({ tabs, selectedTab: controlled, onTabChange, onTabClose, onTa
73728
73719
  const dropIndicatorRef = (0, import_react.useRef)(null);
73729
73720
  const contentOrderRef = (0, import_react.useRef)(tabs.map((tab) => tab.id));
73730
73721
  const rootRef = (0, import_react.useRef)(null);
73722
+ const headerRef = (0, import_react.useRef)(null);
73723
+ const headerShellRef = (0, import_react.useRef)(null);
73724
+ const headerForegroundRef = (0, import_react.useRef)(null);
73725
+ const selectionIndicatorRef = (0, import_react.useRef)(null);
73726
+ const tabsButtonRef = (0, import_react.useRef)(null);
73731
73727
  const triggerRefs = (0, import_react.useRef)(/* @__PURE__ */ new Map());
73732
73728
  const panelRefs = (0, import_react.useRef)(/* @__PURE__ */ new Map());
73733
73729
  const activeTab = controlled ?? uncontrolled;
73734
73730
  const reorderable = typeof onTabOrderChange === "function" && tabs.length > 1;
73735
73731
  const tabIds = tabs.map((tab) => tab.id);
73732
+ const tabLayoutSignature = tabIds.join("|");
73736
73733
  const tabsById = (0, import_react.useMemo)(() => new Map(tabs.map((tab) => [tab.id, tab])), [tabs]);
73737
73734
  const contentTabs = (0, import_react.useMemo)(() => {
73738
73735
  const nextOrder = buildStableContentTabIds(contentOrderRef.current, tabIds);
@@ -73752,10 +73749,69 @@ function TabsImpl({ tabs, selectedTab: controlled, onTabChange, onTabClose, onTa
73752
73749
  getPanel(tabId) {
73753
73750
  return panelRefs.current.get(tabId) ?? null;
73754
73751
  },
73752
+ getHeaderShell() {
73753
+ return headerShellRef.current;
73754
+ },
73755
+ getHeaderForeground() {
73756
+ return headerForegroundRef.current;
73757
+ },
73758
+ getSelectionIndicator() {
73759
+ return selectionIndicatorRef.current;
73760
+ },
73755
73761
  getActiveTabId() {
73756
73762
  return activeTab || null;
73757
73763
  }
73758
73764
  }), [activeTab]);
73765
+ const syncSelectionIndicator = (0, import_react.useCallback)(() => {
73766
+ const indicator = selectionIndicatorRef.current;
73767
+ const header = headerRef.current;
73768
+ const activeTrigger = activeTab ? triggerRefs.current.get(activeTab) : null;
73769
+ if (!indicator) return;
73770
+ if (variant !== "default" || !header || !activeTrigger) {
73771
+ indicator.style.opacity = "0";
73772
+ indicator.style.width = "0px";
73773
+ indicator.style.height = "0px";
73774
+ indicator.style.transform = "translate(0px, 0px)";
73775
+ return;
73776
+ }
73777
+ const headerRect = header.getBoundingClientRect();
73778
+ const triggerRect = activeTrigger.getBoundingClientRect();
73779
+ indicator.style.opacity = "1";
73780
+ indicator.style.width = `${triggerRect.width}px`;
73781
+ indicator.style.height = `${triggerRect.height}px`;
73782
+ indicator.style.transform = `translate(${triggerRect.left - headerRect.left}px, ${triggerRect.top - headerRect.top}px)`;
73783
+ }, [activeTab, variant]);
73784
+ (0, import_react.useLayoutEffect)(() => {
73785
+ syncSelectionIndicator();
73786
+ }, [syncSelectionIndicator, tabLayoutSignature]);
73787
+ (0, import_react.useLayoutEffect)(() => {
73788
+ if (variant !== "default") return;
73789
+ const tabsButton = tabsButtonRef.current;
73790
+ if (!tabsButton) return;
73791
+ const handleScroll = () => {
73792
+ syncSelectionIndicator();
73793
+ };
73794
+ tabsButton.addEventListener("scroll", handleScroll, { passive: true });
73795
+ if (typeof ResizeObserver === "undefined") return () => {
73796
+ tabsButton.removeEventListener("scroll", handleScroll);
73797
+ };
73798
+ const observer = new ResizeObserver(() => {
73799
+ syncSelectionIndicator();
73800
+ });
73801
+ observer.observe(tabsButton);
73802
+ if (headerRef.current) observer.observe(headerRef.current);
73803
+ const activeTrigger = activeTab ? triggerRefs.current.get(activeTab) : null;
73804
+ if (activeTrigger) observer.observe(activeTrigger);
73805
+ return () => {
73806
+ tabsButton.removeEventListener("scroll", handleScroll);
73807
+ observer.disconnect();
73808
+ };
73809
+ }, [
73810
+ activeTab,
73811
+ syncSelectionIndicator,
73812
+ tabLayoutSignature,
73813
+ variant
73814
+ ]);
73759
73815
  const handleChange = (id) => {
73760
73816
  if (!controlled) setUncontrolled(id);
73761
73817
  onTabChange?.(id);
@@ -73835,77 +73891,115 @@ function TabsImpl({ tabs, selectedTab: controlled, onTabChange, onTabClose, onTa
73835
73891
  event.dataTransfer.dropEffect = "move";
73836
73892
  }, [reorderable]);
73837
73893
  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";
73894
+ 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";
73895
+ 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
73896
  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";
73897
+ 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"}`;
73898
+ const activeButtonClassName = variant === "terminal" ? "tab-selected bg-background text-foreground" : "tab-selected text-foreground";
73843
73899
  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";
73900
+ 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
73901
  const handleTabBarDoubleClick = (event) => {
73846
73902
  if (!onTabBarDoubleClick) return;
73847
73903
  if (event.target.closest("[data-tab-item=\"true\"]")) return;
73848
73904
  onTabBarDoubleClick();
73849
73905
  };
73906
+ const tabButtons = tabs.map((tab) => {
73907
+ const dragIndicatorStyle = dropIndicator?.tabId === tab.id ? { boxShadow: dropIndicator.position === "before" ? "inset 2px 0 0 var(--border)" : "inset -2px 0 0 var(--border)" } : void 0;
73908
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", {
73909
+ ref: (element) => {
73910
+ triggerRefs.current.set(tab.id, element);
73911
+ },
73912
+ "data-tab-item": "true",
73913
+ "data-tab-id": tab.id,
73914
+ draggable: reorderable,
73915
+ onClick: () => handleChange(tab.id),
73916
+ onDragStart: (event) => handleDragStart(event, tab.id),
73917
+ onDragEnd: handleDragEnd,
73918
+ onDragOver: (event) => handleItemDragOver(event, tab.id),
73919
+ onDrop: (event) => handleItemDrop(event, tab.id),
73920
+ className: `${buttonBaseClassName} ${activeTab === tab.id ? activeButtonClassName : inactiveButtonClassName} ${reorderable ? "cursor-grab active:cursor-grabbing" : ""}`,
73921
+ style: dragIndicatorStyle,
73922
+ children: [
73923
+ tab.icon,
73924
+ tab.label,
73925
+ tab.closable && onTabClose && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
73926
+ role: "button",
73927
+ tabIndex: 0,
73928
+ onClick: (event) => {
73929
+ event.stopPropagation();
73930
+ onTabClose(tab.id);
73931
+ },
73932
+ onKeyDown: (event) => {
73933
+ if (event.key === "Enter" || event.key === " ") {
73934
+ event.stopPropagation();
73935
+ onTabClose(tab.id);
73936
+ }
73937
+ },
73938
+ draggable: false,
73939
+ 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"}`,
73940
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(X$2, { className: "h-3 w-3" })
73941
+ })
73942
+ ]
73943
+ }, tab.id);
73944
+ });
73850
73945
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
73851
73946
  id,
73852
73947
  ref: rootRef,
73853
73948
  "data-tabs-variant": variant,
73854
73949
  className: `relative isolate flex min-h-0 min-w-0 flex-1 flex-col ${className}`,
73855
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
73950
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
73951
+ ref: headerRef,
73856
73952
  className: headerClassName,
73857
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
73953
+ children: variant === "default" ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
73954
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
73955
+ ref: headerShellRef,
73956
+ "data-tabs-header-shell": "true",
73957
+ 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"
73958
+ }),
73959
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
73960
+ className: "pointer-events-none absolute inset-0 z-10",
73961
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
73962
+ ref: selectionIndicatorRef,
73963
+ "data-tabs-selection-indicator": "true",
73964
+ "aria-hidden": "true",
73965
+ 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)]"
73966
+ })
73967
+ }),
73968
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
73969
+ ref: headerForegroundRef,
73970
+ "data-tabs-header-foreground": "true",
73971
+ className: "tabs-header-foreground relative z-20 flex min-w-0 items-stretch",
73972
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
73973
+ className: stripClassName,
73974
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
73975
+ ref: tabsButtonRef,
73976
+ className: listClassName,
73977
+ onDoubleClick: handleTabBarDoubleClick,
73978
+ onDragOver: handleListDragOver,
73979
+ onDrop: handleListDrop,
73980
+ children: tabButtons
73981
+ })
73982
+ }), actions && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
73983
+ "data-tabs-actions": "true",
73984
+ className: actionsClassName,
73985
+ children: actions
73986
+ })]
73987
+ })
73988
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
73858
73989
  className: stripClassName,
73859
73990
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
73991
+ ref: tabsButtonRef,
73860
73992
  className: listClassName,
73861
73993
  onDoubleClick: handleTabBarDoubleClick,
73862
73994
  onDragOver: handleListDragOver,
73863
73995
  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
- })
73996
+ children: tabButtons
73903
73997
  })
73904
73998
  }), actions && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
73905
73999
  "data-tabs-actions": "true",
73906
74000
  className: actionsClassName,
73907
74001
  children: actions
73908
- })]
74002
+ })] })
73909
74003
  }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
73910
74004
  className: "relative flex min-h-0 flex-1 overflow-hidden",
73911
74005
  children: contentTabs.map((tab) => tab.unmountOnHide ? activeTab === tab.id && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
@@ -73931,6 +74025,249 @@ function TabsImpl({ tabs, selectedTab: controlled, onTabChange, onTabClose, onTa
73931
74025
  }
73932
74026
  var Tabs = (0, import_react.forwardRef)(TabsImpl);
73933
74027
  //#endregion
74028
+ //#region src/lib/view-transitions/tab-scroll-freeze.ts
74029
+ var DATA_VISIBLE_HEIGHT = "tabVisibleHeight";
74030
+ var DATA_TOP_INSET = "tabTopInset";
74031
+ var DATA_SCROLL_OFFSET = "tabScrollOffset";
74032
+ var DATA_LAYOUT_BRIDGE = "tabLayoutBridge";
74033
+ var DATA_LAYOUT_BRIDGE_BASE_PADDING = "tabLayoutBridgeBasePadding";
74034
+ var DATA_LAYOUT_BRIDGE_INLINE_PADDING = "tabLayoutBridgeInlinePadding";
74035
+ var TAB_SCROLL_ROOT_SELECTOR = "[data-tab-scroll-root=\"true\"]";
74036
+ function clamp$2(value, min, max) {
74037
+ return Math.min(Math.max(value, min), max);
74038
+ }
74039
+ function maxViewportScroll(viewport) {
74040
+ return Math.max(viewport.scrollHeight - viewport.clientHeight, 0);
74041
+ }
74042
+ function maxPanelScroll(panel, visibleHeight) {
74043
+ return Math.max(panel.scrollHeight - visibleHeight, 0);
74044
+ }
74045
+ function readPanelLayoutBridge(panel) {
74046
+ return Number(panel.dataset[DATA_LAYOUT_BRIDGE] ?? "0") || 0;
74047
+ }
74048
+ function ensurePanelLayoutBridgeState(panel) {
74049
+ panel.dataset[DATA_LAYOUT_BRIDGE_BASE_PADDING] ||= window.getComputedStyle(panel).paddingBottom;
74050
+ panel.dataset[DATA_LAYOUT_BRIDGE_INLINE_PADDING] ||= panel.style.paddingBottom;
74051
+ }
74052
+ function setPanelLayoutBridge(panel, extraHeight) {
74053
+ ensurePanelLayoutBridgeState(panel);
74054
+ const normalizedHeight = Math.max(Math.ceil(extraHeight), 0);
74055
+ panel.dataset[DATA_LAYOUT_BRIDGE] = String(normalizedHeight);
74056
+ if (normalizedHeight <= 0) {
74057
+ panel.style.paddingBottom = panel.dataset[DATA_LAYOUT_BRIDGE_INLINE_PADDING] ?? "";
74058
+ return;
74059
+ }
74060
+ const basePadding = panel.dataset[DATA_LAYOUT_BRIDGE_BASE_PADDING] ?? "0px";
74061
+ panel.style.paddingBottom = `calc(${basePadding} + ${normalizedHeight}px)`;
74062
+ }
74063
+ function restoreViewportScroll(viewport, panel, targetScrollTop) {
74064
+ const applyScrollTop = () => {
74065
+ if (!viewport.isConnected || !panel.isConnected) return true;
74066
+ const existingBridge = readPanelLayoutBridge(panel);
74067
+ const baseMaxViewportScroll = Math.max(maxViewportScroll(viewport) - existingBridge, 0);
74068
+ setPanelLayoutBridge(panel, Math.max(targetScrollTop - baseMaxViewportScroll, 0));
74069
+ const nextScrollTop = clamp$2(targetScrollTop, 0, maxViewportScroll(viewport));
74070
+ viewport.scrollTop = nextScrollTop;
74071
+ return viewport.scrollTop === nextScrollTop;
74072
+ };
74073
+ if (applyScrollTop() || typeof requestAnimationFrame !== "function") return;
74074
+ let retriesRemaining = 10;
74075
+ const retry = () => {
74076
+ if (applyScrollTop() || retriesRemaining <= 0) return;
74077
+ retriesRemaining -= 1;
74078
+ requestAnimationFrame(retry);
74079
+ };
74080
+ requestAnimationFrame(retry);
74081
+ }
74082
+ function hasVerticalScrollBehavior(overflowY) {
74083
+ return overflowY === "auto" || overflowY === "scroll" || overflowY === "overlay";
74084
+ }
74085
+ function findScrollableDescendant(panel) {
74086
+ const walker = document.createTreeWalker(panel, NodeFilter.SHOW_ELEMENT);
74087
+ let candidate = null;
74088
+ while (walker.nextNode()) {
74089
+ const node = walker.currentNode;
74090
+ if (!(node instanceof HTMLElement)) continue;
74091
+ if (!hasVerticalScrollBehavior(window.getComputedStyle(node).overflowY) || node.scrollHeight <= node.clientHeight) continue;
74092
+ if (node.scrollTop > 0) return node;
74093
+ candidate ??= node;
74094
+ }
74095
+ return candidate;
74096
+ }
74097
+ function resolveContentScrollRoot(panel) {
74098
+ const markedRoot = panel.matches(TAB_SCROLL_ROOT_SELECTOR) ? panel : panel.querySelector(TAB_SCROLL_ROOT_SELECTOR);
74099
+ if (markedRoot) return markedRoot;
74100
+ return findScrollableDescendant(panel);
74101
+ }
74102
+ function panelDocumentTop(panel, viewport) {
74103
+ const panelRect = panel.getBoundingClientRect();
74104
+ const viewportRect = viewport.getBoundingClientRect();
74105
+ return viewport.scrollTop + panelRect.top - viewportRect.top;
74106
+ }
74107
+ function setFrozenMetrics(panel, snapshot) {
74108
+ panel.dataset[DATA_VISIBLE_HEIGHT] = String(snapshot.visibleHeight);
74109
+ panel.dataset[DATA_TOP_INSET] = String(snapshot.topInset);
74110
+ panel.dataset[DATA_SCROLL_OFFSET] = String(snapshot.innerScrollTop);
74111
+ }
74112
+ function clearFrozenMetrics(panel) {
74113
+ delete panel.dataset[DATA_VISIBLE_HEIGHT];
74114
+ delete panel.dataset[DATA_TOP_INSET];
74115
+ delete panel.dataset[DATA_SCROLL_OFFSET];
74116
+ }
74117
+ function applyFrozenStyles(panel, snapshot) {
74118
+ const previousStyles = {
74119
+ height: panel.style.height,
74120
+ maxHeight: panel.style.maxHeight,
74121
+ minHeight: panel.style.minHeight,
74122
+ overflowY: panel.style.overflowY
74123
+ };
74124
+ const height = `${snapshot.visibleHeight}px`;
74125
+ panel.style.height = height;
74126
+ panel.style.minHeight = height;
74127
+ panel.style.maxHeight = height;
74128
+ panel.style.overflowY = "hidden";
74129
+ setFrozenMetrics(panel, snapshot);
74130
+ return previousStyles;
74131
+ }
74132
+ function restorePanel(panel, previousStyles) {
74133
+ panel.style.height = previousStyles.height;
74134
+ panel.style.maxHeight = previousStyles.maxHeight;
74135
+ panel.style.minHeight = previousStyles.minHeight;
74136
+ panel.style.overflowY = previousStyles.overflowY;
74137
+ clearFrozenMetrics(panel);
74138
+ }
74139
+ function restoreContentScrollRoot(element, targetScrollTop) {
74140
+ if (!element) return;
74141
+ const applyScrollTop = () => {
74142
+ if (!element.isConnected) return true;
74143
+ element.scrollTop = targetScrollTop;
74144
+ return element.scrollTop === targetScrollTop;
74145
+ };
74146
+ if (applyScrollTop() || typeof requestAnimationFrame !== "function") return;
74147
+ let retriesRemaining = 10;
74148
+ const retry = () => {
74149
+ if (applyScrollTop() || retriesRemaining <= 0) return;
74150
+ retriesRemaining -= 1;
74151
+ requestAnimationFrame(retry);
74152
+ };
74153
+ requestAnimationFrame(retry);
74154
+ }
74155
+ function normalizeViewportSelectors(viewportSelector) {
74156
+ return typeof viewportSelector === "string" ? viewportSelector.split(",").map((selector) => selector.trim()) : [...viewportSelector];
74157
+ }
74158
+ function resolveViewportBoundary(panel, viewportSelector) {
74159
+ const selectors = normalizeViewportSelectors(viewportSelector);
74160
+ for (const selector of selectors) {
74161
+ if (!selector) continue;
74162
+ try {
74163
+ const match = panel.closest(selector);
74164
+ if (match instanceof HTMLElement) return match;
74165
+ } catch {
74166
+ return null;
74167
+ }
74168
+ }
74169
+ return null;
74170
+ }
74171
+ function resolveScrollViewport(panel, boundary) {
74172
+ let current = panel.parentElement;
74173
+ while (current) {
74174
+ if (hasVerticalScrollBehavior(window.getComputedStyle(current).overflowY) && current.scrollHeight > current.clientHeight) return current;
74175
+ if (boundary && current === boundary) break;
74176
+ current = current.parentElement;
74177
+ }
74178
+ if (!boundary) return null;
74179
+ if (hasVerticalScrollBehavior(window.getComputedStyle(boundary).overflowY)) return boundary;
74180
+ return null;
74181
+ }
74182
+ function resolveTabScrollElements(handle, tabId, viewportSelector) {
74183
+ if (!viewportSelector) return null;
74184
+ const panel = handle?.getPanel(tabId);
74185
+ if (!(panel instanceof HTMLElement)) return null;
74186
+ const viewport = resolveScrollViewport(panel, resolveViewportBoundary(panel, viewportSelector));
74187
+ if (!(viewport instanceof HTMLElement)) return null;
74188
+ return {
74189
+ panel,
74190
+ contentScrollRoot: resolveContentScrollRoot(panel),
74191
+ viewport
74192
+ };
74193
+ }
74194
+ function restorePanelContentScroll(panel, snapshot) {
74195
+ if (!(panel instanceof HTMLElement) || !snapshot) return;
74196
+ const contentScrollRoot = resolveContentScrollRoot(panel);
74197
+ if (contentScrollRoot && contentScrollRoot !== panel) restoreContentScrollRoot(contentScrollRoot, snapshot.contentScrollTop);
74198
+ }
74199
+ function restorePanelViewportScroll(panel, viewport, snapshot) {
74200
+ if (!(panel instanceof HTMLElement) || !(viewport instanceof HTMLElement)) return;
74201
+ if (!snapshot) {
74202
+ setPanelLayoutBridge(panel, 0);
74203
+ return;
74204
+ }
74205
+ restoreViewportScroll(viewport, panel, snapshot.viewportScrollTop);
74206
+ }
74207
+ function captureTabScrollMemory(elements) {
74208
+ const { panel, contentScrollRoot, viewport } = elements;
74209
+ const panelRect = panel.getBoundingClientRect();
74210
+ const viewportRect = viewport.getBoundingClientRect();
74211
+ const visibleHeight = clamp$2(Math.min(panelRect.bottom, viewportRect.bottom) - Math.max(panelRect.top, viewportRect.top), 0, viewport.clientHeight);
74212
+ if (visibleHeight <= 0) return null;
74213
+ const topInset = Math.max(panelRect.top - viewportRect.top, 0);
74214
+ const innerScrollTop = clamp$2(Math.max(viewportRect.top - panelRect.top, 0), 0, maxPanelScroll(panel, visibleHeight));
74215
+ return {
74216
+ contentScrollTop: contentScrollRoot && contentScrollRoot !== panel ? contentScrollRoot.scrollTop : 0,
74217
+ innerScrollTop,
74218
+ topInset,
74219
+ visibleHeight,
74220
+ viewportScrollTop: viewport.scrollTop
74221
+ };
74222
+ }
74223
+ function freezeOutgoingTab(elements, snapshot) {
74224
+ const previousStyles = applyFrozenStyles(elements.panel, snapshot);
74225
+ if (elements.contentScrollRoot && elements.contentScrollRoot !== elements.panel) restoreContentScrollRoot(elements.contentScrollRoot, snapshot.contentScrollTop);
74226
+ elements.panel.scrollTop = snapshot.innerScrollTop;
74227
+ if (snapshot.innerScrollTop > 0) elements.viewport.scrollTop = clamp$2(elements.viewport.scrollTop - snapshot.innerScrollTop, 0, maxViewportScroll(elements.viewport));
74228
+ return {
74229
+ contentScrollRoot: elements.contentScrollRoot && elements.contentScrollRoot !== elements.panel ? elements.contentScrollRoot : null,
74230
+ contentScrollTop: snapshot.contentScrollTop,
74231
+ finalViewportScrollTop: snapshot.viewportScrollTop,
74232
+ panel: elements.panel,
74233
+ previousStyles,
74234
+ viewport: elements.viewport
74235
+ };
74236
+ }
74237
+ function freezeIncomingTab(elements, snapshot) {
74238
+ const normalizedSnapshot = {
74239
+ contentScrollTop: elements.contentScrollRoot && elements.contentScrollRoot !== elements.panel ? snapshot.contentScrollTop : 0,
74240
+ topInset: clamp$2(snapshot.topInset, 0, elements.viewport.clientHeight),
74241
+ visibleHeight: clamp$2(snapshot.visibleHeight, 1, elements.viewport.clientHeight),
74242
+ innerScrollTop: 0,
74243
+ viewportScrollTop: snapshot.viewportScrollTop
74244
+ };
74245
+ normalizedSnapshot.innerScrollTop = clamp$2(snapshot.innerScrollTop, 0, maxPanelScroll(elements.panel, normalizedSnapshot.visibleHeight));
74246
+ const nextViewportScrollTop = clamp$2(panelDocumentTop(elements.panel, elements.viewport) - normalizedSnapshot.topInset, 0, maxViewportScroll(elements.viewport));
74247
+ const previousStyles = applyFrozenStyles(elements.panel, normalizedSnapshot);
74248
+ elements.viewport.scrollTop = nextViewportScrollTop;
74249
+ if (elements.contentScrollRoot && elements.contentScrollRoot !== elements.panel) restoreContentScrollRoot(elements.contentScrollRoot, normalizedSnapshot.contentScrollTop);
74250
+ elements.panel.scrollTop = normalizedSnapshot.innerScrollTop;
74251
+ return {
74252
+ contentScrollRoot: elements.contentScrollRoot && elements.contentScrollRoot !== elements.panel ? elements.contentScrollRoot : null,
74253
+ contentScrollTop: normalizedSnapshot.contentScrollTop,
74254
+ finalViewportScrollTop: snapshot.viewportScrollTop,
74255
+ panel: elements.panel,
74256
+ previousStyles,
74257
+ viewport: elements.viewport
74258
+ };
74259
+ }
74260
+ function finalizeFrozenIncomingTab(state) {
74261
+ restorePanel(state.panel, state.previousStyles);
74262
+ restoreViewportScroll(state.viewport, state.panel, state.finalViewportScrollTop);
74263
+ state.panel.scrollTop = 0;
74264
+ if (state.contentScrollRoot) restoreContentScrollRoot(state.contentScrollRoot, state.contentScrollTop);
74265
+ }
74266
+ function cleanupFrozenTab(state) {
74267
+ restorePanel(state.panel, state.previousStyles);
74268
+ state.panel.scrollTop = 0;
74269
+ }
74270
+ //#endregion
73934
74271
  //#region src/lib/view-transitions/tabs.ts
73935
74272
  var SERVER_LOCATION = {
73936
74273
  pathname: "/",
@@ -73955,6 +74292,11 @@ function resolveTabArea(pathname, area) {
73955
74292
  if (area) return area;
73956
74293
  return isStaticMode() ? "main" : navController.getAreaForPath(pathname);
73957
74294
  }
74295
+ function normalizeViewportSelectorOption(viewportSelector) {
74296
+ if (!viewportSelector) return;
74297
+ const normalized = (typeof viewportSelector === "string" ? viewportSelector.split(",") : [...viewportSelector]).map((selector) => selector.trim()).filter((selector) => selector.length > 0);
74298
+ return normalized.length > 0 ? normalized : void 0;
74299
+ }
73958
74300
  function readWindowLocation() {
73959
74301
  if (typeof window === "undefined") return SERVER_LOCATION;
73960
74302
  return {
@@ -74009,13 +74351,24 @@ function useRoutedTabsLocation() {
74009
74351
  function collectTabEntries(handle, tabId) {
74010
74352
  if (!handle) return [];
74011
74353
  const entries = [];
74354
+ const headerShell = handle.getHeaderShell();
74355
+ if (headerShell) entries.push([headerShell, "vt-tab-header-shell"]);
74356
+ const selectionIndicator = handle.getSelectionIndicator();
74357
+ if (selectionIndicator) entries.push([selectionIndicator, "vt-tab-edge"]);
74358
+ const headerForeground = handle.getHeaderForeground();
74359
+ if (headerForeground) entries.push([headerForeground, "vt-tab-header-foreground"]);
74012
74360
  const panel = handle.getPanel(tabId);
74013
74361
  if (panel) entries.push([panel, "vt-tab-panel"]);
74014
74362
  return entries;
74015
74363
  }
74016
- function useRoutedCarouselTabs({ queryKey, tabs, initialTab, area, history = "replace", allowUnknownSelection = false }) {
74364
+ function useRoutedCarouselTabs({ queryKey, tabs, initialTab, area, history = "replace", allowUnknownSelection = false, viewportSelector }) {
74017
74365
  const { location, router } = useRoutedTabsLocation();
74018
74366
  const tabsRef = (0, import_react.useRef)(null);
74367
+ const viewportSelectorValue = (0, import_react.useMemo)(() => normalizeViewportSelectorOption(viewportSelector), [typeof viewportSelector === "string" ? viewportSelector : (viewportSelector ?? []).join("\0")]);
74368
+ const scrollMemoryByTabRef = (0, import_react.useRef)(/* @__PURE__ */ new Map());
74369
+ const frozenTabsRef = (0, import_react.useRef)(/* @__PURE__ */ new Map());
74370
+ const frozenTabTokenRef = (0, import_react.useRef)(0);
74371
+ const skipNextRestoreTabRef = (0, import_react.useRef)(null);
74019
74372
  const selectedFromLocation = (0, import_react.useMemo)(() => resolveSelectedTab({
74020
74373
  tabs,
74021
74374
  queryKey,
@@ -74039,7 +74392,8 @@ function useRoutedCarouselTabs({ queryKey, tabs, initialTab, area, history = "re
74039
74392
  router,
74040
74393
  selectedFromLocation,
74041
74394
  selectedTab,
74042
- tabs
74395
+ tabs,
74396
+ viewportSelector: viewportSelectorValue
74043
74397
  });
74044
74398
  latestRef.current = {
74045
74399
  allowUnknownSelection,
@@ -74050,15 +74404,107 @@ function useRoutedCarouselTabs({ queryKey, tabs, initialTab, area, history = "re
74050
74404
  router,
74051
74405
  selectedFromLocation,
74052
74406
  selectedTab,
74053
- tabs
74407
+ tabs,
74408
+ viewportSelector: viewportSelectorValue
74054
74409
  };
74410
+ const cleanupFrozenTabById = (0, import_react.useCallback)((tabId, token) => {
74411
+ const frozenEntry = frozenTabsRef.current.get(tabId);
74412
+ if (!frozenEntry || token != null && frozenEntry.token !== token) return;
74413
+ cleanupFrozenTab(frozenEntry.state);
74414
+ frozenTabsRef.current.delete(tabId);
74415
+ }, []);
74416
+ const cleanupAllFrozenTabs = (0, import_react.useCallback)(() => {
74417
+ for (const frozenEntry of frozenTabsRef.current.values()) cleanupFrozenTab(frozenEntry.state);
74418
+ frozenTabsRef.current.clear();
74419
+ }, []);
74420
+ const captureTabSnapshot = (0, import_react.useCallback)((tabId, nextViewportSelector) => {
74421
+ const elements = resolveTabScrollElements(tabsRef.current, tabId, nextViewportSelector);
74422
+ if (!elements) return null;
74423
+ return captureTabScrollMemory(elements);
74424
+ }, []);
74425
+ const captureOutgoingTab = (0, import_react.useCallback)((tabId, nextViewportSelector, snapshotOverride) => {
74426
+ const elements = resolveTabScrollElements(tabsRef.current, tabId, nextViewportSelector);
74427
+ if (!elements) return;
74428
+ const snapshot = snapshotOverride ?? captureTabScrollMemory(elements);
74429
+ if (!snapshot) return;
74430
+ scrollMemoryByTabRef.current.set(tabId, snapshot);
74431
+ cleanupFrozenTabById(tabId);
74432
+ const token = ++frozenTabTokenRef.current;
74433
+ frozenTabsRef.current.set(tabId, {
74434
+ token,
74435
+ state: freezeOutgoingTab(elements, snapshot)
74436
+ });
74437
+ return token;
74438
+ }, [cleanupFrozenTabById]);
74439
+ const prepareIncomingTab = (0, import_react.useCallback)((tabId, nextViewportSelector, fallbackSnapshot) => {
74440
+ const elements = resolveTabScrollElements(tabsRef.current, tabId, nextViewportSelector);
74441
+ if (!elements) return null;
74442
+ const snapshot = scrollMemoryByTabRef.current.get(tabId) ?? fallbackSnapshot ?? captureTabScrollMemory(elements);
74443
+ if (!snapshot) return null;
74444
+ if (!scrollMemoryByTabRef.current.has(tabId)) scrollMemoryByTabRef.current.set(tabId, snapshot);
74445
+ cleanupFrozenTabById(tabId);
74446
+ const token = ++frozenTabTokenRef.current;
74447
+ frozenTabsRef.current.set(tabId, {
74448
+ token,
74449
+ state: freezeIncomingTab(elements, snapshot)
74450
+ });
74451
+ return token;
74452
+ }, [cleanupFrozenTabById]);
74453
+ const finalizeIncomingTab = (0, import_react.useCallback)((tabId, token) => {
74454
+ const frozenEntry = frozenTabsRef.current.get(tabId);
74455
+ if (!frozenEntry || token != null && frozenEntry.token !== token) return;
74456
+ finalizeFrozenIncomingTab(frozenEntry.state);
74457
+ frozenTabsRef.current.delete(tabId);
74458
+ }, []);
74055
74459
  (0, import_react.useEffect)(() => {
74056
74460
  setSelectedTabState((current) => current === selectedFromLocation ? current : selectedFromLocation);
74057
74461
  }, [selectedFromLocation]);
74462
+ (0, import_react.useLayoutEffect)(() => {
74463
+ if (skipNextRestoreTabRef.current === selectedTab) {
74464
+ skipNextRestoreTabRef.current = null;
74465
+ return;
74466
+ }
74467
+ const snapshot = scrollMemoryByTabRef.current.get(selectedTab);
74468
+ const elements = resolveTabScrollElements(tabsRef.current, selectedTab, viewportSelectorValue);
74469
+ const panel = elements?.panel ?? tabsRef.current?.getPanel(selectedTab) ?? null;
74470
+ restorePanelContentScroll(panel, snapshot);
74471
+ restorePanelViewportScroll(panel, elements?.viewport ?? null, snapshot);
74472
+ }, [selectedTab, viewportSelectorValue]);
74473
+ (0, import_react.useEffect)(() => {
74474
+ const elements = resolveTabScrollElements(tabsRef.current, selectedTab, viewportSelectorValue);
74475
+ const contentScrollRoot = elements?.contentScrollRoot;
74476
+ if (!elements || !contentScrollRoot || contentScrollRoot === elements.panel) return;
74477
+ const rememberContentScroll = () => {
74478
+ const existingSnapshot = scrollMemoryByTabRef.current.get(selectedTab);
74479
+ if (!existingSnapshot) return;
74480
+ scrollMemoryByTabRef.current.set(selectedTab, {
74481
+ ...existingSnapshot,
74482
+ contentScrollTop: contentScrollRoot.scrollTop
74483
+ });
74484
+ };
74485
+ rememberContentScroll();
74486
+ contentScrollRoot.addEventListener("scroll", rememberContentScroll, { passive: true });
74487
+ return () => {
74488
+ contentScrollRoot.removeEventListener("scroll", rememberContentScroll);
74489
+ };
74490
+ }, [selectedTab, viewportSelectorValue]);
74491
+ (0, import_react.useEffect)(() => {
74492
+ const validIds = new Set(tabs.map((tab) => tab.id));
74493
+ for (const tabId of scrollMemoryByTabRef.current.keys()) if (!validIds.has(tabId)) scrollMemoryByTabRef.current.delete(tabId);
74494
+ for (const tabId of Array.from(frozenTabsRef.current.keys())) if (!validIds.has(tabId)) cleanupFrozenTabById(tabId);
74495
+ }, [cleanupFrozenTabById, tabs]);
74496
+ (0, import_react.useEffect)(() => {
74497
+ scrollMemoryByTabRef.current.clear();
74498
+ cleanupAllFrozenTabs();
74499
+ }, [cleanupAllFrozenTabs, location.pathname]);
74500
+ (0, import_react.useEffect)(() => () => {
74501
+ cleanupAllFrozenTabs();
74502
+ }, [cleanupAllFrozenTabs]);
74058
74503
  const setSelectedTab = (0, import_react.useCallback)((nextTabId, options) => {
74059
- const { allowUnknownSelection: allowUnknown, area: latestArea, history: defaultHistory, location: latestLocation, queryKey: latestQueryKey, router: latestRouter, selectedFromLocation: latestSelectedFromLocation, selectedTab: currentTab, tabs: latestTabs } = latestRef.current;
74504
+ const { allowUnknownSelection: allowUnknown, area: latestArea, history: defaultHistory, location: latestLocation, queryKey: latestQueryKey, router: latestRouter, selectedFromLocation: latestSelectedFromLocation, selectedTab: currentTab, tabs: latestTabs, viewportSelector: latestViewportSelector } = latestRef.current;
74060
74505
  if (!new Set(latestTabs.map((tab) => tab.id)).has(nextTabId) && !allowUnknown) return;
74061
74506
  const nextHistory = options?.history ?? defaultHistory;
74507
+ const transferScroll = options?.transferScroll ?? true;
74062
74508
  if (currentTab === nextTabId && latestSelectedFromLocation === nextTabId) return;
74063
74509
  const commitSelection = () => {
74064
74510
  setSelectedTabState(nextTabId);
@@ -74082,28 +74528,77 @@ function useRoutedCarouselTabs({ queryKey, tabs, initialTab, area, history = "re
74082
74528
  }
74083
74529
  navController.push(nextArea, href, latestLocation.state);
74084
74530
  };
74531
+ if (!transferScroll) {
74532
+ const outgoingSnapshot = captureTabSnapshot(currentTab, latestViewportSelector);
74533
+ if (outgoingSnapshot) scrollMemoryByTabRef.current.set(currentTab, outgoingSnapshot);
74534
+ skipNextRestoreTabRef.current = nextTabId;
74535
+ if (!options?.animate || currentTab === nextTabId) {
74536
+ commitSelection();
74537
+ return;
74538
+ }
74539
+ runViewTransition({
74540
+ intent: {
74541
+ area: resolveTabArea(latestLocation.pathname, latestArea),
74542
+ kind: "tab-carousel",
74543
+ direction: "forward"
74544
+ },
74545
+ collectBeforeEntries: () => collectTabEntries(tabsRef.current, currentTab),
74546
+ collectAfterEntries: () => collectTabEntries(tabsRef.current, nextTabId),
74547
+ update: commitSelection
74548
+ });
74549
+ return;
74550
+ }
74551
+ const runSelectionWithScrollTransfer = (animated) => {
74552
+ const outgoingSnapshot = captureTabSnapshot(currentTab, latestViewportSelector);
74553
+ const incomingSeedSnapshot = scrollMemoryByTabRef.current.get(nextTabId) ?? outgoingSnapshot ?? captureTabSnapshot(nextTabId, latestViewportSelector);
74554
+ const outgoingToken = captureOutgoingTab(currentTab, latestViewportSelector, outgoingSnapshot);
74555
+ if (!animated) {
74556
+ (0, import_react_dom.flushSync)(() => {
74557
+ commitSelection();
74558
+ });
74559
+ const incomingToken = prepareIncomingTab(nextTabId, latestViewportSelector, incomingSeedSnapshot);
74560
+ if (incomingToken != null) finalizeIncomingTab(nextTabId, incomingToken);
74561
+ if (outgoingToken != null) cleanupFrozenTabById(currentTab, outgoingToken);
74562
+ return;
74563
+ }
74564
+ let incomingToken = null;
74565
+ runViewTransition({
74566
+ intent: {
74567
+ area: resolveTabArea(latestLocation.pathname, latestArea),
74568
+ kind: "tab-carousel",
74569
+ direction
74570
+ },
74571
+ collectBeforeEntries: () => collectTabEntries(tabsRef.current, currentTab),
74572
+ collectAfterEntries: () => {
74573
+ if (incomingToken == null) incomingToken = prepareIncomingTab(nextTabId, latestViewportSelector, incomingSeedSnapshot);
74574
+ return collectTabEntries(tabsRef.current, nextTabId);
74575
+ },
74576
+ update: commitSelection
74577
+ }).finally(() => {
74578
+ if (incomingToken == null) incomingToken = prepareIncomingTab(nextTabId, latestViewportSelector, incomingSeedSnapshot);
74579
+ if (incomingToken != null) finalizeIncomingTab(nextTabId, incomingToken);
74580
+ if (outgoingToken != null) cleanupFrozenTabById(currentTab, outgoingToken);
74581
+ });
74582
+ };
74085
74583
  if (!options?.animate || currentTab === nextTabId) {
74086
- commitSelection();
74584
+ runSelectionWithScrollTransfer(false);
74087
74585
  return;
74088
74586
  }
74089
74587
  const currentIndex = latestTabs.findIndex((tab) => tab.id === currentTab);
74090
74588
  const nextIndex = latestTabs.findIndex((tab) => tab.id === nextTabId);
74091
74589
  if (currentIndex < 0 || nextIndex < 0) {
74092
- commitSelection();
74590
+ runSelectionWithScrollTransfer(false);
74093
74591
  return;
74094
74592
  }
74095
74593
  const direction = nextIndex >= currentIndex ? "forward" : "backward";
74096
- runViewTransition({
74097
- intent: {
74098
- area: resolveTabArea(latestLocation.pathname, latestArea),
74099
- kind: "tab-carousel",
74100
- direction
74101
- },
74102
- collectBeforeEntries: () => collectTabEntries(tabsRef.current, currentTab),
74103
- collectAfterEntries: () => collectTabEntries(tabsRef.current, nextTabId),
74104
- update: commitSelection
74105
- });
74106
- }, []);
74594
+ runSelectionWithScrollTransfer(true);
74595
+ }, [
74596
+ captureOutgoingTab,
74597
+ captureTabSnapshot,
74598
+ cleanupFrozenTabById,
74599
+ finalizeIncomingTab,
74600
+ prepareIncomingTab
74601
+ ]);
74107
74602
  return {
74108
74603
  tabsRef,
74109
74604
  selectedTab,