@assistant-ui/react 0.15.28 → 0.15.29

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 (41) hide show
  1. package/dist/adapters/OpenAIRealtimeVoiceAdapter.d.ts +22 -0
  2. package/dist/adapters/OpenAIRealtimeVoiceAdapter.d.ts.map +1 -0
  3. package/dist/adapters/OpenAIRealtimeVoiceAdapter.js +297 -0
  4. package/dist/adapters/OpenAIRealtimeVoiceAdapter.js.map +1 -0
  5. package/dist/index.d.ts +1 -0
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +3 -2
  8. package/dist/legacy-runtime/runtime-cores/utils/MessageRepository.js +1 -1
  9. package/dist/mcp-apps/McpAppRenderer.d.ts.map +1 -1
  10. package/dist/mcp-apps/McpAppRenderer.js +5 -1
  11. package/dist/mcp-apps/McpAppRenderer.js.map +1 -1
  12. package/dist/model-context/frame/useAssistantFrameHost.d.ts.map +1 -1
  13. package/dist/model-context/frame/useAssistantFrameHost.js +67 -46
  14. package/dist/model-context/frame/useAssistantFrameHost.js.map +1 -1
  15. package/dist/model-context/makeAssistantVisible.js +1 -1
  16. package/dist/primitives/composer/ComposerAddAttachment.d.ts.map +1 -1
  17. package/dist/primitives/composer/ComposerAddAttachment.js +28 -10
  18. package/dist/primitives/composer/ComposerAddAttachment.js.map +1 -1
  19. package/dist/primitives/composer/ComposerInput.d.ts +4 -1
  20. package/dist/primitives/composer/ComposerInput.d.ts.map +1 -1
  21. package/dist/primitives/composer/ComposerInput.js +15 -2
  22. package/dist/primitives/composer/ComposerInput.js.map +1 -1
  23. package/dist/primitives/composer/trigger/TriggerPopoverAction.js +1 -1
  24. package/dist/primitives/composer/trigger/TriggerPopoverDirective.js +1 -1
  25. package/dist/unstable/useMentionAdapter.js +1 -1
  26. package/dist/utils/Primitive.d.ts +14 -14
  27. package/package.json +4 -4
  28. package/src/adapters/OpenAIRealtimeVoiceAdapter.test.ts +748 -0
  29. package/src/adapters/OpenAIRealtimeVoiceAdapter.ts +388 -0
  30. package/src/index.ts +4 -0
  31. package/src/mcp-apps/McpAppRenderer.test.tsx +65 -0
  32. package/src/mcp-apps/McpAppRenderer.tsx +13 -2
  33. package/src/model-context/frame/useAssistantFrameHost.detach.test.tsx +145 -0
  34. package/src/model-context/frame/useAssistantFrameHost.reload.test.tsx +106 -0
  35. package/src/model-context/frame/useAssistantFrameHost.test.tsx +3 -1
  36. package/src/model-context/frame/useAssistantFrameHost.ts +72 -24
  37. package/src/primitives/composer/ComposerAddAttachment.test.tsx +1 -0
  38. package/src/primitives/composer/ComposerAddAttachment.thread-scope.test.tsx +202 -0
  39. package/src/primitives/composer/ComposerAddAttachment.ts +11 -1
  40. package/src/primitives/composer/ComposerInput.focus-on-run-start.test.tsx +130 -0
  41. package/src/primitives/composer/ComposerInput.tsx +29 -3
@@ -1,7 +1,6 @@
1
1
  "use client";
2
- import { c } from "@assistant-ui/tap/react-shim/compiler-runtime";
3
- import { useEffect } from "@assistant-ui/tap/react-shim";
4
2
  import { AssistantFrameHost } from "@assistant-ui/core";
3
+ import { useEffect } from "@assistant-ui/tap/react-shim";
5
4
  //#region src/model-context/frame/useAssistantFrameHost.ts
6
5
  /**
7
6
  * React hook that manages the lifecycle of an AssistantFrameHost and its binding to the current AssistantRuntime.
@@ -20,55 +19,77 @@ import { AssistantFrameHost } from "@assistant-ui/core";
20
19
  * }
21
20
  * ```
22
21
  */
23
- const useAssistantFrameHost = (t0) => {
24
- const $ = c(5);
25
- const { iframeRef, targetOrigin, register } = t0;
26
- let t1;
27
- let t2;
28
- if ($[0] !== iframeRef || $[1] !== register || $[2] !== targetOrigin) {
29
- t1 = () => {
30
- const iframeWindow = iframeRef.current?.contentWindow;
31
- if (!iframeWindow) return;
32
- const frameHost = new AssistantFrameHost(iframeWindow, targetOrigin);
33
- const unsubscribe = register(frameHost);
34
- return () => {
35
- let cleanupFailed = false;
36
- let cleanupError;
22
+ const useAssistantFrameHost = ({ iframeRef, targetOrigin, register }) => {
23
+ useEffect(() => {
24
+ let active;
25
+ const syncFrame = () => {
26
+ const iframe = iframeRef.current;
27
+ const iframeWindow = iframe?.isConnected === false ? null : iframe?.contentWindow;
28
+ if (active && active.iframe === iframe && active.window === iframeWindow) return;
29
+ const previous = active;
30
+ active = void 0;
31
+ let disposalFailed = false;
32
+ let disposalError;
33
+ try {
34
+ previous?.dispose();
35
+ } catch (error) {
36
+ disposalFailed = true;
37
+ disposalError = error;
38
+ }
39
+ if (iframe && iframeWindow) {
40
+ const frameHost = new AssistantFrameHost(iframeWindow, targetOrigin);
41
+ let unsubscribe;
37
42
  try {
43
+ unsubscribe = register(frameHost);
44
+ } catch (error_0) {
38
45
  frameHost.dispose();
39
- } catch (t3) {
40
- const error = t3;
41
- cleanupFailed = true;
42
- cleanupError = error;
46
+ throw error_0;
43
47
  }
44
- try {
45
- unsubscribe();
46
- } catch (t4) {
47
- const error_0 = t4;
48
- if (cleanupFailed) console.error("[assistant-ui] AssistantFrameHost unregistration failed.", error_0);
49
- else {
50
- cleanupFailed = true;
51
- cleanupError = error_0;
48
+ const onLoad = () => frameHost.frameLoaded();
49
+ iframe.addEventListener("load", onLoad);
50
+ active = {
51
+ iframe,
52
+ window: iframeWindow,
53
+ dispose: () => {
54
+ iframe.removeEventListener("load", onLoad);
55
+ let cleanupFailed = false;
56
+ let cleanupError;
57
+ try {
58
+ frameHost.dispose();
59
+ } catch (error_1) {
60
+ cleanupFailed = true;
61
+ cleanupError = error_1;
62
+ }
63
+ try {
64
+ unsubscribe();
65
+ } catch (error_2) {
66
+ if (cleanupFailed) console.error("[assistant-ui] AssistantFrameHost unregistration failed.", error_2);
67
+ else {
68
+ cleanupFailed = true;
69
+ cleanupError = error_2;
70
+ }
71
+ }
72
+ if (cleanupFailed) throw cleanupError;
52
73
  }
53
- }
54
- if (cleanupFailed) throw cleanupError;
55
- };
74
+ };
75
+ }
76
+ if (disposalFailed) throw disposalError;
77
+ };
78
+ syncFrame();
79
+ const observer = new MutationObserver(syncFrame);
80
+ observer.observe(document, {
81
+ childList: true,
82
+ subtree: true
83
+ });
84
+ return () => {
85
+ observer.disconnect();
86
+ active?.dispose();
56
87
  };
57
- t2 = [
58
- iframeRef,
59
- targetOrigin,
60
- register
61
- ];
62
- $[0] = iframeRef;
63
- $[1] = register;
64
- $[2] = targetOrigin;
65
- $[3] = t1;
66
- $[4] = t2;
67
- } else {
68
- t1 = $[3];
69
- t2 = $[4];
70
- }
71
- useEffect(t1, t2);
88
+ }, [
89
+ iframeRef,
90
+ targetOrigin,
91
+ register
92
+ ]);
72
93
  };
73
94
  //#endregion
74
95
  export { useAssistantFrameHost };
@@ -1 +1 @@
1
- {"version":3,"file":"useAssistantFrameHost.js","names":["c","_c","Unsubscribe","useEffect","RefObject","AssistantFrameHost","UseAssistantFrameHostOptions","iframeRef","Readonly","HTMLIFrameElement","targetOrigin","register","frameHost","useAssistantFrameHost","t0","$","t1","t2","iframeWindow","current","contentWindow","unsubscribe","cleanupFailed","cleanupError","dispose","t3","error","t4","console","_c"],"sources":["../../../src/model-context/frame/useAssistantFrameHost.ts"],"sourcesContent":["\"use client\";\n\nimport type { Unsubscribe } from \"@assistant-ui/core\";\nimport { useEffect, type RefObject } from \"react\";\nimport { AssistantFrameHost } from \"@assistant-ui/core\";\n\ntype UseAssistantFrameHostOptions = {\n iframeRef: Readonly<RefObject<HTMLIFrameElement | null | undefined>>;\n targetOrigin?: string;\n register: (frameHost: AssistantFrameHost) => Unsubscribe;\n};\n\n/**\n * React hook that manages the lifecycle of an AssistantFrameHost and its binding to the current AssistantRuntime.\n *\n * Usage example:\n * ```typescript\n * function MyComponent() {\n * const iframeRef = useRef<HTMLIFrameElement>(null);\n *\n * useAssistantFrameHost({\n * iframeRef,\n * targetOrigin: \"https://trusted-domain.com\",\n * });\n *\n * return <iframe ref={iframeRef} src=\"...\" />;\n * }\n * ```\n */\nexport const useAssistantFrameHost = ({\n iframeRef,\n targetOrigin,\n register,\n}: UseAssistantFrameHostOptions): void => {\n useEffect(() => {\n const iframeWindow = iframeRef.current?.contentWindow;\n if (!iframeWindow) return;\n\n const frameHost = new AssistantFrameHost(iframeWindow, targetOrigin);\n\n const unsubscribe = register(frameHost);\n\n return () => {\n let cleanupFailed = false;\n let cleanupError: unknown;\n\n try {\n frameHost.dispose();\n } catch (error) {\n cleanupFailed = true;\n cleanupError = error;\n }\n\n try {\n unsubscribe();\n } catch (error) {\n if (cleanupFailed) {\n console.error(\n \"[assistant-ui] AssistantFrameHost unregistration failed.\",\n error,\n );\n } else {\n cleanupFailed = true;\n cleanupError = error;\n }\n }\n\n if (cleanupFailed) throw cleanupError;\n };\n }, [iframeRef, targetOrigin, register]);\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AA6BA,MAAaa,yBAAwBC,OAAA;CAAA,MAAAC,IAAAd,EAAA,CAAA;CAAC,MAAA,EAAAM,WAAAG,cAAAC,aAAAG;CAIP,IAAAE;CAAA,IAAAC;CAAA,IAAAF,EAAA,OAAAR,aAAAQ,EAAA,OAAAJ,YAAAI,EAAA,OAAAL,cAAA;EACnBM,WAAA;GACR,MAAAE,eAAqBX,UAASY,SAAuBC;GACrD,IAAI,CAACF,cAAY;GAEjB,MAAAN,YAAkB,IAAIP,mBAAmBa,cAAcR,YAAY;GAEnE,MAAAW,cAAoBV,SAASC,SAAS;GAAE,aAEjC;IACL,IAAAU,gBAAoB;IAChBC,IAAAA;IAEJ,IAAA;KACEX,UAASY,QAAS;IAAC,SAAAC,IAAA;KACZC,MAAAA,QAAAA;KACPJ,gBAAgBA;KAChBC,eAAeG;IAAH;IAGd,IAAA;KACEL,YAAY;IAAC,SAAAM,IAAA;KACND,MAAAA,UAAAA;KACP,IAAIJ,eACFM,QAAOF,MACL,4DACAA,OACF;UAAC;MAEDJ,gBAAgBA;MAChBC,eAAeG;KAAH;IACb;IAGH,IAAIJ,eAAe,MAAMC;GAAa;EACvC;EACAN,KAAA;GAACV;GAAWG;GAAcC;EAAQ;EAACI,EAAA,KAAAR;EAAAQ,EAAA,KAAAJ;EAAAI,EAAA,KAAAL;EAAAK,EAAA,KAAAC;EAAAD,EAAA,KAAAE;CAAA,OAAA;EAAAD,KAAAD,EAAA;EAAAE,KAAAF,EAAA;CAAA;CAnCtCZ,UAAUa,IAmCPC,EAAmC;AAAC"}
1
+ {"version":3,"file":"useAssistantFrameHost.js","names":["Unsubscribe","useEffect","RefObject","AssistantFrameHost","UseAssistantFrameHostOptions","iframeRef","Readonly","HTMLIFrameElement","targetOrigin","register","frameHost","useAssistantFrameHost","active","iframe","window","Window","dispose","syncFrame","current","iframeWindow","isConnected","contentWindow","previous","undefined","disposalFailed","disposalError","error","unsubscribe","onLoad","frameLoaded","addEventListener","removeEventListener","cleanupFailed","cleanupError","console","observer","MutationObserver","observe","document","childList","subtree","disconnect"],"sources":["../../../src/model-context/frame/useAssistantFrameHost.ts"],"sourcesContent":["\"use client\";\n\nimport type { Unsubscribe } from \"@assistant-ui/core\";\nimport { useEffect, type RefObject } from \"react\";\nimport { AssistantFrameHost } from \"@assistant-ui/core\";\n\ntype UseAssistantFrameHostOptions = {\n iframeRef: Readonly<RefObject<HTMLIFrameElement | null | undefined>>;\n targetOrigin?: string;\n register: (frameHost: AssistantFrameHost) => Unsubscribe;\n};\n\n/**\n * React hook that manages the lifecycle of an AssistantFrameHost and its binding to the current AssistantRuntime.\n *\n * Usage example:\n * ```typescript\n * function MyComponent() {\n * const iframeRef = useRef<HTMLIFrameElement>(null);\n *\n * useAssistantFrameHost({\n * iframeRef,\n * targetOrigin: \"https://trusted-domain.com\",\n * });\n *\n * return <iframe ref={iframeRef} src=\"...\" />;\n * }\n * ```\n */\nexport const useAssistantFrameHost = ({\n iframeRef,\n targetOrigin,\n register,\n}: UseAssistantFrameHostOptions): void => {\n useEffect(() => {\n let active:\n | {\n iframe: HTMLIFrameElement;\n window: Window;\n dispose: () => void;\n }\n | undefined;\n\n const syncFrame = () => {\n const iframe = iframeRef.current;\n const iframeWindow =\n iframe?.isConnected === false ? null : iframe?.contentWindow;\n if (active && active.iframe === iframe && active.window === iframeWindow)\n return;\n\n const previous = active;\n active = undefined;\n let disposalFailed = false;\n let disposalError: unknown;\n try {\n previous?.dispose();\n } catch (error) {\n disposalFailed = true;\n disposalError = error;\n }\n\n if (iframe && iframeWindow) {\n const frameHost = new AssistantFrameHost(iframeWindow, targetOrigin);\n let unsubscribe: Unsubscribe;\n try {\n unsubscribe = register(frameHost);\n } catch (error) {\n frameHost.dispose();\n throw error;\n }\n const onLoad = () => frameHost.frameLoaded();\n iframe.addEventListener(\"load\", onLoad);\n active = {\n iframe,\n window: iframeWindow,\n dispose: () => {\n iframe.removeEventListener(\"load\", onLoad);\n let cleanupFailed = false;\n let cleanupError: unknown;\n\n try {\n frameHost.dispose();\n } catch (error) {\n cleanupFailed = true;\n cleanupError = error;\n }\n\n try {\n unsubscribe();\n } catch (error) {\n if (cleanupFailed) {\n console.error(\n \"[assistant-ui] AssistantFrameHost unregistration failed.\",\n error,\n );\n } else {\n cleanupFailed = true;\n cleanupError = error;\n }\n }\n\n if (cleanupFailed) throw cleanupError;\n },\n };\n }\n\n if (disposalFailed) throw disposalError;\n };\n\n syncFrame();\n const observer = new MutationObserver(syncFrame);\n observer.observe(document, { childList: true, subtree: true });\n\n return () => {\n observer.disconnect();\n active?.dispose();\n };\n }, [iframeRef, targetOrigin, register]);\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AA6BA,MAAaW,yBAAyB,EACpCN,WACAG,cACAC,eACwC;CACxCR,gBAAgB;EACd,IAAIW;EAQJ,MAAMK,kBAAkB;GACtB,MAAMJ,SAASR,UAAUa;GACzB,MAAMC,eACJN,QAAQO,gBAAgB,QAAQ,OAAOP,QAAQQ;GACjD,IAAIT,UAAUA,OAAOC,WAAWA,UAAUD,OAAOE,WAAWK,cAC1D;GAEF,MAAMG,WAAWV;GACjBA,SAASW,KAAAA;GACT,IAAIC,iBAAiB;GACrB,IAAIC;GACJ,IAAI;IACFH,UAAUN,QAAQ;GACpB,SAASU,OAAO;IACdF,iBAAiB;IACjBC,gBAAgBC;GAClB;GAEA,IAAIb,UAAUM,cAAc;IAC1B,MAAMT,YAAY,IAAIP,mBAAmBgB,cAAcX,YAAY;IACnE,IAAImB;IACJ,IAAI;KACFA,cAAclB,SAASC,SAAS;IAClC,SAASgB,SAAO;KACdhB,UAAUM,QAAQ;KAClB,MAAMU;IACR;IACA,MAAME,eAAelB,UAAUmB,YAAY;IAC3ChB,OAAOiB,iBAAiB,QAAQF,MAAM;IACtChB,SAAS;KACPC;KACAC,QAAQK;KACRH,eAAe;MACbH,OAAOkB,oBAAoB,QAAQH,MAAM;MACzC,IAAII,gBAAgB;MACpB,IAAIC;MAEJ,IAAI;OACFvB,UAAUM,QAAQ;MACpB,SAASU,SAAO;OACdM,gBAAgB;OAChBC,eAAeP;MACjB;MAEA,IAAI;OACFC,YAAY;MACd,SAASD,SAAO;OACd,IAAIM,eACFE,QAAQR,MACN,4DACAA,OACF;YACK;QACLM,gBAAgB;QAChBC,eAAeP;OACjB;MACF;MAEA,IAAIM,eAAe,MAAMC;KAC3B;IACF;GACF;GAEA,IAAIT,gBAAgB,MAAMC;EAC5B;EAEAR,UAAU;EACV,MAAMkB,WAAW,IAAIC,iBAAiBnB,SAAS;EAC/CkB,SAASE,QAAQC,UAAU;GAAEC,WAAW;GAAMC,SAAS;EAAK,CAAC;EAE7D,aAAa;GACXL,SAASM,WAAW;GACpB7B,QAAQI,QAAQ;EAClB;CACF,GAAG;EAACX;EAAWG;EAAcC;CAAQ,CAAC;AACxC"}
@@ -1,9 +1,9 @@
1
1
  "use client";
2
+ import { tool } from "@assistant-ui/core";
2
3
  import { c } from "@assistant-ui/tap/react-shim/compiler-runtime";
3
4
  import { createContext, forwardRef, useContext, useEffect, useId, useRef } from "@assistant-ui/tap/react-shim";
4
5
  import { useAui } from "@assistant-ui/store";
5
6
  import { jsx } from "react/jsx-runtime";
6
- import { tool } from "@assistant-ui/core";
7
7
  import { useComposedRefs } from "radix-ui/internal";
8
8
  //#region src/model-context/makeAssistantVisible.tsx
9
9
  const DEFAULT_ACTION_SETTLE_DELAY_MS = 2e3;
@@ -1 +1 @@
1
- {"version":3,"file":"ComposerAddAttachment.d.ts","sourceRoot":"","sources":["../../../src/primitives/composer/ComposerAddAttachment.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,KAAK,mBAAmB,EACxB,KAAK,iBAAiB,EAEvB,0CAAuC;AAKxC,QAAA,MAAM,wBAAwB,GAAI,gBAE/B;IACD,qCAAqC;IACrC,QAAQ,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;CAC3B,wBA4CL,CAAC;AAEF,yBAAiB,8BAA8B,CAAC;IAC9C,KAAY,OAAO,GAAG,mBAAmB,CAAC;IAC1C,KAAY,KAAK,GAAG,iBAAiB,CAAC,OAAO,wBAAwB,CAAC,CAAC;CACxE;AAED,eAAO,MAAM,8BAA8B;;;;;IArDzC,qCAAqC;eAC1B,OAAO,GAAG,SAAS;qDAwD/B,CAAC"}
1
+ {"version":3,"file":"ComposerAddAttachment.d.ts","sourceRoot":"","sources":["../../../src/primitives/composer/ComposerAddAttachment.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,KAAK,mBAAmB,EACxB,KAAK,iBAAiB,EAEvB,0CAAuC;AAKxC,QAAA,MAAM,wBAAwB,GAAI,gBAE/B;IACD,qCAAqC;IACrC,QAAQ,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;CAC3B,wBAsDL,CAAC;AAEF,yBAAiB,8BAA8B,CAAC;IAC9C,KAAY,OAAO,GAAG,mBAAmB,CAAC;IAC1C,KAAY,KAAK,GAAG,iBAAiB,CAAC,OAAO,wBAAwB,CAAC,CAAC;CACxE;AAED,eAAO,MAAM,8BAA8B;;;;;IA/DzC,qCAAqC;eAC1B,OAAO,GAAG,SAAS;qDAkE/B,CAAC"}
@@ -2,11 +2,11 @@
2
2
  import { createActionButton } from "../../utils/createActionButton.js";
3
3
  import { c } from "@assistant-ui/tap/react-shim/compiler-runtime";
4
4
  import { useComposerAddAttachment } from "@assistant-ui/core/react";
5
- import "@assistant-ui/tap/react-shim";
5
+ import { useInsertionEffect, useRef } from "@assistant-ui/tap/react-shim";
6
6
  import { useAui } from "@assistant-ui/store";
7
7
  //#region src/primitives/composer/ComposerAddAttachment.ts
8
8
  const useComposerAddAttachment$1 = (t0) => {
9
- const $ = c(6);
9
+ const $ = c(10);
10
10
  let t1;
11
11
  if ($[0] !== t0) {
12
12
  t1 = t0 === void 0 ? {} : t0;
@@ -17,9 +17,26 @@ const useComposerAddAttachment$1 = (t0) => {
17
17
  const multiple = t2 === void 0 ? true : t2;
18
18
  const { disabled, addAttachment } = useComposerAddAttachment();
19
19
  const aui = useAui();
20
+ const scopeGenerationRef = useRef(0);
20
21
  let t3;
21
- if ($[2] !== addAttachment || $[3] !== aui.composer || $[4] !== multiple) {
22
- t3 = () => {
22
+ if ($[2] === Symbol.for("react.memo_cache_sentinel")) {
23
+ t3 = () => () => {
24
+ scopeGenerationRef.current = scopeGenerationRef.current + 1;
25
+ };
26
+ $[2] = t3;
27
+ } else t3 = $[2];
28
+ let t4;
29
+ if ($[3] !== aui.composer || $[4] !== aui.optional.threadListItem) {
30
+ t4 = [aui.composer, aui.optional.threadListItem];
31
+ $[3] = aui.composer;
32
+ $[4] = aui.optional.threadListItem;
33
+ $[5] = t4;
34
+ } else t4 = $[5];
35
+ useInsertionEffect(t3, t4);
36
+ let t5;
37
+ if ($[6] !== addAttachment || $[7] !== aui.composer || $[8] !== multiple) {
38
+ t5 = () => {
39
+ const scopeGeneration = scopeGenerationRef.current;
23
40
  const input = document.createElement("input");
24
41
  input.type = "file";
25
42
  input.multiple = multiple;
@@ -32,6 +49,7 @@ const useComposerAddAttachment$1 = (t0) => {
32
49
  if (!fileList) return;
33
50
  const attachmentPromises = Array.from(fileList, async (file) => {
34
51
  try {
52
+ if (scopeGenerationRef.current !== scopeGeneration) return;
35
53
  await addAttachment(file);
36
54
  } catch {}
37
55
  });
@@ -43,12 +61,12 @@ const useComposerAddAttachment$1 = (t0) => {
43
61
  };
44
62
  input.click();
45
63
  };
46
- $[2] = addAttachment;
47
- $[3] = aui.composer;
48
- $[4] = multiple;
49
- $[5] = t3;
50
- } else t3 = $[5];
51
- const callback = t3;
64
+ $[6] = addAttachment;
65
+ $[7] = aui.composer;
66
+ $[8] = multiple;
67
+ $[9] = t5;
68
+ } else t5 = $[9];
69
+ const callback = t5;
52
70
  if (disabled) return null;
53
71
  return callback;
54
72
  };
@@ -1 +1 @@
1
- {"version":3,"file":"ComposerAddAttachment.js","names":["c","_c","ActionButtonElement","ActionButtonProps","createActionButton","useCallback","useAui","useComposerAddAttachment","useComposerAddAttachmentBehavior","t0","$","t1","undefined","multiple","t2","disabled","addAttachment","aui","t3","composer","input","document","createElement","type","hidden","attachmentAccept","getState","accept","body","appendChild","onchange","e","fileList","target","HTMLInputElement","files","attachmentPromises","Array","from","file","removeChild","Promise","all","oncancel","length","click","callback","ComposerPrimitiveAddAttachment","Element","Props","useComposerAddAttachment","_c","useComposerAddAttachmentBehavior"],"sources":["../../../src/primitives/composer/ComposerAddAttachment.ts"],"sourcesContent":["\"use client\";\n\nimport {\n type ActionButtonElement,\n type ActionButtonProps,\n createActionButton,\n} from \"../../utils/createActionButton\";\nimport { useCallback } from \"react\";\nimport { useAui } from \"@assistant-ui/store\";\nimport { useComposerAddAttachment as useComposerAddAttachmentBehavior } from \"@assistant-ui/core/react\";\n\nconst useComposerAddAttachment = ({\n multiple = true,\n}: {\n /** allow selecting multiple files */\n multiple?: boolean | undefined;\n} = {}) => {\n const { disabled, addAttachment } = useComposerAddAttachmentBehavior();\n const aui = useAui();\n\n const callback = useCallback(() => {\n const input = document.createElement(\"input\");\n input.type = \"file\";\n input.multiple = multiple;\n input.hidden = true;\n\n const attachmentAccept = aui.composer.getState().attachmentAccept;\n if (attachmentAccept !== \"*\") {\n input.accept = attachmentAccept;\n }\n\n document.body.appendChild(input);\n\n input.onchange = async (e) => {\n const fileList = (e.target as HTMLInputElement).files;\n if (!fileList) return;\n\n const attachmentPromises = Array.from(fileList, async (file) => {\n try {\n await addAttachment(file);\n } catch {\n // The composer runtime emits composer.attachmentAddError before rejecting.\n }\n });\n\n document.body.removeChild(input);\n await Promise.all(attachmentPromises);\n };\n\n input.oncancel = () => {\n if (!input.files || input.files.length === 0) {\n document.body.removeChild(input);\n }\n };\n\n input.click();\n }, [aui, multiple, addAttachment]);\n\n if (disabled) return null;\n return callback;\n};\n\nexport namespace ComposerPrimitiveAddAttachment {\n export type Element = ActionButtonElement;\n export type Props = ActionButtonProps<typeof useComposerAddAttachment>;\n}\n\nexport const ComposerPrimitiveAddAttachment = createActionButton(\n \"ComposerPrimitive.AddAttachment\",\n useComposerAddAttachment,\n [\"multiple\"],\n);\n"],"mappings":";;;;;;;AAWA,MAAMO,8BAA2BE,OAAA;CAAA,MAAAC,IAAAT,EAAA,CAAA;CAAA,IAAAU;CAAA,IAAAD,EAAA,OAAAD,IAAA;EAACE,KAAAF,OAAAG,KAAAA,IAAA,CAK7B,IAL6BH;EAK5BC,EAAA,KAAAD;EAAAC,EAAA,KAAAC;CAAA,OAAAA,KAAAD,EAAA;CAL4B,MAAA,EAAAG,UAAAC,OAAAH;CAChC,MAAAE,WAAAC,OAAAF,KAAAA,IAAA,OAAAE;CAKA,MAAA,EAAAC,UAAAC,kBAAoCR,yBAAiC;CACrE,MAAAS,MAAYX,OAAO;CAAE,IAAAY;CAAA,IAAAR,EAAA,OAAAM,iBAAAN,EAAA,OAAAO,IAAAE,YAAAT,EAAA,OAAAG,UAAA;EAEQK,WAAA;GAC3B,MAAAE,QAAcC,SAAQC,cAAe,OAAO;GAC5CF,MAAKG,OAAQ;GACbH,MAAKP,WAAYA;GACjBO,MAAKI,SAAU;GAEf,MAAAC,mBAAyBR,IAAGE,SAASO,SAAU,CAAC,CAAAD;GAChD,IAAIA,qBAAqB,KACvBL,MAAKO,SAAUF;GAGjBJ,SAAQO,KAAKC,YAAaT,KAAK;GAE/BA,MAAKU,WAAY,OAAAC,MAAA;IACf,MAAAC,WAAkBD,EAACE,OAA2BE;IAC9C,IAAI,CAACH,UAAQ;IAEb,MAAAI,qBAA2BC,MAAKC,KAAMN,UAAU,OAAAO,SAAA;KAC9C,IAAA;MACE,MAAMvB,cAAcuB,IAAI;KAAC,QAAA,CAAA;IAG1B,CACF;IAEDlB,SAAQO,KAAKY,YAAapB,KAAK;IAC/B,MAAMqB,QAAOC,IAAKN,kBAAkB;GAAC;GAGvChB,MAAKuB,iBAAY;IACf,IAAI,CAACvB,MAAKe,SAAUf,MAAKe,MAAMS,WAAY,GACzCvB,SAAQO,KAAKY,YAAapB,KAAK;GAChC;GAGHA,MAAKyB,MAAO;EAAC;EACdnC,EAAA,KAAAM;EAAAN,EAAA,KAAAO,IAAAE;EAAAT,EAAA,KAAAG;EAAAH,EAAA,KAAAQ;CAAA,OAAAA,KAAAR,EAAA;CApCD,MAAAoC,WAAiB5B;CAsCjB,IAAIH,UAAQ,OAAS;CAAK,OACnB+B;AAAQ;AAQjB,MAAaC,iCAAiC3C,mBAC5C,mCACAG,4BACA,CAAC,UAAU,CACb"}
1
+ {"version":3,"file":"ComposerAddAttachment.js","names":["c","_c","ActionButtonElement","ActionButtonProps","createActionButton","useCallback","useInsertionEffect","useRef","useAui","useComposerAddAttachment","useComposerAddAttachmentBehavior","t0","$","t1","undefined","multiple","t2","disabled","addAttachment","aui","scopeGenerationRef","t3","Symbol","for","current","t4","composer","optional","threadListItem","t5","scopeGeneration","input","document","createElement","type","hidden","attachmentAccept","getState","accept","body","appendChild","onchange","e","fileList","target","HTMLInputElement","files","attachmentPromises","Array","from","file","removeChild","Promise","all","oncancel","length","click","callback","ComposerPrimitiveAddAttachment","Element","Props","useComposerAddAttachment","_c","useComposerAddAttachmentBehavior"],"sources":["../../../src/primitives/composer/ComposerAddAttachment.ts"],"sourcesContent":["\"use client\";\n\nimport {\n type ActionButtonElement,\n type ActionButtonProps,\n createActionButton,\n} from \"../../utils/createActionButton\";\nimport { useCallback, useInsertionEffect, useRef } from \"react\";\nimport { useAui } from \"@assistant-ui/store\";\nimport { useComposerAddAttachment as useComposerAddAttachmentBehavior } from \"@assistant-ui/core/react\";\n\nconst useComposerAddAttachment = ({\n multiple = true,\n}: {\n /** allow selecting multiple files */\n multiple?: boolean | undefined;\n} = {}) => {\n const { disabled, addAttachment } = useComposerAddAttachmentBehavior();\n const aui = useAui();\n const scopeGenerationRef = useRef(0);\n\n useInsertionEffect(\n () => () => {\n scopeGenerationRef.current += 1;\n },\n [aui.composer, aui.optional.threadListItem],\n );\n\n const callback = useCallback(() => {\n const scopeGeneration = scopeGenerationRef.current;\n const input = document.createElement(\"input\");\n input.type = \"file\";\n input.multiple = multiple;\n input.hidden = true;\n\n const attachmentAccept = aui.composer.getState().attachmentAccept;\n if (attachmentAccept !== \"*\") {\n input.accept = attachmentAccept;\n }\n\n document.body.appendChild(input);\n\n input.onchange = async (e) => {\n const fileList = (e.target as HTMLInputElement).files;\n if (!fileList) return;\n\n const attachmentPromises = Array.from(fileList, async (file) => {\n try {\n if (scopeGenerationRef.current !== scopeGeneration) return;\n await addAttachment(file);\n } catch {\n // The composer runtime emits composer.attachmentAddError before rejecting.\n }\n });\n\n document.body.removeChild(input);\n await Promise.all(attachmentPromises);\n };\n\n input.oncancel = () => {\n if (!input.files || input.files.length === 0) {\n document.body.removeChild(input);\n }\n };\n\n input.click();\n }, [aui, multiple, addAttachment]);\n\n if (disabled) return null;\n return callback;\n};\n\nexport namespace ComposerPrimitiveAddAttachment {\n export type Element = ActionButtonElement;\n export type Props = ActionButtonProps<typeof useComposerAddAttachment>;\n}\n\nexport const ComposerPrimitiveAddAttachment = createActionButton(\n \"ComposerPrimitive.AddAttachment\",\n useComposerAddAttachment,\n [\"multiple\"],\n);\n"],"mappings":";;;;;;;AAWA,MAAMS,8BAA2BE,OAAA;CAAA,MAAAC,IAAAX,EAAA,EAAA;CAAA,IAAAY;CAAA,IAAAD,EAAA,OAAAD,IAAA;EAACE,KAAAF,OAAAG,KAAAA,IAAA,CAK7B,IAL6BH;EAK5BC,EAAA,KAAAD;EAAAC,EAAA,KAAAC;CAAA,OAAAA,KAAAD,EAAA;CAL4B,MAAA,EAAAG,UAAAC,OAAAH;CAChC,MAAAE,WAAAC,OAAAF,KAAAA,IAAA,OAAAE;CAKA,MAAA,EAAAC,UAAAC,kBAAoCR,yBAAiC;CACrE,MAAAS,MAAYX,OAAO;CACnB,MAAAY,qBAA2Bb,OAAO,CAAC;CAAE,IAAAc;CAAA,IAAAT,EAAA,OAAAU,OAAAC,IAAA,2BAAA,GAAA;EAGnCF,iBAAM;GACJD,mBAAkBI,UAAlBJ,mBAAkBI,UAAY;EAAJ;EAC3BZ,EAAA,KAAAS;CAAA,OAAAA,KAAAT,EAAA;CAAA,IAAAa;CAAA,IAAAb,EAAA,OAAAO,IAAAO,YAAAd,EAAA,OAAAO,IAAAQ,SAAAC,gBAAA;EACDH,KAAA,CAACN,IAAGO,UAAWP,IAAGQ,SAASC,cAAe;EAAChB,EAAA,KAAAO,IAAAO;EAAAd,EAAA,KAAAO,IAAAQ,SAAAC;EAAAhB,EAAA,KAAAa;CAAA,OAAAA,KAAAb,EAAA;CAJ7CN,mBACEe,IAGAI,EACF;CAAC,IAAAI;CAAA,IAAAjB,EAAA,OAAAM,iBAAAN,EAAA,OAAAO,IAAAO,YAAAd,EAAA,OAAAG,UAAA;EAE4Bc,WAAA;GAC3B,MAAAC,kBAAwBV,mBAAkBI;GAC1C,MAAAO,QAAcC,SAAQC,cAAe,OAAO;GAC5CF,MAAKG,OAAQ;GACbH,MAAKhB,WAAYA;GACjBgB,MAAKI,SAAU;GAEf,MAAAC,mBAAyBjB,IAAGO,SAASW,SAAU,CAAC,CAAAD;GAChD,IAAIA,qBAAqB,KACvBL,MAAKO,SAAUF;GAGjBJ,SAAQO,KAAKC,YAAaT,KAAK;GAE/BA,MAAKU,WAAY,OAAAC,MAAA;IACf,MAAAC,WAAkBD,EAACE,OAA2BE;IAC9C,IAAI,CAACH,UAAQ;IAEb,MAAAI,qBAA2BC,MAAKC,KAAMN,UAAU,OAAAO,SAAA;KAC9C,IAAA;MACE,IAAI9B,mBAAkBI,YAAaM,iBAAe;MAClD,MAAMZ,cAAcgC,IAAI;KAAC,QAAA,CAAA;IAG1B,CACF;IAEDlB,SAAQO,KAAKY,YAAapB,KAAK;IAC/B,MAAMqB,QAAOC,IAAKN,kBAAkB;GAAC;GAGvChB,MAAKuB,iBAAY;IACf,IAAI,CAACvB,MAAKe,SAAUf,MAAKe,MAAMS,WAAY,GACzCvB,SAAQO,KAAKY,YAAapB,KAAK;GAChC;GAGHA,MAAKyB,MAAO;EAAC;EACd5C,EAAA,KAAAM;EAAAN,EAAA,KAAAO,IAAAO;EAAAd,EAAA,KAAAG;EAAAH,EAAA,KAAAiB;CAAA,OAAAA,KAAAjB,EAAA;CAtCD,MAAA6C,WAAiB5B;CAwCjB,IAAIZ,UAAQ,OAAS;CAAK,OACnBwC;AAAQ;AAQjB,MAAaC,iCAAiCtD,mBAC5C,mCACAK,4BACA,CAAC,UAAU,CACb"}
@@ -20,7 +20,10 @@ export declare namespace ComposerPrimitiveInput {
20
20
  */
21
21
  cancelOnEscape?: boolean | undefined;
22
22
  /**
23
- * Whether to automatically focus the input when a new run starts.
23
+ * Whether to focus the input when a new run starts if nothing is focused
24
+ * or focus is already inside the same thread or composer. Keeps the current
25
+ * selection when the input is already focused. Applies when `autoFocus` is
26
+ * enabled.
24
27
  *
25
28
  * @deprecated Experimental since 2024-10-12. Not scheduled for removal; the API may change in any release.
26
29
  *
@@ -1 +1 @@
1
- {"version":3,"file":"ComposerInput.d.ts","sourceRoot":"","sources":["../../../src/primitives/composer/ComposerInput.tsx"],"names":[],"mappings":"AAKA,OAAO,EAGL,KAAK,YAAY,EAOlB,MAAM,OAAO,CAAC;AACf,OAAyB,EACvB,KAAK,qBAAqB,EAE3B,MAAM,yBAAyB,CAAC;AAmBjC,yBAAiB,sBAAsB,CAAC;IACtC,MAAM,MAAM,OAAO,GAAG,mBAAmB,CAAC;IAE1C,KAAK,SAAS,GAAG;QACf;;;WAGG;QACH,OAAO,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;QAC9B;;WAEG;QACH,MAAM,CAAC,EAAE,YAAY,GAAG,SAAS,CAAC;QAClC;;;;;WAKG;QACH,cAAc,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;QACrC;;;;;;WAMG;QACH,wBAAwB,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;QAC/C;;;;;;WAMG;QACH,8BAA8B,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;QACrD;;;;;;WAMG;QACH,8BAA8B,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;QACrD;;;;;;;;;WASG;QACH,kCAAkC,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;QACzD;;;WAGG;QACH,oBAAoB,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;KAC5C,CAAC;IAEF,KAAK,eAAe,GAChB;QACE;;;;;;WAMG;QACH,UAAU,CAAC,EAAE,OAAO,GAAG,WAAW,GAAG,MAAM,GAAG,SAAS,CAAC;QACxD;;;WAGG;QACH,aAAa,CAAC,EAAE,KAAK,CAAC;KACvB,GACD;QACE,UAAU,CAAC,EAAE,KAAK,CAAC;QACnB;;;;WAIG;QACH,aAAa,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;KACrC,CAAC;IAEN,MAAM,MAAM,KAAK,GAAG,qBAAqB,GAAG,SAAS,GAAG,eAAe,CAAC;;CACzE;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,eAAO,MAAM,sBAAsB,8HAoTlC,CAAC"}
1
+ {"version":3,"file":"ComposerInput.d.ts","sourceRoot":"","sources":["../../../src/primitives/composer/ComposerInput.tsx"],"names":[],"mappings":"AAKA,OAAO,EAGL,KAAK,YAAY,EAOlB,MAAM,OAAO,CAAC;AACf,OAAyB,EACvB,KAAK,qBAAqB,EAE3B,MAAM,yBAAyB,CAAC;AAoBjC,yBAAiB,sBAAsB,CAAC;IACtC,MAAM,MAAM,OAAO,GAAG,mBAAmB,CAAC;IAE1C,KAAK,SAAS,GAAG;QACf;;;WAGG;QACH,OAAO,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;QAC9B;;WAEG;QACH,MAAM,CAAC,EAAE,YAAY,GAAG,SAAS,CAAC;QAClC;;;;;WAKG;QACH,cAAc,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;QACrC;;;;;;;;;WASG;QACH,wBAAwB,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;QAC/C;;;;;;WAMG;QACH,8BAA8B,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;QACrD;;;;;;WAMG;QACH,8BAA8B,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;QACrD;;;;;;;;;WASG;QACH,kCAAkC,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;QACzD;;;WAGG;QACH,oBAAoB,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;KAC5C,CAAC;IAEF,KAAK,eAAe,GAChB;QACE;;;;;;WAMG;QACH,UAAU,CAAC,EAAE,OAAO,GAAG,WAAW,GAAG,MAAM,GAAG,SAAS,CAAC;QACxD;;;WAGG;QACH,aAAa,CAAC,EAAE,KAAK,CAAC;KACvB,GACD;QACE,UAAU,CAAC,EAAE,KAAK,CAAC;QACnB;;;;WAIG;QACH,aAAa,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;KACrC,CAAC;IAEN,MAAM,MAAM,KAAK,GAAG,qBAAqB,GAAG,SAAS,GAAG,eAAe,CAAC;;CACzE;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,eAAO,MAAM,sBAAsB,8HA0UlC,CAAC"}
@@ -5,6 +5,7 @@ import { useOnScrollToBottom } from "../../utils/hooks/useOnScrollToBottom.js";
5
5
  import { useMediaQuery } from "../../utils/hooks/useMediaQuery.js";
6
6
  import { isCompositionKey } from "../../utils/isCompositionKey.js";
7
7
  import { useComposerInputPluginRegistryOptional } from "./ComposerInputPluginContext.js";
8
+ import { useThreadRootElementRef } from "../thread/ThreadRootElementContext.js";
8
9
  import { useComposerCancelWithFocus } from "./useComposerCancelWithFocus.js";
9
10
  import { useComposerInputDisabled, useComposerInputValue, useTriggerPopoverAriaProps } from "./useComposerInputState.js";
10
11
  import { forwardRef, isValidElement, useCallback, useEffect, useRef } from "@assistant-ui/tap/react-shim";
@@ -52,6 +53,7 @@ const TOUCH_PRIMARY_QUERY = "(pointer: coarse) and (not (any-pointer: fine))";
52
53
  */
53
54
  const ComposerPrimitiveInput = forwardRef(({ autoFocus = false, asChild, render, disabled: disabledProp, onChange, onKeyDown, onPaste, onSelect, submitOnEnter, submitMode, cancelOnEscape = true, unstable_focusOnRunStart = true, unstable_focusOnScrollToBottom = true, unstable_focusOnThreadSwitched = true, unstable_insertNewlineOnTouchEnter = false, addAttachmentOnPaste = true, onHeightChange, ...rest }, forwardedRef) => {
54
55
  const aui = useAui();
56
+ const threadRootRef = useThreadRootElementRef();
55
57
  const pluginRegistry = useComposerInputPluginRegistryOptional();
56
58
  const compactContext = useComposerCompactContextOptional();
57
59
  const declaredSubmitMode = submitMode ?? (submitOnEnter === false ? "none" : "enter");
@@ -129,16 +131,27 @@ const ComposerPrimitiveInput = forwardRef(({ autoFocus = false, asChild, render,
129
131
  textarea.focus({ preventScroll: true });
130
132
  textarea.setSelectionRange(textarea.value.length, textarea.value.length);
131
133
  }, [autoFocusEnabled]);
134
+ const focusOnRunStart = useCallback(() => {
135
+ const textarea_0 = textareaRef.current;
136
+ if (!textarea_0) return;
137
+ const doc = textarea_0.ownerDocument;
138
+ const active = doc.activeElement;
139
+ if (active === textarea_0) return;
140
+ const root = threadRootRef?.current;
141
+ const composerRoot = textarea_0.closest("[data-aui-composer-type]");
142
+ if (active && active !== doc.body && !composerRoot?.contains(active) && (!root?.contains(active) || active.closest("[data-aui-thread-root]") !== root)) return;
143
+ focus();
144
+ }, [focus, threadRootRef]);
132
145
  useEffect(() => focus(), [focus]);
133
146
  useOnScrollToBottom(() => {
134
147
  if (aui.composer.getState().type === "thread" && unstable_focusOnScrollToBottom) focus();
135
148
  });
136
149
  useEffect(() => {
137
150
  if (aui.composer.getState().type !== "thread" || !unstable_focusOnRunStart) return void 0;
138
- return aui.on("thread.runStart", focus);
151
+ return aui.on("thread.runStart", focusOnRunStart);
139
152
  }, [
140
153
  unstable_focusOnRunStart,
141
- focus,
154
+ focusOnRunStart,
142
155
  aui
143
156
  ]);
144
157
  useEffect(() => {
@@ -1 +1 @@
1
- {"version":3,"file":"ComposerInput.js","names":["composeEventHandlers","useComposedRefs","Slot","ClipboardEvent","KeyboardEvent","ReactElement","ReactNode","forwardRef","useCallback","useEffect","useRef","isValidElement","TextareaAutosize","TextareaAutosizeProps","TextareaHeightChangeMeta","useEscapeKeydown","useOnScrollToBottom","useMediaQuery","renderSlot","isCompositionKey","useAui","flushTapSync","useComposerInputPluginRegistryOptional","useComposerCompactContextOptional","useComposerCancelWithFocus","useComposerInputDisabled","useComposerInputValue","useTriggerPopoverAriaProps","TOUCH_PRIMARY_QUERY","ComposerPrimitiveInput","Element","HTMLTextAreaElement","BaseProps","asChild","render","cancelOnEscape","unstable_focusOnRunStart","unstable_focusOnScrollToBottom","unstable_focusOnThreadSwitched","unstable_insertNewlineOnTouchEnter","addAttachmentOnPaste","SubmitModeProps","submitMode","submitOnEnter","Props","autoFocus","disabled","disabledProp","onChange","onKeyDown","onPaste","onSelect","onHeightChange","rest","forwardedRef","aui","pluginRegistry","compactContext","declaredSubmitMode","isTouchPrimary","effectiveSubmitMode","value","isDisabled","textareaRef","cancel","composer","ref","compositionRef","e","current","contains","target","Node","plugin","getPlugins","handleKeyDown","getState","canCancel","preventDefault","handleKeyPress","nativeEvent","key","threadState","thread","hasQueue","capabilities","queue","shiftKey","ctrlKey","metaKey","canSend","send","steer","isRunning","voice","undefined","shouldSubmit","closest","requestSubmit","handlePaste","threadCapabilities","files","Array","from","clipboardData","attachments","length","Promise","all","map","file","addAttachment","handleHeightChange","height","meta","el","cs","getComputedStyle","contentHeight","boxSizing","parseFloat","paddingTop","paddingBottom","borderTopWidth","borderBottomWidth","Math","round","rowHeight","setMultiline","autoFocusEnabled","focus","textarea","preventScroll","setSelectionRange","type","on","ariaComboboxProps","inputProps","name","const","React","ForwardedRef","ChangeEvent","isEditing","nativeIsComposing","isComposing","setText","pos","selectionStart","setCursorPosition","onCompositionStart","CompositionEventHandler","onCompositionEnd","CompositionEvent","SyntheticEvent","children","Component","Root","displayName"],"sources":["../../../src/primitives/composer/ComposerInput.tsx"],"sourcesContent":["\"use client\";\n\nimport { composeEventHandlers } from \"radix-ui/internal\";\nimport { useComposedRefs } from \"radix-ui/internal\";\nimport { Slot } from \"radix-ui\";\nimport {\n type ClipboardEvent,\n type KeyboardEvent,\n type ReactElement,\n type ReactNode,\n forwardRef,\n useCallback,\n useEffect,\n useRef,\n isValidElement,\n} from \"react\";\nimport TextareaAutosize, {\n type TextareaAutosizeProps,\n type TextareaHeightChangeMeta,\n} from \"react-textarea-autosize\";\nimport { useEscapeKeydown } from \"radix-ui/internal\";\nimport { useOnScrollToBottom } from \"../../utils/hooks/useOnScrollToBottom\";\nimport { useMediaQuery } from \"../../utils/hooks/useMediaQuery\";\nimport { renderSlot } from \"../../utils/Primitive\";\nimport { isCompositionKey } from \"../../utils/isCompositionKey\";\nimport { useAui } from \"@assistant-ui/store\";\nimport { flushTapSync } from \"@assistant-ui/tap\";\nimport { useComposerInputPluginRegistryOptional } from \"./ComposerInputPluginContext\";\nimport { useComposerCompactContextOptional } from \"./ComposerCompactContext\";\nimport { useComposerCancelWithFocus } from \"./useComposerCancelWithFocus\";\nimport {\n useComposerInputDisabled,\n useComposerInputValue,\n useTriggerPopoverAriaProps,\n} from \"./useComposerInputState\";\n\nconst TOUCH_PRIMARY_QUERY = \"(pointer: coarse) and (not (any-pointer: fine))\";\n\nexport namespace ComposerPrimitiveInput {\n export type Element = HTMLTextAreaElement;\n\n type BaseProps = {\n /**\n * Whether to render as a child component using Slot.\n * When true, the component will merge its props with its child.\n */\n asChild?: boolean | undefined;\n /**\n * A React element to use as the input container, with props merged in.\n */\n render?: ReactElement | undefined;\n /**\n * Whether to cancel message composition when Escape is pressed.\n * After cancelling an edit, focus returns to the main thread composer when\n * available, unless a handler has moved focus elsewhere.\n * @default true\n */\n cancelOnEscape?: boolean | undefined;\n /**\n * Whether to automatically focus the input when a new run starts.\n *\n * @deprecated Experimental since 2024-10-12. Not scheduled for removal; the API may change in any release.\n *\n * @default true\n */\n unstable_focusOnRunStart?: boolean | undefined;\n /**\n * Whether to automatically focus the input when scrolling to bottom.\n *\n * @deprecated Experimental since 2024-10-12. Not scheduled for removal; the API may change in any release.\n *\n * @default true\n */\n unstable_focusOnScrollToBottom?: boolean | undefined;\n /**\n * Whether to automatically focus the input when switching threads.\n *\n * @deprecated Experimental since 2024-10-12. Not scheduled for removal; the API may change in any release.\n *\n * @default true\n */\n unstable_focusOnThreadSwitched?: boolean | undefined;\n /**\n * Whether plain Enter on a touch-primary device should insert a newline\n * instead of submitting, detected via\n * `(pointer: coarse) and (not (any-pointer: fine))`. Only takes effect\n * when `submitMode` resolves to `\"enter\"`.\n *\n * @deprecated Experimental since 2026-05-24. Not scheduled for removal; the API may change in any release.\n *\n * @default false\n */\n unstable_insertNewlineOnTouchEnter?: boolean | undefined;\n /**\n * Whether to automatically add pasted files as attachments.\n * @default true\n */\n addAttachmentOnPaste?: boolean | undefined;\n };\n\n type SubmitModeProps =\n | {\n /**\n * Controls how the Enter key submits messages.\n * - \"enter\": Plain Enter submits (Shift+Enter for newline)\n * - \"ctrlEnter\": Ctrl/Cmd+Enter submits (plain Enter for newline)\n * - \"none\": Keyboard submission disabled\n * @default \"enter\"\n */\n submitMode?: \"enter\" | \"ctrlEnter\" | \"none\" | undefined;\n /**\n * @deprecated Use `submitMode` instead\n * @ignore\n */\n submitOnEnter?: never;\n }\n | {\n submitMode?: never;\n /**\n * Whether to submit the message when Enter is pressed (without Shift).\n * @default true\n * @deprecated Use `submitMode` instead. Will be removed in a future version.\n */\n submitOnEnter?: boolean | undefined;\n };\n\n export type Props = TextareaAutosizeProps & BaseProps & SubmitModeProps;\n}\n\n/**\n * A text input component for composing messages.\n *\n * This component provides a rich text input experience with automatic resizing,\n * keyboard shortcuts, file paste support, and intelligent focus management.\n * It integrates with the composer context to manage message state and submission.\n *\n * When rendered inside `TriggerPopoverRoot` and a popover is open, the\n * underlying `<textarea>` automatically receives `aria-controls`,\n * `aria-expanded`, `aria-haspopup`, and `aria-activedescendant` for the\n * combobox relationship. These computed attributes override user-provided\n * values for those four ARIA props while the popover is open.\n *\n * @example\n * ```tsx\n * // Ctrl/Cmd+Enter to submit (plain Enter inserts newline)\n * <ComposerPrimitive.Input\n * placeholder=\"Type your message...\"\n * submitMode=\"ctrlEnter\"\n * />\n *\n * // Insert a newline on Enter on touch-primary devices.\n * <ComposerPrimitive.Input\n * placeholder=\"Type your message...\"\n * unstable_insertNewlineOnTouchEnter\n * />\n *\n * // Old API (deprecated, still supported)\n * <ComposerPrimitive.Input\n * placeholder=\"Type your message...\"\n * submitOnEnter={true}\n * />\n * ```\n */\nexport const ComposerPrimitiveInput = forwardRef<\n ComposerPrimitiveInput.Element,\n ComposerPrimitiveInput.Props\n>(\n (\n {\n autoFocus = false,\n asChild,\n render,\n disabled: disabledProp,\n onChange,\n onKeyDown,\n onPaste,\n onSelect,\n submitOnEnter,\n submitMode,\n cancelOnEscape = true,\n unstable_focusOnRunStart = true,\n unstable_focusOnScrollToBottom = true,\n unstable_focusOnThreadSwitched = true,\n unstable_insertNewlineOnTouchEnter = false,\n addAttachmentOnPaste = true,\n onHeightChange,\n ...rest\n },\n forwardedRef,\n ) => {\n const aui = useAui();\n const pluginRegistry = useComposerInputPluginRegistryOptional();\n const compactContext = useComposerCompactContextOptional();\n\n const declaredSubmitMode =\n submitMode ?? (submitOnEnter === false ? \"none\" : \"enter\");\n const isTouchPrimary = useMediaQuery(\n unstable_insertNewlineOnTouchEnter ? TOUCH_PRIMARY_QUERY : null,\n );\n const effectiveSubmitMode =\n unstable_insertNewlineOnTouchEnter &&\n isTouchPrimary &&\n declaredSubmitMode === \"enter\"\n ? \"none\"\n : declaredSubmitMode;\n\n const value = useComposerInputValue();\n const isDisabled = useComposerInputDisabled(disabledProp);\n const textareaRef = useRef<HTMLTextAreaElement>(null);\n const cancel = useComposerCancelWithFocus(\n useCallback(() => aui.composer.cancel(), [aui]),\n );\n const ref = useComposedRefs(forwardedRef, textareaRef);\n // suppress text/cursor broadcasts during IME composition\n const compositionRef = useRef(false);\n\n useEscapeKeydown((e) => {\n // Only handle ESC if it originated from within this input\n if (!textareaRef.current?.contains(e.target as Node)) return;\n\n // ignore IME composition events\n if (isCompositionKey(e)) return;\n\n // Let registered plugins (mention, slash command, etc.) handle Escape first\n if (pluginRegistry) {\n for (const plugin of pluginRegistry.getPlugins()) {\n if (plugin.handleKeyDown(e)) return;\n }\n }\n\n if (!cancelOnEscape) return;\n\n const composer = aui.composer;\n if (composer.getState().canCancel) {\n cancel(textareaRef.current);\n e.preventDefault();\n }\n });\n\n const handleKeyPress = (e: KeyboardEvent) => {\n if (isDisabled) return;\n\n // ignore IME composition events\n if (isCompositionKey(e.nativeEvent)) return;\n\n // Let registered plugins (mention, slash command, etc.) handle keyboard events first\n if (pluginRegistry) {\n for (const plugin of pluginRegistry.getPlugins()) {\n if (plugin.handleKeyDown(e)) return;\n }\n }\n\n if (e.key === \"Enter\") {\n const threadState = aui.thread.getState();\n const hasQueue = threadState.capabilities.queue;\n\n // Steer hotkey: Cmd/Ctrl+Shift+Enter (respects submitMode=\"none\" and canSend)\n if (\n e.shiftKey &&\n (e.ctrlKey || e.metaKey) &&\n hasQueue &&\n declaredSubmitMode !== \"none\" &&\n aui.composer.getState().canSend\n ) {\n e.preventDefault();\n aui.composer.send({ steer: true });\n return;\n }\n\n // Regular newline: Shift+Enter\n if (e.shiftKey) return;\n\n // Block submission when running unless queue is supported; a voice\n // session leaves the decision to canSend\n if (\n threadState.isRunning &&\n !hasQueue &&\n threadState.voice === undefined\n )\n return;\n\n let shouldSubmit = false;\n if (effectiveSubmitMode === \"ctrlEnter\") {\n shouldSubmit = e.ctrlKey || e.metaKey;\n } else if (effectiveSubmitMode === \"enter\") {\n shouldSubmit = true;\n }\n\n if (shouldSubmit) {\n e.preventDefault();\n textareaRef.current?.closest(\"form\")?.requestSubmit();\n }\n }\n };\n\n const handlePaste = async (e: ClipboardEvent<HTMLTextAreaElement>) => {\n if (!addAttachmentOnPaste) return;\n const threadCapabilities = aui.thread.getState().capabilities;\n const files = Array.from(e.clipboardData?.files || []);\n\n if (threadCapabilities.attachments && files.length > 0) {\n e.preventDefault();\n await Promise.all(\n files.map(async (file) => {\n try {\n await aui.composer.addAttachment(file);\n } catch {\n // The composer runtime emits composer.attachmentAddError before rejecting.\n }\n }),\n );\n }\n };\n\n const handleHeightChange = (\n height: number,\n meta: TextareaHeightChangeMeta,\n ) => {\n onHeightChange?.(height, meta);\n if (!compactContext) return;\n const el = textareaRef.current;\n if (!el || el.value === \"\") return;\n const cs = getComputedStyle(el);\n const contentHeight =\n cs.boxSizing === \"border-box\"\n ? height -\n parseFloat(cs.paddingTop) -\n parseFloat(cs.paddingBottom) -\n parseFloat(cs.borderTopWidth) -\n parseFloat(cs.borderBottomWidth)\n : height;\n if (Math.round(contentHeight / meta.rowHeight) > 1) {\n compactContext.setMultiline(true);\n }\n };\n\n const autoFocusEnabled = autoFocus && !isDisabled;\n const focus = useCallback(() => {\n const textarea = textareaRef.current;\n if (!textarea || !autoFocusEnabled) return;\n\n textarea.focus({ preventScroll: true });\n textarea.setSelectionRange(textarea.value.length, textarea.value.length);\n }, [autoFocusEnabled]);\n\n useEffect(() => focus(), [focus]);\n\n useOnScrollToBottom(() => {\n if (\n aui.composer.getState().type === \"thread\" &&\n unstable_focusOnScrollToBottom\n ) {\n focus();\n }\n });\n\n useEffect(() => {\n if (\n aui.composer.getState().type !== \"thread\" ||\n !unstable_focusOnRunStart\n )\n return undefined;\n\n return aui.on(\"thread.runStart\", focus);\n }, [unstable_focusOnRunStart, focus, aui]);\n\n useEffect(() => {\n if (\n aui.composer.getState().type !== \"thread\" ||\n !unstable_focusOnThreadSwitched\n )\n return undefined;\n\n return aui.on(\"threads.selectionChanged\", focus);\n }, [unstable_focusOnThreadSwitched, focus, aui]);\n\n useEffect(() => {\n if (value === \"\") compactContext?.setMultiline(false);\n }, [value, compactContext]);\n\n const ariaComboboxProps = useTriggerPopoverAriaProps();\n\n const inputProps = {\n name: \"input\" as const,\n value,\n ...rest,\n ...ariaComboboxProps,\n ref: ref as React.ForwardedRef<HTMLTextAreaElement>,\n disabled: isDisabled,\n onChange: composeEventHandlers(\n onChange,\n (e: React.ChangeEvent<HTMLTextAreaElement>) => {\n if (!aui.composer.getState().isEditing) return;\n const nativeIsComposing =\n (e.nativeEvent as { isComposing?: boolean }).isComposing === true;\n // recover stuck compositionRef when the browser drops compositionend\n if (compositionRef.current && !nativeIsComposing) {\n compositionRef.current = false;\n }\n const isComposing = nativeIsComposing || compositionRef.current;\n // keep controlled value in sync mid-IME so react does not reset the textarea to a stale value\n flushTapSync(() => {\n aui.composer.setText(e.target.value);\n });\n if (isComposing) return;\n const pos = e.target.selectionStart ?? e.target.value.length;\n if (pluginRegistry) {\n for (const plugin of pluginRegistry.getPlugins()) {\n plugin.setCursorPosition(pos);\n }\n }\n },\n ),\n onKeyDown: composeEventHandlers(onKeyDown, handleKeyPress),\n onCompositionStart: composeEventHandlers(\n (rest as { onCompositionStart?: React.CompositionEventHandler })\n .onCompositionStart,\n () => {\n compositionRef.current = true;\n },\n ),\n onCompositionEnd: composeEventHandlers(\n (rest as { onCompositionEnd?: React.CompositionEventHandler })\n .onCompositionEnd,\n (e: React.CompositionEvent<HTMLTextAreaElement>) => {\n compositionRef.current = false;\n if (!aui.composer.getState().isEditing) return;\n const target = e.target as HTMLTextAreaElement;\n flushTapSync(() => {\n aui.composer.setText(target.value);\n });\n const pos = target.selectionStart ?? target.value.length;\n if (pluginRegistry) {\n for (const plugin of pluginRegistry.getPlugins()) {\n plugin.setCursorPosition(pos);\n }\n }\n },\n ),\n onSelect: composeEventHandlers(\n onSelect,\n (e: React.SyntheticEvent<HTMLTextAreaElement>) => {\n if (compositionRef.current) return;\n const target = e.target as HTMLTextAreaElement;\n const pos = target.selectionStart ?? target.value.length;\n if (pluginRegistry) {\n for (const plugin of pluginRegistry.getPlugins()) {\n plugin.setCursorPosition(pos);\n }\n }\n },\n ),\n onPaste: composeEventHandlers(onPaste, handlePaste),\n ...(compactContext\n ? { onHeightChange: handleHeightChange }\n : onHeightChange !== undefined\n ? { onHeightChange }\n : {}),\n };\n\n if (render && isValidElement(render)) {\n return renderSlot(\n render,\n (rest as { children?: ReactNode }).children,\n inputProps,\n );\n }\n\n const Component = asChild ? Slot.Root : TextareaAutosize;\n return <Component {...inputProps} />;\n },\n);\n\nComposerPrimitiveInput.displayName = \"ComposerPrimitive.Input\";\n"],"mappings":"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"}
1
+ {"version":3,"file":"ComposerInput.js","names":["composeEventHandlers","useComposedRefs","Slot","ClipboardEvent","KeyboardEvent","ReactElement","ReactNode","forwardRef","useCallback","useEffect","useRef","isValidElement","TextareaAutosize","TextareaAutosizeProps","TextareaHeightChangeMeta","useEscapeKeydown","useOnScrollToBottom","useMediaQuery","renderSlot","isCompositionKey","useAui","flushTapSync","useComposerInputPluginRegistryOptional","useComposerCompactContextOptional","useComposerCancelWithFocus","useThreadRootElementRef","useComposerInputDisabled","useComposerInputValue","useTriggerPopoverAriaProps","TOUCH_PRIMARY_QUERY","ComposerPrimitiveInput","Element","HTMLTextAreaElement","BaseProps","asChild","render","cancelOnEscape","unstable_focusOnRunStart","unstable_focusOnScrollToBottom","unstable_focusOnThreadSwitched","unstable_insertNewlineOnTouchEnter","addAttachmentOnPaste","SubmitModeProps","submitMode","submitOnEnter","Props","autoFocus","disabled","disabledProp","onChange","onKeyDown","onPaste","onSelect","onHeightChange","rest","forwardedRef","aui","threadRootRef","pluginRegistry","compactContext","declaredSubmitMode","isTouchPrimary","effectiveSubmitMode","value","isDisabled","textareaRef","cancel","composer","ref","compositionRef","e","current","contains","target","Node","plugin","getPlugins","handleKeyDown","getState","canCancel","preventDefault","handleKeyPress","nativeEvent","key","threadState","thread","hasQueue","capabilities","queue","shiftKey","ctrlKey","metaKey","canSend","send","steer","isRunning","voice","undefined","shouldSubmit","closest","requestSubmit","handlePaste","threadCapabilities","files","Array","from","clipboardData","attachments","length","Promise","all","map","file","addAttachment","handleHeightChange","height","meta","el","cs","getComputedStyle","contentHeight","boxSizing","parseFloat","paddingTop","paddingBottom","borderTopWidth","borderBottomWidth","Math","round","rowHeight","setMultiline","autoFocusEnabled","focus","textarea","preventScroll","setSelectionRange","focusOnRunStart","doc","ownerDocument","active","activeElement","root","composerRoot","body","type","on","ariaComboboxProps","inputProps","name","const","React","ForwardedRef","ChangeEvent","isEditing","nativeIsComposing","isComposing","setText","pos","selectionStart","setCursorPosition","onCompositionStart","CompositionEventHandler","onCompositionEnd","CompositionEvent","SyntheticEvent","children","Component","Root","displayName"],"sources":["../../../src/primitives/composer/ComposerInput.tsx"],"sourcesContent":["\"use client\";\n\nimport { composeEventHandlers } from \"radix-ui/internal\";\nimport { useComposedRefs } from \"radix-ui/internal\";\nimport { Slot } from \"radix-ui\";\nimport {\n type ClipboardEvent,\n type KeyboardEvent,\n type ReactElement,\n type ReactNode,\n forwardRef,\n useCallback,\n useEffect,\n useRef,\n isValidElement,\n} from \"react\";\nimport TextareaAutosize, {\n type TextareaAutosizeProps,\n type TextareaHeightChangeMeta,\n} from \"react-textarea-autosize\";\nimport { useEscapeKeydown } from \"radix-ui/internal\";\nimport { useOnScrollToBottom } from \"../../utils/hooks/useOnScrollToBottom\";\nimport { useMediaQuery } from \"../../utils/hooks/useMediaQuery\";\nimport { renderSlot } from \"../../utils/Primitive\";\nimport { isCompositionKey } from \"../../utils/isCompositionKey\";\nimport { useAui } from \"@assistant-ui/store\";\nimport { flushTapSync } from \"@assistant-ui/tap\";\nimport { useComposerInputPluginRegistryOptional } from \"./ComposerInputPluginContext\";\nimport { useComposerCompactContextOptional } from \"./ComposerCompactContext\";\nimport { useComposerCancelWithFocus } from \"./useComposerCancelWithFocus\";\nimport { useThreadRootElementRef } from \"../thread/ThreadRootElementContext\";\nimport {\n useComposerInputDisabled,\n useComposerInputValue,\n useTriggerPopoverAriaProps,\n} from \"./useComposerInputState\";\n\nconst TOUCH_PRIMARY_QUERY = \"(pointer: coarse) and (not (any-pointer: fine))\";\n\nexport namespace ComposerPrimitiveInput {\n export type Element = HTMLTextAreaElement;\n\n type BaseProps = {\n /**\n * Whether to render as a child component using Slot.\n * When true, the component will merge its props with its child.\n */\n asChild?: boolean | undefined;\n /**\n * A React element to use as the input container, with props merged in.\n */\n render?: ReactElement | undefined;\n /**\n * Whether to cancel message composition when Escape is pressed.\n * After cancelling an edit, focus returns to the main thread composer when\n * available, unless a handler has moved focus elsewhere.\n * @default true\n */\n cancelOnEscape?: boolean | undefined;\n /**\n * Whether to focus the input when a new run starts if nothing is focused\n * or focus is already inside the same thread or composer. Keeps the current\n * selection when the input is already focused. Applies when `autoFocus` is\n * enabled.\n *\n * @deprecated Experimental since 2024-10-12. Not scheduled for removal; the API may change in any release.\n *\n * @default true\n */\n unstable_focusOnRunStart?: boolean | undefined;\n /**\n * Whether to automatically focus the input when scrolling to bottom.\n *\n * @deprecated Experimental since 2024-10-12. Not scheduled for removal; the API may change in any release.\n *\n * @default true\n */\n unstable_focusOnScrollToBottom?: boolean | undefined;\n /**\n * Whether to automatically focus the input when switching threads.\n *\n * @deprecated Experimental since 2024-10-12. Not scheduled for removal; the API may change in any release.\n *\n * @default true\n */\n unstable_focusOnThreadSwitched?: boolean | undefined;\n /**\n * Whether plain Enter on a touch-primary device should insert a newline\n * instead of submitting, detected via\n * `(pointer: coarse) and (not (any-pointer: fine))`. Only takes effect\n * when `submitMode` resolves to `\"enter\"`.\n *\n * @deprecated Experimental since 2026-05-24. Not scheduled for removal; the API may change in any release.\n *\n * @default false\n */\n unstable_insertNewlineOnTouchEnter?: boolean | undefined;\n /**\n * Whether to automatically add pasted files as attachments.\n * @default true\n */\n addAttachmentOnPaste?: boolean | undefined;\n };\n\n type SubmitModeProps =\n | {\n /**\n * Controls how the Enter key submits messages.\n * - \"enter\": Plain Enter submits (Shift+Enter for newline)\n * - \"ctrlEnter\": Ctrl/Cmd+Enter submits (plain Enter for newline)\n * - \"none\": Keyboard submission disabled\n * @default \"enter\"\n */\n submitMode?: \"enter\" | \"ctrlEnter\" | \"none\" | undefined;\n /**\n * @deprecated Use `submitMode` instead\n * @ignore\n */\n submitOnEnter?: never;\n }\n | {\n submitMode?: never;\n /**\n * Whether to submit the message when Enter is pressed (without Shift).\n * @default true\n * @deprecated Use `submitMode` instead. Will be removed in a future version.\n */\n submitOnEnter?: boolean | undefined;\n };\n\n export type Props = TextareaAutosizeProps & BaseProps & SubmitModeProps;\n}\n\n/**\n * A text input component for composing messages.\n *\n * This component provides a rich text input experience with automatic resizing,\n * keyboard shortcuts, file paste support, and intelligent focus management.\n * It integrates with the composer context to manage message state and submission.\n *\n * When rendered inside `TriggerPopoverRoot` and a popover is open, the\n * underlying `<textarea>` automatically receives `aria-controls`,\n * `aria-expanded`, `aria-haspopup`, and `aria-activedescendant` for the\n * combobox relationship. These computed attributes override user-provided\n * values for those four ARIA props while the popover is open.\n *\n * @example\n * ```tsx\n * // Ctrl/Cmd+Enter to submit (plain Enter inserts newline)\n * <ComposerPrimitive.Input\n * placeholder=\"Type your message...\"\n * submitMode=\"ctrlEnter\"\n * />\n *\n * // Insert a newline on Enter on touch-primary devices.\n * <ComposerPrimitive.Input\n * placeholder=\"Type your message...\"\n * unstable_insertNewlineOnTouchEnter\n * />\n *\n * // Old API (deprecated, still supported)\n * <ComposerPrimitive.Input\n * placeholder=\"Type your message...\"\n * submitOnEnter={true}\n * />\n * ```\n */\nexport const ComposerPrimitiveInput = forwardRef<\n ComposerPrimitiveInput.Element,\n ComposerPrimitiveInput.Props\n>(\n (\n {\n autoFocus = false,\n asChild,\n render,\n disabled: disabledProp,\n onChange,\n onKeyDown,\n onPaste,\n onSelect,\n submitOnEnter,\n submitMode,\n cancelOnEscape = true,\n unstable_focusOnRunStart = true,\n unstable_focusOnScrollToBottom = true,\n unstable_focusOnThreadSwitched = true,\n unstable_insertNewlineOnTouchEnter = false,\n addAttachmentOnPaste = true,\n onHeightChange,\n ...rest\n },\n forwardedRef,\n ) => {\n const aui = useAui();\n const threadRootRef = useThreadRootElementRef();\n const pluginRegistry = useComposerInputPluginRegistryOptional();\n const compactContext = useComposerCompactContextOptional();\n\n const declaredSubmitMode =\n submitMode ?? (submitOnEnter === false ? \"none\" : \"enter\");\n const isTouchPrimary = useMediaQuery(\n unstable_insertNewlineOnTouchEnter ? TOUCH_PRIMARY_QUERY : null,\n );\n const effectiveSubmitMode =\n unstable_insertNewlineOnTouchEnter &&\n isTouchPrimary &&\n declaredSubmitMode === \"enter\"\n ? \"none\"\n : declaredSubmitMode;\n\n const value = useComposerInputValue();\n const isDisabled = useComposerInputDisabled(disabledProp);\n const textareaRef = useRef<HTMLTextAreaElement>(null);\n const cancel = useComposerCancelWithFocus(\n useCallback(() => aui.composer.cancel(), [aui]),\n );\n const ref = useComposedRefs(forwardedRef, textareaRef);\n // suppress text/cursor broadcasts during IME composition\n const compositionRef = useRef(false);\n\n useEscapeKeydown((e) => {\n // Only handle ESC if it originated from within this input\n if (!textareaRef.current?.contains(e.target as Node)) return;\n\n // ignore IME composition events\n if (isCompositionKey(e)) return;\n\n // Let registered plugins (mention, slash command, etc.) handle Escape first\n if (pluginRegistry) {\n for (const plugin of pluginRegistry.getPlugins()) {\n if (plugin.handleKeyDown(e)) return;\n }\n }\n\n if (!cancelOnEscape) return;\n\n const composer = aui.composer;\n if (composer.getState().canCancel) {\n cancel(textareaRef.current);\n e.preventDefault();\n }\n });\n\n const handleKeyPress = (e: KeyboardEvent) => {\n if (isDisabled) return;\n\n // ignore IME composition events\n if (isCompositionKey(e.nativeEvent)) return;\n\n // Let registered plugins (mention, slash command, etc.) handle keyboard events first\n if (pluginRegistry) {\n for (const plugin of pluginRegistry.getPlugins()) {\n if (plugin.handleKeyDown(e)) return;\n }\n }\n\n if (e.key === \"Enter\") {\n const threadState = aui.thread.getState();\n const hasQueue = threadState.capabilities.queue;\n\n // Steer hotkey: Cmd/Ctrl+Shift+Enter (respects submitMode=\"none\" and canSend)\n if (\n e.shiftKey &&\n (e.ctrlKey || e.metaKey) &&\n hasQueue &&\n declaredSubmitMode !== \"none\" &&\n aui.composer.getState().canSend\n ) {\n e.preventDefault();\n aui.composer.send({ steer: true });\n return;\n }\n\n // Regular newline: Shift+Enter\n if (e.shiftKey) return;\n\n // Block submission when running unless queue is supported; a voice\n // session leaves the decision to canSend\n if (\n threadState.isRunning &&\n !hasQueue &&\n threadState.voice === undefined\n )\n return;\n\n let shouldSubmit = false;\n if (effectiveSubmitMode === \"ctrlEnter\") {\n shouldSubmit = e.ctrlKey || e.metaKey;\n } else if (effectiveSubmitMode === \"enter\") {\n shouldSubmit = true;\n }\n\n if (shouldSubmit) {\n e.preventDefault();\n textareaRef.current?.closest(\"form\")?.requestSubmit();\n }\n }\n };\n\n const handlePaste = async (e: ClipboardEvent<HTMLTextAreaElement>) => {\n if (!addAttachmentOnPaste) return;\n const threadCapabilities = aui.thread.getState().capabilities;\n const files = Array.from(e.clipboardData?.files || []);\n\n if (threadCapabilities.attachments && files.length > 0) {\n e.preventDefault();\n await Promise.all(\n files.map(async (file) => {\n try {\n await aui.composer.addAttachment(file);\n } catch {\n // The composer runtime emits composer.attachmentAddError before rejecting.\n }\n }),\n );\n }\n };\n\n const handleHeightChange = (\n height: number,\n meta: TextareaHeightChangeMeta,\n ) => {\n onHeightChange?.(height, meta);\n if (!compactContext) return;\n const el = textareaRef.current;\n if (!el || el.value === \"\") return;\n const cs = getComputedStyle(el);\n const contentHeight =\n cs.boxSizing === \"border-box\"\n ? height -\n parseFloat(cs.paddingTop) -\n parseFloat(cs.paddingBottom) -\n parseFloat(cs.borderTopWidth) -\n parseFloat(cs.borderBottomWidth)\n : height;\n if (Math.round(contentHeight / meta.rowHeight) > 1) {\n compactContext.setMultiline(true);\n }\n };\n\n const autoFocusEnabled = autoFocus && !isDisabled;\n const focus = useCallback(() => {\n const textarea = textareaRef.current;\n if (!textarea || !autoFocusEnabled) return;\n\n textarea.focus({ preventScroll: true });\n textarea.setSelectionRange(textarea.value.length, textarea.value.length);\n }, [autoFocusEnabled]);\n\n const focusOnRunStart = useCallback(() => {\n const textarea = textareaRef.current;\n if (!textarea) return;\n const doc = textarea.ownerDocument;\n const active = doc.activeElement;\n if (active === textarea) return;\n\n const root = threadRootRef?.current;\n const composerRoot = textarea.closest(\"[data-aui-composer-type]\");\n if (\n active &&\n active !== doc.body &&\n !composerRoot?.contains(active) &&\n (!root?.contains(active) ||\n active.closest(\"[data-aui-thread-root]\") !== root)\n )\n return;\n\n focus();\n }, [focus, threadRootRef]);\n\n useEffect(() => focus(), [focus]);\n\n useOnScrollToBottom(() => {\n if (\n aui.composer.getState().type === \"thread\" &&\n unstable_focusOnScrollToBottom\n ) {\n focus();\n }\n });\n\n useEffect(() => {\n if (\n aui.composer.getState().type !== \"thread\" ||\n !unstable_focusOnRunStart\n )\n return undefined;\n\n return aui.on(\"thread.runStart\", focusOnRunStart);\n }, [unstable_focusOnRunStart, focusOnRunStart, aui]);\n\n useEffect(() => {\n if (\n aui.composer.getState().type !== \"thread\" ||\n !unstable_focusOnThreadSwitched\n )\n return undefined;\n\n return aui.on(\"threads.selectionChanged\", focus);\n }, [unstable_focusOnThreadSwitched, focus, aui]);\n\n useEffect(() => {\n if (value === \"\") compactContext?.setMultiline(false);\n }, [value, compactContext]);\n\n const ariaComboboxProps = useTriggerPopoverAriaProps();\n\n const inputProps = {\n name: \"input\" as const,\n value,\n ...rest,\n ...ariaComboboxProps,\n ref: ref as React.ForwardedRef<HTMLTextAreaElement>,\n disabled: isDisabled,\n onChange: composeEventHandlers(\n onChange,\n (e: React.ChangeEvent<HTMLTextAreaElement>) => {\n if (!aui.composer.getState().isEditing) return;\n const nativeIsComposing =\n (e.nativeEvent as { isComposing?: boolean }).isComposing === true;\n // recover stuck compositionRef when the browser drops compositionend\n if (compositionRef.current && !nativeIsComposing) {\n compositionRef.current = false;\n }\n const isComposing = nativeIsComposing || compositionRef.current;\n // keep controlled value in sync mid-IME so react does not reset the textarea to a stale value\n flushTapSync(() => {\n aui.composer.setText(e.target.value);\n });\n if (isComposing) return;\n const pos = e.target.selectionStart ?? e.target.value.length;\n if (pluginRegistry) {\n for (const plugin of pluginRegistry.getPlugins()) {\n plugin.setCursorPosition(pos);\n }\n }\n },\n ),\n onKeyDown: composeEventHandlers(onKeyDown, handleKeyPress),\n onCompositionStart: composeEventHandlers(\n (rest as { onCompositionStart?: React.CompositionEventHandler })\n .onCompositionStart,\n () => {\n compositionRef.current = true;\n },\n ),\n onCompositionEnd: composeEventHandlers(\n (rest as { onCompositionEnd?: React.CompositionEventHandler })\n .onCompositionEnd,\n (e: React.CompositionEvent<HTMLTextAreaElement>) => {\n compositionRef.current = false;\n if (!aui.composer.getState().isEditing) return;\n const target = e.target as HTMLTextAreaElement;\n flushTapSync(() => {\n aui.composer.setText(target.value);\n });\n const pos = target.selectionStart ?? target.value.length;\n if (pluginRegistry) {\n for (const plugin of pluginRegistry.getPlugins()) {\n plugin.setCursorPosition(pos);\n }\n }\n },\n ),\n onSelect: composeEventHandlers(\n onSelect,\n (e: React.SyntheticEvent<HTMLTextAreaElement>) => {\n if (compositionRef.current) return;\n const target = e.target as HTMLTextAreaElement;\n const pos = target.selectionStart ?? target.value.length;\n if (pluginRegistry) {\n for (const plugin of pluginRegistry.getPlugins()) {\n plugin.setCursorPosition(pos);\n }\n }\n },\n ),\n onPaste: composeEventHandlers(onPaste, handlePaste),\n ...(compactContext\n ? { onHeightChange: handleHeightChange }\n : onHeightChange !== undefined\n ? { onHeightChange }\n : {}),\n };\n\n if (render && isValidElement(render)) {\n return renderSlot(\n render,\n (rest as { children?: ReactNode }).children,\n inputProps,\n );\n }\n\n const Component = asChild ? Slot.Root : TextareaAutosize;\n return <Component {...inputProps} />;\n },\n);\n\nComposerPrimitiveInput.displayName = \"ComposerPrimitive.Input\";\n"],"mappings":"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"}
@@ -1,8 +1,8 @@
1
1
  "use client";
2
2
  import { useTriggerBehaviorRegistration } from "./TriggerPopover.js";
3
+ import { defaultDirectiveFormatter } from "@assistant-ui/core";
3
4
  import { c } from "@assistant-ui/tap/react-shim/compiler-runtime";
4
5
  import { useEffect, useInsertionEffect, useRef } from "@assistant-ui/tap/react-shim";
5
- import { defaultDirectiveFormatter } from "@assistant-ui/core";
6
6
  //#region src/primitives/composer/trigger/TriggerPopoverAction.tsx
7
7
  /**
8
8
  * Configures a `<TriggerPopover>` to fire a handler when an item is selected,
@@ -1,8 +1,8 @@
1
1
  "use client";
2
2
  import { useTriggerBehaviorRegistration } from "./TriggerPopover.js";
3
+ import { defaultDirectiveFormatter } from "@assistant-ui/core";
3
4
  import { c } from "@assistant-ui/tap/react-shim/compiler-runtime";
4
5
  import { useEffect, useInsertionEffect, useRef } from "@assistant-ui/tap/react-shim";
5
- import { defaultDirectiveFormatter } from "@assistant-ui/core";
6
6
  //#region src/primitives/composer/trigger/TriggerPopoverDirective.tsx
7
7
  /**
8
8
  * Configures a `<TriggerPopover>` to insert a directive chip when an item is
@@ -1,9 +1,9 @@
1
1
  "use client";
2
2
  import { matchesTriggerItemQuery } from "../primitives/composer/trigger/matchesTriggerItemQuery.js";
3
3
  import { shallowEqualRecords, useModelContextSnapshot } from "./useModelContextSnapshot.js";
4
+ import { unstable_defaultDirectiveFormatter } from "@assistant-ui/core";
4
5
  import { useMemo } from "@assistant-ui/tap/react-shim";
5
6
  import { useAui } from "@assistant-ui/store";
6
- import { unstable_defaultDirectiveFormatter } from "@assistant-ui/core";
7
7
  //#region src/unstable/useMentionAdapter.ts
8
8
  const EMPTY_TOOL_MENTIONS = Object.freeze({});
9
9
  const toolMentionSource = {
@@ -25,31 +25,21 @@ type PrimitiveProps<E extends PrimitiveNode> = WithRenderPropProps<(typeof Radix
25
25
  declare function renderSlot(render: ReactElement, children: ReactNode, props: ComponentProps<typeof Slot.Root>): ReactElement;
26
26
  declare function withRenderProp<T extends ElementType>(Component: T): ForwardRefExoticComponent<WithRenderPropProps<T> & RefAttributes<ComponentRef<T>>>;
27
27
  declare const Primitive: {
28
- input: ForwardRefExoticComponent<Omit<import("react").ClassAttributes<HTMLInputElement> & import("react").InputHTMLAttributes<HTMLInputElement> & {
28
+ a: ForwardRefExoticComponent<Omit<import("react").ClassAttributes<HTMLAnchorElement> & import("react").AnchorHTMLAttributes<HTMLAnchorElement> & {
29
29
  asChild?: boolean;
30
30
  }, "ref"> & {
31
31
  render?: ReactElement | undefined;
32
- } & RefAttributes<HTMLInputElement>>;
33
- select: ForwardRefExoticComponent<Omit<import("react").ClassAttributes<HTMLSelectElement> & import("react").SelectHTMLAttributes<HTMLSelectElement> & {
32
+ } & RefAttributes<HTMLAnchorElement>>;
33
+ button: ForwardRefExoticComponent<Omit<import("react").ClassAttributes<HTMLButtonElement> & import("react").ButtonHTMLAttributes<HTMLButtonElement> & {
34
34
  asChild?: boolean;
35
35
  }, "ref"> & {
36
36
  render?: ReactElement | undefined;
37
- } & RefAttributes<HTMLSelectElement>>;
37
+ } & RefAttributes<HTMLButtonElement>>;
38
38
  div: ForwardRefExoticComponent<Omit<import("react").ClassAttributes<HTMLDivElement> & import("react").HTMLAttributes<HTMLDivElement> & {
39
39
  asChild?: boolean;
40
40
  }, "ref"> & {
41
41
  render?: ReactElement | undefined;
42
42
  } & RefAttributes<HTMLDivElement>>;
43
- a: ForwardRefExoticComponent<Omit<import("react").ClassAttributes<HTMLAnchorElement> & import("react").AnchorHTMLAttributes<HTMLAnchorElement> & {
44
- asChild?: boolean;
45
- }, "ref"> & {
46
- render?: ReactElement | undefined;
47
- } & RefAttributes<HTMLAnchorElement>>;
48
- button: ForwardRefExoticComponent<Omit<import("react").ClassAttributes<HTMLButtonElement> & import("react").ButtonHTMLAttributes<HTMLButtonElement> & {
49
- asChild?: boolean;
50
- }, "ref"> & {
51
- render?: ReactElement | undefined;
52
- } & RefAttributes<HTMLButtonElement>>;
53
43
  form: ForwardRefExoticComponent<Omit<import("react").ClassAttributes<HTMLFormElement> & import("react").FormHTMLAttributes<HTMLFormElement> & {
54
44
  asChild?: boolean;
55
45
  }, "ref"> & {
@@ -70,6 +60,11 @@ declare const Primitive: {
70
60
  }, "ref"> & {
71
61
  render?: ReactElement | undefined;
72
62
  } & RefAttributes<HTMLImageElement>>;
63
+ input: ForwardRefExoticComponent<Omit<import("react").ClassAttributes<HTMLInputElement> & import("react").InputHTMLAttributes<HTMLInputElement> & {
64
+ asChild?: boolean;
65
+ }, "ref"> & {
66
+ render?: ReactElement | undefined;
67
+ } & RefAttributes<HTMLInputElement>>;
73
68
  label: ForwardRefExoticComponent<Omit<import("react").ClassAttributes<HTMLLabelElement> & import("react").LabelHTMLAttributes<HTMLLabelElement> & {
74
69
  asChild?: boolean;
75
70
  }, "ref"> & {
@@ -95,6 +90,11 @@ declare const Primitive: {
95
90
  }, "ref"> & {
96
91
  render?: ReactElement | undefined;
97
92
  } & RefAttributes<HTMLParagraphElement>>;
93
+ select: ForwardRefExoticComponent<Omit<import("react").ClassAttributes<HTMLSelectElement> & import("react").SelectHTMLAttributes<HTMLSelectElement> & {
94
+ asChild?: boolean;
95
+ }, "ref"> & {
96
+ render?: ReactElement | undefined;
97
+ } & RefAttributes<HTMLSelectElement>>;
98
98
  span: ForwardRefExoticComponent<Omit<import("react").ClassAttributes<HTMLSpanElement> & import("react").HTMLAttributes<HTMLSpanElement> & {
99
99
  asChild?: boolean;
100
100
  }, "ref"> & {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@assistant-ui/react",
3
- "version": "0.15.28",
3
+ "version": "0.15.29",
4
4
  "description": "Open-source TypeScript/React library for building production-grade AI chat experiences",
5
5
  "keywords": [
6
6
  "ai",
@@ -55,11 +55,11 @@
55
55
  ],
56
56
  "sideEffects": false,
57
57
  "dependencies": {
58
- "@assistant-ui/core": "^0.3.27",
59
- "@assistant-ui/store": "^0.3.19",
58
+ "@assistant-ui/core": "^0.3.29",
59
+ "@assistant-ui/store": "^0.3.21",
60
60
  "@assistant-ui/tap": "^0.9.23",
61
61
  "assistant-cloud": "^0.2.7",
62
- "assistant-stream": "^0.3.50",
62
+ "assistant-stream": "^0.3.52",
63
63
  "radix-ui": "^1.7.0",
64
64
  "react-textarea-autosize": "^8.5.9",
65
65
  "safe-content-frame": "^0.0.34",