@assistant-ui/react 0.15.25 → 0.15.26

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 (144) hide show
  1. package/dist/hooks/useToolCallElapsed.d.ts.map +1 -1
  2. package/dist/hooks/useToolCallElapsed.js +3 -2
  3. package/dist/hooks/useToolCallElapsed.js.map +1 -1
  4. package/dist/index.d.ts +30 -4
  5. package/dist/index.d.ts.map +1 -1
  6. package/dist/mcp-apps/McpAppRenderer.d.ts.map +1 -1
  7. package/dist/mcp-apps/McpAppRenderer.js +3 -2
  8. package/dist/mcp-apps/McpAppRenderer.js.map +1 -1
  9. package/dist/mcp-apps/app-frame.d.ts.map +1 -1
  10. package/dist/mcp-apps/app-frame.js +56 -46
  11. package/dist/mcp-apps/app-frame.js.map +1 -1
  12. package/dist/mcp-apps/csp.d.ts +4 -0
  13. package/dist/mcp-apps/csp.d.ts.map +1 -0
  14. package/dist/mcp-apps/csp.js +93 -0
  15. package/dist/mcp-apps/csp.js.map +1 -0
  16. package/dist/mcp-apps/types.d.ts +1 -0
  17. package/dist/mcp-apps/types.d.ts.map +1 -1
  18. package/dist/mcp-apps/types.js.map +1 -1
  19. package/dist/primitives/assistantModal/AssistantModalRoot.d.ts +1 -0
  20. package/dist/primitives/assistantModal/AssistantModalRoot.d.ts.map +1 -1
  21. package/dist/primitives/assistantModal/AssistantModalRoot.js.map +1 -1
  22. package/dist/primitives/attachment.d.ts +3 -1
  23. package/dist/primitives/attachment.d.ts.map +1 -1
  24. package/dist/primitives/attachment.js.map +1 -1
  25. package/dist/primitives/composer/ComposerCancel.d.ts +5 -1
  26. package/dist/primitives/composer/ComposerCancel.d.ts.map +1 -1
  27. package/dist/primitives/composer/ComposerCancel.js +14 -1
  28. package/dist/primitives/composer/ComposerCancel.js.map +1 -1
  29. package/dist/primitives/composer/ComposerInput.d.ts +14 -0
  30. package/dist/primitives/composer/ComposerInput.d.ts.map +1 -1
  31. package/dist/primitives/composer/ComposerInput.js +4 -3
  32. package/dist/primitives/composer/ComposerInput.js.map +1 -1
  33. package/dist/primitives/composer/ComposerRoot.d.ts.map +1 -1
  34. package/dist/primitives/composer/ComposerRoot.js +24 -19
  35. package/dist/primitives/composer/ComposerRoot.js.map +1 -1
  36. package/dist/primitives/composer/composerInputSelector.d.ts +3 -0
  37. package/dist/primitives/composer/composerInputSelector.d.ts.map +1 -0
  38. package/dist/primitives/composer/composerInputSelector.js +7 -0
  39. package/dist/primitives/composer/composerInputSelector.js.map +1 -0
  40. package/dist/primitives/composer/useComposerCancelWithFocus.d.ts +2 -0
  41. package/dist/primitives/composer/useComposerCancelWithFocus.d.ts.map +1 -0
  42. package/dist/primitives/composer/useComposerCancelWithFocus.js +42 -0
  43. package/dist/primitives/composer/useComposerCancelWithFocus.js.map +1 -0
  44. package/dist/primitives/composer.d.ts +35 -14
  45. package/dist/primitives/composer.d.ts.map +1 -1
  46. package/dist/primitives/composer.js.map +1 -1
  47. package/dist/primitives/message/MessagePartsGrouped.d.ts +8 -1
  48. package/dist/primitives/message/MessagePartsGrouped.d.ts.map +1 -1
  49. package/dist/primitives/message/MessagePartsGrouped.js +58 -278
  50. package/dist/primitives/message/MessagePartsGrouped.js.map +1 -1
  51. package/dist/primitives/selectionToolbar/SelectionToolbarRoot.d.ts.map +1 -1
  52. package/dist/primitives/selectionToolbar/SelectionToolbarRoot.js +5 -2
  53. package/dist/primitives/selectionToolbar/SelectionToolbarRoot.js.map +1 -1
  54. package/dist/primitives/thread/ThreadViewport.d.ts.map +1 -1
  55. package/dist/primitives/thread/ThreadViewport.js +4 -3
  56. package/dist/primitives/thread/ThreadViewport.js.map +1 -1
  57. package/dist/primitives/thread/topAnchor/useTopAnchorReserve.js +2 -2
  58. package/dist/primitives/thread/topAnchor/useTopAnchorReserve.js.map +1 -1
  59. package/dist/primitives/thread/useThreadViewportAutoScroll.d.ts.map +1 -1
  60. package/dist/primitives/thread/useThreadViewportAutoScroll.js +90 -21
  61. package/dist/primitives/thread/useThreadViewportAutoScroll.js.map +1 -1
  62. package/dist/sandbox-host/SandboxHost.d.ts.map +1 -1
  63. package/dist/sandbox-host/SandboxHost.js +3 -2
  64. package/dist/sandbox-host/SandboxHost.js.map +1 -1
  65. package/dist/unstable/useComposerInput.d.ts +7 -4
  66. package/dist/unstable/useComposerInput.d.ts.map +1 -1
  67. package/dist/unstable/useComposerInput.js +4 -4
  68. package/dist/unstable/useComposerInput.js.map +1 -1
  69. package/dist/unstable/useComposerInputHistory.d.ts +3 -2
  70. package/dist/unstable/useComposerInputHistory.d.ts.map +1 -1
  71. package/dist/unstable/useComposerInputHistory.js +2 -2
  72. package/dist/unstable/useComposerInputHistory.js.map +1 -1
  73. package/dist/unstable/useLiveCompletionAdapter.d.ts +3 -2
  74. package/dist/unstable/useLiveCompletionAdapter.d.ts.map +1 -1
  75. package/dist/unstable/useLiveCompletionAdapter.js +8 -7
  76. package/dist/unstable/useLiveCompletionAdapter.js.map +1 -1
  77. package/dist/unstable/useMentionAdapter.d.ts +12 -3
  78. package/dist/unstable/useMentionAdapter.d.ts.map +1 -1
  79. package/dist/unstable/useMentionAdapter.js +2 -2
  80. package/dist/unstable/useMentionAdapter.js.map +1 -1
  81. package/dist/unstable/useMessageStallDetection.d.ts +4 -2
  82. package/dist/unstable/useMessageStallDetection.d.ts.map +1 -1
  83. package/dist/unstable/useMessageStallDetection.js +2 -2
  84. package/dist/unstable/useMessageStallDetection.js.map +1 -1
  85. package/dist/unstable/useSlashCommandAdapter.d.ts +5 -2
  86. package/dist/unstable/useSlashCommandAdapter.d.ts.map +1 -1
  87. package/dist/unstable/useSlashCommandAdapter.js +5 -4
  88. package/dist/unstable/useSlashCommandAdapter.js.map +1 -1
  89. package/dist/unstable/webmcp/useWebMcpProvider.d.ts +4 -0
  90. package/dist/unstable/webmcp/useWebMcpProvider.d.ts.map +1 -1
  91. package/dist/unstable/webmcp/useWebMcpProvider.js +2 -0
  92. package/dist/unstable/webmcp/useWebMcpProvider.js.map +1 -1
  93. package/dist/utils/getSelectionMessageId.d.ts.map +1 -1
  94. package/dist/utils/getSelectionMessageId.js +31 -8
  95. package/dist/utils/getSelectionMessageId.js.map +1 -1
  96. package/dist/utils/hooks/useOnResizeContent.d.ts.map +1 -1
  97. package/dist/utils/hooks/useOnResizeContent.js +2 -22
  98. package/dist/utils/hooks/useOnResizeContent.js.map +1 -1
  99. package/dist/utils/useIsomorphicLayoutEffect.d.ts +3 -0
  100. package/dist/utils/useIsomorphicLayoutEffect.d.ts.map +1 -0
  101. package/dist/utils/useIsomorphicLayoutEffect.js +6 -0
  102. package/dist/utils/useIsomorphicLayoutEffect.js.map +1 -0
  103. package/package.json +10 -10
  104. package/src/hooks/useToolCallElapsed.test.tsx +4 -2
  105. package/src/hooks/useToolCallElapsed.ts +3 -2
  106. package/src/index.ts +19 -2
  107. package/src/mcp-apps/McpAppRenderer.tsx +3 -9
  108. package/src/mcp-apps/app-frame.test.tsx +38 -0
  109. package/src/mcp-apps/app-frame.tsx +5 -8
  110. package/src/mcp-apps/csp.test.ts +112 -0
  111. package/src/mcp-apps/csp.ts +124 -0
  112. package/src/mcp-apps/types.ts +1 -0
  113. package/src/model-context/frame/AssistantFrame.test.ts +45 -0
  114. package/src/model-context/frame/SPEC_AssistantFrame.md +1 -0
  115. package/src/primitives/assistantModal/AssistantModalRoot.tsx +1 -0
  116. package/src/primitives/attachment.ts +4 -1
  117. package/src/primitives/composer/ComposerCancel.focus.test.tsx +307 -0
  118. package/src/primitives/composer/ComposerCancel.ts +8 -1
  119. package/src/primitives/composer/ComposerInput.tsx +19 -1
  120. package/src/primitives/composer/ComposerRoot.tsx +6 -5
  121. package/src/primitives/composer/composerInputSelector.ts +4 -0
  122. package/src/primitives/composer/useComposerCancelWithFocus.ts +57 -0
  123. package/src/primitives/composer.ts +56 -21
  124. package/src/primitives/message/MessagePartsGrouped.test.tsx +106 -2
  125. package/src/primitives/message/MessagePartsGrouped.tsx +36 -134
  126. package/src/primitives/selectionToolbar/SelectionToolbarRoot.test.tsx +20 -12
  127. package/src/primitives/selectionToolbar/SelectionToolbarRoot.tsx +6 -2
  128. package/src/primitives/thread/ThreadViewport.tsx +3 -3
  129. package/src/primitives/thread/topAnchor/useTopAnchorReserve.ts +2 -2
  130. package/src/primitives/thread/useThreadViewportAutoScroll.test.tsx +666 -63
  131. package/src/primitives/thread/useThreadViewportAutoScroll.ts +201 -73
  132. package/src/sandbox-host/SandboxHost.tsx +3 -8
  133. package/src/unstable/useComposerInput.ts +7 -4
  134. package/src/unstable/useComposerInputHistory.ts +3 -2
  135. package/src/unstable/useLiveCompletionAdapter.ts +9 -14
  136. package/src/unstable/useMentionAdapter.ts +12 -3
  137. package/src/unstable/useMessageStallDetection.ts +4 -2
  138. package/src/unstable/useSlashCommandAdapter.ts +8 -4
  139. package/src/unstable/webmcp/useWebMcpProvider.ts +4 -0
  140. package/src/utils/getSelectionMessageId.test.ts +231 -0
  141. package/src/utils/getSelectionMessageId.ts +45 -7
  142. package/src/utils/hooks/useOnResizeContent.ts +2 -29
  143. package/src/utils/useIsomorphicLayoutEffect.test.tsx +21 -0
  144. package/src/utils/useIsomorphicLayoutEffect.ts +9 -0
@@ -1,6 +1,7 @@
1
1
  "use client";
2
2
  import { useThreadViewport, useThreadViewportStore } from "../../context/react/ThreadViewportContext.js";
3
3
  import { ThreadPrimitiveViewportProvider } from "../../context/providers/ThreadViewportProvider.js";
4
+ import { useIsomorphicLayoutEffect } from "../../utils/useIsomorphicLayoutEffect.js";
4
5
  import { Primitive as Primitive$1 } from "../../utils/Primitive.js";
5
6
  import { useManagedRef } from "../../utils/hooks/useManagedRef.js";
6
7
  import { useThreadViewportAutoScroll } from "./useThreadViewportAutoScroll.js";
@@ -8,7 +9,7 @@ import { useSizeHandle } from "../../utils/hooks/useSizeHandle.js";
8
9
  import { useTopAnchorReserve } from "./topAnchor/useTopAnchorReserve.js";
9
10
  import { getActiveTopAnchorAnchorId, getActiveTopAnchorTargetId, isTopAnchorTurnValid } from "./topAnchor/topAnchorTurn.js";
10
11
  import { c } from "@assistant-ui/tap/react-shim/compiler-runtime";
11
- import { forwardRef, useLayoutEffect } from "@assistant-ui/tap/react-shim";
12
+ import { forwardRef } from "@assistant-ui/tap/react-shim";
12
13
  import { useAuiState } from "@assistant-ui/store";
13
14
  import { jsx } from "react/jsx-runtime";
14
15
  import { useComposedRefs } from "radix-ui/internal";
@@ -93,7 +94,7 @@ const useTopAnchorTurn = (enabled) => {
93
94
  t4 = $[13];
94
95
  t5 = $[14];
95
96
  }
96
- useLayoutEffect(t4, t5);
97
+ useIsomorphicLayoutEffect(t4, t5);
97
98
  let t6;
98
99
  let t7;
99
100
  if ($[15] !== activeTurn || $[16] !== threadViewportStore) {
@@ -113,7 +114,7 @@ const useTopAnchorTurn = (enabled) => {
113
114
  t6 = $[17];
114
115
  t7 = $[18];
115
116
  }
116
- useLayoutEffect(t6, t7);
117
+ useIsomorphicLayoutEffect(t6, t7);
117
118
  };
118
119
  const ThreadPrimitiveViewportScrollable = forwardRef((t0, forwardedRef) => {
119
120
  const $ = c(18);
@@ -1 +1 @@
1
- {"version":3,"file":"ThreadViewport.js","names":["c","_c","useComposedRefs","Primitive","ComponentRef","forwardRef","ComponentPropsWithoutRef","useCallback","useLayoutEffect","useMemo","useAuiState","useManagedRef","useThreadViewportAutoScroll","ThreadPrimitiveViewportProvider","useSizeHandle","useThreadViewport","useThreadViewportStore","useTopAnchorReserve","getActiveTopAnchorAnchorId","getActiveTopAnchorTargetId","isTopAnchorTurnValid","ThreadPrimitiveViewport","Element","div","Props","autoScroll","turnAnchor","topAnchorMessageClamp","tallerThan","visibleHeight","scrollToBottomOnRunStart","scrollToBottomOnInitialize","scrollToBottomOnThreadSwitch","useViewportSizeRef","register","_temp","getHeight","_temp2","useViewportElementRef","registerViewportElement","_temp3","useTopAnchorTurn","enabled","$","threadViewportStore","t0","s","thread","activeAnchorId","t1","s_0","activeTargetId","topAnchorTurn","_temp4","t2","bb0","t3","anchorId","targetId","activeTurn","s_2","messages","topAnchorTurnIsValid","t4","t5","getState","setTopAnchorTurn","t6","t7","state","current","ThreadPrimitiveViewportScrollable","forwardedRef","children","rest","autoScrollRef","viewportSizeRef","viewportElementRef","topAnchorEnabled","ref","displayName","props","registerViewport","el","clientHeight","s_1","getHeight","_c","Primitive"],"sources":["../../../src/primitives/thread/ThreadViewport.tsx"],"sourcesContent":["\"use client\";\n\nimport { useComposedRefs } from \"radix-ui/internal\";\nimport { Primitive } from \"../../utils/Primitive\";\nimport {\n type ComponentRef,\n forwardRef,\n type ComponentPropsWithoutRef,\n useCallback,\n useLayoutEffect,\n useMemo,\n} from \"react\";\nimport { useAuiState } from \"@assistant-ui/store\";\nimport { useManagedRef } from \"../../utils/hooks/useManagedRef\";\nimport { useThreadViewportAutoScroll } from \"./useThreadViewportAutoScroll\";\nimport { ThreadPrimitiveViewportProvider } from \"../../context/providers/ThreadViewportProvider\";\nimport { useSizeHandle } from \"../../utils/hooks/useSizeHandle\";\nimport {\n useThreadViewport,\n useThreadViewportStore,\n} from \"../../context/react/ThreadViewportContext\";\nimport { useTopAnchorReserve } from \"./topAnchor/useTopAnchorReserve\";\nimport {\n getActiveTopAnchorAnchorId,\n getActiveTopAnchorTargetId,\n isTopAnchorTurnValid,\n} from \"./topAnchor/topAnchorTurn\";\n\nexport namespace ThreadPrimitiveViewport {\n export type Element = ComponentRef<typeof Primitive.div>;\n export type Props = ComponentPropsWithoutRef<typeof Primitive.div> & {\n /**\n * Whether to automatically scroll to the bottom when new messages are added.\n * When enabled, the viewport will automatically scroll to show the latest content.\n *\n * Default false if `turnAnchor` is \"top\", otherwise defaults to true.\n */\n autoScroll?: boolean | undefined;\n\n /**\n * Controls scroll anchoring behavior for new messages.\n * - \"bottom\" (default): Messages anchor at the bottom, classic chat behavior.\n * - \"top\": New user messages anchor at the top of the viewport for a focused reading experience.\n */\n turnAnchor?: \"top\" | \"bottom\" | undefined;\n\n /**\n * Clamps tall user messages so the assistant response stays in view.\n *\n * @default { tallerThan: \"10em\", visibleHeight: \"6em\" }\n */\n topAnchorMessageClamp?: {\n /**\n * Clamp messages taller than this. Supports `px`, `em`, and `rem`.\n *\n * @default \"10em\"\n */\n tallerThan?: string;\n /**\n * Visible portion of clamped messages. Supports `px`, `em`, and `rem`.\n *\n * @default \"6em\"\n */\n visibleHeight?: string;\n };\n\n /**\n * Whether to scroll to bottom when a new run starts.\n *\n * Defaults to true.\n */\n scrollToBottomOnRunStart?: boolean | undefined;\n\n /**\n * Whether to scroll to bottom when thread history is first loaded.\n *\n * Defaults to true.\n */\n scrollToBottomOnInitialize?: boolean | undefined;\n\n /**\n * Whether to scroll to bottom when switching to a different thread.\n *\n * Defaults to true.\n */\n scrollToBottomOnThreadSwitch?: boolean | undefined;\n };\n}\n\nconst useViewportSizeRef = () => {\n const register = useThreadViewport((s) => s.registerViewport);\n const getHeight = useCallback((el: HTMLElement) => el.clientHeight, []);\n return useSizeHandle(register, getHeight);\n};\n\nconst useViewportElementRef = () => {\n const registerViewportElement = useThreadViewport(\n (s) => s.registerViewportElement,\n );\n\n return useManagedRef(registerViewportElement);\n};\n\nconst useTopAnchorTurn = (enabled: boolean) => {\n const threadViewportStore = useThreadViewportStore();\n const activeAnchorId = useAuiState((s) => {\n if (!enabled) return undefined;\n return getActiveTopAnchorAnchorId(s.thread);\n });\n const activeTargetId = useAuiState((s) => {\n if (!enabled) return undefined;\n return getActiveTopAnchorTargetId(s.thread);\n });\n const topAnchorTurn = useThreadViewport((s) => s.topAnchorTurn);\n const activeTurn = useMemo(() => {\n if (!activeAnchorId || !activeTargetId) return null;\n return { anchorId: activeAnchorId, targetId: activeTargetId };\n }, [activeAnchorId, activeTargetId]);\n\n const topAnchorTurnIsValid = useAuiState(\n (s) =>\n enabled &&\n !!topAnchorTurn &&\n isTopAnchorTurnValid(topAnchorTurn, s.thread.messages),\n );\n\n useLayoutEffect(() => {\n if (!topAnchorTurn || topAnchorTurnIsValid) return;\n\n threadViewportStore.getState().setTopAnchorTurn(null);\n }, [threadViewportStore, topAnchorTurn, topAnchorTurnIsValid]);\n\n useLayoutEffect(() => {\n if (!activeTurn) return;\n\n const state = threadViewportStore.getState();\n const current = state.topAnchorTurn;\n if (\n current?.anchorId === activeTurn.anchorId &&\n current.targetId === activeTurn.targetId\n ) {\n return;\n }\n\n state.setTopAnchorTurn(activeTurn);\n }, [activeTurn, threadViewportStore]);\n};\n\nconst ThreadPrimitiveViewportScrollable = forwardRef<\n ThreadPrimitiveViewport.Element,\n ThreadPrimitiveViewport.Props\n>(\n (\n {\n autoScroll,\n scrollToBottomOnRunStart,\n scrollToBottomOnInitialize,\n scrollToBottomOnThreadSwitch,\n children,\n ...rest\n },\n forwardedRef,\n ) => {\n const autoScrollRef = useThreadViewportAutoScroll<HTMLDivElement>({\n autoScroll,\n scrollToBottomOnRunStart,\n scrollToBottomOnInitialize,\n scrollToBottomOnThreadSwitch,\n });\n const viewportSizeRef = useViewportSizeRef();\n const viewportElementRef = useViewportElementRef();\n const threadViewportStore = useThreadViewportStore();\n const turnAnchor = threadViewportStore.getState().turnAnchor;\n const topAnchorEnabled = turnAnchor === \"top\";\n useTopAnchorTurn(topAnchorEnabled);\n useTopAnchorReserve(topAnchorEnabled);\n const ref = useComposedRefs(\n forwardedRef,\n autoScrollRef,\n viewportSizeRef,\n viewportElementRef,\n );\n\n return (\n <Primitive.div {...rest} ref={ref}>\n {children}\n </Primitive.div>\n );\n },\n);\n\nThreadPrimitiveViewportScrollable.displayName =\n \"ThreadPrimitive.ViewportScrollable\";\n\n/**\n * A scrollable viewport container for thread messages.\n *\n * This component provides a scrollable area for displaying thread messages with\n * automatic scrolling capabilities. It manages the viewport state and provides\n * context for child components to access viewport-related functionality.\n *\n * @example\n * ```tsx\n * <ThreadPrimitive.Viewport turnAnchor=\"top\">\n * <ThreadPrimitive.Messages>\n * {() => <MyMessage />}\n * </ThreadPrimitive.Messages>\n * </ThreadPrimitive.Viewport>\n * ```\n */\nexport const ThreadPrimitiveViewport = forwardRef<\n ThreadPrimitiveViewport.Element,\n ThreadPrimitiveViewport.Props\n>(({ turnAnchor, topAnchorMessageClamp, ...props }, ref) => {\n return (\n <ThreadPrimitiveViewportProvider\n options={{ turnAnchor, topAnchorMessageClamp }}\n >\n <ThreadPrimitiveViewportScrollable {...props} ref={ref} />\n </ThreadPrimitiveViewportProvider>\n );\n});\n\nThreadPrimitiveViewport.displayName = \"ThreadPrimitive.Viewport\";\n"],"mappings":"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"}
1
+ {"version":3,"file":"ThreadViewport.js","names":["c","_c","useComposedRefs","Primitive","ComponentRef","forwardRef","ComponentPropsWithoutRef","useCallback","useMemo","useAuiState","useManagedRef","useThreadViewportAutoScroll","ThreadPrimitiveViewportProvider","useSizeHandle","useThreadViewport","useThreadViewportStore","useTopAnchorReserve","useIsomorphicLayoutEffect","getActiveTopAnchorAnchorId","getActiveTopAnchorTargetId","isTopAnchorTurnValid","ThreadPrimitiveViewport","Element","div","Props","autoScroll","turnAnchor","topAnchorMessageClamp","tallerThan","visibleHeight","scrollToBottomOnRunStart","scrollToBottomOnInitialize","scrollToBottomOnThreadSwitch","useViewportSizeRef","register","_temp","getHeight","_temp2","useViewportElementRef","registerViewportElement","_temp3","useTopAnchorTurn","enabled","$","threadViewportStore","t0","s","thread","activeAnchorId","t1","s_0","activeTargetId","topAnchorTurn","_temp4","t2","bb0","t3","anchorId","targetId","activeTurn","s_2","messages","topAnchorTurnIsValid","t4","t5","getState","setTopAnchorTurn","t6","t7","state","current","ThreadPrimitiveViewportScrollable","forwardedRef","children","rest","autoScrollRef","viewportSizeRef","viewportElementRef","topAnchorEnabled","ref","displayName","props","registerViewport","el","clientHeight","s_1","getHeight","_c","Primitive"],"sources":["../../../src/primitives/thread/ThreadViewport.tsx"],"sourcesContent":["\"use client\";\n\nimport { useComposedRefs } from \"radix-ui/internal\";\nimport { Primitive } from \"../../utils/Primitive\";\nimport {\n type ComponentRef,\n forwardRef,\n type ComponentPropsWithoutRef,\n useCallback,\n useMemo,\n} from \"react\";\nimport { useAuiState } from \"@assistant-ui/store\";\nimport { useManagedRef } from \"../../utils/hooks/useManagedRef\";\nimport { useThreadViewportAutoScroll } from \"./useThreadViewportAutoScroll\";\nimport { ThreadPrimitiveViewportProvider } from \"../../context/providers/ThreadViewportProvider\";\nimport { useSizeHandle } from \"../../utils/hooks/useSizeHandle\";\nimport {\n useThreadViewport,\n useThreadViewportStore,\n} from \"../../context/react/ThreadViewportContext\";\nimport { useTopAnchorReserve } from \"./topAnchor/useTopAnchorReserve\";\nimport { useIsomorphicLayoutEffect } from \"../../utils/useIsomorphicLayoutEffect\";\nimport {\n getActiveTopAnchorAnchorId,\n getActiveTopAnchorTargetId,\n isTopAnchorTurnValid,\n} from \"./topAnchor/topAnchorTurn\";\n\nexport namespace ThreadPrimitiveViewport {\n export type Element = ComponentRef<typeof Primitive.div>;\n export type Props = ComponentPropsWithoutRef<typeof Primitive.div> & {\n /**\n * Whether to automatically scroll to the bottom when new messages are added.\n * When enabled, the viewport will automatically scroll to show the latest content.\n *\n * Default false if `turnAnchor` is \"top\", otherwise defaults to true.\n */\n autoScroll?: boolean | undefined;\n\n /**\n * Controls scroll anchoring behavior for new messages.\n * - \"bottom\" (default): Messages anchor at the bottom, classic chat behavior.\n * - \"top\": New user messages anchor at the top of the viewport for a focused reading experience.\n */\n turnAnchor?: \"top\" | \"bottom\" | undefined;\n\n /**\n * Clamps tall user messages so the assistant response stays in view.\n *\n * @default { tallerThan: \"10em\", visibleHeight: \"6em\" }\n */\n topAnchorMessageClamp?: {\n /**\n * Clamp messages taller than this. Supports `px`, `em`, and `rem`.\n *\n * @default \"10em\"\n */\n tallerThan?: string;\n /**\n * Visible portion of clamped messages. Supports `px`, `em`, and `rem`.\n *\n * @default \"6em\"\n */\n visibleHeight?: string;\n };\n\n /**\n * Whether to scroll to bottom when a new run starts.\n *\n * Defaults to true.\n */\n scrollToBottomOnRunStart?: boolean | undefined;\n\n /**\n * Whether to scroll to bottom when thread history is first loaded.\n *\n * Defaults to true.\n */\n scrollToBottomOnInitialize?: boolean | undefined;\n\n /**\n * Whether to scroll to bottom when switching to a different thread.\n *\n * Defaults to true.\n */\n scrollToBottomOnThreadSwitch?: boolean | undefined;\n };\n}\n\nconst useViewportSizeRef = () => {\n const register = useThreadViewport((s) => s.registerViewport);\n const getHeight = useCallback((el: HTMLElement) => el.clientHeight, []);\n return useSizeHandle(register, getHeight);\n};\n\nconst useViewportElementRef = () => {\n const registerViewportElement = useThreadViewport(\n (s) => s.registerViewportElement,\n );\n\n return useManagedRef(registerViewportElement);\n};\n\nconst useTopAnchorTurn = (enabled: boolean) => {\n const threadViewportStore = useThreadViewportStore();\n const activeAnchorId = useAuiState((s) => {\n if (!enabled) return undefined;\n return getActiveTopAnchorAnchorId(s.thread);\n });\n const activeTargetId = useAuiState((s) => {\n if (!enabled) return undefined;\n return getActiveTopAnchorTargetId(s.thread);\n });\n const topAnchorTurn = useThreadViewport((s) => s.topAnchorTurn);\n const activeTurn = useMemo(() => {\n if (!activeAnchorId || !activeTargetId) return null;\n return { anchorId: activeAnchorId, targetId: activeTargetId };\n }, [activeAnchorId, activeTargetId]);\n\n const topAnchorTurnIsValid = useAuiState(\n (s) =>\n enabled &&\n !!topAnchorTurn &&\n isTopAnchorTurnValid(topAnchorTurn, s.thread.messages),\n );\n\n useIsomorphicLayoutEffect(() => {\n if (!topAnchorTurn || topAnchorTurnIsValid) return;\n\n threadViewportStore.getState().setTopAnchorTurn(null);\n }, [threadViewportStore, topAnchorTurn, topAnchorTurnIsValid]);\n\n useIsomorphicLayoutEffect(() => {\n if (!activeTurn) return;\n\n const state = threadViewportStore.getState();\n const current = state.topAnchorTurn;\n if (\n current?.anchorId === activeTurn.anchorId &&\n current.targetId === activeTurn.targetId\n ) {\n return;\n }\n\n state.setTopAnchorTurn(activeTurn);\n }, [activeTurn, threadViewportStore]);\n};\n\nconst ThreadPrimitiveViewportScrollable = forwardRef<\n ThreadPrimitiveViewport.Element,\n ThreadPrimitiveViewport.Props\n>(\n (\n {\n autoScroll,\n scrollToBottomOnRunStart,\n scrollToBottomOnInitialize,\n scrollToBottomOnThreadSwitch,\n children,\n ...rest\n },\n forwardedRef,\n ) => {\n const autoScrollRef = useThreadViewportAutoScroll<HTMLDivElement>({\n autoScroll,\n scrollToBottomOnRunStart,\n scrollToBottomOnInitialize,\n scrollToBottomOnThreadSwitch,\n });\n const viewportSizeRef = useViewportSizeRef();\n const viewportElementRef = useViewportElementRef();\n const threadViewportStore = useThreadViewportStore();\n const turnAnchor = threadViewportStore.getState().turnAnchor;\n const topAnchorEnabled = turnAnchor === \"top\";\n useTopAnchorTurn(topAnchorEnabled);\n useTopAnchorReserve(topAnchorEnabled);\n const ref = useComposedRefs(\n forwardedRef,\n autoScrollRef,\n viewportSizeRef,\n viewportElementRef,\n );\n\n return (\n <Primitive.div {...rest} ref={ref}>\n {children}\n </Primitive.div>\n );\n },\n);\n\nThreadPrimitiveViewportScrollable.displayName =\n \"ThreadPrimitive.ViewportScrollable\";\n\n/**\n * A scrollable viewport container for thread messages.\n *\n * This component provides a scrollable area for displaying thread messages with\n * automatic scrolling capabilities. It manages the viewport state and provides\n * context for child components to access viewport-related functionality.\n *\n * @example\n * ```tsx\n * <ThreadPrimitive.Viewport turnAnchor=\"top\">\n * <ThreadPrimitive.Messages>\n * {() => <MyMessage />}\n * </ThreadPrimitive.Messages>\n * </ThreadPrimitive.Viewport>\n * ```\n */\nexport const ThreadPrimitiveViewport = forwardRef<\n ThreadPrimitiveViewport.Element,\n ThreadPrimitiveViewport.Props\n>(({ turnAnchor, topAnchorMessageClamp, ...props }, ref) => {\n return (\n <ThreadPrimitiveViewportProvider\n options={{ turnAnchor, topAnchorMessageClamp }}\n >\n <ThreadPrimitiveViewportScrollable {...props} ref={ref} />\n </ThreadPrimitiveViewportProvider>\n );\n});\n\nThreadPrimitiveViewport.displayName = \"ThreadPrimitive.Viewport\";\n"],"mappings":"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"}
@@ -1,8 +1,8 @@
1
1
  "use client";
2
2
  import { useThreadViewportStore } from "../../../context/react/ThreadViewportContext.js";
3
+ import { useIsomorphicLayoutEffect } from "../../../utils/useIsomorphicLayoutEffect.js";
3
4
  import { mountTopAnchorReserve } from "./mountTopAnchorReserve.js";
4
5
  import { c } from "@assistant-ui/tap/react-shim/compiler-runtime";
5
- import { useLayoutEffect } from "@assistant-ui/tap/react-shim";
6
6
  //#region src/primitives/thread/topAnchor/useTopAnchorReserve.ts
7
7
  /**
8
8
  * Mounts the top-turn-anchor reserve element against the active
@@ -28,7 +28,7 @@ const useTopAnchorReserve = (enabled) => {
28
28
  t0 = $[2];
29
29
  t1 = $[3];
30
30
  }
31
- useLayoutEffect(t0, t1);
31
+ useIsomorphicLayoutEffect(t0, t1);
32
32
  };
33
33
  //#endregion
34
34
  export { useTopAnchorReserve };
@@ -1 +1 @@
1
- {"version":3,"file":"useTopAnchorReserve.js","names":["c","_c","useLayoutEffect","useThreadViewportStore","mountTopAnchorReserve","useTopAnchorReserve","enabled","$","threadViewportStore","t0","t1","_c"],"sources":["../../../../src/primitives/thread/topAnchor/useTopAnchorReserve.ts"],"sourcesContent":["\"use client\";\n\nimport { useLayoutEffect } from \"react\";\nimport { useThreadViewportStore } from \"../../../context/react/ThreadViewportContext\";\nimport { mountTopAnchorReserve } from \"./mountTopAnchorReserve\";\n\n/**\n * Mounts the top-turn-anchor reserve element against the active\n * `ThreadViewport` store. Call this from inside the scrollable viewport so\n * the reserve `<div>` is appended next to the streaming assistant message.\n */\nexport const useTopAnchorReserve = (enabled: boolean) => {\n const threadViewportStore = useThreadViewportStore();\n\n useLayoutEffect(() => {\n if (!enabled) return;\n return mountTopAnchorReserve(threadViewportStore);\n }, [enabled, threadViewportStore]);\n};\n"],"mappings":";;;;;;;;;;;AAWA,MAAaK,uBAAsBC,YAAA;CAAA,MAAAC,IAAAN,EAAA,CAAA;CACjC,MAAAO,sBAA4BL,uBAAuB;CAAE,IAAAM;CAAA,IAAAC;CAAA,IAAAH,EAAA,OAAAD,WAAAC,EAAA,OAAAC,qBAAA;EAErCC,WAAA;GACd,IAAI,CAACH,SAAO;GAAS,OACdF,sBAAsBI,mBAAmB;EAAC;EAChDE,KAAA,CAACJ,SAASE,mBAAmB;EAACD,EAAA,KAAAD;EAAAC,EAAA,KAAAC;EAAAD,EAAA,KAAAE;EAAAF,EAAA,KAAAG;CAAA,OAAA;EAAAD,KAAAF,EAAA;EAAAG,KAAAH,EAAA;CAAA;CAHjCL,gBAAgBO,IAGbC,EAA8B;AAAC"}
1
+ {"version":3,"file":"useTopAnchorReserve.js","names":["c","_c","useIsomorphicLayoutEffect","useThreadViewportStore","mountTopAnchorReserve","useTopAnchorReserve","enabled","$","threadViewportStore","t0","t1","_c"],"sources":["../../../../src/primitives/thread/topAnchor/useTopAnchorReserve.ts"],"sourcesContent":["\"use client\";\n\nimport { useIsomorphicLayoutEffect } from \"../../../utils/useIsomorphicLayoutEffect\";\nimport { useThreadViewportStore } from \"../../../context/react/ThreadViewportContext\";\nimport { mountTopAnchorReserve } from \"./mountTopAnchorReserve\";\n\n/**\n * Mounts the top-turn-anchor reserve element against the active\n * `ThreadViewport` store. Call this from inside the scrollable viewport so\n * the reserve `<div>` is appended next to the streaming assistant message.\n */\nexport const useTopAnchorReserve = (enabled: boolean) => {\n const threadViewportStore = useThreadViewportStore();\n\n useIsomorphicLayoutEffect(() => {\n if (!enabled) return;\n return mountTopAnchorReserve(threadViewportStore);\n }, [enabled, threadViewportStore]);\n};\n"],"mappings":";;;;;;;;;;;AAWA,MAAaK,uBAAsBC,YAAA;CAAA,MAAAC,IAAAN,EAAA,CAAA;CACjC,MAAAO,sBAA4BL,uBAAuB;CAAE,IAAAM;CAAA,IAAAC;CAAA,IAAAH,EAAA,OAAAD,WAAAC,EAAA,OAAAC,qBAAA;EAE3BC,WAAA;GACxB,IAAI,CAACH,SAAO;GAAS,OACdF,sBAAsBI,mBAAmB;EAAC;EAChDE,KAAA,CAACJ,SAASE,mBAAmB;EAACD,EAAA,KAAAD;EAAAC,EAAA,KAAAC;EAAAD,EAAA,KAAAE;EAAAF,EAAA,KAAAG;CAAA,OAAA;EAAAD,KAAAF,EAAA;EAAAG,KAAAH,EAAA;CAAA;CAHjCL,0BAA0BO,IAGvBC,EAA8B;AAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"useThreadViewportAutoScroll.d.ts","sourceRoot":"","sources":["../../../src/primitives/thread/useThreadViewportAutoScroll.ts"],"names":[],"mappings":"AAGA,OAAO,EAAwC,KAAK,WAAW,EAAE,MAAM,OAAO,CAAC;AA+F/E,yBAAiB,2BAA2B,CAAC;IAC3C,KAAY,OAAO,GAAG;QACpB;;;;;WAKG;QACH,UAAU,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;QAEjC;;;;WAIG;QACH,wBAAwB,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;QAE/C;;;;WAIG;QACH,0BAA0B,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;QAEjD;;;;WAIG;QACH,4BAA4B,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;KACpD,CAAC;CACH;AAED,eAAO,MAAM,2BAA2B,GAAI,QAAQ,SAAS,WAAW,EAAE,qGAKvE,2BAA2B,CAAC,OAAO,KAAG,WAAW,CAAC,QAAQ,CAwS5D,CAAC"}
1
+ {"version":3,"file":"useThreadViewportAutoScroll.d.ts","sourceRoot":"","sources":["../../../src/primitives/thread/useThreadViewportAutoScroll.ts"],"names":[],"mappings":"AAGA,OAAO,EAAuB,KAAK,WAAW,EAAE,MAAM,OAAO,CAAC;AAwH9D,yBAAiB,2BAA2B,CAAC;IAC3C,KAAY,OAAO,GAAG;QACpB;;;;;WAKG;QACH,UAAU,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;QAEjC;;;;WAIG;QACH,wBAAwB,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;QAE/C;;;;WAIG;QACH,0BAA0B,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;QAEjD;;;;WAIG;QACH,4BAA4B,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;KACpD,CAAC;CACH;AAED,eAAO,MAAM,2BAA2B,GAAI,QAAQ,SAAS,WAAW,EAAE,qGAKvE,2BAA2B,CAAC,OAAO,KAAG,WAAW,CAAC,QAAQ,CA+Y5D,CAAC"}
@@ -1,15 +1,25 @@
1
1
  "use client";
2
2
  import { useThreadViewportStore } from "../../context/react/ThreadViewportContext.js";
3
3
  import { writableStore } from "../../context/ReadonlyStore.js";
4
+ import { useIsomorphicLayoutEffect } from "../../utils/useIsomorphicLayoutEffect.js";
4
5
  import { useOnScrollToBottom } from "../../utils/hooks/useOnScrollToBottom.js";
5
6
  import { useManagedRef } from "../../utils/hooks/useManagedRef.js";
6
7
  import { useOnResizeContent } from "../../utils/hooks/useOnResizeContent.js";
7
- import { useCallback, useLayoutEffect, useRef } from "@assistant-ui/tap/react-shim";
8
+ import { useCallback, useRef } from "@assistant-ui/tap/react-shim";
8
9
  import { useAui, useAuiEvent, useAuiState } from "@assistant-ui/store";
9
10
  import { useComposedRefs } from "radix-ui/internal";
10
11
  import { isUserScrollUp, isViewportAtBottom, viewportOverflows } from "@assistant-ui/store/client";
11
12
  //#region src/primitives/thread/useThreadViewportAutoScroll.ts
12
- const ACTIVATION_KEYS = /* @__PURE__ */ new Set(["Enter", " "]);
13
+ const SCROLL_KEYS = /* @__PURE__ */ new Set([
14
+ " ",
15
+ "ArrowUp",
16
+ "ArrowDown",
17
+ "PageUp",
18
+ "PageDown",
19
+ "Home",
20
+ "End"
21
+ ]);
22
+ const INTENT_CANCEL_KEYS = /* @__PURE__ */ new Set(["Enter", ...SCROLL_KEYS]);
13
23
  const TEXT_ENTRY_SELECTOR = [
14
24
  "textarea",
15
25
  "select",
@@ -17,6 +27,13 @@ const TEXT_ENTRY_SELECTOR = [
17
27
  "input:not([type='checkbox']):not([type='radio']):not([type='button']):not([type='submit']):not([type='reset']):not([type='image']):not([type='range']):not([type='file']):not([type='color'])"
18
28
  ].join(", ");
19
29
  const MESSAGE_SELECTOR = "[data-message-id]";
30
+ const DISCLOSURE_SELECTOR = "[aria-expanded], summary";
31
+ const isOpeningDisclosure = (target) => {
32
+ const control = target.closest(DISCLOSURE_SELECTOR);
33
+ if (!control) return false;
34
+ if (control.tagName === "SUMMARY") return control.parentElement?.hasAttribute("open") === false;
35
+ return control.getAttribute("aria-expanded") === "false" && (control.getAttribute("aria-haspopup") ?? "false") === "false" && control.getAttribute("role") !== "combobox";
36
+ };
20
37
  const offsetInViewport = (element, viewport) => element.getBoundingClientRect().top - viewport.getBoundingClientRect().top;
21
38
  const measureFirstMessage = (viewport) => {
22
39
  const element = viewport.querySelector(MESSAGE_SELECTOR);
@@ -88,8 +105,10 @@ const useThreadViewportAutoScroll = ({ autoScroll, scrollToBottomOnRunStart = tr
88
105
  const heldScrollTopRef = useRef(0);
89
106
  const scrollingToBottomBehaviorRef = useRef(null);
90
107
  const followBottomRef = useRef(autoScroll);
108
+ const followPausedRef = useRef(false);
109
+ const scrolledSincePauseRef = useRef(false);
91
110
  const previousAutoScrollRef = useRef(autoScroll);
92
- useLayoutEffect(() => {
111
+ useIsomorphicLayoutEffect(() => {
93
112
  const previousAutoScroll = previousAutoScrollRef.current;
94
113
  previousAutoScrollRef.current = autoScroll;
95
114
  if (previousAutoScroll || !autoScroll) return;
@@ -100,12 +119,15 @@ const useThreadViewportAutoScroll = ({ autoScroll, scrollToBottomOnRunStart = tr
100
119
  const div_0 = divRef.current;
101
120
  if (!div_0) return;
102
121
  followBottomRef.current = true;
122
+ followPausedRef.current = false;
103
123
  scrollingToBottomBehaviorRef.current = behavior;
104
124
  prependAnchorRef.current = null;
105
125
  div_0.scrollTo({
106
126
  top: div_0.scrollHeight,
107
127
  behavior
108
128
  });
129
+ lastScrollTop.current = div_0.scrollTop;
130
+ lastScrollHeight.current = div_0.scrollHeight;
109
131
  }, []);
110
132
  const cancelScheduledFrame = useCallback(() => {
111
133
  if (scheduledFrameRef.current === null) return;
@@ -120,24 +142,27 @@ const useThreadViewportAutoScroll = ({ autoScroll, scrollToBottomOnRunStart = tr
120
142
  scrollToBottom(behavior_0);
121
143
  });
122
144
  }, [cancelScheduledFrame, scrollToBottom]);
123
- useLayoutEffect(() => () => cancelScheduledFrame(), [cancelScheduledFrame]);
145
+ useIsomorphicLayoutEffect(() => () => cancelScheduledFrame(), [cancelScheduledFrame]);
124
146
  const hasActiveTopAnchor = useCallback(() => {
125
147
  const state = threadViewportStore.getState();
126
148
  return state.turnAnchor === "top" && state.element.viewport === divRef.current && state.element.anchor !== null;
127
149
  }, [threadViewportStore]);
128
- const handleScroll = () => {
150
+ const onScroll = () => {
129
151
  const div_1 = divRef.current;
130
152
  if (!div_1) return;
131
153
  if (prependAnchorRef.current && Math.abs(div_1.scrollTop - heldScrollTopRef.current) >= 1) prependAnchorRef.current = null;
132
154
  const isAtBottom = threadViewportStore.getState().isAtBottom;
133
155
  const newIsAtBottom = isViewportAtBottom(div_1);
134
- if (!newIsAtBottom && lastScrollTop.current < div_1.scrollTop) {} else {
156
+ if (scrollingToBottomBehaviorRef.current !== null && !newIsAtBottom && lastScrollTop.current < div_1.scrollTop) {} else {
135
157
  const userScrolledUp = isUserScrollUp({
136
158
  scrollTop: lastScrollTop.current,
137
159
  scrollHeight: lastScrollHeight.current
138
160
  }, div_1);
139
161
  if (newIsAtBottom) {
140
- if (viewportOverflows(div_1)) scrollingToBottomBehaviorRef.current = null;
162
+ if (viewportOverflows(div_1)) {
163
+ scrollingToBottomBehaviorRef.current = null;
164
+ if (scrolledSincePauseRef.current) followPausedRef.current = false;
165
+ }
141
166
  if (autoScroll) followBottomRef.current = true;
142
167
  } else if (userScrolledUp) {
143
168
  cancelScheduledFrame();
@@ -150,30 +175,58 @@ const useThreadViewportAutoScroll = ({ autoScroll, scrollToBottomOnRunStart = tr
150
175
  lastScrollHeight.current = div_1.scrollHeight;
151
176
  firstMessageRef.current = measureFirstMessage(div_1);
152
177
  };
153
- const resizeRef = useOnResizeContent(() => {
178
+ const onScrollRef = useRef(onScroll);
179
+ useIsomorphicLayoutEffect(() => {
180
+ onScrollRef.current = onScroll;
181
+ });
182
+ const handleScroll = useCallback(() => onScrollRef.current(), []);
183
+ const onResize = () => {
154
184
  const div_2 = divRef.current;
155
185
  if (!div_2) return;
156
186
  const { scrollHeight, clientHeight } = div_2;
157
187
  if (scrollHeight === lastObservedScrollHeight.current && clientHeight === lastObservedClientHeight.current) return;
158
188
  lastObservedScrollHeight.current = scrollHeight;
159
189
  lastObservedClientHeight.current = clientHeight;
160
- const scrollBehavior = scrollingToBottomBehaviorRef.current;
161
- if (scrollBehavior && hasActiveTopAnchor()) scrollingToBottomBehaviorRef.current = null;
162
- else if (scrollBehavior) scrollToBottom(scrollBehavior);
163
- else if (autoScroll && !(isRunning && hasActiveTopAnchor()) && followBottomRef.current) scrollToBottom("instant");
164
- else if (prependAnchorRef.current && !keepMessageAt(div_2, prependAnchorRef.current, heldScrollTopRef)) prependAnchorRef.current = null;
190
+ if (scheduledFrameRef.current === null) {
191
+ const scrollBehavior = scrollingToBottomBehaviorRef.current;
192
+ if (scrollBehavior && hasActiveTopAnchor()) scrollingToBottomBehaviorRef.current = null;
193
+ else if (scrollBehavior) scrollToBottom(scrollBehavior);
194
+ else if (autoScroll && !(isRunning && hasActiveTopAnchor()) && followBottomRef.current && !followPausedRef.current) scrollToBottom("instant");
195
+ else if (prependAnchorRef.current && !keepMessageAt(div_2, prependAnchorRef.current, heldScrollTopRef)) prependAnchorRef.current = null;
196
+ }
165
197
  handleScroll();
198
+ };
199
+ const onResizeRef = useRef(onResize);
200
+ useIsomorphicLayoutEffect(() => {
201
+ onResizeRef.current = onResize;
166
202
  });
167
- const scrollRef = useManagedRef((el) => {
203
+ const resizeRef = useOnResizeContent(() => onResizeRef.current());
204
+ const scrollRef = useManagedRef(useCallback((el) => {
168
205
  const cancelPendingScrollToBottom = () => {
206
+ if (scrollingToBottomBehaviorRef.current === null && scheduledFrameRef.current === null) return;
169
207
  cancelScheduledFrame();
170
208
  scrollingToBottomBehaviorRef.current = null;
209
+ handleScroll();
171
210
  };
172
211
  const cancelOnKeyDown = (event) => {
173
- if (!ACTIVATION_KEYS.has(event.key)) return;
212
+ if (!INTENT_CANCEL_KEYS.has(event.key)) return;
174
213
  if (event.target?.closest?.(TEXT_ENTRY_SELECTOR)) return;
175
214
  cancelPendingScrollToBottom();
176
215
  };
216
+ const pauseFollowOnExpand = (event_0) => {
217
+ const target_0 = event_0.target;
218
+ if (!target_0?.closest?.(MESSAGE_SELECTOR) || target_0.closest(TEXT_ENTRY_SELECTOR) || !isOpeningDisclosure(target_0)) return;
219
+ followPausedRef.current = true;
220
+ scrolledSincePauseRef.current = false;
221
+ cancelPendingScrollToBottom();
222
+ };
223
+ const noteScrollGesture = (event_1) => {
224
+ if (event_1 instanceof KeyboardEvent) {
225
+ if (!SCROLL_KEYS.has(event_1.key)) return;
226
+ if (event_1.target?.closest?.(TEXT_ENTRY_SELECTOR)) return;
227
+ } else if (event_1.type === "pointerdown" && event_1.target !== el) return;
228
+ scrolledSincePauseRef.current = true;
229
+ };
177
230
  const releasePrependAnchor = () => {
178
231
  prependAnchorRef.current = null;
179
232
  };
@@ -183,22 +236,36 @@ const useThreadViewportAutoScroll = ({ autoScroll, scrollToBottomOnRunStart = tr
183
236
  "touchstart",
184
237
  "keydown"
185
238
  ];
239
+ const resumeGestures = [
240
+ "pointerdown",
241
+ "wheel",
242
+ "touchmove",
243
+ "keydown"
244
+ ];
186
245
  el.addEventListener("scroll", handleScroll);
187
246
  el.addEventListener("pointerdown", cancelPendingScrollToBottom);
247
+ el.addEventListener("wheel", cancelPendingScrollToBottom, { passive: true });
248
+ el.addEventListener("touchstart", cancelPendingScrollToBottom, { passive: true });
188
249
  el.addEventListener("keydown", cancelOnKeyDown);
189
- for (const gesture of gestures) el.addEventListener(gesture, releasePrependAnchor, { passive: true });
250
+ el.addEventListener("click", pauseFollowOnExpand, { capture: true });
251
+ for (const gesture of resumeGestures) el.addEventListener(gesture, noteScrollGesture, { passive: true });
252
+ for (const gesture_0 of gestures) el.addEventListener(gesture_0, releasePrependAnchor, { passive: true });
190
253
  return () => {
191
254
  el.removeEventListener("scroll", handleScroll);
192
255
  el.removeEventListener("pointerdown", cancelPendingScrollToBottom);
256
+ el.removeEventListener("wheel", cancelPendingScrollToBottom);
257
+ el.removeEventListener("touchstart", cancelPendingScrollToBottom);
193
258
  el.removeEventListener("keydown", cancelOnKeyDown);
194
- for (const gesture_0 of gestures) el.removeEventListener(gesture_0, releasePrependAnchor);
259
+ el.removeEventListener("click", pauseFollowOnExpand, { capture: true });
260
+ for (const gesture_1 of resumeGestures) el.removeEventListener(gesture_1, noteScrollGesture);
261
+ for (const gesture_2 of gestures) el.removeEventListener(gesture_2, releasePrependAnchor);
195
262
  };
196
- });
263
+ }, [cancelScheduledFrame, handleScroll]));
197
264
  const previousRef = useRef({
198
265
  threadId,
199
266
  firstMessageId
200
267
  });
201
- useLayoutEffect(() => {
268
+ useIsomorphicLayoutEffect(() => {
202
269
  const previous = previousRef.current;
203
270
  previousRef.current = {
204
271
  threadId,
@@ -210,7 +277,7 @@ const useThreadViewportAutoScroll = ({ autoScroll, scrollToBottomOnRunStart = tr
210
277
  }
211
278
  const div_3 = divRef.current;
212
279
  const previousFirstMessageId = previous.firstMessageId;
213
- if (!div_3 || previousFirstMessageId === void 0 || previousFirstMessageId === firstMessageId || autoScroll && followBottomRef.current || scrollingToBottomBehaviorRef.current !== null) return;
280
+ if (!div_3 || previousFirstMessageId === void 0 || previousFirstMessageId === firstMessageId || autoScroll && followBottomRef.current && !followPausedRef.current || scrollingToBottomBehaviorRef.current !== null) return;
214
281
  if (!aui.thread.getState().messages.some((message, index) => index > 0 && message.id === previousFirstMessageId)) return;
215
282
  const anchor = firstMessageRef.current;
216
283
  if (anchor?.id === previousFirstMessageId && keepMessageAt(div_3, anchor, heldScrollTopRef)) prependAnchorRef.current = measureReaderMessage(div_3) ?? anchor;
@@ -229,7 +296,7 @@ const useThreadViewportAutoScroll = ({ autoScroll, scrollToBottomOnRunStart = tr
229
296
  firstMessageId,
230
297
  threadId
231
298
  ]);
232
- useLayoutEffect(() => {
299
+ useIsomorphicLayoutEffect(() => {
233
300
  if (!scrollToBottomOnInitialize) return;
234
301
  if (!hasMessages) {
235
302
  initializeScrollRequestedRef.current = false;
@@ -249,11 +316,13 @@ const useThreadViewportAutoScroll = ({ autoScroll, scrollToBottomOnRunStart = tr
249
316
  });
250
317
  useAuiEvent("thread.runStart", () => {
251
318
  prependAnchorRef.current = null;
319
+ followPausedRef.current = false;
252
320
  if (!scrollToBottomOnRunStart) return;
253
321
  if (threadViewportStore.getState().turnAnchor === "top") return;
254
322
  scheduleScrollToBottom("auto");
255
323
  });
256
324
  useAuiEvent("threads.selectionChanged", () => {
325
+ followPausedRef.current = false;
257
326
  if (!scrollToBottomOnThreadSwitch) return;
258
327
  scheduleScrollToBottom("instant");
259
328
  });
@@ -1 +1 @@
1
- {"version":3,"file":"useThreadViewportAutoScroll.js","names":["useComposedRefs","useCallback","useLayoutEffect","useRef","RefCallback","useAui","useAuiEvent","useAuiState","isUserScrollUp","isViewportAtBottom","viewportOverflows","useOnResizeContent","useOnScrollToBottom","useManagedRef","writableStore","useThreadViewportStore","ACTIVATION_KEYS","Set","TEXT_ENTRY_SELECTOR","join","MESSAGE_SELECTOR","MessageOffset","id","offset","offsetInViewport","element","Element","viewport","getBoundingClientRect","top","measureFirstMessage","HTMLElement","querySelector","dataset","measureReaderMessage","bottom","spanning","querySelectorAll","rect","findMessage","keepMessageAt","anchor","heldScrollTop","current","delta","scrollTop","Math","abs","scrollTo","behavior","useThreadViewportAutoScroll","Options","autoScroll","scrollToBottomOnRunStart","scrollToBottomOnInitialize","scrollToBottomOnThreadSwitch","TElement","aui","divRef","hasMessages","s","thread","messages","length","firstMessageId","threadId","threadListItem","isRunning","initializeScrollRequestedRef","scheduledFrameRef","threadViewportStore","undefined","getState","turnAnchor","lastScrollTop","lastScrollHeight","lastObservedScrollHeight","lastObservedClientHeight","firstMessageRef","prependAnchorRef","heldScrollTopRef","scrollingToBottomBehaviorRef","ScrollBehavior","followBottomRef","previousAutoScrollRef","previousAutoScroll","div","scrollToBottom","scrollHeight","cancelScheduledFrame","cancelAnimationFrame","scheduleScrollToBottom","requestAnimationFrame","hasActiveTopAnchor","state","handleScroll","isAtBottom","newIsAtBottom","isInFlightDownwardScroll","userScrolledUp","shouldUpdate","setState","resizeRef","clientHeight","scrollBehavior","scrollRef","el","cancelPendingScrollToBottom","cancelOnKeyDown","event","KeyboardEvent","has","key","target","closest","releasePrependAnchor","gestures","const","addEventListener","gesture","passive","removeEventListener","previousRef","previous","previousFirstMessageId","prepended","some","message","index","autoScrollRef"],"sources":["../../../src/primitives/thread/useThreadViewportAutoScroll.ts"],"sourcesContent":["\"use client\";\n\nimport { useComposedRefs } from \"radix-ui/internal\";\nimport { useCallback, useLayoutEffect, useRef, type RefCallback } from \"react\";\nimport { useAui, useAuiEvent, useAuiState } from \"@assistant-ui/store\";\nimport {\n isUserScrollUp,\n isViewportAtBottom,\n viewportOverflows,\n} from \"@assistant-ui/store/client\";\nimport { useOnResizeContent } from \"../../utils/hooks/useOnResizeContent\";\nimport { useOnScrollToBottom } from \"../../utils/hooks/useOnScrollToBottom\";\nimport { useManagedRef } from \"../../utils/hooks/useManagedRef\";\nimport { writableStore } from \"../../context/ReadonlyStore\";\nimport { useThreadViewportStore } from \"../../context/react/ThreadViewportContext\";\n\n// Enter and Space activate a focused control, which is how a collapsible tool\n// call expands without a pointer event ever firing. No other key changes thread\n// content: the keys that scroll the viewport reach handleScroll, which already\n// clears the intent when the user scrolls up.\nconst ACTIVATION_KEYS = new Set([\"Enter\", \" \"]);\n\n// A control that consumes the activation key itself instead of acting on thread\n// content. `contenteditable=\"false\"` marks a non-editable island inside an\n// editable tree, so it is excluded the way ComposerRoot already excludes it;\n// the input types left out are the ones a key activates rather than fills.\nconst TEXT_ENTRY_SELECTOR = [\n \"textarea\",\n \"select\",\n \"[contenteditable]:not([contenteditable='false'])\",\n \"input:not([type='checkbox']):not([type='radio']):not([type='button'])\" +\n \":not([type='submit']):not([type='reset']):not([type='image'])\" +\n \":not([type='range']):not([type='file']):not([type='color'])\",\n].join(\", \");\n\nconst MESSAGE_SELECTOR = \"[data-message-id]\";\n\ntype MessageOffset = { readonly id: string; readonly offset: number };\n\nconst offsetInViewport = (element: Element, viewport: Element) =>\n element.getBoundingClientRect().top - viewport.getBoundingClientRect().top;\n\nconst measureFirstMessage = (viewport: HTMLElement): MessageOffset | null => {\n const element = viewport.querySelector<HTMLElement>(MESSAGE_SELECTOR);\n const id = element?.dataset[\"messageId\"];\n return element && id\n ? { id, offset: offsetInViewport(element, viewport) }\n : null;\n};\n\n/** The first message that starts in view, else the one spanning its top edge. */\nconst measureReaderMessage = (viewport: HTMLElement): MessageOffset | null => {\n const { top, bottom } = viewport.getBoundingClientRect();\n let spanning: MessageOffset | null = null;\n for (const element of viewport.querySelectorAll<HTMLElement>(\n MESSAGE_SELECTOR,\n )) {\n const rect = element.getBoundingClientRect();\n if (rect.bottom <= top) continue;\n if (rect.top >= bottom) break;\n const id = element.dataset[\"messageId\"];\n if (!id) continue;\n if (rect.top >= top) return { id, offset: rect.top - top };\n spanning ??= { id, offset: rect.top - top };\n }\n return spanning;\n};\n\nconst findMessage = (viewport: HTMLElement, id: string) => {\n for (const element of viewport.querySelectorAll<HTMLElement>(\n MESSAGE_SELECTOR,\n )) {\n if (element.dataset[\"messageId\"] === id) return element;\n }\n return null;\n};\n\n/**\n * Scrolls so the message sits at `anchor.offset` again, recording the\n * position it scrolls to in `heldScrollTop` before the scroll event can\n * report it; false when the message is gone.\n */\nconst keepMessageAt = (\n viewport: HTMLElement,\n anchor: MessageOffset,\n heldScrollTop: { current: number },\n) => {\n const element = findMessage(viewport, anchor.id);\n if (!element) return false;\n const delta = offsetInViewport(element, viewport) - anchor.offset;\n heldScrollTop.current = viewport.scrollTop + delta;\n if (Math.abs(delta) >= 1) {\n viewport.scrollTo({ top: heldScrollTop.current, behavior: \"instant\" });\n }\n heldScrollTop.current = viewport.scrollTop;\n return true;\n};\n\nexport namespace useThreadViewportAutoScroll {\n export type Options = {\n /**\n * Whether to automatically scroll to the bottom when new messages are added.\n * When enabled, the viewport will automatically scroll to show the latest content.\n *\n * Default false if `turnAnchor` is \"top\", otherwise defaults to true.\n */\n autoScroll?: boolean | undefined;\n\n /**\n * Whether to scroll to bottom when a new run starts.\n *\n * Defaults to true.\n */\n scrollToBottomOnRunStart?: boolean | undefined;\n\n /**\n * Whether to scroll to bottom when messages first appear in the thread.\n *\n * Defaults to true.\n */\n scrollToBottomOnInitialize?: boolean | undefined;\n\n /**\n * Whether to scroll to bottom when switching to a different thread.\n *\n * Defaults to true.\n */\n scrollToBottomOnThreadSwitch?: boolean | undefined;\n };\n}\n\nexport const useThreadViewportAutoScroll = <TElement extends HTMLElement>({\n autoScroll,\n scrollToBottomOnRunStart = true,\n scrollToBottomOnInitialize = true,\n scrollToBottomOnThreadSwitch = true,\n}: useThreadViewportAutoScroll.Options): RefCallback<TElement> => {\n const aui = useAui();\n const divRef = useRef<TElement>(null);\n const hasMessages = useAuiState((s) => s.thread.messages.length > 0);\n const firstMessageId = useAuiState((s) => s.thread.messages[0]?.id);\n const threadId = useAuiState((s) => s.threadListItem.id);\n const isRunning = useAuiState((s) => s.thread.isRunning);\n const initializeScrollRequestedRef = useRef(false);\n const scheduledFrameRef = useRef<number | null>(null);\n\n const threadViewportStore = useThreadViewportStore();\n if (autoScroll === undefined) {\n autoScroll = threadViewportStore.getState().turnAnchor !== \"top\";\n }\n\n const lastScrollTop = useRef<number>(0);\n const lastScrollHeight = useRef<number>(0);\n const lastObservedScrollHeight = useRef<number>(0);\n const lastObservedClientHeight = useRef<number>(0);\n const firstMessageRef = useRef<MessageOffset | null>(null);\n // The row the reader sees when earlier messages land, held until a gesture\n // or a scroll this hook did not make, so content above it that settles\n // afterwards (lazy layout, late media) does not move it on engines without\n // native scroll anchoring.\n const prependAnchorRef = useRef<MessageOffset | null>(null);\n const heldScrollTopRef = useRef(0);\n\n // Pending bottom-scroll intent. Planted by initialize/run-start/switch/button\n // triggers, cleared when handleScroll confirms we reached bottom, or when the\n // user actively scrolls up while content size is stable.\n const scrollingToBottomBehaviorRef = useRef<ScrollBehavior | null>(null);\n const followBottomRef = useRef(autoScroll);\n const previousAutoScrollRef = useRef(autoScroll);\n\n useLayoutEffect(() => {\n const previousAutoScroll = previousAutoScrollRef.current;\n previousAutoScrollRef.current = autoScroll;\n if (previousAutoScroll || !autoScroll) return;\n\n const div = divRef.current;\n followBottomRef.current = div !== null && isViewportAtBottom(div);\n }, [autoScroll]);\n\n const scrollToBottom = useCallback((behavior: ScrollBehavior) => {\n const div = divRef.current;\n if (!div) return;\n\n followBottomRef.current = true;\n scrollingToBottomBehaviorRef.current = behavior;\n prependAnchorRef.current = null;\n div.scrollTo({ top: div.scrollHeight, behavior });\n }, []);\n\n const cancelScheduledFrame = useCallback(() => {\n if (scheduledFrameRef.current === null) return;\n cancelAnimationFrame(scheduledFrameRef.current);\n scheduledFrameRef.current = null;\n }, []);\n\n const scheduleScrollToBottom = useCallback(\n (behavior: ScrollBehavior) => {\n scrollingToBottomBehaviorRef.current = behavior;\n cancelScheduledFrame();\n scheduledFrameRef.current = requestAnimationFrame(() => {\n scheduledFrameRef.current = null;\n scrollToBottom(behavior);\n });\n },\n [cancelScheduledFrame, scrollToBottom],\n );\n\n useLayoutEffect(() => () => cancelScheduledFrame(), [cancelScheduledFrame]);\n\n const hasActiveTopAnchor = useCallback(() => {\n const state = threadViewportStore.getState();\n return (\n state.turnAnchor === \"top\" &&\n state.element.viewport === divRef.current &&\n state.element.anchor !== null\n );\n }, [threadViewportStore]);\n\n const handleScroll = () => {\n const div = divRef.current;\n if (!div) return;\n\n if (\n prependAnchorRef.current &&\n Math.abs(div.scrollTop - heldScrollTopRef.current) >= 1\n ) {\n prependAnchorRef.current = null;\n }\n\n const isAtBottom = threadViewportStore.getState().isAtBottom;\n const newIsAtBottom = isViewportAtBottom(div);\n\n const isInFlightDownwardScroll =\n !newIsAtBottom && lastScrollTop.current < div.scrollTop;\n if (isInFlightDownwardScroll) {\n // no-op: a smooth scroll-to-bottom fires many midpoint scroll events\n // before landing, don't flicker isAtBottom or clear intent mid-animation\n } else {\n const userScrolledUp = isUserScrollUp(\n {\n scrollTop: lastScrollTop.current,\n scrollHeight: lastScrollHeight.current,\n },\n div,\n );\n\n if (newIsAtBottom) {\n // newIsAtBottom is ambiguous when the viewport doesn't overflow —\n // keep intent alive until content can actually scroll\n if (viewportOverflows(div)) {\n scrollingToBottomBehaviorRef.current = null;\n }\n if (autoScroll) followBottomRef.current = true;\n } else if (userScrolledUp) {\n cancelScheduledFrame();\n scrollingToBottomBehaviorRef.current = null;\n followBottomRef.current = false;\n }\n\n const shouldUpdate =\n newIsAtBottom || scrollingToBottomBehaviorRef.current === null;\n\n if (shouldUpdate && newIsAtBottom !== isAtBottom) {\n writableStore(threadViewportStore).setState({\n isAtBottom: newIsAtBottom,\n });\n }\n }\n\n lastScrollTop.current = div.scrollTop;\n lastScrollHeight.current = div.scrollHeight;\n firstMessageRef.current = measureFirstMessage(div);\n };\n\n const resizeRef = useOnResizeContent(() => {\n const div = divRef.current;\n if (!div) return;\n\n const { scrollHeight, clientHeight } = div;\n if (\n scrollHeight === lastObservedScrollHeight.current &&\n clientHeight === lastObservedClientHeight.current\n ) {\n return;\n }\n lastObservedScrollHeight.current = scrollHeight;\n lastObservedClientHeight.current = clientHeight;\n\n const scrollBehavior = scrollingToBottomBehaviorRef.current;\n if (scrollBehavior && hasActiveTopAnchor()) {\n // Let the top-anchor reserve own scrolling while a run starts to avoid a bottom-scroll race.\n scrollingToBottomBehaviorRef.current = null;\n } else if (scrollBehavior) {\n scrollToBottom(scrollBehavior);\n } else if (\n autoScroll &&\n !(isRunning && hasActiveTopAnchor()) &&\n followBottomRef.current\n ) {\n scrollToBottom(\"instant\");\n } else if (\n prependAnchorRef.current &&\n !keepMessageAt(div, prependAnchorRef.current, heldScrollTopRef)\n ) {\n prependAnchorRef.current = null;\n }\n\n handleScroll();\n });\n\n const scrollRef = useManagedRef<HTMLElement>((el) => {\n // A user gesture invalidates pending bottom-scroll intent; otherwise an\n // intent kept alive by a non-overflowing thread (see handleScroll) hijacks\n // the next content growth, e.g. expanding a collapsible tool call. Keyboard\n // activation reaches that same content without ever emitting a pointer\n // event, so it has to cancel the intent too.\n const cancelPendingScrollToBottom = () => {\n // A scheduled frame re-plants the intent when it runs, so clearing the\n // ref alone leaves the gesture undone.\n cancelScheduledFrame();\n scrollingToBottomBehaviorRef.current = null;\n };\n // The composer renders inside the viewport, so its keystrokes bubble here;\n // only an activation key aimed at something other than a text field is a\n // gesture on thread content.\n const cancelOnKeyDown = (event: KeyboardEvent) => {\n if (!ACTIVATION_KEYS.has(event.key)) return;\n const target = event.target as Element | null;\n if (target?.closest?.(TEXT_ENTRY_SELECTOR)) return;\n cancelPendingScrollToBottom();\n };\n const releasePrependAnchor = () => {\n prependAnchorRef.current = null;\n };\n const gestures = [\"pointerdown\", \"wheel\", \"touchstart\", \"keydown\"] as const;\n el.addEventListener(\"scroll\", handleScroll);\n el.addEventListener(\"pointerdown\", cancelPendingScrollToBottom);\n el.addEventListener(\"keydown\", cancelOnKeyDown);\n for (const gesture of gestures) {\n el.addEventListener(gesture, releasePrependAnchor, { passive: true });\n }\n return () => {\n el.removeEventListener(\"scroll\", handleScroll);\n el.removeEventListener(\"pointerdown\", cancelPendingScrollToBottom);\n el.removeEventListener(\"keydown\", cancelOnKeyDown);\n for (const gesture of gestures) {\n el.removeEventListener(gesture, releasePrependAnchor);\n }\n };\n });\n\n // Earlier messages prepended above the reader put the old first message back\n // where the reader saw it, so growth below it in the same commit (a\n // streaming reply) is not mistaken for the page; where the browser already\n // anchored the scroll, this is a no-op.\n const previousRef = useRef({ threadId, firstMessageId });\n useLayoutEffect(() => {\n const previous = previousRef.current;\n previousRef.current = { threadId, firstMessageId };\n if (previous.threadId !== threadId) {\n prependAnchorRef.current = null;\n return;\n }\n const div = divRef.current;\n const previousFirstMessageId = previous.firstMessageId;\n if (\n !div ||\n previousFirstMessageId === undefined ||\n previousFirstMessageId === firstMessageId ||\n (autoScroll && followBottomRef.current) ||\n scrollingToBottomBehaviorRef.current !== null\n )\n return;\n const prepended = aui.thread\n .getState()\n .messages.some(\n (message, index) => index > 0 && message.id === previousFirstMessageId,\n );\n if (!prepended) return;\n\n const anchor = firstMessageRef.current;\n if (\n anchor?.id === previousFirstMessageId &&\n keepMessageAt(div, anchor, heldScrollTopRef)\n ) {\n prependAnchorRef.current = measureReaderMessage(div) ?? anchor;\n } else {\n // Message roots that render no `data-message-id` leave only the total\n // growth to go on, which counts any growth below the reader too.\n const top =\n div.scrollHeight - (lastScrollHeight.current - lastScrollTop.current);\n if (Math.abs(div.scrollTop - top) >= 1) {\n div.scrollTo({ top, behavior: \"instant\" });\n }\n }\n lastScrollTop.current = div.scrollTop;\n lastScrollHeight.current = div.scrollHeight;\n }, [aui, autoScroll, firstMessageId, threadId]);\n\n useLayoutEffect(() => {\n if (!scrollToBottomOnInitialize) return;\n if (!hasMessages) {\n initializeScrollRequestedRef.current = false;\n return;\n }\n if (initializeScrollRequestedRef.current) return;\n\n initializeScrollRequestedRef.current = true;\n // defer to an in-flight run (e.g. first message on a new thread) that\n // already planted intent — otherwise we'd downgrade its \"auto\" to \"instant\"\n if (scrollingToBottomBehaviorRef.current !== null) return;\n scheduleScrollToBottom(\"instant\");\n }, [hasMessages, scheduleScrollToBottom, scrollToBottomOnInitialize]);\n\n useOnScrollToBottom(({ behavior }) => {\n scrollToBottom(behavior);\n });\n\n useAuiEvent(\"thread.runStart\", () => {\n prependAnchorRef.current = null;\n if (!scrollToBottomOnRunStart) return;\n if (threadViewportStore.getState().turnAnchor === \"top\") return;\n scheduleScrollToBottom(\"auto\");\n });\n\n useAuiEvent(\"threads.selectionChanged\", () => {\n if (!scrollToBottomOnThreadSwitch) return;\n scheduleScrollToBottom(\"instant\");\n });\n\n const autoScrollRef = useComposedRefs<TElement>(resizeRef, scrollRef, divRef);\n return autoScrollRef as RefCallback<TElement>;\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"useThreadViewportAutoScroll.js","names":["useComposedRefs","useCallback","useRef","RefCallback","useAui","useAuiEvent","useAuiState","isUserScrollUp","isViewportAtBottom","viewportOverflows","useOnResizeContent","useOnScrollToBottom","useManagedRef","writableStore","useThreadViewportStore","useIsomorphicLayoutEffect","SCROLL_KEYS","Set","INTENT_CANCEL_KEYS","TEXT_ENTRY_SELECTOR","join","MESSAGE_SELECTOR","DISCLOSURE_SELECTOR","isOpeningDisclosure","target","Element","control","closest","tagName","parentElement","hasAttribute","getAttribute","MessageOffset","id","offset","offsetInViewport","element","viewport","getBoundingClientRect","top","measureFirstMessage","HTMLElement","querySelector","dataset","measureReaderMessage","bottom","spanning","querySelectorAll","rect","findMessage","keepMessageAt","anchor","heldScrollTop","current","delta","scrollTop","Math","abs","scrollTo","behavior","useThreadViewportAutoScroll","Options","autoScroll","scrollToBottomOnRunStart","scrollToBottomOnInitialize","scrollToBottomOnThreadSwitch","TElement","aui","divRef","hasMessages","s","thread","messages","length","firstMessageId","threadId","threadListItem","isRunning","initializeScrollRequestedRef","scheduledFrameRef","threadViewportStore","undefined","getState","turnAnchor","lastScrollTop","lastScrollHeight","lastObservedScrollHeight","lastObservedClientHeight","firstMessageRef","prependAnchorRef","heldScrollTopRef","scrollingToBottomBehaviorRef","ScrollBehavior","followBottomRef","followPausedRef","scrolledSincePauseRef","previousAutoScrollRef","previousAutoScroll","div","scrollToBottom","scrollHeight","cancelScheduledFrame","cancelAnimationFrame","scheduleScrollToBottom","requestAnimationFrame","hasActiveTopAnchor","state","onScroll","isAtBottom","newIsAtBottom","isInFlightDownwardScroll","userScrolledUp","shouldUpdate","setState","onScrollRef","handleScroll","onResize","clientHeight","scrollBehavior","onResizeRef","resizeRef","scrollRef","el","cancelPendingScrollToBottom","cancelOnKeyDown","event","KeyboardEvent","has","key","pauseFollowOnExpand","MouseEvent","noteScrollGesture","Event","type","releasePrependAnchor","gestures","const","resumeGestures","addEventListener","passive","capture","gesture","removeEventListener","previousRef","previous","previousFirstMessageId","prepended","some","message","index","autoScrollRef"],"sources":["../../../src/primitives/thread/useThreadViewportAutoScroll.ts"],"sourcesContent":["\"use client\";\n\nimport { useComposedRefs } from \"radix-ui/internal\";\nimport { useCallback, useRef, type RefCallback } from \"react\";\nimport { useAui, useAuiEvent, useAuiState } from \"@assistant-ui/store\";\nimport {\n isUserScrollUp,\n isViewportAtBottom,\n viewportOverflows,\n} from \"@assistant-ui/store/client\";\nimport { useOnResizeContent } from \"../../utils/hooks/useOnResizeContent\";\nimport { useOnScrollToBottom } from \"../../utils/hooks/useOnScrollToBottom\";\nimport { useManagedRef } from \"../../utils/hooks/useManagedRef\";\nimport { writableStore } from \"../../context/ReadonlyStore\";\nimport { useThreadViewportStore } from \"../../context/react/ThreadViewportContext\";\nimport { useIsomorphicLayoutEffect } from \"../../utils/useIsomorphicLayoutEffect\";\n\nconst SCROLL_KEYS = new Set([\n \" \",\n \"ArrowUp\",\n \"ArrowDown\",\n \"PageUp\",\n \"PageDown\",\n \"Home\",\n \"End\",\n]);\n\n// Enter and Space activate focused controls, while navigation keys can\n// interrupt scrolling without producing a scroll event.\nconst INTENT_CANCEL_KEYS = new Set([\"Enter\", ...SCROLL_KEYS]);\n\n// A control that consumes the activation key itself instead of acting on thread\n// content. `contenteditable=\"false\"` marks a non-editable island inside an\n// editable tree, so it is excluded the way ComposerRoot already excludes it;\n// the input types left out are the ones a key activates rather than fills.\nconst TEXT_ENTRY_SELECTOR = [\n \"textarea\",\n \"select\",\n \"[contenteditable]:not([contenteditable='false'])\",\n \"input:not([type='checkbox']):not([type='radio']):not([type='button'])\" +\n \":not([type='submit']):not([type='reset']):not([type='image'])\" +\n \":not([type='range']):not([type='file']):not([type='color'])\",\n].join(\", \");\n\nconst MESSAGE_SELECTOR = \"[data-message-id]\";\n\nconst DISCLOSURE_SELECTOR = \"[aria-expanded], summary\";\n\n// A popup trigger (menu, dialog, listbox) also carries aria-expanded, but what\n// it opens is portaled away from the thread, so only disclosures count.\nconst isOpeningDisclosure = (target: Element): boolean => {\n const control = target.closest(DISCLOSURE_SELECTOR);\n if (!control) return false;\n if (control.tagName === \"SUMMARY\")\n return control.parentElement?.hasAttribute(\"open\") === false;\n return (\n control.getAttribute(\"aria-expanded\") === \"false\" &&\n (control.getAttribute(\"aria-haspopup\") ?? \"false\") === \"false\" &&\n control.getAttribute(\"role\") !== \"combobox\"\n );\n};\n\ntype MessageOffset = { readonly id: string; readonly offset: number };\n\nconst offsetInViewport = (element: Element, viewport: Element) =>\n element.getBoundingClientRect().top - viewport.getBoundingClientRect().top;\n\nconst measureFirstMessage = (viewport: HTMLElement): MessageOffset | null => {\n const element = viewport.querySelector<HTMLElement>(MESSAGE_SELECTOR);\n const id = element?.dataset[\"messageId\"];\n return element && id\n ? { id, offset: offsetInViewport(element, viewport) }\n : null;\n};\n\n/** The first message that starts in view, else the one spanning its top edge. */\nconst measureReaderMessage = (viewport: HTMLElement): MessageOffset | null => {\n const { top, bottom } = viewport.getBoundingClientRect();\n let spanning: MessageOffset | null = null;\n for (const element of viewport.querySelectorAll<HTMLElement>(\n MESSAGE_SELECTOR,\n )) {\n const rect = element.getBoundingClientRect();\n if (rect.bottom <= top) continue;\n if (rect.top >= bottom) break;\n const id = element.dataset[\"messageId\"];\n if (!id) continue;\n if (rect.top >= top) return { id, offset: rect.top - top };\n spanning ??= { id, offset: rect.top - top };\n }\n return spanning;\n};\n\nconst findMessage = (viewport: HTMLElement, id: string) => {\n for (const element of viewport.querySelectorAll<HTMLElement>(\n MESSAGE_SELECTOR,\n )) {\n if (element.dataset[\"messageId\"] === id) return element;\n }\n return null;\n};\n\n/**\n * Scrolls so the message sits at `anchor.offset` again, recording the\n * position it scrolls to in `heldScrollTop` before the scroll event can\n * report it; false when the message is gone.\n */\nconst keepMessageAt = (\n viewport: HTMLElement,\n anchor: MessageOffset,\n heldScrollTop: { current: number },\n) => {\n const element = findMessage(viewport, anchor.id);\n if (!element) return false;\n const delta = offsetInViewport(element, viewport) - anchor.offset;\n heldScrollTop.current = viewport.scrollTop + delta;\n if (Math.abs(delta) >= 1) {\n viewport.scrollTo({ top: heldScrollTop.current, behavior: \"instant\" });\n }\n heldScrollTop.current = viewport.scrollTop;\n return true;\n};\n\nexport namespace useThreadViewportAutoScroll {\n export type Options = {\n /**\n * Whether to automatically scroll to the bottom when new messages are added.\n * When enabled, the viewport will automatically scroll to show the latest content.\n *\n * Default false if `turnAnchor` is \"top\", otherwise defaults to true.\n */\n autoScroll?: boolean | undefined;\n\n /**\n * Whether to scroll to bottom when a new run starts.\n *\n * Defaults to true.\n */\n scrollToBottomOnRunStart?: boolean | undefined;\n\n /**\n * Whether to scroll to bottom when messages first appear in the thread.\n *\n * Defaults to true.\n */\n scrollToBottomOnInitialize?: boolean | undefined;\n\n /**\n * Whether to scroll to bottom when switching to a different thread.\n *\n * Defaults to true.\n */\n scrollToBottomOnThreadSwitch?: boolean | undefined;\n };\n}\n\nexport const useThreadViewportAutoScroll = <TElement extends HTMLElement>({\n autoScroll,\n scrollToBottomOnRunStart = true,\n scrollToBottomOnInitialize = true,\n scrollToBottomOnThreadSwitch = true,\n}: useThreadViewportAutoScroll.Options): RefCallback<TElement> => {\n const aui = useAui();\n const divRef = useRef<TElement>(null);\n const hasMessages = useAuiState((s) => s.thread.messages.length > 0);\n const firstMessageId = useAuiState((s) => s.thread.messages[0]?.id);\n const threadId = useAuiState((s) => s.threadListItem.id);\n const isRunning = useAuiState((s) => s.thread.isRunning);\n const initializeScrollRequestedRef = useRef(false);\n const scheduledFrameRef = useRef<number | null>(null);\n\n const threadViewportStore = useThreadViewportStore();\n if (autoScroll === undefined) {\n autoScroll = threadViewportStore.getState().turnAnchor !== \"top\";\n }\n\n const lastScrollTop = useRef<number>(0);\n const lastScrollHeight = useRef<number>(0);\n const lastObservedScrollHeight = useRef<number>(0);\n const lastObservedClientHeight = useRef<number>(0);\n const firstMessageRef = useRef<MessageOffset | null>(null);\n // The row the reader sees when earlier messages land, held until a gesture\n // or a scroll this hook did not make, so content above it that settles\n // afterwards (lazy layout, late media) does not move it on engines without\n // native scroll anchoring.\n const prependAnchorRef = useRef<MessageOffset | null>(null);\n const heldScrollTopRef = useRef(0);\n\n // Pending bottom-scroll intent. Planted by initialize/run-start/switch/button\n // triggers, cleared when handleScroll confirms we reached bottom, or when the\n // user actively scrolls up while content size is stable.\n const scrollingToBottomBehaviorRef = useRef<ScrollBehavior | null>(null);\n const followBottomRef = useRef(autoScroll);\n // Set by expanding a disclosure; suspends follow without clearing its intent\n // until a scroll gesture reaches an overflowing bottom (content that fits\n // reads as at the bottom), a run starts, the thread changes, or the reader\n // asks for the bottom again.\n const followPausedRef = useRef(false);\n const scrolledSincePauseRef = useRef(false);\n const previousAutoScrollRef = useRef(autoScroll);\n\n useIsomorphicLayoutEffect(() => {\n const previousAutoScroll = previousAutoScrollRef.current;\n previousAutoScrollRef.current = autoScroll;\n if (previousAutoScroll || !autoScroll) return;\n\n const div = divRef.current;\n followBottomRef.current = div !== null && isViewportAtBottom(div);\n }, [autoScroll]);\n\n const scrollToBottom = useCallback((behavior: ScrollBehavior) => {\n const div = divRef.current;\n if (!div) return;\n\n followBottomRef.current = true;\n followPausedRef.current = false;\n scrollingToBottomBehaviorRef.current = behavior;\n prependAnchorRef.current = null;\n div.scrollTo({ top: div.scrollHeight, behavior });\n lastScrollTop.current = div.scrollTop;\n lastScrollHeight.current = div.scrollHeight;\n }, []);\n\n const cancelScheduledFrame = useCallback(() => {\n if (scheduledFrameRef.current === null) return;\n cancelAnimationFrame(scheduledFrameRef.current);\n scheduledFrameRef.current = null;\n }, []);\n\n const scheduleScrollToBottom = useCallback(\n (behavior: ScrollBehavior) => {\n scrollingToBottomBehaviorRef.current = behavior;\n cancelScheduledFrame();\n scheduledFrameRef.current = requestAnimationFrame(() => {\n scheduledFrameRef.current = null;\n scrollToBottom(behavior);\n });\n },\n [cancelScheduledFrame, scrollToBottom],\n );\n\n useIsomorphicLayoutEffect(\n () => () => cancelScheduledFrame(),\n [cancelScheduledFrame],\n );\n\n const hasActiveTopAnchor = useCallback(() => {\n const state = threadViewportStore.getState();\n return (\n state.turnAnchor === \"top\" &&\n state.element.viewport === divRef.current &&\n state.element.anchor !== null\n );\n }, [threadViewportStore]);\n\n const onScroll = () => {\n const div = divRef.current;\n if (!div) return;\n\n if (\n prependAnchorRef.current &&\n Math.abs(div.scrollTop - heldScrollTopRef.current) >= 1\n ) {\n prependAnchorRef.current = null;\n }\n\n const isAtBottom = threadViewportStore.getState().isAtBottom;\n const newIsAtBottom = isViewportAtBottom(div);\n\n const isInFlightDownwardScroll =\n scrollingToBottomBehaviorRef.current !== null &&\n !newIsAtBottom &&\n lastScrollTop.current < div.scrollTop;\n if (isInFlightDownwardScroll) {\n // no-op: a smooth scroll-to-bottom fires many midpoint scroll events\n // before landing, don't flicker isAtBottom or clear intent mid-animation\n } else {\n const userScrolledUp = isUserScrollUp(\n {\n scrollTop: lastScrollTop.current,\n scrollHeight: lastScrollHeight.current,\n },\n div,\n );\n\n if (newIsAtBottom) {\n // newIsAtBottom is ambiguous when the viewport doesn't overflow —\n // keep intent alive until content can actually scroll\n if (viewportOverflows(div)) {\n scrollingToBottomBehaviorRef.current = null;\n if (scrolledSincePauseRef.current) followPausedRef.current = false;\n }\n if (autoScroll) followBottomRef.current = true;\n } else if (userScrolledUp) {\n cancelScheduledFrame();\n scrollingToBottomBehaviorRef.current = null;\n followBottomRef.current = false;\n }\n\n const shouldUpdate =\n newIsAtBottom || scrollingToBottomBehaviorRef.current === null;\n\n if (shouldUpdate && newIsAtBottom !== isAtBottom) {\n writableStore(threadViewportStore).setState({\n isAtBottom: newIsAtBottom,\n });\n }\n }\n\n lastScrollTop.current = div.scrollTop;\n lastScrollHeight.current = div.scrollHeight;\n firstMessageRef.current = measureFirstMessage(div);\n };\n const onScrollRef = useRef(onScroll);\n useIsomorphicLayoutEffect(() => {\n onScrollRef.current = onScroll;\n });\n const handleScroll = useCallback(() => onScrollRef.current(), []);\n\n const onResize = () => {\n const div = divRef.current;\n if (!div) return;\n\n const { scrollHeight, clientHeight } = div;\n if (\n scrollHeight === lastObservedScrollHeight.current &&\n clientHeight === lastObservedClientHeight.current\n ) {\n return;\n }\n lastObservedScrollHeight.current = scrollHeight;\n lastObservedClientHeight.current = clientHeight;\n\n // A scheduled frame owns the pending behavior; resize callbacks must not apply it early.\n if (scheduledFrameRef.current === null) {\n const scrollBehavior = scrollingToBottomBehaviorRef.current;\n if (scrollBehavior && hasActiveTopAnchor()) {\n // Let the top-anchor reserve own scrolling while a run starts to avoid a bottom-scroll race.\n scrollingToBottomBehaviorRef.current = null;\n } else if (scrollBehavior) {\n scrollToBottom(scrollBehavior);\n } else if (\n autoScroll &&\n !(isRunning && hasActiveTopAnchor()) &&\n followBottomRef.current &&\n !followPausedRef.current\n ) {\n scrollToBottom(\"instant\");\n } else if (\n prependAnchorRef.current &&\n !keepMessageAt(div, prependAnchorRef.current, heldScrollTopRef)\n ) {\n prependAnchorRef.current = null;\n }\n }\n\n handleScroll();\n };\n const onResizeRef = useRef(onResize);\n useIsomorphicLayoutEffect(() => {\n onResizeRef.current = onResize;\n });\n const resizeRef = useOnResizeContent(() => onResizeRef.current());\n\n const scrollRef = useManagedRef<HTMLElement>(\n useCallback(\n (el) => {\n // A user gesture invalidates pending bottom-scroll intent; otherwise an\n // intent kept alive by a non-overflowing thread (see handleScroll) hijacks\n // the next content growth, e.g. expanding a collapsible tool call. Keyboard\n // activation reaches that same content without ever emitting a pointer\n // event, so it has to cancel the intent too.\n const cancelPendingScrollToBottom = () => {\n // With nothing pending, the last scroll event already published isAtBottom.\n if (\n scrollingToBottomBehaviorRef.current === null &&\n scheduledFrameRef.current === null\n )\n return;\n // A scheduled frame re-plants the intent when it runs, so clearing the\n // ref alone leaves the gesture undone.\n cancelScheduledFrame();\n scrollingToBottomBehaviorRef.current = null;\n handleScroll();\n };\n // The composer renders inside the viewport, so its keystrokes bubble here;\n // cancellation keys only represent a gesture on thread content when they\n // originate outside text entry.\n const cancelOnKeyDown = (event: KeyboardEvent) => {\n if (!INTENT_CANCEL_KEYS.has(event.key)) return;\n const target = event.target as Element | null;\n if (target?.closest?.(TEXT_ENTRY_SELECTOR)) return;\n cancelPendingScrollToBottom();\n };\n // Expanding a disclosure inside a message, such as a reasoning block or a\n // tool call card, means the reader is inspecting it, so bottom follow\n // pauses until the reader scrolls or asks for the bottom again. Every\n // other interaction, including copying or selecting text, keeps\n // following. Activation is read from click, which keyboard activation also\n // dispatches, in the capture phase so the state is read before any handler\n // on the disclosure flips it or stops the event.\n const pauseFollowOnExpand = (event: MouseEvent) => {\n const target = event.target as Element | null;\n if (\n !target?.closest?.(MESSAGE_SELECTOR) ||\n target.closest(TEXT_ENTRY_SELECTOR) ||\n !isOpeningDisclosure(target)\n )\n return;\n followPausedRef.current = true;\n scrolledSincePauseRef.current = false;\n cancelPendingScrollToBottom();\n };\n const noteScrollGesture = (event: Event) => {\n if (event instanceof KeyboardEvent) {\n if (!SCROLL_KEYS.has(event.key)) return;\n if (\n (event.target as Element | null)?.closest?.(TEXT_ENTRY_SELECTOR)\n )\n return;\n } else if (event.type === \"pointerdown\" && event.target !== el) {\n return;\n }\n scrolledSincePauseRef.current = true;\n };\n const releasePrependAnchor = () => {\n prependAnchorRef.current = null;\n };\n const gestures = [\n \"pointerdown\",\n \"wheel\",\n \"touchstart\",\n \"keydown\",\n ] as const;\n const resumeGestures = [\n \"pointerdown\",\n \"wheel\",\n \"touchmove\",\n \"keydown\",\n ] as const;\n el.addEventListener(\"scroll\", handleScroll);\n el.addEventListener(\"pointerdown\", cancelPendingScrollToBottom);\n el.addEventListener(\"wheel\", cancelPendingScrollToBottom, {\n passive: true,\n });\n el.addEventListener(\"touchstart\", cancelPendingScrollToBottom, {\n passive: true,\n });\n el.addEventListener(\"keydown\", cancelOnKeyDown);\n el.addEventListener(\"click\", pauseFollowOnExpand, { capture: true });\n for (const gesture of resumeGestures) {\n el.addEventListener(gesture, noteScrollGesture, { passive: true });\n }\n for (const gesture of gestures) {\n el.addEventListener(gesture, releasePrependAnchor, { passive: true });\n }\n return () => {\n el.removeEventListener(\"scroll\", handleScroll);\n el.removeEventListener(\"pointerdown\", cancelPendingScrollToBottom);\n el.removeEventListener(\"wheel\", cancelPendingScrollToBottom);\n el.removeEventListener(\"touchstart\", cancelPendingScrollToBottom);\n el.removeEventListener(\"keydown\", cancelOnKeyDown);\n el.removeEventListener(\"click\", pauseFollowOnExpand, {\n capture: true,\n });\n for (const gesture of resumeGestures) {\n el.removeEventListener(gesture, noteScrollGesture);\n }\n for (const gesture of gestures) {\n el.removeEventListener(gesture, releasePrependAnchor);\n }\n };\n },\n [cancelScheduledFrame, handleScroll],\n ),\n );\n\n // Earlier messages prepended above the reader put the old first message back\n // where the reader saw it, so growth below it in the same commit (a\n // streaming reply) is not mistaken for the page; where the browser already\n // anchored the scroll, this is a no-op.\n const previousRef = useRef({ threadId, firstMessageId });\n useIsomorphicLayoutEffect(() => {\n const previous = previousRef.current;\n previousRef.current = { threadId, firstMessageId };\n if (previous.threadId !== threadId) {\n prependAnchorRef.current = null;\n return;\n }\n const div = divRef.current;\n const previousFirstMessageId = previous.firstMessageId;\n if (\n !div ||\n previousFirstMessageId === undefined ||\n previousFirstMessageId === firstMessageId ||\n (autoScroll && followBottomRef.current && !followPausedRef.current) ||\n scrollingToBottomBehaviorRef.current !== null\n )\n return;\n const prepended = aui.thread\n .getState()\n .messages.some(\n (message, index) => index > 0 && message.id === previousFirstMessageId,\n );\n if (!prepended) return;\n\n const anchor = firstMessageRef.current;\n if (\n anchor?.id === previousFirstMessageId &&\n keepMessageAt(div, anchor, heldScrollTopRef)\n ) {\n prependAnchorRef.current = measureReaderMessage(div) ?? anchor;\n } else {\n // Message roots that render no `data-message-id` leave only the total\n // growth to go on, which counts any growth below the reader too.\n const top =\n div.scrollHeight - (lastScrollHeight.current - lastScrollTop.current);\n if (Math.abs(div.scrollTop - top) >= 1) {\n div.scrollTo({ top, behavior: \"instant\" });\n }\n }\n lastScrollTop.current = div.scrollTop;\n lastScrollHeight.current = div.scrollHeight;\n }, [aui, autoScroll, firstMessageId, threadId]);\n\n useIsomorphicLayoutEffect(() => {\n if (!scrollToBottomOnInitialize) return;\n if (!hasMessages) {\n initializeScrollRequestedRef.current = false;\n return;\n }\n if (initializeScrollRequestedRef.current) return;\n\n initializeScrollRequestedRef.current = true;\n // defer to an in-flight run (e.g. first message on a new thread) that\n // already planted intent — otherwise we'd downgrade its \"auto\" to \"instant\"\n if (scrollingToBottomBehaviorRef.current !== null) return;\n scheduleScrollToBottom(\"instant\");\n }, [hasMessages, scheduleScrollToBottom, scrollToBottomOnInitialize]);\n\n useOnScrollToBottom(({ behavior }) => {\n scrollToBottom(behavior);\n });\n\n useAuiEvent(\"thread.runStart\", () => {\n prependAnchorRef.current = null;\n followPausedRef.current = false;\n if (!scrollToBottomOnRunStart) return;\n if (threadViewportStore.getState().turnAnchor === \"top\") return;\n scheduleScrollToBottom(\"auto\");\n });\n\n useAuiEvent(\"threads.selectionChanged\", () => {\n followPausedRef.current = false;\n if (!scrollToBottomOnThreadSwitch) return;\n scheduleScrollToBottom(\"instant\");\n });\n\n const autoScrollRef = useComposedRefs<TElement>(resizeRef, scrollRef, divRef);\n return autoScrollRef as RefCallback<TElement>;\n};\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"SandboxHost.d.ts","sourceRoot":"","sources":["../../src/sandbox-host/SandboxHost.tsx"],"names":[],"mappings":"AAEA,OAAO,EACL,KAAK,aAAa,EAKnB,MAAM,OAAO,CAAC;AACf,OAAO,EAEL,KAAK,aAAa,EAElB,KAAK,aAAa,EACnB,MAAM,oBAAoB,CAAC;AAO5B,MAAM,MAAM,iBAAiB,GAAG;IAC9B,OAAO,CAAC,EAAE,aAAa,EAAE,CAAC;IAC1B,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,aAAa,CAAC;IACtB,mBAAmB,CAAC,EAAE,OAAO,CAAC;CAC/B,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG,IAAI,CACjC,aAAa,EACb,QAAQ,GAAG,QAAQ,GAAG,aAAa,CACpC,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG;IAC3B,SAAS,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;CACrC,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC1B,SAAS,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,CAAC;IACzC,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAE9C,MAAM,MAAM,gBAAgB,GAAG;IAC7B,OAAO,EAAE,cAAc,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,iBAAiB,GAAG,SAAS,CAAC;IACxC,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC/B,YAAY,EAAE,CACZ,KAAK,EAAE,gBAAgB,EACvB,IAAI,EAAE,cAAc,KACjB,aAAa,CAAC;IACnB,OAAO,CAAC,EAAE,CAAC,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC,GAAG,SAAS,CAAC;IAC/C,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,GAAG,SAAS,CAAC;CACjE,CAAC;AAEF,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,YAAY,EACnB,KAAK,EAAE;IAAE,MAAM,EAAE,iBAAiB,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GACnD,OAAO,CAIT;AASD,wBAAgB,WAAW,CAAC,EAC1B,OAAO,EACP,UAAU,EACV,OAAO,EACP,SAA8B,EAC9B,YAAY,EACZ,OAAO,EACP,cAAc,GACf,EAAE,gBAAgB,+BAkKlB"}
1
+ {"version":3,"file":"SandboxHost.d.ts","sourceRoot":"","sources":["../../src/sandbox-host/SandboxHost.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,aAAa,EAA+B,MAAM,OAAO,CAAC;AAExE,OAAO,EAEL,KAAK,aAAa,EAElB,KAAK,aAAa,EACnB,MAAM,oBAAoB,CAAC;AAO5B,MAAM,MAAM,iBAAiB,GAAG;IAC9B,OAAO,CAAC,EAAE,aAAa,EAAE,CAAC;IAC1B,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,aAAa,CAAC;IACtB,mBAAmB,CAAC,EAAE,OAAO,CAAC;CAC/B,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG,IAAI,CACjC,aAAa,EACb,QAAQ,GAAG,QAAQ,GAAG,aAAa,CACpC,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG;IAC3B,SAAS,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;CACrC,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC1B,SAAS,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,CAAC;IACzC,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAE9C,MAAM,MAAM,gBAAgB,GAAG;IAC7B,OAAO,EAAE,cAAc,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,iBAAiB,GAAG,SAAS,CAAC;IACxC,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC/B,YAAY,EAAE,CACZ,KAAK,EAAE,gBAAgB,EACvB,IAAI,EAAE,cAAc,KACjB,aAAa,CAAC;IACnB,OAAO,CAAC,EAAE,CAAC,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC,GAAG,SAAS,CAAC;IAC/C,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,GAAG,SAAS,CAAC;CACjE,CAAC;AAEF,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,YAAY,EACnB,KAAK,EAAE;IAAE,MAAM,EAAE,iBAAiB,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GACnD,OAAO,CAIT;AASD,wBAAgB,WAAW,CAAC,EAC1B,OAAO,EACP,UAAU,EACV,OAAO,EACP,SAA8B,EAC9B,YAAY,EACZ,OAAO,EACP,cAAc,GACf,EAAE,gBAAgB,+BAkKlB"}
@@ -1,6 +1,7 @@
1
1
  "use client";
2
+ import { useIsomorphicLayoutEffect } from "../utils/useIsomorphicLayoutEffect.js";
2
3
  import { c } from "@assistant-ui/tap/react-shim/compiler-runtime";
3
- import { useEffect, useLayoutEffect, useRef, useState } from "@assistant-ui/tap/react-shim";
4
+ import { useEffect, useRef, useState } from "@assistant-ui/tap/react-shim";
4
5
  import { invokeUserCallback } from "@assistant-ui/core/internal";
5
6
  import { jsx } from "react/jsx-runtime";
6
7
  import { SafeContentFrame, isShimLoadError } from "safe-content-frame";
@@ -59,7 +60,7 @@ function SandboxHost(t0) {
59
60
  t3 = $[9];
60
61
  t4 = $[10];
61
62
  }
62
- useLayoutEffect(t3, t4);
63
+ useIsomorphicLayoutEffect(t3, t4);
63
64
  let t5;
64
65
  if ($[11] === Symbol.for("react.memo_cache_sentinel")) {
65
66
  t5 = () => {