@assistant-ui/react 0.14.15 → 0.14.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client/ExternalThread.d.ts +4 -3
- package/dist/client/ExternalThread.d.ts.map +1 -1
- package/dist/client/ExternalThread.js +46 -21
- package/dist/client/ExternalThread.js.map +1 -1
- package/dist/client/InMemoryThreadList.d.ts +1 -1
- package/dist/client/InMemoryThreadList.d.ts.map +1 -1
- package/dist/client/InMemoryThreadList.js +7 -5
- package/dist/client/InMemoryThreadList.js.map +1 -1
- package/dist/client/SingleThreadList.d.ts +1 -6
- package/dist/client/SingleThreadList.d.ts.map +1 -1
- package/dist/client/SingleThreadList.js +6 -4
- package/dist/client/SingleThreadList.js.map +1 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.js +3 -1
- package/dist/mcp-apps/McpAppRenderer.d.ts +2 -10
- package/dist/mcp-apps/McpAppRenderer.d.ts.map +1 -1
- package/dist/mcp-apps/McpAppRenderer.js +3 -2
- package/dist/mcp-apps/McpAppRenderer.js.map +1 -1
- package/dist/mcp-apps/McpAppsRemoteHost.d.ts +1 -8
- package/dist/mcp-apps/McpAppsRemoteHost.d.ts.map +1 -1
- package/dist/mcp-apps/McpAppsRemoteHost.js +3 -2
- package/dist/mcp-apps/McpAppsRemoteHost.js.map +1 -1
- package/dist/primitives/composer/ComposerInput.js +3 -3
- package/dist/primitives/composer/ComposerInput.js.map +1 -1
- package/dist/primitives/composer/trigger/TriggerPopoverResource.d.ts +2 -10
- package/dist/primitives/composer/trigger/TriggerPopoverResource.d.ts.map +1 -1
- package/dist/primitives/composer/trigger/TriggerPopoverResource.js +3 -2
- package/dist/primitives/composer/trigger/TriggerPopoverResource.js.map +1 -1
- package/dist/primitives/composer/trigger/triggerDetectionResource.d.ts +2 -6
- package/dist/primitives/composer/trigger/triggerDetectionResource.d.ts.map +1 -1
- package/dist/primitives/composer/trigger/triggerDetectionResource.js +3 -2
- package/dist/primitives/composer/trigger/triggerDetectionResource.js.map +1 -1
- package/dist/primitives/composer/trigger/triggerKeyboardResource.d.ts +2 -17
- package/dist/primitives/composer/trigger/triggerKeyboardResource.d.ts.map +1 -1
- package/dist/primitives/composer/trigger/triggerKeyboardResource.js +3 -2
- package/dist/primitives/composer/trigger/triggerKeyboardResource.js.map +1 -1
- package/dist/primitives/composer/trigger/triggerNavigationResource.d.ts +2 -10
- package/dist/primitives/composer/trigger/triggerNavigationResource.d.ts.map +1 -1
- package/dist/primitives/composer/trigger/triggerNavigationResource.js +3 -2
- package/dist/primitives/composer/trigger/triggerNavigationResource.js.map +1 -1
- package/dist/primitives/composer/trigger/triggerSelectionResource.d.ts +2 -10
- package/dist/primitives/composer/trigger/triggerSelectionResource.d.ts.map +1 -1
- package/dist/primitives/composer/trigger/triggerSelectionResource.js +3 -2
- package/dist/primitives/composer/trigger/triggerSelectionResource.js.map +1 -1
- package/dist/primitives/messagePart/MessagePartText.d.ts +5 -2
- package/dist/primitives/messagePart/MessagePartText.d.ts.map +1 -1
- package/dist/primitives/messagePart/MessagePartText.js.map +1 -1
- package/dist/primitives/reasoning/useScrollLock.js +11 -2
- package/dist/primitives/reasoning/useScrollLock.js.map +1 -1
- package/dist/primitives/thread/useThreadViewportAutoScroll.d.ts.map +1 -1
- package/dist/primitives/thread/useThreadViewportAutoScroll.js +5 -0
- package/dist/primitives/thread/useThreadViewportAutoScroll.js.map +1 -1
- package/dist/unstable/useComposerInputHistory.d.ts +30 -0
- package/dist/unstable/useComposerInputHistory.d.ts.map +1 -0
- package/dist/unstable/useComposerInputHistory.js +117 -0
- package/dist/unstable/useComposerInputHistory.js.map +1 -0
- package/dist/utils/smooth/useSmooth.d.ts +40 -2
- package/dist/utils/smooth/useSmooth.d.ts.map +1 -1
- package/dist/utils/smooth/useSmooth.js +48 -9
- package/dist/utils/smooth/useSmooth.js.map +1 -1
- package/package.json +31 -24
- package/src/client/ExternalThread.ts +70 -27
- package/src/client/InMemoryThreadList.ts +11 -7
- package/src/client/SingleThreadList.ts +29 -27
- package/src/index.ts +8 -0
- package/src/mcp-apps/McpAppRenderer.tsx +5 -3
- package/src/mcp-apps/McpAppsRemoteHost.ts +5 -3
- package/src/primitives/composer/ComposerInput.test.tsx +1 -1
- package/src/primitives/composer/ComposerInput.tsx +3 -3
- package/src/primitives/composer/trigger/TriggerPopoverResource.ts +5 -3
- package/src/primitives/composer/trigger/triggerDetectionResource.ts +21 -21
- package/src/primitives/composer/trigger/triggerKeyboardResource.test.ts +5 -4
- package/src/primitives/composer/trigger/triggerKeyboardResource.ts +99 -101
- package/src/primitives/composer/trigger/triggerNavigationResource.ts +92 -98
- package/src/primitives/composer/trigger/triggerSelectionResource.ts +76 -76
- package/src/primitives/messagePart/MessagePartText.tsx +3 -2
- package/src/primitives/reasoning/useScrollLock.ts +25 -2
- package/src/primitives/thread/useThreadViewportAutoScroll.ts +8 -0
- package/src/tests/external-thread-branches.test.tsx +160 -0
- package/src/tests/shouldContinue.test.ts +33 -0
- package/src/unstable/useComposerInputHistory.test.tsx +201 -0
- package/src/unstable/useComposerInputHistory.ts +160 -0
- package/src/utils/smooth/useSmooth.test.tsx +95 -0
- package/src/utils/smooth/useSmooth.ts +82 -10
package/dist/index.js
CHANGED
|
@@ -39,6 +39,7 @@ import { useTriggerPopoverScopeContext, useTriggerPopoverScopeContextOptional }
|
|
|
39
39
|
import { composer_exports } from "./primitives/composer.js";
|
|
40
40
|
import { queueItem_exports } from "./primitives/queueItem.js";
|
|
41
41
|
import { useMessagePartText } from "./primitives/messagePart/useMessagePartText.js";
|
|
42
|
+
import { useSmooth } from "./utils/smooth/useSmooth.js";
|
|
42
43
|
import { useMessagePartImage } from "./primitives/messagePart/useMessagePartImage.js";
|
|
43
44
|
import { messagePart_exports } from "./primitives/messagePart.js";
|
|
44
45
|
import { error_exports } from "./primitives/error.js";
|
|
@@ -63,6 +64,7 @@ import { InMemoryThreadList } from "./client/InMemoryThreadList.js";
|
|
|
63
64
|
import { internal_exports } from "./internal.js";
|
|
64
65
|
import { unstable_useMentionAdapter } from "./unstable/useMentionAdapter.js";
|
|
65
66
|
import { unstable_useSlashCommandAdapter } from "./unstable/useSlashCommandAdapter.js";
|
|
67
|
+
import { unstable_useComposerInputHistory } from "./unstable/useComposerInputHistory.js";
|
|
66
68
|
import { getMcpAppFromToolPart } from "./mcp-apps/utils.js";
|
|
67
69
|
import { McpAppRenderer } from "./mcp-apps/McpAppRenderer.js";
|
|
68
70
|
import { McpAppsRemoteHost } from "./mcp-apps/McpAppsRemoteHost.js";
|
|
@@ -71,4 +73,4 @@ import { DataRenderers, GenerativeUIRender, GenerativeUIRenderError, Interactabl
|
|
|
71
73
|
import { AssistantCloud } from "assistant-cloud";
|
|
72
74
|
import { AssistantFrameHost, AssistantFrameProvider, CompositeAttachmentAdapter, ExportedMessageRepository, FRAME_MESSAGE_CHANNEL, InMemoryThreadListAdapter, ModelContextRegistry, SimpleImageAttachmentAdapter, SimpleTextAttachmentAdapter, WebSpeechDictationAdapter, WebSpeechSynthesisAdapter, bindExternalStoreMessage, createMessageQueue, createVoiceSession, getExternalStoreMessages, mergeModelContexts, pickExternalStoreSharedOptions, tool, unstable_defaultDirectiveFormatter } from "@assistant-ui/core";
|
|
73
75
|
import { ChainOfThoughtClient, ModelContext as ModelContextClient, Suggestions } from "@assistant-ui/core/store";
|
|
74
|
-
export { actionBarMore_exports as ActionBarMorePrimitive, actionBar_exports as ActionBarPrimitive, AssistantCloud, AssistantFrameHost, AssistantFrameProvider, assistantModal_exports as AssistantModalPrimitive, AssistantRuntimeProvider, attachment_exports as AttachmentPrimitive, AuiIf, AuiProvider, branchPicker_exports as BranchPickerPrimitive, ChainOfThoughtByIndicesProvider, ChainOfThoughtClient, chainOfThought_exports as ChainOfThoughtPrimitive, CloudFileAttachmentAdapter, ComposerAttachmentByIndexProvider, composer_exports as ComposerPrimitive, CompositeAttachmentAdapter, DataRenderers, DevToolsHooks, DevToolsProviderApi, error_exports as ErrorPrimitive, ExportedMessageRepository, ExternalThread, FRAME_MESSAGE_CHANNEL, GenerativeUIRender, GenerativeUIRenderError, internal_exports as INTERNAL, InMemoryThreadList, InMemoryThreadListAdapter, Interactables, McpAppRenderer, McpAppsRemoteHost, MessageAttachmentByIndexProvider, MessageByIndexProvider, messagePart_exports as MessagePartPrimitive, message_exports as MessagePrimitive, MessageProvider, ModelContextClient, ModelContextRegistry, PartByIndexProvider, queueItem_exports as QueueItemPrimitive, ReadonlyThreadProvider, RuntimeAdapterProvider, selectionToolbar_exports as SelectionToolbarPrimitive, SimpleImageAttachmentAdapter, SimpleTextAttachmentAdapter, SingleThreadList, SuggestionByIndexProvider, suggestion_exports as SuggestionPrimitive, Suggestions, TextMessagePartProvider, ThreadListItemByIndexProvider, threadListItemMore_exports as ThreadListItemMorePrimitive, threadListItem_exports as ThreadListItemPrimitive, ThreadListItemRuntimeProvider, threadList_exports as ThreadListPrimitive, thread_exports as ThreadPrimitive, Tools, WebSpeechDictationAdapter, WebSpeechSynthesisAdapter, bindExternalStoreMessage, createMessageQueue, createVoiceSession, defineMcpToolkit, defineToolkit, externalTool, getExternalStoreMessages, getMcpAppFromToolPart, groupPartByType, hitl, hitlTool, humanTool, makeAssistantDataUI, makeAssistantTool, makeAssistantToolUI, makeAssistantVisible, mergeModelContexts, pickExternalStoreSharedOptions, providerTool, stubTool, tool, convertExternalMessages as unstable_convertExternalMessages, createMessageConverter as unstable_createMessageConverter, unstable_defaultDirectiveFormatter, unstable_useMentionAdapter, unstable_useSlashCommandAdapter, useTriggerPopoverRootContext as unstable_useTriggerPopoverRootContext, useTriggerPopoverRootContextOptional as unstable_useTriggerPopoverRootContextOptional, useTriggerPopoverScopeContext as unstable_useTriggerPopoverScopeContext, useTriggerPopoverScopeContextOptional as unstable_useTriggerPopoverScopeContextOptional, useTriggerPopoverTriggers as unstable_useTriggerPopoverTriggers, useTriggerPopoverTriggersOptional as unstable_useTriggerPopoverTriggersOptional, useAssistantContext, useAssistantDataUI, useAssistantFrameHost, useAssistantInstructions, useAssistantInteractable, useAssistantRuntime, useAssistantTool, useAssistantToolUI, useAssistantTransportRuntime, useAssistantTransportSendCommand, useAssistantTransportState, useAttachment, useAttachmentRuntime, useAui, useAuiEvent, useAuiState, useAuiToolOverrides, useCloudThreadListAdapter, useCloudThreadListRuntime, useComposer, useComposerRuntime, useEditComposer, useEditComposerAttachment, useEditComposerAttachmentRuntime, useExternalMessageConverter, useExternalStoreRuntime, useExternalStoreSharedOptions, useInlineRender, useInteractableState, useLocalRuntime, useMessage, useMessageAttachment, useMessageAttachmentRuntime, useMessagePart, useMessagePartData, useMessagePartFile, useMessagePartImage, useMessagePartReasoning, useMessagePartRuntime, useMessagePartSource, useMessagePartText, useMessageQuote, useMessageRuntime, useMessageTiming, useRemoteThreadListRuntime, useRuntimeAdapters, useScrollLock, useThread, useThreadComposer, useThreadComposerAttachment, useThreadComposerAttachmentRuntime, useThreadList, useThreadListItem, useThreadListItemRuntime, useThreadModelContext, useThreadRuntime, useThreadViewport, useThreadViewportAutoScroll, useThreadViewportStore, useToolArgsStatus, useVoiceControls, useVoiceState, useVoiceVolume };
|
|
76
|
+
export { actionBarMore_exports as ActionBarMorePrimitive, actionBar_exports as ActionBarPrimitive, AssistantCloud, AssistantFrameHost, AssistantFrameProvider, assistantModal_exports as AssistantModalPrimitive, AssistantRuntimeProvider, attachment_exports as AttachmentPrimitive, AuiIf, AuiProvider, branchPicker_exports as BranchPickerPrimitive, ChainOfThoughtByIndicesProvider, ChainOfThoughtClient, chainOfThought_exports as ChainOfThoughtPrimitive, CloudFileAttachmentAdapter, ComposerAttachmentByIndexProvider, composer_exports as ComposerPrimitive, CompositeAttachmentAdapter, DataRenderers, DevToolsHooks, DevToolsProviderApi, error_exports as ErrorPrimitive, ExportedMessageRepository, ExternalThread, FRAME_MESSAGE_CHANNEL, GenerativeUIRender, GenerativeUIRenderError, internal_exports as INTERNAL, InMemoryThreadList, InMemoryThreadListAdapter, Interactables, McpAppRenderer, McpAppsRemoteHost, MessageAttachmentByIndexProvider, MessageByIndexProvider, messagePart_exports as MessagePartPrimitive, message_exports as MessagePrimitive, MessageProvider, ModelContextClient, ModelContextRegistry, PartByIndexProvider, queueItem_exports as QueueItemPrimitive, ReadonlyThreadProvider, RuntimeAdapterProvider, selectionToolbar_exports as SelectionToolbarPrimitive, SimpleImageAttachmentAdapter, SimpleTextAttachmentAdapter, SingleThreadList, SuggestionByIndexProvider, suggestion_exports as SuggestionPrimitive, Suggestions, TextMessagePartProvider, ThreadListItemByIndexProvider, threadListItemMore_exports as ThreadListItemMorePrimitive, threadListItem_exports as ThreadListItemPrimitive, ThreadListItemRuntimeProvider, threadList_exports as ThreadListPrimitive, thread_exports as ThreadPrimitive, Tools, WebSpeechDictationAdapter, WebSpeechSynthesisAdapter, bindExternalStoreMessage, createMessageQueue, createVoiceSession, defineMcpToolkit, defineToolkit, externalTool, getExternalStoreMessages, getMcpAppFromToolPart, groupPartByType, hitl, hitlTool, humanTool, makeAssistantDataUI, makeAssistantTool, makeAssistantToolUI, makeAssistantVisible, mergeModelContexts, pickExternalStoreSharedOptions, providerTool, stubTool, tool, convertExternalMessages as unstable_convertExternalMessages, createMessageConverter as unstable_createMessageConverter, unstable_defaultDirectiveFormatter, unstable_useComposerInputHistory, unstable_useMentionAdapter, unstable_useSlashCommandAdapter, useTriggerPopoverRootContext as unstable_useTriggerPopoverRootContext, useTriggerPopoverRootContextOptional as unstable_useTriggerPopoverRootContextOptional, useTriggerPopoverScopeContext as unstable_useTriggerPopoverScopeContext, useTriggerPopoverScopeContextOptional as unstable_useTriggerPopoverScopeContextOptional, useTriggerPopoverTriggers as unstable_useTriggerPopoverTriggers, useTriggerPopoverTriggersOptional as unstable_useTriggerPopoverTriggersOptional, useAssistantContext, useAssistantDataUI, useAssistantFrameHost, useAssistantInstructions, useAssistantInteractable, useAssistantRuntime, useAssistantTool, useAssistantToolUI, useAssistantTransportRuntime, useAssistantTransportSendCommand, useAssistantTransportState, useAttachment, useAttachmentRuntime, useAui, useAuiEvent, useAuiState, useAuiToolOverrides, useCloudThreadListAdapter, useCloudThreadListRuntime, useComposer, useComposerRuntime, useEditComposer, useEditComposerAttachment, useEditComposerAttachmentRuntime, useExternalMessageConverter, useExternalStoreRuntime, useExternalStoreSharedOptions, useInlineRender, useInteractableState, useLocalRuntime, useMessage, useMessageAttachment, useMessageAttachmentRuntime, useMessagePart, useMessagePartData, useMessagePartFile, useMessagePartImage, useMessagePartReasoning, useMessagePartRuntime, useMessagePartSource, useMessagePartText, useMessageQuote, useMessageRuntime, useMessageTiming, useRemoteThreadListRuntime, useRuntimeAdapters, useScrollLock, useSmooth, useThread, useThreadComposer, useThreadComposerAttachment, useThreadComposerAttachmentRuntime, useThreadList, useThreadListItem, useThreadListItemRuntime, useThreadModelContext, useThreadRuntime, useThreadViewport, useThreadViewportAutoScroll, useThreadViewportStore, useToolArgsStatus, useVoiceControls, useVoiceState, useVoiceVolume };
|
|
@@ -23,17 +23,9 @@ type McpAppRendererOptions = {
|
|
|
23
23
|
loadingFallback?: ReactNode; /** Rendered when the resource load rejects. Defaults to `fallback`. */
|
|
24
24
|
errorFallback?: ReactNode | ((error: Error) => ReactNode);
|
|
25
25
|
};
|
|
26
|
-
|
|
27
|
-
* Creates a tool-call renderer for MCP Apps embedded in assistant messages.
|
|
28
|
-
*
|
|
29
|
-
* Compose this into the `Tools` resource through its `mcpApp` option. When a
|
|
30
|
-
* tool-call part carries `mcp.app` metadata for a `ui://` resource, the
|
|
31
|
-
* renderer loads that resource from the configured host and displays it in a
|
|
32
|
-
* sandboxed frame.
|
|
33
|
-
*/
|
|
34
|
-
declare const McpAppRenderer: (props: McpAppRendererOptions) => ResourceElement<{
|
|
26
|
+
declare const McpAppRenderer: import("@assistant-ui/tap").Resource<{
|
|
35
27
|
readonly render: ToolCallMessagePartComponent;
|
|
36
|
-
}, McpAppRendererOptions>;
|
|
28
|
+
}, [options: McpAppRendererOptions]>;
|
|
37
29
|
//#endregion
|
|
38
30
|
export { McpAppRenderer, McpAppRendererOptions };
|
|
39
31
|
//# sourceMappingURL=McpAppRenderer.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"McpAppRenderer.d.ts","names":[],"sources":["../../src/mcp-apps/McpAppRenderer.tsx"],"mappings":";;;;;;KA6BY,qBAAA;;AAAZ;;;;EAME,IAAA,EAAM,eAAA,CAAgB,WAAA,GAEZ;EAAV,OAAA,GAAU,mBAAA;EASI;;;;EAJd,SAAA,WAU+C;EAR/C,QAAA,GAAW,cAAA,EAQ6C;EANxD,WAAA,GAAc,iBAAA,EAXR;EAaN,QAAA,GAAW,SAAA,EAXX;EAaA,eAAA,GAAkB,SAAA,EARlB;EAUA,aAAA,GAAgB,SAAA,KAAc,KAAA,EAAO,KAAA,KAAU,SAAA;AAAA
|
|
1
|
+
{"version":3,"file":"McpAppRenderer.d.ts","names":[],"sources":["../../src/mcp-apps/McpAppRenderer.tsx"],"mappings":";;;;;;KA6BY,qBAAA;;AAAZ;;;;EAME,IAAA,EAAM,eAAA,CAAgB,WAAA,GAEZ;EAAV,OAAA,GAAU,mBAAA;EASI;;;;EAJd,SAAA,WAU+C;EAR/C,QAAA,GAAW,cAAA,EAQ6C;EANxD,WAAA,GAAc,iBAAA,EAXR;EAaN,QAAA,GAAW,SAAA,EAXX;EAaA,eAAA,GAAkB,SAAA,EARlB;EAUA,aAAA,GAAgB,SAAA,KAAc,KAAA,EAAO,KAAA,KAAU,SAAA;AAAA;AAAA,cA2KpC,cAAA,8BAAc,QAAA;EAAA,iBAxBL,4BAAA;AAAA,aAAA,qBAAA"}
|
|
@@ -99,7 +99,7 @@ function InlineRenderer({ part, internalsRef, optionsRef }) {
|
|
|
99
99
|
* renderer loads that resource from the configured host and displays it in a
|
|
100
100
|
* sandboxed frame.
|
|
101
101
|
*/
|
|
102
|
-
const
|
|
102
|
+
const useMcpAppRenderer = (options) => {
|
|
103
103
|
const host = useResource(options.host);
|
|
104
104
|
const optionsRef = useRef(options);
|
|
105
105
|
optionsRef.current = options;
|
|
@@ -114,7 +114,8 @@ const McpAppRenderer = resource(function McpAppRenderer(options) {
|
|
|
114
114
|
Render.displayName = "McpAppRenderer";
|
|
115
115
|
return Render;
|
|
116
116
|
}, []) };
|
|
117
|
-
}
|
|
117
|
+
};
|
|
118
|
+
const McpAppRenderer = resource(useMcpAppRenderer);
|
|
118
119
|
//#endregion
|
|
119
120
|
export { McpAppRenderer };
|
|
120
121
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"McpAppRenderer.js","names":[],"sources":["../../src/mcp-apps/McpAppRenderer.tsx"],"sourcesContent":["\"use client\";\n\nimport {\n useEffect,\n useMemo,\n useRef,\n useState,\n type MutableRefObject,\n type ReactNode,\n} from \"react\";\nimport type { McpAppMetadata } from \"@assistant-ui/core\";\nimport type {\n ToolCallMessagePartComponent,\n ToolCallMessagePartProps,\n} from \"@assistant-ui/core/react\";\nimport { useAui } from \"@assistant-ui/store\";\n\nimport { useResource, resource, type ResourceElement } from \"@assistant-ui/tap\";\nimport { McpAppFrame } from \"./app-frame\";\nimport type {\n McpAppBridgeHandlers,\n McpAppHostContext,\n McpAppHostInfo,\n McpAppResource,\n McpAppSandboxConfig,\n McpAppsHost,\n} from \"./types\";\nimport { getMcpAppFromToolPart } from \"./utils\";\n\nexport type McpAppRendererOptions = {\n /**\n * Provides the data-plane operations the widget can request\n * (`loadResource`, `callTool`, `readResource`, `listResources`). Use\n * `McpAppsRemoteHost({ url })` for the default HTTP-route convention.\n */\n host: ResourceElement<McpAppsHost>;\n /** Sandbox + container styling. Passes through to SafeContentFrame. */\n sandbox?: McpAppSandboxConfig;\n /**\n * Upper bound (in pixels) applied to the widget-driven auto-resize height.\n * Defaults to 800.\n */\n maxHeight?: number;\n /** Identifies the host to the widget in the `ui/initialize` response. */\n hostInfo?: McpAppHostInfo;\n /** Delivered to the widget on initialize and pushed via `notifications/host_context/changed` on change. */\n hostContext?: McpAppHostContext;\n /** Rendered when no MCP app is on the part, or while load is in flight / failed (unless overridden). */\n fallback?: ReactNode;\n /** Rendered while the resource is loading. Defaults to `fallback`. */\n loadingFallback?: ReactNode;\n /** Rendered when the resource load rejects. Defaults to `fallback`. */\n errorFallback?: ReactNode | ((error: Error) => ReactNode);\n};\n\ntype LoadedResourceState = {\n resourceUri: string;\n resource?: McpAppResource;\n error?: Error;\n};\n\nfunction getInput(part: {\n status: { type: string };\n argsText: string;\n args: unknown;\n}): unknown {\n if (\n part.status.type === \"running\" &&\n (part.argsText === \"\" || part.argsText === \"{}\")\n ) {\n return undefined;\n }\n return part.args;\n}\n\nconst defaultOpenLink = ({ url }: { url: string }) => {\n window.open(url, \"_blank\", \"noopener,noreferrer\");\n};\n\nfunction extractSendMessageText(params: unknown): string | undefined {\n if (typeof params === \"string\") return params;\n if (!params || typeof params !== \"object\") return undefined;\n const obj = params as Record<string, unknown>;\n if (typeof obj[\"prompt\"] === \"string\") return obj[\"prompt\"];\n if (typeof obj[\"text\"] === \"string\") return obj[\"text\"];\n if (typeof obj[\"message\"] === \"string\") return obj[\"message\"];\n return undefined;\n}\n\nfunction InlineRenderer({\n part,\n internalsRef,\n optionsRef,\n}: {\n part: ToolCallMessagePartProps;\n internalsRef: MutableRefObject<{ host: McpAppsHost }>;\n optionsRef: MutableRefObject<McpAppRendererOptions>;\n}) {\n const opts = optionsRef.current;\n const aui = useAui();\n const app = getMcpAppFromToolPart(part);\n const cachedAppRef = useRef<McpAppMetadata | undefined>(undefined);\n if (app != null && cachedAppRef.current?.resourceUri !== app.resourceUri) {\n cachedAppRef.current = app;\n }\n const appForRender = app ?? cachedAppRef.current;\n\n const [loadedResource, setLoadedResource] = useState<LoadedResourceState>();\n\n const resourceUri = appForRender?.resourceUri;\n useEffect(() => {\n if (appForRender == null || resourceUri == null) return;\n let cancelled = false;\n const targetUri = resourceUri;\n\n internalsRef.current.host\n .loadResource({ uri: targetUri })\n .then((res) => {\n if (!cancelled)\n setLoadedResource({ resourceUri: targetUri, resource: res });\n })\n .catch((error: unknown) => {\n if (!cancelled) {\n setLoadedResource({\n resourceUri: targetUri,\n error: error instanceof Error ? error : new Error(String(error)),\n });\n }\n });\n\n return () => {\n cancelled = true;\n };\n // oxlint-disable-next-line react/exhaustive-deps -- re-fetch only when URI changes; appForRender identity is unstable and internalsRef is a stable ref\n }, [resourceUri]);\n\n const bridgeHandlers = useMemo<McpAppBridgeHandlers>(\n () => ({\n openLink: defaultOpenLink,\n sendMessage: (params) => {\n const text = extractSendMessageText(params);\n if (!text) return { ok: false, reason: \"unrecognised params shape\" };\n aui.thread().append({ content: [{ type: \"text\", text }] });\n return { ok: true };\n },\n callTool: (params) => internalsRef.current.host.callTool(params),\n readResource: (params) => internalsRef.current.host.readResource(params),\n listResources: (params) =>\n internalsRef.current.host.listResources(params),\n }),\n [aui, internalsRef],\n );\n\n const loadedResourceForApp =\n loadedResource?.resourceUri === appForRender?.resourceUri\n ? loadedResource\n : undefined;\n const appResource = loadedResourceForApp?.resource;\n const error = loadedResourceForApp?.error;\n\n const fallback = opts.fallback ?? null;\n if (appForRender == null) {\n return <>{fallback}</>;\n }\n if (error != null) {\n const errorFallback = opts.errorFallback;\n if (errorFallback === undefined) return <>{fallback}</>;\n if (typeof errorFallback === \"function\") return <>{errorFallback(error)}</>;\n return <>{errorFallback}</>;\n }\n if (appResource == null) {\n return <>{opts.loadingFallback ?? fallback}</>;\n }\n\n return (\n <McpAppFrame\n app={appForRender}\n resource={appResource}\n input={getInput(part)}\n output={part.result}\n sandbox={opts.sandbox}\n handlers={bridgeHandlers}\n hostInfo={opts.hostInfo}\n hostContext={opts.hostContext}\n maxHeight={opts.maxHeight}\n />\n );\n}\n\n/**\n * Creates a tool-call renderer for MCP Apps embedded in assistant messages.\n *\n * Compose this into the `Tools` resource through its `mcpApp` option. When a\n * tool-call part carries `mcp.app` metadata for a `ui://` resource, the\n * renderer loads that resource from the configured host and displays it in a\n * sandboxed frame.\n */\
|
|
1
|
+
{"version":3,"file":"McpAppRenderer.js","names":[],"sources":["../../src/mcp-apps/McpAppRenderer.tsx"],"sourcesContent":["\"use client\";\n\nimport {\n useEffect,\n useMemo,\n useRef,\n useState,\n type MutableRefObject,\n type ReactNode,\n} from \"react\";\nimport type { McpAppMetadata } from \"@assistant-ui/core\";\nimport type {\n ToolCallMessagePartComponent,\n ToolCallMessagePartProps,\n} from \"@assistant-ui/core/react\";\nimport { useAui } from \"@assistant-ui/store\";\n\nimport { useResource, resource, type ResourceElement } from \"@assistant-ui/tap\";\nimport { McpAppFrame } from \"./app-frame\";\nimport type {\n McpAppBridgeHandlers,\n McpAppHostContext,\n McpAppHostInfo,\n McpAppResource,\n McpAppSandboxConfig,\n McpAppsHost,\n} from \"./types\";\nimport { getMcpAppFromToolPart } from \"./utils\";\n\nexport type McpAppRendererOptions = {\n /**\n * Provides the data-plane operations the widget can request\n * (`loadResource`, `callTool`, `readResource`, `listResources`). Use\n * `McpAppsRemoteHost({ url })` for the default HTTP-route convention.\n */\n host: ResourceElement<McpAppsHost>;\n /** Sandbox + container styling. Passes through to SafeContentFrame. */\n sandbox?: McpAppSandboxConfig;\n /**\n * Upper bound (in pixels) applied to the widget-driven auto-resize height.\n * Defaults to 800.\n */\n maxHeight?: number;\n /** Identifies the host to the widget in the `ui/initialize` response. */\n hostInfo?: McpAppHostInfo;\n /** Delivered to the widget on initialize and pushed via `notifications/host_context/changed` on change. */\n hostContext?: McpAppHostContext;\n /** Rendered when no MCP app is on the part, or while load is in flight / failed (unless overridden). */\n fallback?: ReactNode;\n /** Rendered while the resource is loading. Defaults to `fallback`. */\n loadingFallback?: ReactNode;\n /** Rendered when the resource load rejects. Defaults to `fallback`. */\n errorFallback?: ReactNode | ((error: Error) => ReactNode);\n};\n\ntype LoadedResourceState = {\n resourceUri: string;\n resource?: McpAppResource;\n error?: Error;\n};\n\nfunction getInput(part: {\n status: { type: string };\n argsText: string;\n args: unknown;\n}): unknown {\n if (\n part.status.type === \"running\" &&\n (part.argsText === \"\" || part.argsText === \"{}\")\n ) {\n return undefined;\n }\n return part.args;\n}\n\nconst defaultOpenLink = ({ url }: { url: string }) => {\n window.open(url, \"_blank\", \"noopener,noreferrer\");\n};\n\nfunction extractSendMessageText(params: unknown): string | undefined {\n if (typeof params === \"string\") return params;\n if (!params || typeof params !== \"object\") return undefined;\n const obj = params as Record<string, unknown>;\n if (typeof obj[\"prompt\"] === \"string\") return obj[\"prompt\"];\n if (typeof obj[\"text\"] === \"string\") return obj[\"text\"];\n if (typeof obj[\"message\"] === \"string\") return obj[\"message\"];\n return undefined;\n}\n\nfunction InlineRenderer({\n part,\n internalsRef,\n optionsRef,\n}: {\n part: ToolCallMessagePartProps;\n internalsRef: MutableRefObject<{ host: McpAppsHost }>;\n optionsRef: MutableRefObject<McpAppRendererOptions>;\n}) {\n const opts = optionsRef.current;\n const aui = useAui();\n const app = getMcpAppFromToolPart(part);\n const cachedAppRef = useRef<McpAppMetadata | undefined>(undefined);\n if (app != null && cachedAppRef.current?.resourceUri !== app.resourceUri) {\n cachedAppRef.current = app;\n }\n const appForRender = app ?? cachedAppRef.current;\n\n const [loadedResource, setLoadedResource] = useState<LoadedResourceState>();\n\n const resourceUri = appForRender?.resourceUri;\n useEffect(() => {\n if (appForRender == null || resourceUri == null) return;\n let cancelled = false;\n const targetUri = resourceUri;\n\n internalsRef.current.host\n .loadResource({ uri: targetUri })\n .then((res) => {\n if (!cancelled)\n setLoadedResource({ resourceUri: targetUri, resource: res });\n })\n .catch((error: unknown) => {\n if (!cancelled) {\n setLoadedResource({\n resourceUri: targetUri,\n error: error instanceof Error ? error : new Error(String(error)),\n });\n }\n });\n\n return () => {\n cancelled = true;\n };\n // oxlint-disable-next-line react/exhaustive-deps -- re-fetch only when URI changes; appForRender identity is unstable and internalsRef is a stable ref\n }, [resourceUri]);\n\n const bridgeHandlers = useMemo<McpAppBridgeHandlers>(\n () => ({\n openLink: defaultOpenLink,\n sendMessage: (params) => {\n const text = extractSendMessageText(params);\n if (!text) return { ok: false, reason: \"unrecognised params shape\" };\n aui.thread().append({ content: [{ type: \"text\", text }] });\n return { ok: true };\n },\n callTool: (params) => internalsRef.current.host.callTool(params),\n readResource: (params) => internalsRef.current.host.readResource(params),\n listResources: (params) =>\n internalsRef.current.host.listResources(params),\n }),\n [aui, internalsRef],\n );\n\n const loadedResourceForApp =\n loadedResource?.resourceUri === appForRender?.resourceUri\n ? loadedResource\n : undefined;\n const appResource = loadedResourceForApp?.resource;\n const error = loadedResourceForApp?.error;\n\n const fallback = opts.fallback ?? null;\n if (appForRender == null) {\n return <>{fallback}</>;\n }\n if (error != null) {\n const errorFallback = opts.errorFallback;\n if (errorFallback === undefined) return <>{fallback}</>;\n if (typeof errorFallback === \"function\") return <>{errorFallback(error)}</>;\n return <>{errorFallback}</>;\n }\n if (appResource == null) {\n return <>{opts.loadingFallback ?? fallback}</>;\n }\n\n return (\n <McpAppFrame\n app={appForRender}\n resource={appResource}\n input={getInput(part)}\n output={part.result}\n sandbox={opts.sandbox}\n handlers={bridgeHandlers}\n hostInfo={opts.hostInfo}\n hostContext={opts.hostContext}\n maxHeight={opts.maxHeight}\n />\n );\n}\n\n/**\n * Creates a tool-call renderer for MCP Apps embedded in assistant messages.\n *\n * Compose this into the `Tools` resource through its `mcpApp` option. When a\n * tool-call part carries `mcp.app` metadata for a `ui://` resource, the\n * renderer loads that resource from the configured host and displays it in a\n * sandboxed frame.\n */\nconst useMcpAppRenderer = (\n options: McpAppRendererOptions,\n): { readonly render: ToolCallMessagePartComponent } => {\n const host = useResource(options.host);\n\n const optionsRef = useRef<McpAppRendererOptions>(options);\n optionsRef.current = options;\n\n const internalsRef = useRef<{ host: McpAppsHost }>({ host });\n internalsRef.current = { host };\n\n const render = useMemo((): ToolCallMessagePartComponent => {\n const Render: ToolCallMessagePartComponent = (props) => (\n <InlineRenderer\n part={props}\n internalsRef={internalsRef}\n optionsRef={optionsRef}\n />\n );\n Render.displayName = \"McpAppRenderer\";\n return Render;\n }, []);\n\n return { render };\n};\n\nexport const McpAppRenderer = resource(useMcpAppRenderer);\n"],"mappings":"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"}
|
|
@@ -1,14 +1,7 @@
|
|
|
1
1
|
import { McpAppsHost, McpAppsRemoteHostOptions } from "./types.js";
|
|
2
2
|
|
|
3
3
|
//#region src/mcp-apps/McpAppsRemoteHost.d.ts
|
|
4
|
-
|
|
5
|
-
* Creates the default HTTP host for MCP App widgets.
|
|
6
|
-
*
|
|
7
|
-
* The host POSTs widget requests to the configured route as `{ method,
|
|
8
|
-
* params }`, using the method names expected by the assistant-ui MCP Apps
|
|
9
|
-
* guide.
|
|
10
|
-
*/
|
|
11
|
-
declare const McpAppsRemoteHost: (props: McpAppsRemoteHostOptions) => import("@assistant-ui/tap").ResourceElement<McpAppsHost, McpAppsRemoteHostOptions>;
|
|
4
|
+
declare const McpAppsRemoteHost: import("@assistant-ui/tap").Resource<McpAppsHost, [options: McpAppsRemoteHostOptions]>;
|
|
12
5
|
//#endregion
|
|
13
6
|
export { McpAppsRemoteHost };
|
|
14
7
|
//# sourceMappingURL=McpAppsRemoteHost.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"McpAppsRemoteHost.d.ts","names":[],"sources":["../../src/mcp-apps/McpAppsRemoteHost.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"McpAppsRemoteHost.d.ts","names":[],"sources":["../../src/mcp-apps/McpAppsRemoteHost.ts"],"mappings":";;;cA6Da,iBAAA,8BAAiB,QAAA,CAAA,WAAA,GAAA,OAAA,EAAA,wBAAA"}
|
|
@@ -25,7 +25,7 @@ async function postToHost(options, method, params) {
|
|
|
25
25
|
* params }`, using the method names expected by the assistant-ui MCP Apps
|
|
26
26
|
* guide.
|
|
27
27
|
*/
|
|
28
|
-
const
|
|
28
|
+
const useMcpAppsRemoteHost = (options) => {
|
|
29
29
|
const optionsRef = useRef(options);
|
|
30
30
|
optionsRef.current = options;
|
|
31
31
|
return useMemo(() => ({
|
|
@@ -34,7 +34,8 @@ const McpAppsRemoteHost = resource(function McpAppsRemoteHost(options) {
|
|
|
34
34
|
readResource: (params) => postToHost(optionsRef.current, "resources/read", params),
|
|
35
35
|
listResources: (params) => postToHost(optionsRef.current, "resources/list", params)
|
|
36
36
|
}), []);
|
|
37
|
-
}
|
|
37
|
+
};
|
|
38
|
+
const McpAppsRemoteHost = resource(useMcpAppsRemoteHost);
|
|
38
39
|
//#endregion
|
|
39
40
|
export { McpAppsRemoteHost };
|
|
40
41
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"McpAppsRemoteHost.js","names":[],"sources":["../../src/mcp-apps/McpAppsRemoteHost.ts"],"sourcesContent":["import { useMemo, useRef } from \"react\";\nimport { resource } from \"@assistant-ui/tap\";\nimport type {\n McpAppResource,\n McpAppsHost,\n McpAppsRemoteHostOptions,\n} from \"./types\";\n\nasync function postToHost(\n options: McpAppsRemoteHostOptions,\n method: string,\n params: unknown,\n): Promise<unknown> {\n const doFetch = options.fetch ?? fetch;\n const extraHeaders =\n typeof options.headers === \"function\"\n ? await options.headers()\n : (options.headers ?? {});\n const res = await doFetch(options.url, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\", ...extraHeaders },\n body: JSON.stringify({ method, params }),\n });\n if (!res.ok) {\n throw new Error(`MCP App host request failed: ${res.status}`);\n }\n return res.json();\n}\n\n/**\n * Creates the default HTTP host for MCP App widgets.\n *\n * The host POSTs widget requests to the configured route as `{ method,\n * params }`, using the method names expected by the assistant-ui MCP Apps\n * guide.\n */\
|
|
1
|
+
{"version":3,"file":"McpAppsRemoteHost.js","names":[],"sources":["../../src/mcp-apps/McpAppsRemoteHost.ts"],"sourcesContent":["import { useMemo, useRef } from \"react\";\nimport { resource } from \"@assistant-ui/tap\";\nimport type {\n McpAppResource,\n McpAppsHost,\n McpAppsRemoteHostOptions,\n} from \"./types\";\n\nasync function postToHost(\n options: McpAppsRemoteHostOptions,\n method: string,\n params: unknown,\n): Promise<unknown> {\n const doFetch = options.fetch ?? fetch;\n const extraHeaders =\n typeof options.headers === \"function\"\n ? await options.headers()\n : (options.headers ?? {});\n const res = await doFetch(options.url, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\", ...extraHeaders },\n body: JSON.stringify({ method, params }),\n });\n if (!res.ok) {\n throw new Error(`MCP App host request failed: ${res.status}`);\n }\n return res.json();\n}\n\n/**\n * Creates the default HTTP host for MCP App widgets.\n *\n * The host POSTs widget requests to the configured route as `{ method,\n * params }`, using the method names expected by the assistant-ui MCP Apps\n * guide.\n */\nconst useMcpAppsRemoteHost = (\n options: McpAppsRemoteHostOptions,\n): McpAppsHost => {\n const optionsRef = useRef(options);\n optionsRef.current = options;\n\n return useMemo(\n (): McpAppsHost => ({\n loadResource: (params) =>\n postToHost(\n optionsRef.current,\n \"mcp-apps/read-resource\",\n params,\n ) as Promise<McpAppResource>,\n callTool: (params) =>\n postToHost(optionsRef.current, \"tools/call\", params),\n readResource: (params) =>\n postToHost(optionsRef.current, \"resources/read\", params),\n listResources: (params) =>\n postToHost(optionsRef.current, \"resources/list\", params),\n }),\n [],\n );\n};\n\nexport const McpAppsRemoteHost = resource(useMcpAppsRemoteHost);\n"],"mappings":";;;AAQA,eAAe,WACb,SACA,QACA,QACkB;CAClB,MAAM,UAAU,QAAQ,SAAS;CACjC,MAAM,eACJ,OAAO,QAAQ,YAAY,aACvB,MAAM,QAAQ,QAAQ,IACrB,QAAQ,WAAW,CAAC;CAC3B,MAAM,MAAM,MAAM,QAAQ,QAAQ,KAAK;EACrC,QAAQ;EACR,SAAS;GAAE,gBAAgB;GAAoB,GAAG;EAAa;EAC/D,MAAM,KAAK,UAAU;GAAE;GAAQ;EAAO,CAAC;CACzC,CAAC;CACD,IAAI,CAAC,IAAI,IACP,MAAM,IAAI,MAAM,gCAAgC,IAAI,QAAQ;CAE9D,OAAO,IAAI,KAAK;AAClB;;;;;;;;AASA,MAAM,wBACJ,YACgB;CAChB,MAAM,aAAa,OAAO,OAAO;CACjC,WAAW,UAAU;CAErB,OAAO,eACe;EAClB,eAAe,WACb,WACE,WAAW,SACX,0BACA,MACF;EACF,WAAW,WACT,WAAW,WAAW,SAAS,cAAc,MAAM;EACrD,eAAe,WACb,WAAW,WAAW,SAAS,kBAAkB,MAAM;EACzD,gBAAgB,WACd,WAAW,WAAW,SAAS,kBAAkB,MAAM;CAC3D,IACA,CAAC,CACH;AACF;AAEA,MAAa,oBAAoB,SAAS,oBAAoB"}
|
|
@@ -11,7 +11,7 @@ import { composeEventHandlers } from "@radix-ui/primitive";
|
|
|
11
11
|
import { useEscapeKeydown } from "@radix-ui/react-use-escape-keydown";
|
|
12
12
|
import { Slot } from "radix-ui";
|
|
13
13
|
import TextareaAutosize from "react-textarea-autosize";
|
|
14
|
-
import {
|
|
14
|
+
import { flushTapSync } from "@assistant-ui/tap";
|
|
15
15
|
//#region src/primitives/composer/ComposerInput.tsx
|
|
16
16
|
const TOUCH_PRIMARY_QUERY = "(pointer: coarse) and (not (any-pointer: fine))";
|
|
17
17
|
/**
|
|
@@ -156,7 +156,7 @@ const ComposerPrimitiveInput = forwardRef(({ autoFocus = false, asChild, render,
|
|
|
156
156
|
const nativeIsComposing = e.nativeEvent.isComposing === true;
|
|
157
157
|
if (compositionRef.current && !nativeIsComposing) compositionRef.current = false;
|
|
158
158
|
const isComposing = nativeIsComposing || compositionRef.current;
|
|
159
|
-
|
|
159
|
+
flushTapSync(() => {
|
|
160
160
|
aui.composer().setText(e.target.value);
|
|
161
161
|
});
|
|
162
162
|
if (isComposing) return;
|
|
@@ -171,7 +171,7 @@ const ComposerPrimitiveInput = forwardRef(({ autoFocus = false, asChild, render,
|
|
|
171
171
|
compositionRef.current = false;
|
|
172
172
|
if (!aui.composer().getState().isEditing) return;
|
|
173
173
|
const target = e.target;
|
|
174
|
-
|
|
174
|
+
flushTapSync(() => {
|
|
175
175
|
aui.composer().setText(target.value);
|
|
176
176
|
});
|
|
177
177
|
const pos = target.selectionStart ?? target.value.length;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ComposerInput.js","names":[],"sources":["../../../src/primitives/composer/ComposerInput.tsx"],"sourcesContent":["\"use client\";\n\nimport { composeEventHandlers } from \"@radix-ui/primitive\";\nimport { useComposedRefs } from \"@radix-ui/react-compose-refs\";\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 cloneElement,\n isValidElement,\n} from \"react\";\nimport TextareaAutosize, {\n type TextareaAutosizeProps,\n} from \"react-textarea-autosize\";\nimport { useEscapeKeydown } from \"@radix-ui/react-use-escape-keydown\";\nimport { useOnScrollToBottom } from \"../../utils/hooks/useOnScrollToBottom\";\nimport { useMediaQuery } from \"../../utils/hooks/useMediaQuery\";\nimport { useAuiState, useAui } from \"@assistant-ui/store\";\nimport { flushResourcesSync } from \"@assistant-ui/tap\";\nimport { useComposerInputPluginRegistryOptional } from \"./ComposerInputPluginContext\";\nimport { useTriggerPopoverActiveAriaOptional } from \"./trigger/TriggerPopoverRootContext\";\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 * @default true\n */\n cancelOnEscape?: boolean | undefined;\n /**\n * Whether to automatically focus the input when a new run starts.\n * @default true\n */\n unstable_focusOnRunStart?: boolean | undefined;\n /**\n * Whether to automatically focus the input when scrolling to bottom.\n * @default true\n */\n unstable_focusOnScrollToBottom?: boolean | undefined;\n /**\n * Whether to automatically focus the input when switching threads.\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 * @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 `Unstable_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 ...rest\n },\n forwardedRef,\n ) => {\n const aui = useAui();\n const pluginRegistry = useComposerInputPluginRegistryOptional();\n const activeAria = useTriggerPopoverActiveAriaOptional();\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 = useAuiState((s) => {\n if (!s.composer.isEditing) return \"\";\n return s.composer.text;\n });\n\n const isDisabled =\n useAuiState(\n (s) => s.thread.isDisabled || s.composer.dictation?.inputDisabled,\n ) || disabledProp;\n const textareaRef = useRef<HTMLTextAreaElement>(null);\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 // 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 composer.cancel();\n e.preventDefault();\n }\n });\n\n const handleKeyPress = (e: KeyboardEvent) => {\n if (isDisabled) return;\n\n // ignore IME composition events\n if (e.nativeEvent.isComposing) 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\n if (threadState.isRunning && !hasQueue) 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 try {\n e.preventDefault();\n await Promise.all(\n files.map((file) => aui.composer().addAttachment(file)),\n );\n } catch (error) {\n console.error(\"Error adding attachment:\", error);\n }\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(\"threadListItem.switchedTo\", focus);\n }, [unstable_focusOnThreadSwitched, focus, aui]);\n\n const ariaComboboxProps = activeAria\n ? {\n \"aria-controls\": activeAria.popoverId,\n \"aria-expanded\": true as const,\n \"aria-haspopup\": \"listbox\" as const,\n \"aria-activedescendant\": activeAria.highlightedItemId,\n }\n : {};\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 flushResourcesSync(() => {\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 flushResourcesSync(() => {\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 };\n\n if (render && isValidElement(render)) {\n const renderChildren =\n (rest as any).children !== undefined\n ? ((rest as any).children as ReactNode)\n : ((render.props as Record<string, unknown>).children as ReactNode);\n return (\n <Slot.Root {...inputProps}>\n {cloneElement(render, undefined, renderChildren)}\n </Slot.Root>\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":[],"sources":["../../../src/primitives/composer/ComposerInput.tsx"],"sourcesContent":["\"use client\";\n\nimport { composeEventHandlers } from \"@radix-ui/primitive\";\nimport { useComposedRefs } from \"@radix-ui/react-compose-refs\";\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 cloneElement,\n isValidElement,\n} from \"react\";\nimport TextareaAutosize, {\n type TextareaAutosizeProps,\n} from \"react-textarea-autosize\";\nimport { useEscapeKeydown } from \"@radix-ui/react-use-escape-keydown\";\nimport { useOnScrollToBottom } from \"../../utils/hooks/useOnScrollToBottom\";\nimport { useMediaQuery } from \"../../utils/hooks/useMediaQuery\";\nimport { useAuiState, useAui } from \"@assistant-ui/store\";\nimport { flushTapSync } from \"@assistant-ui/tap\";\nimport { useComposerInputPluginRegistryOptional } from \"./ComposerInputPluginContext\";\nimport { useTriggerPopoverActiveAriaOptional } from \"./trigger/TriggerPopoverRootContext\";\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 * @default true\n */\n cancelOnEscape?: boolean | undefined;\n /**\n * Whether to automatically focus the input when a new run starts.\n * @default true\n */\n unstable_focusOnRunStart?: boolean | undefined;\n /**\n * Whether to automatically focus the input when scrolling to bottom.\n * @default true\n */\n unstable_focusOnScrollToBottom?: boolean | undefined;\n /**\n * Whether to automatically focus the input when switching threads.\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 * @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 `Unstable_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 ...rest\n },\n forwardedRef,\n ) => {\n const aui = useAui();\n const pluginRegistry = useComposerInputPluginRegistryOptional();\n const activeAria = useTriggerPopoverActiveAriaOptional();\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 = useAuiState((s) => {\n if (!s.composer.isEditing) return \"\";\n return s.composer.text;\n });\n\n const isDisabled =\n useAuiState(\n (s) => s.thread.isDisabled || s.composer.dictation?.inputDisabled,\n ) || disabledProp;\n const textareaRef = useRef<HTMLTextAreaElement>(null);\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 // 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 composer.cancel();\n e.preventDefault();\n }\n });\n\n const handleKeyPress = (e: KeyboardEvent) => {\n if (isDisabled) return;\n\n // ignore IME composition events\n if (e.nativeEvent.isComposing) 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\n if (threadState.isRunning && !hasQueue) 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 try {\n e.preventDefault();\n await Promise.all(\n files.map((file) => aui.composer().addAttachment(file)),\n );\n } catch (error) {\n console.error(\"Error adding attachment:\", error);\n }\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(\"threadListItem.switchedTo\", focus);\n }, [unstable_focusOnThreadSwitched, focus, aui]);\n\n const ariaComboboxProps = activeAria\n ? {\n \"aria-controls\": activeAria.popoverId,\n \"aria-expanded\": true as const,\n \"aria-haspopup\": \"listbox\" as const,\n \"aria-activedescendant\": activeAria.highlightedItemId,\n }\n : {};\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 };\n\n if (render && isValidElement(render)) {\n const renderChildren =\n (rest as any).children !== undefined\n ? ((rest as any).children as ReactNode)\n : ((render.props as Record<string, unknown>).children as ReactNode);\n return (\n <Slot.Root {...inputProps}>\n {cloneElement(render, undefined, renderChildren)}\n </Slot.Root>\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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"}
|
|
@@ -29,22 +29,14 @@ type TriggerPopoverResourceOutput = {
|
|
|
29
29
|
setCursorPosition(pos: number): void;
|
|
30
30
|
registerSelectItemOverride(fn: SelectItemOverride): () => void;
|
|
31
31
|
};
|
|
32
|
-
|
|
33
|
-
declare const TriggerPopoverResource: (props: {
|
|
32
|
+
declare const TriggerPopoverResource: import("@assistant-ui/tap").Resource<TriggerPopoverResourceOutput, [{
|
|
34
33
|
adapter: Unstable_TriggerAdapter | undefined;
|
|
35
34
|
text: string;
|
|
36
35
|
triggerChar: string;
|
|
37
36
|
behavior: TriggerBehavior | undefined;
|
|
38
37
|
aui: AssistantClient; /** Stable ID for accessible element IDs (pass React's useId() from component layer). */
|
|
39
38
|
popoverId: string;
|
|
40
|
-
}
|
|
41
|
-
adapter: Unstable_TriggerAdapter | undefined;
|
|
42
|
-
text: string;
|
|
43
|
-
triggerChar: string;
|
|
44
|
-
behavior: TriggerBehavior | undefined;
|
|
45
|
-
aui: AssistantClient; /** Stable ID for accessible element IDs (pass React's useId() from component layer). */
|
|
46
|
-
popoverId: string;
|
|
47
|
-
}>;
|
|
39
|
+
}]>;
|
|
48
40
|
//#endregion
|
|
49
41
|
export { OnSelectBehavior, type SelectItemOverride, type TriggerBehavior, type TriggerPopoverKeyEvent, TriggerPopoverResource, TriggerPopoverResourceOutput };
|
|
50
42
|
//# sourceMappingURL=TriggerPopoverResource.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TriggerPopoverResource.d.ts","names":[],"sources":["../../../../src/primitives/composer/trigger/TriggerPopoverResource.ts"],"mappings":";;;;;;;KAqBY,gBAAA,GAAmB,eAAe;AAAA,KAElC,4BAAA;EAAA,SACD,IAAA;EAAA,SACA,KAAA;EAAA,SACA,gBAAA;EAAA,SACA,UAAA,WAAqB,wBAAA;EAAA,SACrB,KAAA,WAAgB,oBAAA;EAAA,SAChB,gBAAA;EAAA,SACA,YAAA,WAFgB;EAAA,SAIhB,SAAA,UAiBsB;EAAA,SAftB,iBAAA;EAET,cAAA,CAAe,UAAA;EACf,MAAA;EACA,UAAA,CAAW,IAAA,EAAM,oBAAA;EACjB,KAAA,UAZS;EAcT,cAAA,CAAe,KAAA;EACf,aAAA,CAAc,CAAA;IAAA,SACH,GAAA;IAAA,SACA,QAAA;IACT,cAAA;EAAA;EAGF,iBAAA,CAAkB,GAAA;EAClB,0BAAA,CAA2B,EAAA,EAAI,kBAAA;AAAA
|
|
1
|
+
{"version":3,"file":"TriggerPopoverResource.d.ts","names":[],"sources":["../../../../src/primitives/composer/trigger/TriggerPopoverResource.ts"],"mappings":";;;;;;;KAqBY,gBAAA,GAAmB,eAAe;AAAA,KAElC,4BAAA;EAAA,SACD,IAAA;EAAA,SACA,KAAA;EAAA,SACA,gBAAA;EAAA,SACA,UAAA,WAAqB,wBAAA;EAAA,SACrB,KAAA,WAAgB,oBAAA;EAAA,SAChB,gBAAA;EAAA,SACA,YAAA,WAFgB;EAAA,SAIhB,SAAA,UAiBsB;EAAA,SAftB,iBAAA;EAET,cAAA,CAAe,UAAA;EACf,MAAA;EACA,UAAA,CAAW,IAAA,EAAM,oBAAA;EACjB,KAAA,UAZS;EAcT,cAAA,CAAe,KAAA;EACf,aAAA,CAAc,CAAA;IAAA,SACH,GAAA;IAAA,SACA,QAAA;IACT,cAAA;EAAA;EAGF,iBAAA,CAAkB,GAAA;EAClB,0BAAA,CAA2B,EAAA,EAAI,kBAAA;AAAA;AAAA,cAwFpB,sBAAA,8BAAsB,QAAA,CAAA,4BAAA;WA5ExB,uBAAA;;;YAGC,eAAA;OACL,eAAA,EAxBU"}
|
|
@@ -6,7 +6,7 @@ import { useEffectEvent } from "@assistant-ui/tap/react-shim";
|
|
|
6
6
|
import { resource, useResource } from "@assistant-ui/tap";
|
|
7
7
|
//#region src/primitives/composer/trigger/TriggerPopoverResource.ts
|
|
8
8
|
/** Composes detection, navigation, keyboard, and selection sub-resources. */
|
|
9
|
-
const
|
|
9
|
+
const useTriggerPopoverResource = ({ adapter, text, triggerChar, behavior, aui, popoverId }) => {
|
|
10
10
|
const detection = useResource(TriggerDetectionResource({
|
|
11
11
|
text,
|
|
12
12
|
triggerChar
|
|
@@ -59,7 +59,8 @@ const TriggerPopoverResource = resource(function TriggerPopoverResource({ adapte
|
|
|
59
59
|
setCursorPosition: detection.setCursorPosition,
|
|
60
60
|
registerSelectItemOverride: selection.registerSelectItemOverride
|
|
61
61
|
};
|
|
62
|
-
}
|
|
62
|
+
};
|
|
63
|
+
const TriggerPopoverResource = resource(useTriggerPopoverResource);
|
|
63
64
|
//#endregion
|
|
64
65
|
export { TriggerPopoverResource };
|
|
65
66
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TriggerPopoverResource.js","names":[],"sources":["../../../../src/primitives/composer/trigger/TriggerPopoverResource.ts"],"sourcesContent":["import { useEffectEvent } from \"react\";\nimport { useResource, resource } from \"@assistant-ui/tap\";\nimport type {\n Unstable_TriggerAdapter,\n Unstable_TriggerCategory,\n Unstable_TriggerItem,\n} from \"@assistant-ui/core\";\nimport type { AssistantClient } from \"@assistant-ui/store\";\nimport { TriggerDetectionResource } from \"./triggerDetectionResource\";\nimport { TriggerKeyboardResource } from \"./triggerKeyboardResource\";\nimport { TriggerNavigationResource } from \"./triggerNavigationResource\";\nimport {\n TriggerSelectionResource,\n type SelectItemOverride,\n type TriggerBehavior,\n} from \"./triggerSelectionResource\";\n\nexport type { SelectItemOverride, TriggerBehavior };\nexport type { TriggerPopoverKeyEvent } from \"./triggerKeyboardResource\";\n\n/** @deprecated Use `TriggerBehavior`. */\nexport type OnSelectBehavior = TriggerBehavior;\n\nexport type TriggerPopoverResourceOutput = {\n readonly open: boolean;\n readonly query: string;\n readonly activeCategoryId: string | null;\n readonly categories: readonly Unstable_TriggerCategory[];\n readonly items: readonly Unstable_TriggerItem[];\n readonly highlightedIndex: number;\n readonly isSearchMode: boolean;\n /** Stable ID prefix for generating accessible element IDs. */\n readonly popoverId: string;\n /** ID of the currently highlighted item (for aria-activedescendant). */\n readonly highlightedItemId: string | undefined;\n\n selectCategory(categoryId: string): void;\n goBack(): void;\n selectItem(item: Unstable_TriggerItem): void;\n close(): void;\n /** Move the highlight to an entry index (e.g. from pointer hover). Out-of-range values are ignored. */\n highlightIndex(index: number): void;\n handleKeyDown(e: {\n readonly key: string;\n readonly shiftKey: boolean;\n preventDefault(): void;\n }): boolean;\n\n setCursorPosition(pos: number): void;\n registerSelectItemOverride(fn: SelectItemOverride): () => void;\n};\n\n/** Composes detection, navigation, keyboard, and selection sub-resources. */\
|
|
1
|
+
{"version":3,"file":"TriggerPopoverResource.js","names":[],"sources":["../../../../src/primitives/composer/trigger/TriggerPopoverResource.ts"],"sourcesContent":["import { useEffectEvent } from \"react\";\nimport { useResource, resource } from \"@assistant-ui/tap\";\nimport type {\n Unstable_TriggerAdapter,\n Unstable_TriggerCategory,\n Unstable_TriggerItem,\n} from \"@assistant-ui/core\";\nimport type { AssistantClient } from \"@assistant-ui/store\";\nimport { TriggerDetectionResource } from \"./triggerDetectionResource\";\nimport { TriggerKeyboardResource } from \"./triggerKeyboardResource\";\nimport { TriggerNavigationResource } from \"./triggerNavigationResource\";\nimport {\n TriggerSelectionResource,\n type SelectItemOverride,\n type TriggerBehavior,\n} from \"./triggerSelectionResource\";\n\nexport type { SelectItemOverride, TriggerBehavior };\nexport type { TriggerPopoverKeyEvent } from \"./triggerKeyboardResource\";\n\n/** @deprecated Use `TriggerBehavior`. */\nexport type OnSelectBehavior = TriggerBehavior;\n\nexport type TriggerPopoverResourceOutput = {\n readonly open: boolean;\n readonly query: string;\n readonly activeCategoryId: string | null;\n readonly categories: readonly Unstable_TriggerCategory[];\n readonly items: readonly Unstable_TriggerItem[];\n readonly highlightedIndex: number;\n readonly isSearchMode: boolean;\n /** Stable ID prefix for generating accessible element IDs. */\n readonly popoverId: string;\n /** ID of the currently highlighted item (for aria-activedescendant). */\n readonly highlightedItemId: string | undefined;\n\n selectCategory(categoryId: string): void;\n goBack(): void;\n selectItem(item: Unstable_TriggerItem): void;\n close(): void;\n /** Move the highlight to an entry index (e.g. from pointer hover). Out-of-range values are ignored. */\n highlightIndex(index: number): void;\n handleKeyDown(e: {\n readonly key: string;\n readonly shiftKey: boolean;\n preventDefault(): void;\n }): boolean;\n\n setCursorPosition(pos: number): void;\n registerSelectItemOverride(fn: SelectItemOverride): () => void;\n};\n\n/** Composes detection, navigation, keyboard, and selection sub-resources. */\nconst useTriggerPopoverResource = ({\n adapter,\n text,\n triggerChar,\n behavior,\n aui,\n popoverId,\n}: {\n adapter: Unstable_TriggerAdapter | undefined;\n text: string;\n triggerChar: string;\n behavior: TriggerBehavior | undefined;\n aui: AssistantClient;\n /** Stable ID for accessible element IDs (pass React's useId() from component layer). */\n popoverId: string;\n}): TriggerPopoverResourceOutput => {\n const detection = useResource(\n TriggerDetectionResource({ text, triggerChar }),\n );\n\n const open =\n detection.trigger !== null &&\n adapter !== undefined &&\n behavior !== undefined;\n\n const navigation = useResource(\n TriggerNavigationResource({\n adapter,\n query: detection.query,\n open,\n }),\n );\n\n const onSelected = useEffectEvent(() => {\n navigation.goBack();\n });\n\n const selection = useResource(\n TriggerSelectionResource({\n behavior,\n trigger: detection.trigger,\n aui,\n triggerChar,\n setCursorPosition: detection.setCursorPosition,\n onSelected,\n }),\n );\n\n const keyboard = useResource(\n TriggerKeyboardResource({\n navigableList: navigation.navigableList,\n isSearchMode: navigation.isSearchMode,\n activeCategoryId: navigation.activeCategoryId,\n query: detection.query,\n popoverId,\n open,\n selectItem: selection.selectItem,\n selectCategory: navigation.selectCategory,\n goBack: navigation.goBack,\n close: selection.close,\n }),\n );\n\n return {\n open,\n query: detection.query,\n activeCategoryId: navigation.activeCategoryId,\n categories: navigation.categories,\n items: navigation.items,\n highlightedIndex: keyboard.highlightedIndex,\n isSearchMode: navigation.isSearchMode,\n popoverId,\n highlightedItemId: keyboard.highlightedItemId,\n selectCategory: navigation.selectCategory,\n goBack: navigation.goBack,\n selectItem: selection.selectItem,\n close: selection.close,\n highlightIndex: keyboard.highlightIndex,\n handleKeyDown: keyboard.handleKeyDown,\n setCursorPosition: detection.setCursorPosition,\n registerSelectItemOverride: selection.registerSelectItemOverride,\n };\n};\n\nexport const TriggerPopoverResource = resource(useTriggerPopoverResource);\n"],"mappings":";;;;;;;;AAqDA,MAAM,6BAA6B,EACjC,SACA,MACA,aACA,UACA,KACA,gBASkC;CAClC,MAAM,YAAY,YAChB,yBAAyB;EAAE;EAAM;CAAY,CAAC,CAChD;CAEA,MAAM,OACJ,UAAU,YAAY,QACtB,YAAY,KAAA,KACZ,aAAa,KAAA;CAEf,MAAM,aAAa,YACjB,0BAA0B;EACxB;EACA,OAAO,UAAU;EACjB;CACF,CAAC,CACH;CAEA,MAAM,aAAa,qBAAqB;EACtC,WAAW,OAAO;CACpB,CAAC;CAED,MAAM,YAAY,YAChB,yBAAyB;EACvB;EACA,SAAS,UAAU;EACnB;EACA;EACA,mBAAmB,UAAU;EAC7B;CACF,CAAC,CACH;CAEA,MAAM,WAAW,YACf,wBAAwB;EACtB,eAAe,WAAW;EAC1B,cAAc,WAAW;EACzB,kBAAkB,WAAW;EAC7B,OAAO,UAAU;EACjB;EACA;EACA,YAAY,UAAU;EACtB,gBAAgB,WAAW;EAC3B,QAAQ,WAAW;EACnB,OAAO,UAAU;CACnB,CAAC,CACH;CAEA,OAAO;EACL;EACA,OAAO,UAAU;EACjB,kBAAkB,WAAW;EAC7B,YAAY,WAAW;EACvB,OAAO,WAAW;EAClB,kBAAkB,SAAS;EAC3B,cAAc,WAAW;EACzB;EACA,mBAAmB,SAAS;EAC5B,gBAAgB,WAAW;EAC3B,QAAQ,WAAW;EACnB,YAAY,UAAU;EACtB,OAAO,UAAU;EACjB,gBAAgB,SAAS;EACzB,eAAe,SAAS;EACxB,mBAAmB,UAAU;EAC7B,4BAA4B,UAAU;CACxC;AACF;AAEA,MAAa,yBAAyB,SAAS,yBAAyB"}
|
|
@@ -9,14 +9,10 @@ type TriggerDetectionResourceOutput = {
|
|
|
9
9
|
readonly query: string; /** Update the tracked cursor position (wired to composer input). */
|
|
10
10
|
setCursorPosition(pos: number): void;
|
|
11
11
|
};
|
|
12
|
-
|
|
13
|
-
declare const TriggerDetectionResource: (props: {
|
|
12
|
+
declare const TriggerDetectionResource: import("@assistant-ui/tap").Resource<TriggerDetectionResourceOutput, [{
|
|
14
13
|
text: string;
|
|
15
14
|
triggerChar: string;
|
|
16
|
-
}
|
|
17
|
-
text: string;
|
|
18
|
-
triggerChar: string;
|
|
19
|
-
}>;
|
|
15
|
+
}]>;
|
|
20
16
|
//#endregion
|
|
21
17
|
export { DetectedTrigger, TriggerDetectionResource, TriggerDetectionResourceOutput };
|
|
22
18
|
//# sourceMappingURL=triggerDetectionResource.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"triggerDetectionResource.d.ts","names":[],"sources":["../../../../src/primitives/composer/trigger/triggerDetectionResource.ts"],"mappings":";;KAKY,eAAA;EAAA,SACD,MAAA;EAAA,SACA,KAAK;AAAA;AAAA,KAGJ,8BAAA;EAAA,2DAED,OAAA,EAAS,eAAe;WAExB,KAAA,UAFA;EAIT,iBAAA,CAAkB,GAAA;AAAA
|
|
1
|
+
{"version":3,"file":"triggerDetectionResource.d.ts","names":[],"sources":["../../../../src/primitives/composer/trigger/triggerDetectionResource.ts"],"mappings":";;KAKY,eAAA;EAAA,SACD,MAAA;EAAA,SACA,KAAK;AAAA;AAAA,KAGJ,8BAAA;EAAA,2DAED,OAAA,EAAS,eAAe;WAExB,KAAA,UAFA;EAIT,iBAAA,CAAkB,GAAA;AAAA;AAAA,cA2BP,wBAAA,8BAAwB,QAAA,CAAA,8BAAA"}
|
|
@@ -3,7 +3,7 @@ import { useMemo, useState } from "@assistant-ui/tap/react-shim";
|
|
|
3
3
|
import { resource } from "@assistant-ui/tap";
|
|
4
4
|
//#region src/primitives/composer/trigger/triggerDetectionResource.ts
|
|
5
5
|
/** Tracks cursor position and derives the active trigger + query from composer text. */
|
|
6
|
-
const
|
|
6
|
+
const useTriggerDetectionResource = ({ text, triggerChar }) => {
|
|
7
7
|
const [cursorPosition, setCursorPosition] = useState(text.length);
|
|
8
8
|
const trigger = useMemo(() => {
|
|
9
9
|
return detectTrigger(text, triggerChar, Math.min(cursorPosition, text.length));
|
|
@@ -17,7 +17,8 @@ const TriggerDetectionResource = resource(function TriggerDetectionResource({ te
|
|
|
17
17
|
query: trigger?.query ?? "",
|
|
18
18
|
setCursorPosition
|
|
19
19
|
};
|
|
20
|
-
}
|
|
20
|
+
};
|
|
21
|
+
const TriggerDetectionResource = resource(useTriggerDetectionResource);
|
|
21
22
|
//#endregion
|
|
22
23
|
export { TriggerDetectionResource };
|
|
23
24
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"triggerDetectionResource.js","names":[],"sources":["../../../../src/primitives/composer/trigger/triggerDetectionResource.ts"],"sourcesContent":["import { useMemo, useState } from \"react\";\nimport { resource } from \"@assistant-ui/tap\";\nimport { detectTrigger } from \"./detectTrigger\";\n\n/** Detected trigger position within the composer text. */\nexport type DetectedTrigger = {\n readonly offset: number;\n readonly query: string;\n};\n\nexport type TriggerDetectionResourceOutput = {\n /** Detected trigger (or `null` when inactive). */\n readonly trigger: DetectedTrigger | null;\n /** Current query string (empty when no trigger active). */\n readonly query: string;\n /** Update the tracked cursor position (wired to composer input). */\n setCursorPosition(pos: number): void;\n};\n\n/** Tracks cursor position and derives the active trigger + query from composer text. */\
|
|
1
|
+
{"version":3,"file":"triggerDetectionResource.js","names":[],"sources":["../../../../src/primitives/composer/trigger/triggerDetectionResource.ts"],"sourcesContent":["import { useMemo, useState } from \"react\";\nimport { resource } from \"@assistant-ui/tap\";\nimport { detectTrigger } from \"./detectTrigger\";\n\n/** Detected trigger position within the composer text. */\nexport type DetectedTrigger = {\n readonly offset: number;\n readonly query: string;\n};\n\nexport type TriggerDetectionResourceOutput = {\n /** Detected trigger (or `null` when inactive). */\n readonly trigger: DetectedTrigger | null;\n /** Current query string (empty when no trigger active). */\n readonly query: string;\n /** Update the tracked cursor position (wired to composer input). */\n setCursorPosition(pos: number): void;\n};\n\n/** Tracks cursor position and derives the active trigger + query from composer text. */\nconst useTriggerDetectionResource = ({\n text,\n triggerChar,\n}: {\n text: string;\n triggerChar: string;\n}): TriggerDetectionResourceOutput => {\n const [cursorPosition, setCursorPosition] = useState(text.length);\n\n const trigger = useMemo(() => {\n const pos = Math.min(cursorPosition, text.length);\n return detectTrigger(text, triggerChar, pos);\n }, [cursorPosition, text, triggerChar]);\n\n const query = trigger?.query ?? \"\";\n\n return {\n trigger,\n query,\n setCursorPosition,\n };\n};\n\nexport const TriggerDetectionResource = resource(useTriggerDetectionResource);\n"],"mappings":";;;;;AAoBA,MAAM,+BAA+B,EACnC,MACA,kBAIoC;CACpC,MAAM,CAAC,gBAAgB,qBAAqB,SAAS,KAAK,MAAM;CAEhE,MAAM,UAAU,cAAc;EAE5B,OAAO,cAAc,MAAM,aADf,KAAK,IAAI,gBAAgB,KAAK,MACA,CAAC;CAC7C,GAAG;EAAC;EAAgB;EAAM;CAAW,CAAC;CAItC,OAAO;EACL;EACA,OAJY,SAAS,SAAS;EAK9B;CACF;AACF;AAEA,MAAa,2BAA2B,SAAS,2BAA2B"}
|
|
@@ -13,11 +13,7 @@ type TriggerKeyboardResourceOutput = {
|
|
|
13
13
|
highlightIndex(index: number): void; /** Handle a key event; returns `true` if it was consumed. */
|
|
14
14
|
handleKeyDown(e: TriggerPopoverKeyEvent): boolean;
|
|
15
15
|
};
|
|
16
|
-
|
|
17
|
-
* Owns keyboard-driven highlight state for the popover. Delegates selection,
|
|
18
|
-
* category drill-in, back, and close to the callbacks supplied by the parent.
|
|
19
|
-
*/
|
|
20
|
-
declare const TriggerKeyboardResource: (props: {
|
|
16
|
+
declare const TriggerKeyboardResource: import("@assistant-ui/tap").Resource<TriggerKeyboardResourceOutput, [{
|
|
21
17
|
navigableList: readonly (Unstable_TriggerCategory | Unstable_TriggerItem)[];
|
|
22
18
|
isSearchMode: boolean;
|
|
23
19
|
activeCategoryId: string | null;
|
|
@@ -28,18 +24,7 @@ declare const TriggerKeyboardResource: (props: {
|
|
|
28
24
|
selectCategory: (categoryId: string) => void;
|
|
29
25
|
goBack: () => void;
|
|
30
26
|
close: () => void;
|
|
31
|
-
}
|
|
32
|
-
navigableList: readonly (Unstable_TriggerCategory | Unstable_TriggerItem)[];
|
|
33
|
-
isSearchMode: boolean;
|
|
34
|
-
activeCategoryId: string | null;
|
|
35
|
-
query: string;
|
|
36
|
-
popoverId: string;
|
|
37
|
-
open: boolean;
|
|
38
|
-
selectItem: (item: Unstable_TriggerItem) => void;
|
|
39
|
-
selectCategory: (categoryId: string) => void;
|
|
40
|
-
goBack: () => void;
|
|
41
|
-
close: () => void;
|
|
42
|
-
}>;
|
|
27
|
+
}]>;
|
|
43
28
|
//#endregion
|
|
44
29
|
export { TriggerKeyboardResource, TriggerKeyboardResourceOutput, TriggerPopoverKeyEvent };
|
|
45
30
|
//# sourceMappingURL=triggerKeyboardResource.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"triggerKeyboardResource.d.ts","names":[],"sources":["../../../../src/primitives/composer/trigger/triggerKeyboardResource.ts"],"mappings":";;;;KAeY,sBAAA;EAAA,SACD,GAAA;EAAA,SACA,QAAA;EACT,cAAA;AAAA;AAAA,KAGU,6BAAA;EAJD,mFAMA,gBAAA,UALK;EAAA,SAOL,iBAAA,sBAJC;EAMV,cAAA,CAAe,KAAA;EAEf,aAAA,CAAc,CAAA,EAAG,sBAAsB;AAAA
|
|
1
|
+
{"version":3,"file":"triggerKeyboardResource.d.ts","names":[],"sources":["../../../../src/primitives/composer/trigger/triggerKeyboardResource.ts"],"mappings":";;;;KAeY,sBAAA;EAAA,SACD,GAAA;EAAA,SACA,QAAA;EACT,cAAA;AAAA;AAAA,KAGU,6BAAA;EAJD,mFAMA,gBAAA,UALK;EAAA,SAOL,iBAAA,sBAJC;EAMV,cAAA,CAAe,KAAA;EAEf,aAAA,CAAc,CAAA,EAAG,sBAAsB;AAAA;AAAA,cAkH5B,uBAAA,8BAAuB,QAAA,CAAA,6BAAA;2BA/FT,wBAAA,GAA2B,oBAAA;;;;;;eAMvC,IAAA,EAAM,oBAAA;mBACF,UAAA"}
|
|
@@ -9,7 +9,7 @@ function isTriggerItem(x) {
|
|
|
9
9
|
* Owns keyboard-driven highlight state for the popover. Delegates selection,
|
|
10
10
|
* category drill-in, back, and close to the callbacks supplied by the parent.
|
|
11
11
|
*/
|
|
12
|
-
const
|
|
12
|
+
const useTriggerKeyboardResource = ({ navigableList, isSearchMode, activeCategoryId, query, popoverId, open, selectItem, selectCategory, goBack, close }) => {
|
|
13
13
|
const [highlightedIndex, setHighlightedIndex] = useState(0);
|
|
14
14
|
useEffect(() => {
|
|
15
15
|
setHighlightedIndex(0);
|
|
@@ -72,7 +72,8 @@ const TriggerKeyboardResource = resource(function TriggerKeyboardResource({ navi
|
|
|
72
72
|
highlightIndex,
|
|
73
73
|
handleKeyDown
|
|
74
74
|
};
|
|
75
|
-
}
|
|
75
|
+
};
|
|
76
|
+
const TriggerKeyboardResource = resource(useTriggerKeyboardResource);
|
|
76
77
|
//#endregion
|
|
77
78
|
export { TriggerKeyboardResource };
|
|
78
79
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"triggerKeyboardResource.js","names":[],"sources":["../../../../src/primitives/composer/trigger/triggerKeyboardResource.ts"],"sourcesContent":["import { useEffect, useEffectEvent, useState } from \"react\";\nimport { resource } from \"@assistant-ui/tap\";\nimport type {\n Unstable_TriggerCategory,\n Unstable_TriggerItem,\n} from \"@assistant-ui/core\";\n\n/** Relies on `Unstable_TriggerCategory` never carrying a `type` field. */\nfunction isTriggerItem(\n x: Unstable_TriggerItem | Unstable_TriggerCategory,\n): x is Unstable_TriggerItem {\n return \"type\" in x;\n}\n\n/** Key event shape accepted by the keyboard handler. */\nexport type TriggerPopoverKeyEvent = {\n readonly key: string;\n readonly shiftKey: boolean;\n preventDefault(): void;\n};\n\nexport type TriggerKeyboardResourceOutput = {\n /** Index of the currently highlighted entry within the navigable list. */\n readonly highlightedIndex: number;\n /** ID of the currently highlighted item (for `aria-activedescendant`). */\n readonly highlightedItemId: string | undefined;\n /** Move the highlight to an entry index (e.g. from pointer hover). Out-of-range values are ignored. */\n highlightIndex(index: number): void;\n /** Handle a key event; returns `true` if it was consumed. */\n handleKeyDown(e: TriggerPopoverKeyEvent): boolean;\n};\n\n/**\n * Owns keyboard-driven highlight state for the popover. Delegates selection,\n * category drill-in, back, and close to the callbacks supplied by the parent.\n */\
|
|
1
|
+
{"version":3,"file":"triggerKeyboardResource.js","names":[],"sources":["../../../../src/primitives/composer/trigger/triggerKeyboardResource.ts"],"sourcesContent":["import { useEffect, useEffectEvent, useState } from \"react\";\nimport { resource } from \"@assistant-ui/tap\";\nimport type {\n Unstable_TriggerCategory,\n Unstable_TriggerItem,\n} from \"@assistant-ui/core\";\n\n/** Relies on `Unstable_TriggerCategory` never carrying a `type` field. */\nfunction isTriggerItem(\n x: Unstable_TriggerItem | Unstable_TriggerCategory,\n): x is Unstable_TriggerItem {\n return \"type\" in x;\n}\n\n/** Key event shape accepted by the keyboard handler. */\nexport type TriggerPopoverKeyEvent = {\n readonly key: string;\n readonly shiftKey: boolean;\n preventDefault(): void;\n};\n\nexport type TriggerKeyboardResourceOutput = {\n /** Index of the currently highlighted entry within the navigable list. */\n readonly highlightedIndex: number;\n /** ID of the currently highlighted item (for `aria-activedescendant`). */\n readonly highlightedItemId: string | undefined;\n /** Move the highlight to an entry index (e.g. from pointer hover). Out-of-range values are ignored. */\n highlightIndex(index: number): void;\n /** Handle a key event; returns `true` if it was consumed. */\n handleKeyDown(e: TriggerPopoverKeyEvent): boolean;\n};\n\n/**\n * Owns keyboard-driven highlight state for the popover. Delegates selection,\n * category drill-in, back, and close to the callbacks supplied by the parent.\n */\nconst useTriggerKeyboardResource = ({\n navigableList,\n isSearchMode,\n activeCategoryId,\n query,\n popoverId,\n open,\n selectItem,\n selectCategory,\n goBack,\n close,\n}: {\n navigableList: readonly (Unstable_TriggerCategory | Unstable_TriggerItem)[];\n isSearchMode: boolean;\n activeCategoryId: string | null;\n query: string;\n popoverId: string;\n open: boolean;\n selectItem: (item: Unstable_TriggerItem) => void;\n selectCategory: (categoryId: string) => void;\n goBack: () => void;\n close: () => void;\n}): TriggerKeyboardResourceOutput => {\n const [highlightedIndex, setHighlightedIndex] = useState(0);\n\n useEffect(() => {\n setHighlightedIndex(0);\n }, [navigableList]);\n\n useEffect(() => {\n setHighlightedIndex(0);\n }, [isSearchMode, activeCategoryId]);\n\n const highlightIndex = useEffectEvent((index: number) => {\n if (index < 0 || index >= navigableList.length) return;\n if (index === highlightedIndex) return;\n setHighlightedIndex(index);\n });\n\n const handleKeyDown = useEffectEvent((e: TriggerPopoverKeyEvent): boolean => {\n if (!open) return false;\n\n switch (e.key) {\n case \"ArrowDown\": {\n e.preventDefault();\n setHighlightedIndex((prev) => {\n const len = navigableList.length;\n if (len === 0) return 0;\n return prev < len - 1 ? prev + 1 : 0;\n });\n return true;\n }\n case \"ArrowUp\": {\n e.preventDefault();\n setHighlightedIndex((prev) => {\n const len = navigableList.length;\n if (len === 0) return 0;\n return prev > 0 ? prev - 1 : len - 1;\n });\n return true;\n }\n case \"Enter\":\n case \"Tab\": {\n if (e.shiftKey) return false;\n e.preventDefault();\n const item = navigableList[highlightedIndex];\n if (!item) return true;\n\n if (isTriggerItem(item)) {\n selectItem(item);\n } else {\n selectCategory(item.id);\n }\n return true;\n }\n case \"Escape\": {\n e.preventDefault();\n close();\n return true;\n }\n case \"Backspace\": {\n if (activeCategoryId && query === \"\") {\n e.preventDefault();\n goBack();\n return true;\n }\n return false;\n }\n default:\n return false;\n }\n });\n\n const highlightedEntry = navigableList[highlightedIndex];\n const highlightedItemId =\n open && highlightedEntry\n ? `${popoverId}-option-${highlightedEntry.id}`\n : undefined;\n\n return {\n highlightedIndex,\n highlightedItemId,\n highlightIndex,\n handleKeyDown,\n };\n};\n\nexport const TriggerKeyboardResource = resource(useTriggerKeyboardResource);\n"],"mappings":";;;;AAQA,SAAS,cACP,GAC2B;CAC3B,OAAO,UAAU;AACnB;;;;;AAwBA,MAAM,8BAA8B,EAClC,eACA,cACA,kBACA,OACA,WACA,MACA,YACA,gBACA,QACA,YAYmC;CACnC,MAAM,CAAC,kBAAkB,uBAAuB,SAAS,CAAC;CAE1D,gBAAgB;EACd,oBAAoB,CAAC;CACvB,GAAG,CAAC,aAAa,CAAC;CAElB,gBAAgB;EACd,oBAAoB,CAAC;CACvB,GAAG,CAAC,cAAc,gBAAgB,CAAC;CAEnC,MAAM,iBAAiB,gBAAgB,UAAkB;EACvD,IAAI,QAAQ,KAAK,SAAS,cAAc,QAAQ;EAChD,IAAI,UAAU,kBAAkB;EAChC,oBAAoB,KAAK;CAC3B,CAAC;CAED,MAAM,gBAAgB,gBAAgB,MAAuC;EAC3E,IAAI,CAAC,MAAM,OAAO;EAElB,QAAQ,EAAE,KAAV;GACE,KAAK;IACH,EAAE,eAAe;IACjB,qBAAqB,SAAS;KAC5B,MAAM,MAAM,cAAc;KAC1B,IAAI,QAAQ,GAAG,OAAO;KACtB,OAAO,OAAO,MAAM,IAAI,OAAO,IAAI;IACrC,CAAC;IACD,OAAO;GAET,KAAK;IACH,EAAE,eAAe;IACjB,qBAAqB,SAAS;KAC5B,MAAM,MAAM,cAAc;KAC1B,IAAI,QAAQ,GAAG,OAAO;KACtB,OAAO,OAAO,IAAI,OAAO,IAAI,MAAM;IACrC,CAAC;IACD,OAAO;GAET,KAAK;GACL,KAAK,OAAO;IACV,IAAI,EAAE,UAAU,OAAO;IACvB,EAAE,eAAe;IACjB,MAAM,OAAO,cAAc;IAC3B,IAAI,CAAC,MAAM,OAAO;IAElB,IAAI,cAAc,IAAI,GACpB,WAAW,IAAI;SAEf,eAAe,KAAK,EAAE;IAExB,OAAO;GACT;GACA,KAAK;IACH,EAAE,eAAe;IACjB,MAAM;IACN,OAAO;GAET,KAAK;IACH,IAAI,oBAAoB,UAAU,IAAI;KACpC,EAAE,eAAe;KACjB,OAAO;KACP,OAAO;IACT;IACA,OAAO;GAET,SACE,OAAO;EACX;CACF,CAAC;CAED,MAAM,mBAAmB,cAAc;CAMvC,OAAO;EACL;EACA,mBANA,QAAQ,mBACJ,GAAG,UAAU,UAAU,iBAAiB,OACxC,KAAA;EAKJ;EACA;CACF;AACF;AAEA,MAAa,0BAA0B,SAAS,0BAA0B"}
|
|
@@ -10,19 +10,11 @@ type TriggerNavigationResourceOutput = {
|
|
|
10
10
|
selectCategory(categoryId: string): void; /** Return to the top-level category list. */
|
|
11
11
|
goBack(): void;
|
|
12
12
|
};
|
|
13
|
-
|
|
14
|
-
* Computes categories, items, search results, and navigation state from the
|
|
15
|
-
* adapter + current query. Pure derivation — no side effects on the composer.
|
|
16
|
-
*/
|
|
17
|
-
declare const TriggerNavigationResource: (props: {
|
|
13
|
+
declare const TriggerNavigationResource: import("@assistant-ui/tap").Resource<TriggerNavigationResourceOutput, [{
|
|
18
14
|
adapter: Unstable_TriggerAdapter | undefined;
|
|
19
15
|
query: string;
|
|
20
16
|
open: boolean;
|
|
21
|
-
}
|
|
22
|
-
adapter: Unstable_TriggerAdapter | undefined;
|
|
23
|
-
query: string;
|
|
24
|
-
open: boolean;
|
|
25
|
-
}>;
|
|
17
|
+
}]>;
|
|
26
18
|
//#endregion
|
|
27
19
|
export { TriggerNavigationResource, TriggerNavigationResourceOutput };
|
|
28
20
|
//# sourceMappingURL=triggerNavigationResource.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"triggerNavigationResource.d.ts","names":[],"sources":["../../../../src/primitives/composer/trigger/triggerNavigationResource.ts"],"mappings":";;;KAgBY,+BAAA;iFAED,UAAA,WAAqB,wBAAA,IAFW;EAAA,SAIhC,KAAA,WAAgB,oBAAA,IAFK;EAAA,SAIrB,YAAA,WAKL;EAAA,SAHK,gBAAA,iBAIe;EAAA,SAFf,aAAA,YACL,wBAAA,GACA,oBAAA,KAVK;EAaT,cAAA,CAAe,UAAA,iBAXN;EAaT,MAAA;AAAA
|
|
1
|
+
{"version":3,"file":"triggerNavigationResource.d.ts","names":[],"sources":["../../../../src/primitives/composer/trigger/triggerNavigationResource.ts"],"mappings":";;;KAgBY,+BAAA;iFAED,UAAA,WAAqB,wBAAA,IAFW;EAAA,SAIhC,KAAA,WAAgB,oBAAA,IAFK;EAAA,SAIrB,YAAA,WAKL;EAAA,SAHK,gBAAA,iBAIe;EAAA,SAFf,aAAA,YACL,wBAAA,GACA,oBAAA,KAVK;EAaT,cAAA,CAAe,UAAA,iBAXN;EAaT,MAAA;AAAA;AAAA,cAoGW,yBAAA,8BAAyB,QAAA,CAAA,+BAAA;WAxF3B,uBAAuB"}
|