@assistant-ui/react 0.14.20 → 0.14.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/dist/context/react/utils/createContextStoreHook.d.ts.map +1 -1
  2. package/dist/context/react/utils/createContextStoreHook.js +3 -18
  3. package/dist/context/react/utils/createContextStoreHook.js.map +1 -1
  4. package/dist/index.d.ts +3 -2
  5. package/dist/index.js +3 -2
  6. package/dist/internal.d.ts +3 -3
  7. package/dist/internal.js +2 -4
  8. package/dist/internal.js.map +1 -1
  9. package/dist/primitives/composer/trigger/TriggerPopover.d.ts +4 -2
  10. package/dist/primitives/composer/trigger/TriggerPopover.d.ts.map +1 -1
  11. package/dist/primitives/composer/trigger/TriggerPopover.js +125 -119
  12. package/dist/primitives/composer/trigger/TriggerPopover.js.map +1 -1
  13. package/dist/primitives/composer/trigger/TriggerPopoverResource.d.ts +3 -1
  14. package/dist/primitives/composer/trigger/TriggerPopoverResource.d.ts.map +1 -1
  15. package/dist/primitives/composer/trigger/TriggerPopoverResource.js +22 -20
  16. package/dist/primitives/composer/trigger/TriggerPopoverResource.js.map +1 -1
  17. package/dist/primitives/composer/trigger/index.d.ts +1 -0
  18. package/dist/unstable/useLiveCompletionAdapter.d.ts +47 -0
  19. package/dist/unstable/useLiveCompletionAdapter.d.ts.map +1 -0
  20. package/dist/unstable/useLiveCompletionAdapter.js +116 -0
  21. package/dist/unstable/useLiveCompletionAdapter.js.map +1 -0
  22. package/dist/utils/smooth/useSmooth.d.ts +8 -0
  23. package/dist/utils/smooth/useSmooth.d.ts.map +1 -1
  24. package/dist/utils/smooth/useSmooth.js +11 -2
  25. package/dist/utils/smooth/useSmooth.js.map +1 -1
  26. package/package.json +4 -4
  27. package/src/context/react/utils/createContextStoreHook.ts +4 -3
  28. package/src/index.ts +7 -0
  29. package/src/internal.ts +0 -2
  30. package/src/primitives/composer/trigger/TriggerPopover.tsx +11 -1
  31. package/src/primitives/composer/trigger/TriggerPopoverResource.ts +5 -0
  32. package/src/tests/ExternalStoreThreadRuntimeCore.test.ts +113 -0
  33. package/src/unstable/useLiveCompletionAdapter.test.tsx +176 -0
  34. package/src/unstable/useLiveCompletionAdapter.ts +145 -0
  35. package/src/utils/smooth/useSmooth.test.tsx +106 -2
  36. package/src/utils/smooth/useSmooth.ts +20 -2
@@ -1 +1 @@
1
- {"version":3,"file":"TriggerPopover.js","names":["c","_c","useAui","useAuiState","useResource","Unstable_TriggerAdapter","createContext","forwardRef","useCallback","useContext","useEffect","useEffectEvent","useId","useMemo","useRef","useState","ComponentPropsWithoutRef","ComponentRef","Primitive","useComposerInputPluginRegistryOptional","TriggerPopoverResource","TriggerPopoverResourceOutput","TriggerBehavior","useTriggerPopoverAriaPublish","useTriggerPopoverRootContext","TriggerPopoverScopeContext","useTriggerPopoverScopeContext","ctx","Error","useTriggerPopoverScopeContextOptional","TriggerBehaviorRegistration","register","behavior","TriggerBehaviorRegistrationContext","useTriggerBehaviorRegistration","ComposerPrimitiveTriggerPopover","Element","div","Props","Omit","char","adapter","t0","forwardedRef","$","ariaLabel","children","props","aui","text","_temp","popoverId","behaviorRef","setBehavior","registrationCountRef","t1","next","current","process","env","NODE_ENV","console","warn","Math","max","t2","registration","t3","undefined","t4","triggerChar","resource","t5","getResource","root","t6","t7","pluginRegistry","t8","t9","open","aria","t10","t11","setActiveAria","t12","t13","highlightedItemId","t14","t15","t16","displayName","s","composer"],"sources":["../../../../src/primitives/composer/trigger/TriggerPopover.tsx"],"sourcesContent":["\"use client\";\n\nimport { useAui, useAuiState } from \"@assistant-ui/store\";\nimport { useResource } from \"@assistant-ui/tap\";\nimport type { Unstable_TriggerAdapter } from \"@assistant-ui/core\";\nimport {\n createContext,\n forwardRef,\n useCallback,\n useContext,\n useEffect,\n useEffectEvent,\n useId,\n useMemo,\n useRef,\n useState,\n type ComponentPropsWithoutRef,\n type ComponentRef,\n} from \"react\";\nimport { Primitive } from \"../../../utils/Primitive\";\nimport { useComposerInputPluginRegistryOptional } from \"../ComposerInputPluginContext\";\nimport {\n TriggerPopoverResource,\n type TriggerPopoverResourceOutput,\n} from \"./TriggerPopoverResource\";\nimport type { TriggerBehavior } from \"./triggerSelectionResource\";\nimport {\n useTriggerPopoverAriaPublish,\n useTriggerPopoverRootContext,\n} from \"./TriggerPopoverRootContext\";\n\nconst TriggerPopoverScopeContext =\n createContext<TriggerPopoverResourceOutput | null>(null);\n\nexport const useTriggerPopoverScopeContext = () => {\n const ctx = useContext(TriggerPopoverScopeContext);\n if (!ctx)\n throw new Error(\n \"useTriggerPopoverScopeContext must be used within ComposerPrimitive.TriggerPopover\",\n );\n return ctx;\n};\n\nexport const useTriggerPopoverScopeContextOptional = () =>\n useContext(TriggerPopoverScopeContext);\n\n/** Registration API exposed to behavior sub-primitives. */\nexport type TriggerBehaviorRegistration = {\n register(behavior: TriggerBehavior): () => void;\n};\n\nconst TriggerBehaviorRegistrationContext =\n createContext<TriggerBehaviorRegistration | null>(null);\n\n/** Obtain the registration handle from the parent `<TriggerPopover>`. */\nexport const useTriggerBehaviorRegistration = () => {\n const ctx = useContext(TriggerBehaviorRegistrationContext);\n if (!ctx)\n throw new Error(\n \"TriggerPopover.Directive / TriggerPopover.Action must be rendered inside ComposerPrimitive.TriggerPopover\",\n );\n return ctx;\n};\n\nexport namespace ComposerPrimitiveTriggerPopover {\n export type Element = ComponentRef<typeof Primitive.div>;\n export type Props = Omit<\n ComponentPropsWithoutRef<typeof Primitive.div>,\n \"onSelect\"\n > & {\n /** The character(s) that activate this trigger (e.g. `\"@\"`, `\"/\"`). Also serves as the trigger identity within the root. */\n readonly char: string;\n /** Adapter providing categories and items. */\n readonly adapter?: Unstable_TriggerAdapter | undefined;\n };\n}\n\n/**\n * Declares a trigger and renders its popover container. The popover only\n * renders its DOM (and children) when the trigger character is active in the\n * composer input and a behavior sub-primitive has been registered.\n *\n * A behavior is contributed by rendering exactly one of\n * `<TriggerPopover.Directive>` or `<TriggerPopover.Action>` as a child. Without\n * a behavior the trigger stays closed.\n *\n * Must be placed inside `ComposerPrimitive.Unstable_TriggerPopoverRoot`.\n *\n * @example\n * ```tsx\n * <ComposerPrimitive.Unstable_TriggerPopover\n * char=\"@\"\n * adapter={mentionAdapter}\n * >\n * <ComposerPrimitive.Unstable_TriggerPopover.Directive formatter={formatter} />\n * <ComposerPrimitive.Unstable_TriggerPopoverCategories>\n * {(cats) => cats.map(...)}\n * </ComposerPrimitive.Unstable_TriggerPopoverCategories>\n * <ComposerPrimitive.Unstable_TriggerPopoverItems>\n * {(items) => items.map(...)}\n * </ComposerPrimitive.Unstable_TriggerPopoverItems>\n * </ComposerPrimitive.Unstable_TriggerPopover>\n * ```\n */\nexport const ComposerPrimitiveTriggerPopover = forwardRef<\n ComposerPrimitiveTriggerPopover.Element,\n ComposerPrimitiveTriggerPopover.Props\n>(\n (\n { char, adapter, \"aria-label\": ariaLabel, children, ...props },\n forwardedRef,\n ) => {\n const aui = useAui();\n const text = useAuiState((s) => s.composer.text);\n const popoverId = useId();\n\n // Track in state (for resource reactivity) + ref (dev warning on duplicate registrations).\n const behaviorRef = useRef<TriggerBehavior | null>(null);\n const [behavior, setBehavior] = useState<TriggerBehavior | null>(null);\n const registrationCountRef = useRef(0);\n\n const register = useCallback<TriggerBehaviorRegistration[\"register\"]>(\n (next) => {\n registrationCountRef.current += 1;\n if (\n process.env.NODE_ENV !== \"production\" &&\n registrationCountRef.current > 1\n ) {\n console.warn(\n `[assistant-ui] TriggerPopover \"${char}\" received more than one behavior child. Exactly one <TriggerPopover.Directive> or <TriggerPopover.Action> is allowed per TriggerPopover; the last registration wins.`,\n );\n }\n behaviorRef.current = next;\n setBehavior(next);\n return () => {\n registrationCountRef.current = Math.max(\n 0,\n registrationCountRef.current - 1,\n );\n if (behaviorRef.current === next) {\n behaviorRef.current = null;\n setBehavior(null);\n }\n };\n },\n [char],\n );\n\n const registration = useMemo<TriggerBehaviorRegistration>(\n () => ({ register }),\n [register],\n );\n\n const resource = useResource(\n TriggerPopoverResource({\n adapter,\n text,\n triggerChar: char,\n behavior: behavior ?? undefined,\n aui,\n popoverId,\n }),\n );\n\n const getResource = useEffectEvent(() => resource);\n\n const root = useTriggerPopoverRootContext();\n useEffect(() => {\n return root.register({\n char,\n ...(behavior ? { behavior } : {}),\n resource: getResource(),\n });\n }, [root, char, behavior]);\n\n const pluginRegistry = useComposerInputPluginRegistryOptional();\n useEffect(() => {\n if (!pluginRegistry) return undefined;\n return pluginRegistry.register(getResource());\n }, [pluginRegistry]);\n\n const open = behavior !== null && resource.open;\n\n const aria = useTriggerPopoverAriaPublish();\n\n useEffect(() => {\n if (!open) return undefined;\n return () => {\n aria.setActiveAria(char, null);\n };\n }, [aria, char, open]);\n\n useEffect(() => {\n if (!open) return;\n aria.setActiveAria(char, {\n popoverId,\n highlightedItemId: resource.highlightedItemId,\n });\n }, [aria, char, popoverId, open, resource.highlightedItemId]);\n\n return (\n <TriggerBehaviorRegistrationContext.Provider value={registration}>\n <TriggerPopoverScopeContext.Provider value={resource}>\n {open ? (\n <Primitive.div\n role=\"listbox\"\n id={popoverId}\n aria-label={ariaLabel ?? \"Suggestions\"}\n aria-activedescendant={resource.highlightedItemId}\n data-state=\"open\"\n {...props}\n ref={forwardedRef}\n >\n {children}\n </Primitive.div>\n ) : (\n children\n )}\n </TriggerPopoverScopeContext.Provider>\n </TriggerBehaviorRegistrationContext.Provider>\n );\n },\n);\n\nComposerPrimitiveTriggerPopover.displayName =\n \"ComposerPrimitive.TriggerPopover\";\n"],"mappings":"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"}
1
+ {"version":3,"file":"TriggerPopover.js","names":["c","_c","useAui","useAuiState","useResource","Unstable_TriggerAdapter","createContext","forwardRef","useCallback","useContext","useEffect","useEffectEvent","useId","useMemo","useRef","useState","ComponentPropsWithoutRef","ComponentRef","Primitive","useComposerInputPluginRegistryOptional","TriggerPopoverResource","TriggerPopoverResourceOutput","TriggerBehavior","useTriggerPopoverAriaPublish","useTriggerPopoverRootContext","TriggerPopoverScopeContext","useTriggerPopoverScopeContext","ctx","Error","useTriggerPopoverScopeContextOptional","TriggerBehaviorRegistration","register","behavior","TriggerBehaviorRegistrationContext","useTriggerBehaviorRegistration","ComposerPrimitiveTriggerPopover","Element","div","Props","Omit","char","adapter","isLoading","t0","forwardedRef","$","ariaLabel","children","props","t1","undefined","aui","text","_temp","popoverId","behaviorRef","setBehavior","registrationCountRef","t2","next","current","process","env","NODE_ENV","console","warn","Math","max","t3","registration","t4","t5","triggerChar","resource","t6","getResource","root","t7","t8","pluginRegistry","t9","t10","open","aria","t11","t12","setActiveAria","t13","t14","highlightedItemId","t15","t16","t17","displayName","s","composer"],"sources":["../../../../src/primitives/composer/trigger/TriggerPopover.tsx"],"sourcesContent":["\"use client\";\n\nimport { useAui, useAuiState } from \"@assistant-ui/store\";\nimport { useResource } from \"@assistant-ui/tap\";\nimport type { Unstable_TriggerAdapter } from \"@assistant-ui/core\";\nimport {\n createContext,\n forwardRef,\n useCallback,\n useContext,\n useEffect,\n useEffectEvent,\n useId,\n useMemo,\n useRef,\n useState,\n type ComponentPropsWithoutRef,\n type ComponentRef,\n} from \"react\";\nimport { Primitive } from \"../../../utils/Primitive\";\nimport { useComposerInputPluginRegistryOptional } from \"../ComposerInputPluginContext\";\nimport {\n TriggerPopoverResource,\n type TriggerPopoverResourceOutput,\n} from \"./TriggerPopoverResource\";\nimport type { TriggerBehavior } from \"./triggerSelectionResource\";\nimport {\n useTriggerPopoverAriaPublish,\n useTriggerPopoverRootContext,\n} from \"./TriggerPopoverRootContext\";\n\nconst TriggerPopoverScopeContext =\n createContext<TriggerPopoverResourceOutput | null>(null);\n\nexport const useTriggerPopoverScopeContext = () => {\n const ctx = useContext(TriggerPopoverScopeContext);\n if (!ctx)\n throw new Error(\n \"useTriggerPopoverScopeContext must be used within ComposerPrimitive.TriggerPopover\",\n );\n return ctx;\n};\n\nexport const useTriggerPopoverScopeContextOptional = () =>\n useContext(TriggerPopoverScopeContext);\n\n/** Registration API exposed to behavior sub-primitives. */\nexport type TriggerBehaviorRegistration = {\n register(behavior: TriggerBehavior): () => void;\n};\n\nconst TriggerBehaviorRegistrationContext =\n createContext<TriggerBehaviorRegistration | null>(null);\n\n/** Obtain the registration handle from the parent `<TriggerPopover>`. */\nexport const useTriggerBehaviorRegistration = () => {\n const ctx = useContext(TriggerBehaviorRegistrationContext);\n if (!ctx)\n throw new Error(\n \"TriggerPopover.Directive / TriggerPopover.Action must be rendered inside ComposerPrimitive.TriggerPopover\",\n );\n return ctx;\n};\n\nexport namespace ComposerPrimitiveTriggerPopover {\n export type Element = ComponentRef<typeof Primitive.div>;\n export type Props = Omit<\n ComponentPropsWithoutRef<typeof Primitive.div>,\n \"onSelect\"\n > & {\n /** The character(s) that activate this trigger (e.g. `\"@\"`, `\"/\"`). Also serves as the trigger identity within the root. */\n readonly char: string;\n /** Adapter providing categories and items. */\n readonly adapter?: Unstable_TriggerAdapter | undefined;\n /** Whether the adapter is resolving items, surfaced to the popover scope for async sources. @default false */\n readonly isLoading?: boolean | undefined;\n };\n}\n\n/**\n * Declares a trigger and renders its popover container. The popover only\n * renders its DOM (and children) when the trigger character is active in the\n * composer input and a behavior sub-primitive has been registered.\n *\n * A behavior is contributed by rendering exactly one of\n * `<TriggerPopover.Directive>` or `<TriggerPopover.Action>` as a child. Without\n * a behavior the trigger stays closed.\n *\n * Must be placed inside `ComposerPrimitive.Unstable_TriggerPopoverRoot`.\n *\n * @example\n * ```tsx\n * <ComposerPrimitive.Unstable_TriggerPopover\n * char=\"@\"\n * adapter={mentionAdapter}\n * >\n * <ComposerPrimitive.Unstable_TriggerPopover.Directive formatter={formatter} />\n * <ComposerPrimitive.Unstable_TriggerPopoverCategories>\n * {(cats) => cats.map(...)}\n * </ComposerPrimitive.Unstable_TriggerPopoverCategories>\n * <ComposerPrimitive.Unstable_TriggerPopoverItems>\n * {(items) => items.map(...)}\n * </ComposerPrimitive.Unstable_TriggerPopoverItems>\n * </ComposerPrimitive.Unstable_TriggerPopover>\n * ```\n */\nexport const ComposerPrimitiveTriggerPopover = forwardRef<\n ComposerPrimitiveTriggerPopover.Element,\n ComposerPrimitiveTriggerPopover.Props\n>(\n (\n {\n char,\n adapter,\n isLoading = false,\n \"aria-label\": ariaLabel,\n children,\n ...props\n },\n forwardedRef,\n ) => {\n const aui = useAui();\n const text = useAuiState((s) => s.composer.text);\n const popoverId = useId();\n\n // Track in state (for resource reactivity) + ref (dev warning on duplicate registrations).\n const behaviorRef = useRef<TriggerBehavior | null>(null);\n const [behavior, setBehavior] = useState<TriggerBehavior | null>(null);\n const registrationCountRef = useRef(0);\n\n const register = useCallback<TriggerBehaviorRegistration[\"register\"]>(\n (next) => {\n registrationCountRef.current += 1;\n if (\n process.env.NODE_ENV !== \"production\" &&\n registrationCountRef.current > 1\n ) {\n console.warn(\n `[assistant-ui] TriggerPopover \"${char}\" received more than one behavior child. Exactly one <TriggerPopover.Directive> or <TriggerPopover.Action> is allowed per TriggerPopover; the last registration wins.`,\n );\n }\n behaviorRef.current = next;\n setBehavior(next);\n return () => {\n registrationCountRef.current = Math.max(\n 0,\n registrationCountRef.current - 1,\n );\n if (behaviorRef.current === next) {\n behaviorRef.current = null;\n setBehavior(null);\n }\n };\n },\n [char],\n );\n\n const registration = useMemo<TriggerBehaviorRegistration>(\n () => ({ register }),\n [register],\n );\n\n const resource = useResource(\n TriggerPopoverResource({\n adapter,\n text,\n triggerChar: char,\n behavior: behavior ?? undefined,\n aui,\n popoverId,\n isLoading,\n }),\n );\n\n const getResource = useEffectEvent(() => resource);\n\n const root = useTriggerPopoverRootContext();\n useEffect(() => {\n return root.register({\n char,\n ...(behavior ? { behavior } : {}),\n resource: getResource(),\n });\n }, [root, char, behavior]);\n\n const pluginRegistry = useComposerInputPluginRegistryOptional();\n useEffect(() => {\n if (!pluginRegistry) return undefined;\n return pluginRegistry.register(getResource());\n }, [pluginRegistry]);\n\n const open = behavior !== null && resource.open;\n\n const aria = useTriggerPopoverAriaPublish();\n\n useEffect(() => {\n if (!open) return undefined;\n return () => {\n aria.setActiveAria(char, null);\n };\n }, [aria, char, open]);\n\n useEffect(() => {\n if (!open) return;\n aria.setActiveAria(char, {\n popoverId,\n highlightedItemId: resource.highlightedItemId,\n });\n }, [aria, char, popoverId, open, resource.highlightedItemId]);\n\n return (\n <TriggerBehaviorRegistrationContext.Provider value={registration}>\n <TriggerPopoverScopeContext.Provider value={resource}>\n {open ? (\n <Primitive.div\n role=\"listbox\"\n id={popoverId}\n aria-label={ariaLabel ?? \"Suggestions\"}\n aria-activedescendant={resource.highlightedItemId}\n data-state=\"open\"\n {...props}\n ref={forwardedRef}\n >\n {children}\n </Primitive.div>\n ) : (\n children\n )}\n </TriggerPopoverScopeContext.Provider>\n </TriggerBehaviorRegistrationContext.Provider>\n );\n },\n);\n\nComposerPrimitiveTriggerPopover.displayName =\n \"ComposerPrimitive.TriggerPopover\";\n"],"mappings":"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"}
@@ -13,7 +13,8 @@ type TriggerPopoverResourceOutput = {
13
13
  readonly categories: readonly Unstable_TriggerCategory[];
14
14
  readonly items: readonly Unstable_TriggerItem[];
15
15
  readonly highlightedIndex: number;
16
- readonly isSearchMode: boolean; /** Stable ID prefix for generating accessible element IDs. */
16
+ readonly isSearchMode: boolean; /** Whether the adapter is currently resolving items (async sources). */
17
+ readonly isLoading: boolean; /** Stable ID prefix for generating accessible element IDs. */
17
18
  readonly popoverId: string; /** ID of the currently highlighted item (for aria-activedescendant). */
18
19
  readonly highlightedItemId: string | undefined;
19
20
  selectCategory(categoryId: string): void;
@@ -36,6 +37,7 @@ declare const TriggerPopoverResource: import("@assistant-ui/tap").Resource<Trigg
36
37
  behavior: TriggerBehavior | undefined;
37
38
  aui: AssistantClient; /** Stable ID for accessible element IDs (pass React's useId() from component layer). */
38
39
  popoverId: string;
40
+ isLoading: boolean;
39
41
  }]>;
40
42
  //#endregion
41
43
  export { OnSelectBehavior, type SelectItemOverride, type TriggerBehavior, type TriggerPopoverKeyEvent, TriggerPopoverResource, TriggerPopoverResourceOutput };
@@ -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;AAAA,cAwFpB,sBAAA,8BAAsB,QAAA,CAAA,4BAAA;WA5ExB,uBAAA;;;YAGC,eAAA;OACL,eAAA,EAxBU"}
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,WAmBsB;EAAA,SAjBtB,SAAA,UAiBwC;EAAA,SAfxC,iBAAA;EAET,cAAA,CAAe,UAAA;EACf,MAAA;EACA,UAAA,CAAW,IAAA,EAAM,oBAAA;EACjB,KAAA,UAbS;EAeT,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,cA2FpB,sBAAA,8BAAsB,QAAA,CAAA,4BAAA;WA9ExB,uBAAA;;;YAGC,eAAA;OACL,eAAA,EAxBL"}
@@ -8,8 +8,8 @@ import { resource, useResource } from "@assistant-ui/tap";
8
8
  //#region src/primitives/composer/trigger/TriggerPopoverResource.ts
9
9
  /** Composes detection, navigation, keyboard, and selection sub-resources. */
10
10
  const useTriggerPopoverResource = (t0) => {
11
- const $ = c(45);
12
- const { adapter, text, triggerChar, behavior, aui, popoverId } = t0;
11
+ const $ = c(46);
12
+ const { adapter, text, triggerChar, behavior, aui, popoverId, isLoading } = t0;
13
13
  let t1;
14
14
  if ($[0] !== text || $[1] !== triggerChar) {
15
15
  t1 = TriggerDetectionResource({
@@ -91,7 +91,7 @@ const useTriggerPopoverResource = (t0) => {
91
91
  } else t5 = $[26];
92
92
  const keyboard = useResource(t5);
93
93
  let t6;
94
- if ($[27] !== detection.query || $[28] !== detection.setCursorPosition || $[29] !== keyboard.handleKeyDown || $[30] !== keyboard.highlightIndex || $[31] !== keyboard.highlightedIndex || $[32] !== keyboard.highlightedItemId || $[33] !== navigation.activeCategoryId || $[34] !== navigation.categories || $[35] !== navigation.goBack || $[36] !== navigation.isSearchMode || $[37] !== navigation.items || $[38] !== navigation.selectCategory || $[39] !== open || $[40] !== popoverId || $[41] !== selection.close || $[42] !== selection.registerSelectItemOverride || $[43] !== selection.selectItem) {
94
+ if ($[27] !== detection.query || $[28] !== detection.setCursorPosition || $[29] !== isLoading || $[30] !== keyboard.handleKeyDown || $[31] !== keyboard.highlightIndex || $[32] !== keyboard.highlightedIndex || $[33] !== keyboard.highlightedItemId || $[34] !== navigation.activeCategoryId || $[35] !== navigation.categories || $[36] !== navigation.goBack || $[37] !== navigation.isSearchMode || $[38] !== navigation.items || $[39] !== navigation.selectCategory || $[40] !== open || $[41] !== popoverId || $[42] !== selection.close || $[43] !== selection.registerSelectItemOverride || $[44] !== selection.selectItem) {
95
95
  t6 = {
96
96
  open,
97
97
  query: detection.query,
@@ -100,6 +100,7 @@ const useTriggerPopoverResource = (t0) => {
100
100
  items: navigation.items,
101
101
  highlightedIndex: keyboard.highlightedIndex,
102
102
  isSearchMode: navigation.isSearchMode,
103
+ isLoading,
103
104
  popoverId,
104
105
  highlightedItemId: keyboard.highlightedItemId,
105
106
  selectCategory: navigation.selectCategory,
@@ -113,23 +114,24 @@ const useTriggerPopoverResource = (t0) => {
113
114
  };
114
115
  $[27] = detection.query;
115
116
  $[28] = detection.setCursorPosition;
116
- $[29] = keyboard.handleKeyDown;
117
- $[30] = keyboard.highlightIndex;
118
- $[31] = keyboard.highlightedIndex;
119
- $[32] = keyboard.highlightedItemId;
120
- $[33] = navigation.activeCategoryId;
121
- $[34] = navigation.categories;
122
- $[35] = navigation.goBack;
123
- $[36] = navigation.isSearchMode;
124
- $[37] = navigation.items;
125
- $[38] = navigation.selectCategory;
126
- $[39] = open;
127
- $[40] = popoverId;
128
- $[41] = selection.close;
129
- $[42] = selection.registerSelectItemOverride;
130
- $[43] = selection.selectItem;
131
- $[44] = t6;
132
- } else t6 = $[44];
117
+ $[29] = isLoading;
118
+ $[30] = keyboard.handleKeyDown;
119
+ $[31] = keyboard.highlightIndex;
120
+ $[32] = keyboard.highlightedIndex;
121
+ $[33] = keyboard.highlightedItemId;
122
+ $[34] = navigation.activeCategoryId;
123
+ $[35] = navigation.categories;
124
+ $[36] = navigation.goBack;
125
+ $[37] = navigation.isSearchMode;
126
+ $[38] = navigation.items;
127
+ $[39] = navigation.selectCategory;
128
+ $[40] = open;
129
+ $[41] = popoverId;
130
+ $[42] = selection.close;
131
+ $[43] = selection.registerSelectItemOverride;
132
+ $[44] = selection.selectItem;
133
+ $[45] = t6;
134
+ } else t6 = $[45];
133
135
  return t6;
134
136
  };
135
137
  const TriggerPopoverResource = resource(useTriggerPopoverResource);
@@ -1 +1 @@
1
- {"version":3,"file":"TriggerPopoverResource.js","names":["useEffectEvent","useResource","resource","Unstable_TriggerAdapter","Unstable_TriggerCategory","Unstable_TriggerItem","AssistantClient","TriggerDetectionResource","TriggerKeyboardResource","TriggerNavigationResource","TriggerSelectionResource","SelectItemOverride","TriggerBehavior","TriggerPopoverKeyEvent","OnSelectBehavior","TriggerPopoverResourceOutput","open","query","activeCategoryId","categories","items","highlightedIndex","isSearchMode","popoverId","highlightedItemId","selectCategory","categoryId","goBack","selectItem","item","close","highlightIndex","index","handleKeyDown","e","key","shiftKey","preventDefault","setCursorPosition","pos","registerSelectItemOverride","fn","useTriggerPopoverResource","t0","$","_c","adapter","text","triggerChar","behavior","aui","t1","detection","trigger","undefined","t2","navigation","t3","onSelected","t4","selection","t5","navigableList","keyboard","t6","TriggerPopoverResource"],"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":"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"}
1
+ {"version":3,"file":"TriggerPopoverResource.js","names":["useEffectEvent","useResource","resource","Unstable_TriggerAdapter","Unstable_TriggerCategory","Unstable_TriggerItem","AssistantClient","TriggerDetectionResource","TriggerKeyboardResource","TriggerNavigationResource","TriggerSelectionResource","SelectItemOverride","TriggerBehavior","TriggerPopoverKeyEvent","OnSelectBehavior","TriggerPopoverResourceOutput","open","query","activeCategoryId","categories","items","highlightedIndex","isSearchMode","isLoading","popoverId","highlightedItemId","selectCategory","categoryId","goBack","selectItem","item","close","highlightIndex","index","handleKeyDown","e","key","shiftKey","preventDefault","setCursorPosition","pos","registerSelectItemOverride","fn","useTriggerPopoverResource","t0","$","_c","adapter","text","triggerChar","behavior","aui","t1","detection","trigger","undefined","t2","navigation","t3","onSelected","t4","selection","t5","navigableList","keyboard","t6","TriggerPopoverResource"],"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 /** Whether the adapter is currently resolving items (async sources). */\n readonly isLoading: 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 isLoading,\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 isLoading: boolean;\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 isLoading,\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":"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"}
@@ -15,6 +15,7 @@ declare const ComposerPrimitiveTriggerPopover: import("react").ForwardRefExoticC
15
15
  } & import("react").RefAttributes<HTMLDivElement>, "ref">, "onSelect"> & {
16
16
  readonly char: string;
17
17
  readonly adapter?: import("@assistant-ui/core").Unstable_TriggerAdapter | undefined;
18
+ readonly isLoading?: boolean | undefined;
18
19
  } & import("react").RefAttributes<HTMLDivElement>> & {
19
20
  Directive: import("react").FC<ComposerPrimitiveTriggerPopoverDirective.Props>;
20
21
  Action: import("react").FC<ComposerPrimitiveTriggerPopoverAction.Props>;
@@ -0,0 +1,47 @@
1
+ import { Unstable_TriggerAdapter, Unstable_TriggerItem } from "@assistant-ui/core";
2
+
3
+ //#region src/unstable/useLiveCompletionAdapter.d.ts
4
+ type Unstable_UseLiveCompletionAdapterOptions = {
5
+ /**
6
+ * Fetches the items for a query from an async source. Called debounced; the
7
+ * resolved items are cached and returned synchronously to the popover on the
8
+ * next render.
9
+ */
10
+ readonly fetcher: (query: string) => Promise<readonly Unstable_TriggerItem[]>; /** Debounce applied before a fetch fires, in milliseconds. @default 60 */
11
+ readonly debounceMs?: number | undefined; /** When `false`, no fetch is scheduled and the adapter stays empty. @default true */
12
+ readonly enabled?: boolean | undefined;
13
+ };
14
+ /**
15
+ * @deprecated Under active development and may change without notice.
16
+ *
17
+ * Bridges an async completion source (a server search, a gateway RPC) into the
18
+ * synchronous `Unstable_TriggerAdapter` that `ComposerTriggerPopover` consumes.
19
+ * `search(query)` returns the last fetched items synchronously and schedules a
20
+ * debounced fetch when the query changes; when results arrive the returned
21
+ * `adapter` identity changes, which re-runs the popover's lookup so the fresh
22
+ * items render. This is a search-only adapter (`categories` are empty).
23
+ *
24
+ * `isLoading` is `true` while a fetch is in flight. Pass it to the popover's
25
+ * `isLoading` prop to render a loading state.
26
+ *
27
+ * @example
28
+ * ```tsx
29
+ * const mentions = unstable_useLiveCompletionAdapter({
30
+ * fetcher: (query) => searchUsers(query),
31
+ * });
32
+ *
33
+ * <ComposerTriggerPopover
34
+ * char="@"
35
+ * adapter={mentions.adapter}
36
+ * isLoading={mentions.isLoading}
37
+ * directive={{ onInserted }}
38
+ * />
39
+ * ```
40
+ */
41
+ declare function unstable_useLiveCompletionAdapter(options: Unstable_UseLiveCompletionAdapterOptions): {
42
+ adapter: Unstable_TriggerAdapter;
43
+ isLoading: boolean;
44
+ };
45
+ //#endregion
46
+ export { Unstable_UseLiveCompletionAdapterOptions, unstable_useLiveCompletionAdapter };
47
+ //# sourceMappingURL=useLiveCompletionAdapter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useLiveCompletionAdapter.d.ts","names":[],"sources":["../../src/unstable/useLiveCompletionAdapter.ts"],"mappings":";;;KAQY,wCAAA;;AAAZ;;;;WAMW,OAAA,GAAU,KAAA,aAAkB,OAAO,UAAU,oBAAA,KAAnC;EAAA,SAEV,UAAA,uBAF6C;EAAA,SAI7C,OAAA;AAAA;;AAAO;AAiClB;;;;;;;;;;AAEgD;;;;;;;;;;;;;;;iBAFhC,iCAAA,CACd,OAAA,EAAS,wCAAA;EACN,OAAA,EAAS,uBAAuB;EAAE,SAAA;AAAA"}
@@ -0,0 +1,116 @@
1
+ "use client";
2
+ import { useCallback, useEffect, useMemo, useRef, useState } from "@assistant-ui/tap/react-shim";
3
+ //#region src/unstable/useLiveCompletionAdapter.ts
4
+ /** Sentinel that no real query (including the empty string) equals, so the first query always fetches. */
5
+ const NO_QUERY = "\0";
6
+ /**
7
+ * @deprecated Under active development and may change without notice.
8
+ *
9
+ * Bridges an async completion source (a server search, a gateway RPC) into the
10
+ * synchronous `Unstable_TriggerAdapter` that `ComposerTriggerPopover` consumes.
11
+ * `search(query)` returns the last fetched items synchronously and schedules a
12
+ * debounced fetch when the query changes; when results arrive the returned
13
+ * `adapter` identity changes, which re-runs the popover's lookup so the fresh
14
+ * items render. This is a search-only adapter (`categories` are empty).
15
+ *
16
+ * `isLoading` is `true` while a fetch is in flight. Pass it to the popover's
17
+ * `isLoading` prop to render a loading state.
18
+ *
19
+ * @example
20
+ * ```tsx
21
+ * const mentions = unstable_useLiveCompletionAdapter({
22
+ * fetcher: (query) => searchUsers(query),
23
+ * });
24
+ *
25
+ * <ComposerTriggerPopover
26
+ * char="@"
27
+ * adapter={mentions.adapter}
28
+ * isLoading={mentions.isLoading}
29
+ * directive={{ onInserted }}
30
+ * />
31
+ * ```
32
+ */
33
+ function unstable_useLiveCompletionAdapter(options) {
34
+ const { fetcher, debounceMs = 60, enabled = true } = options;
35
+ const [state, setState] = useState({
36
+ query: NO_QUERY,
37
+ items: []
38
+ });
39
+ const [isLoading, setIsLoading] = useState(false);
40
+ const fetcherRef = useRef(fetcher);
41
+ fetcherRef.current = fetcher;
42
+ const timerRef = useRef(null);
43
+ const tokenRef = useRef(0);
44
+ const pendingQueryRef = useRef(null);
45
+ const cancelTimer = useCallback(() => {
46
+ if (timerRef.current !== null) {
47
+ clearTimeout(timerRef.current);
48
+ timerRef.current = null;
49
+ }
50
+ }, []);
51
+ const scheduleFetch = useCallback((query) => {
52
+ if (!enabled) return;
53
+ if (pendingQueryRef.current === query) return;
54
+ pendingQueryRef.current = query;
55
+ cancelTimer();
56
+ const token = ++tokenRef.current;
57
+ setIsLoading(true);
58
+ timerRef.current = setTimeout(() => {
59
+ timerRef.current = null;
60
+ fetcherRef.current(query).then((items) => {
61
+ if (token !== tokenRef.current) return;
62
+ setState({
63
+ query,
64
+ items
65
+ });
66
+ setIsLoading(false);
67
+ }, () => {
68
+ if (token !== tokenRef.current) return;
69
+ setState({
70
+ query,
71
+ items: []
72
+ });
73
+ setIsLoading(false);
74
+ });
75
+ }, debounceMs);
76
+ }, [
77
+ enabled,
78
+ debounceMs,
79
+ cancelTimer
80
+ ]);
81
+ const invalidatePending = useCallback(() => {
82
+ cancelTimer();
83
+ pendingQueryRef.current = null;
84
+ tokenRef.current += 1;
85
+ setIsLoading(false);
86
+ }, [cancelTimer]);
87
+ useEffect(() => {
88
+ if (enabled) return;
89
+ invalidatePending();
90
+ setState((s) => s.query === NO_QUERY ? s : {
91
+ query: NO_QUERY,
92
+ items: []
93
+ });
94
+ }, [enabled, invalidatePending]);
95
+ useEffect(() => cancelTimer, [cancelTimer]);
96
+ return {
97
+ adapter: useMemo(() => ({
98
+ categories: () => [],
99
+ categoryItems: () => [],
100
+ search: (query) => {
101
+ if (query !== state.query) queueMicrotask(() => scheduleFetch(query));
102
+ else if (pendingQueryRef.current !== null && pendingQueryRef.current !== query) queueMicrotask(invalidatePending);
103
+ return state.items;
104
+ }
105
+ }), [
106
+ state,
107
+ scheduleFetch,
108
+ invalidatePending
109
+ ]),
110
+ isLoading
111
+ };
112
+ }
113
+ //#endregion
114
+ export { unstable_useLiveCompletionAdapter };
115
+
116
+ //# sourceMappingURL=useLiveCompletionAdapter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useLiveCompletionAdapter.js","names":["useCallback","useEffect","useMemo","useRef","useState","Unstable_TriggerAdapter","Unstable_TriggerItem","Unstable_UseLiveCompletionAdapterOptions","fetcher","query","Promise","debounceMs","enabled","NO_QUERY","unstable_useLiveCompletionAdapter","options","adapter","isLoading","state","setState","items","setIsLoading","fetcherRef","current","timerRef","ReturnType","setTimeout","tokenRef","pendingQueryRef","cancelTimer","clearTimeout","scheduleFetch","token","then","invalidatePending","s","categories","categoryItems","search","queueMicrotask"],"sources":["../../src/unstable/useLiveCompletionAdapter.ts"],"sourcesContent":["\"use client\";\n\nimport { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport type {\n Unstable_TriggerAdapter,\n Unstable_TriggerItem,\n} from \"@assistant-ui/core\";\n\nexport type Unstable_UseLiveCompletionAdapterOptions = {\n /**\n * Fetches the items for a query from an async source. Called debounced; the\n * resolved items are cached and returned synchronously to the popover on the\n * next render.\n */\n readonly fetcher: (query: string) => Promise<readonly Unstable_TriggerItem[]>;\n /** Debounce applied before a fetch fires, in milliseconds. @default 60 */\n readonly debounceMs?: number | undefined;\n /** When `false`, no fetch is scheduled and the adapter stays empty. @default true */\n readonly enabled?: boolean | undefined;\n};\n\n/** Sentinel that no real query (including the empty string) equals, so the first query always fetches. */\nconst NO_QUERY = \"\\u0000\";\n\n/**\n * @deprecated Under active development and may change without notice.\n *\n * Bridges an async completion source (a server search, a gateway RPC) into the\n * synchronous `Unstable_TriggerAdapter` that `ComposerTriggerPopover` consumes.\n * `search(query)` returns the last fetched items synchronously and schedules a\n * debounced fetch when the query changes; when results arrive the returned\n * `adapter` identity changes, which re-runs the popover's lookup so the fresh\n * items render. This is a search-only adapter (`categories` are empty).\n *\n * `isLoading` is `true` while a fetch is in flight. Pass it to the popover's\n * `isLoading` prop to render a loading state.\n *\n * @example\n * ```tsx\n * const mentions = unstable_useLiveCompletionAdapter({\n * fetcher: (query) => searchUsers(query),\n * });\n *\n * <ComposerTriggerPopover\n * char=\"@\"\n * adapter={mentions.adapter}\n * isLoading={mentions.isLoading}\n * directive={{ onInserted }}\n * />\n * ```\n */\nexport function unstable_useLiveCompletionAdapter(\n options: Unstable_UseLiveCompletionAdapterOptions,\n): { adapter: Unstable_TriggerAdapter; isLoading: boolean } {\n const { fetcher, debounceMs = 60, enabled = true } = options;\n\n const [state, setState] = useState<{\n query: string;\n items: readonly Unstable_TriggerItem[];\n }>({ query: NO_QUERY, items: [] });\n const [isLoading, setIsLoading] = useState(false);\n\n const fetcherRef = useRef(fetcher);\n fetcherRef.current = fetcher;\n\n const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const tokenRef = useRef(0);\n const pendingQueryRef = useRef<string | null>(null);\n\n const cancelTimer = useCallback(() => {\n if (timerRef.current !== null) {\n clearTimeout(timerRef.current);\n timerRef.current = null;\n }\n }, []);\n\n const scheduleFetch = useCallback(\n (query: string) => {\n if (!enabled) return;\n if (pendingQueryRef.current === query) return;\n pendingQueryRef.current = query;\n cancelTimer();\n const token = ++tokenRef.current;\n setIsLoading(true);\n timerRef.current = setTimeout(() => {\n timerRef.current = null;\n fetcherRef.current(query).then(\n (items) => {\n if (token !== tokenRef.current) return;\n setState({ query, items });\n setIsLoading(false);\n },\n () => {\n if (token !== tokenRef.current) return;\n setState({ query, items: [] });\n setIsLoading(false);\n },\n );\n }, debounceMs);\n },\n [enabled, debounceMs, cancelTimer],\n );\n\n const invalidatePending = useCallback(() => {\n cancelTimer();\n pendingQueryRef.current = null;\n tokenRef.current += 1;\n setIsLoading(false);\n }, [cancelTimer]);\n\n useEffect(() => {\n if (enabled) return;\n invalidatePending();\n setState((s) =>\n s.query === NO_QUERY ? s : { query: NO_QUERY, items: [] },\n );\n }, [enabled, invalidatePending]);\n\n useEffect(() => cancelTimer, [cancelTimer]);\n\n const adapter = useMemo<Unstable_TriggerAdapter>(\n () => ({\n categories: () => [],\n categoryItems: () => [],\n search: (query: string) => {\n // search() runs inside the popover's render; defer state updates with\n // queueMicrotask so they are not dispatched while another component renders.\n if (query !== state.query) {\n queueMicrotask(() => scheduleFetch(query));\n } else if (\n pendingQueryRef.current !== null &&\n pendingQueryRef.current !== query\n ) {\n // the query returned to a cached value while a fetch for a different\n // query is in flight; drop it so its result cannot overwrite the cache\n queueMicrotask(invalidatePending);\n }\n return state.items;\n },\n }),\n [state, scheduleFetch, invalidatePending],\n );\n\n return { adapter, isLoading };\n}\n"],"mappings":";;;;AAsBA,MAAMa,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6BjB,SAAgBC,kCACdC,SAC0D;CAC1D,MAAM,EAAEP,SAASG,aAAa,IAAIC,UAAU,SAASG;CAErD,MAAM,CAACG,OAAOC,YAAYf,SAGvB;EAAEK,OAAOI;EAAUO,OAAO,CAAA;CAAG,CAAC;CACjC,MAAM,CAACH,WAAWI,gBAAgBjB,SAAS,KAAK;CAEhD,MAAMkB,aAAanB,OAAOK,OAAO;CACjCc,WAAWC,UAAUf;CAErB,MAAMgB,WAAWrB,OAA6C,IAAI;CAClE,MAAMwB,WAAWxB,OAAO,CAAC;CACzB,MAAMyB,kBAAkBzB,OAAsB,IAAI;CAElD,MAAM0B,cAAc7B,kBAAkB;EACpC,IAAIwB,SAASD,YAAY,MAAM;GAC7BO,aAAaN,SAASD,OAAO;GAC7BC,SAASD,UAAU;EACrB;CACF,GAAG,CAAA,CAAE;CAEL,MAAMQ,gBAAgB/B,aACnBS,UAAkB;EACjB,IAAI,CAACG,SAAS;EACd,IAAIgB,gBAAgBL,YAAYd,OAAO;EACvCmB,gBAAgBL,UAAUd;EAC1BoB,YAAY;EACZ,MAAMG,QAAQ,EAAEL,SAASJ;EACzBF,aAAa,IAAI;EACjBG,SAASD,UAAUG,iBAAiB;GAClCF,SAASD,UAAU;GACnBD,WAAWC,QAAQd,KAAK,CAAC,CAACwB,MACvBb,UAAU;IACT,IAAIY,UAAUL,SAASJ,SAAS;IAChCJ,SAAS;KAAEV;KAAOW;IAAM,CAAC;IACzBC,aAAa,KAAK;GACpB,SACM;IACJ,IAAIW,UAAUL,SAASJ,SAAS;IAChCJ,SAAS;KAAEV;KAAOW,OAAO,CAAA;IAAG,CAAC;IAC7BC,aAAa,KAAK;GACpB,CACF;EACF,GAAGV,UAAU;CACf,GACA;EAACC;EAASD;EAAYkB;CAAW,CACnC;CAEA,MAAMK,oBAAoBlC,kBAAkB;EAC1C6B,YAAY;EACZD,gBAAgBL,UAAU;EAC1BI,SAASJ,WAAW;EACpBF,aAAa,KAAK;CACpB,GAAG,CAACQ,WAAW,CAAC;CAEhB5B,gBAAgB;EACd,IAAIW,SAAS;EACbsB,kBAAkB;EAClBf,UAAUgB,MACRA,EAAE1B,UAAUI,WAAWsB,IAAI;GAAE1B,OAAOI;GAAUO,OAAO,CAAA;EAAG,CAC1D;CACF,GAAG,CAACR,SAASsB,iBAAiB,CAAC;CAE/BjC,gBAAgB4B,aAAa,CAACA,WAAW,CAAC;CAyB1C,OAAO;EAAEb,SAvBOd,eACP;GACLkC,kBAAkB,CAAA;GAClBC,qBAAqB,CAAA;GACrBC,SAAS7B,UAAkB;IAGzB,IAAIA,UAAUS,MAAMT,OAClB8B,qBAAqBR,cAActB,KAAK,CAAC;SACpC,IACLmB,gBAAgBL,YAAY,QAC5BK,gBAAgBL,YAAYd,OAI5B8B,eAAeL,iBAAiB;IAElC,OAAOhB,MAAME;GACf;EACF,IACA;GAACF;GAAOa;GAAeG;EAAiB,CAGjClB;EAASC;CAAU;AAC9B"}
@@ -22,6 +22,14 @@ type SmoothOptions = {
22
22
  * @default Infinity
23
23
  */
24
24
  maxCharsPerFrame?: number | undefined;
25
+ /**
26
+ * Minimum time in milliseconds between committed updates. The reveal keeps
27
+ * advancing every frame, but the visible text (and the downstream re-render
28
+ * and markdown re-parse it triggers) is committed at most once per interval.
29
+ * The final frame always commits. `0` commits every frame.
30
+ * @default 0
31
+ */
32
+ minCommitMs?: number | undefined;
25
33
  };
26
34
  /**
27
35
  * Animates streamed message part text with a typewriter-style reveal.
@@ -1 +1 @@
1
- {"version":3,"file":"useSmooth.d.ts","names":[],"sources":["../../../src/utils/smooth/useSmooth.ts"],"mappings":";;;;;AAiBA;KAAY,aAAA;;;;;;EAMV,OAAA;EAWgB;AA+FlB;;;;EApGE,iBAAA;EAqG6C;;;;EAhG7C,gBAAA;AAAA;;;;;;;;;;;;AAkG2D;;;;;cAHhD,SAAA,GACX,KAAA,EAAO,gBAAA,IAAoB,eAAA,GAAkB,oBAAA,GAC7C,MAAA,aAAkB,aAAA,KACjB,gBAAA,IAAoB,eAAA,GAAkB,oBAAA"}
1
+ {"version":3,"file":"useSmooth.d.ts","names":[],"sources":["../../../src/utils/smooth/useSmooth.ts"],"mappings":";;;;;AAiBA;KAAY,aAAA;;;;;;EAMV,OAAA;EAmBW;AAAA;AAsGb;;;EAnHE,iBAAA;EAoH2B;;;;EA/G3B,gBAAA;EAiHuC;;;;;;;EAzGvC,WAAA;AAAA;;;;;AAyG2D;;;;;;;;;;;;cAHhD,SAAA,GACX,KAAA,EAAO,gBAAA,IAAoB,eAAA,GAAkB,oBAAA,GAC7C,MAAA,aAAkB,aAAA,KACjB,gBAAA,IAAoB,eAAA,GAAkB,oBAAA"}
@@ -12,10 +12,12 @@ var TextStreamAnimator = class {
12
12
  setText;
13
13
  animationFrameId = null;
14
14
  lastUpdateTime = Date.now();
15
+ lastCommitTime = 0;
15
16
  targetText = "";
16
17
  drainMs = DEFAULT_DRAIN_MS;
17
18
  maxCharIntervalMs = DEFAULT_MAX_CHAR_INTERVAL_MS;
18
19
  maxCharsPerFrame = Infinity;
20
+ minCommitMs = 0;
19
21
  constructor(currentText, setText) {
20
22
  this.currentText = currentText;
21
23
  this.setText = setText;
@@ -48,7 +50,10 @@ var TextStreamAnimator = class {
48
50
  if (charsToAdd === 0) return;
49
51
  this.currentText = this.targetText.slice(0, this.currentText.length + charsToAdd);
50
52
  this.lastUpdateTime = currentTime - timeToConsume;
51
- this.setText(this.currentText);
53
+ if (charsToAdd === remainingChars || currentTime - this.lastCommitTime >= this.minCommitMs) {
54
+ this.lastCommitTime = currentTime;
55
+ this.setText(this.currentText);
56
+ }
52
57
  };
53
58
  };
54
59
  const SMOOTH_STATUS = Object.freeze({ type: "running" });
@@ -76,6 +81,7 @@ const useSmooth = (state, smooth = false) => {
76
81
  const drainMs = positiveOr(options?.drainMs, DEFAULT_DRAIN_MS);
77
82
  const maxCharIntervalMs = positiveOr(options?.maxCharIntervalMs, DEFAULT_MAX_CHAR_INTERVAL_MS);
78
83
  const maxCharsPerFrame = positiveOr(options?.maxCharsPerFrame, Infinity);
84
+ const minCommitMs = positiveOr(options?.minCommitMs, 0);
79
85
  const [displayedText, setDisplayedText] = useState(state.status.type === "running" ? "" : text);
80
86
  const aui = useAui();
81
87
  const part = useAuiState(() => aui.part());
@@ -109,11 +115,13 @@ const useSmooth = (state, smooth = false) => {
109
115
  animatorRef.drainMs = drainMs;
110
116
  animatorRef.maxCharIntervalMs = maxCharIntervalMs;
111
117
  animatorRef.maxCharsPerFrame = maxCharsPerFrame;
118
+ animatorRef.minCommitMs = minCommitMs;
112
119
  }, [
113
120
  animatorRef,
114
121
  drainMs,
115
122
  maxCharIntervalMs,
116
- maxCharsPerFrame
123
+ maxCharsPerFrame,
124
+ minCommitMs
117
125
  ]);
118
126
  const animatorPartRef = useRef(part);
119
127
  useEffect(() => {
@@ -127,6 +135,7 @@ const useSmooth = (state, smooth = false) => {
127
135
  if (state.status.type === "running") {
128
136
  animatorRef.currentText = "";
129
137
  animatorRef.targetText = text;
138
+ animatorRef.lastCommitTime = 0;
130
139
  animatorRef.start();
131
140
  } else {
132
141
  animatorRef.currentText = text;
@@ -1 +1 @@
1
- {"version":3,"file":"useSmooth.js","names":["useEffect","useMemo","useRef","useState","useAui","useAuiState","MessagePartStatus","ReasoningMessagePart","TextMessagePart","MessagePartState","useCallbackRef","useSmoothStatusStore","writableStore","SmoothOptions","drainMs","maxCharIntervalMs","maxCharsPerFrame","DEFAULT_DRAIN_MS","DEFAULT_MAX_CHAR_INTERVAL_MS","TextStreamAnimator","animationFrameId","lastUpdateTime","Date","now","targetText","Infinity","constructor","currentText","setText","newText","start","animate","stop","cancelAnimationFrame","currentTime","deltaTime","timeToConsume","remainingChars","length","baseTimePerChar","Math","min","frameLimit","charsToAdd","requestAnimationFrame","slice","SMOOTH_STATUS","Object","freeze","type","positiveOr","value","fallback","undefined","useSmooth","state","smooth","text","options","enabled","displayedText","setDisplayedText","status","aui","part","prevPart","setPrevPart","startsWith","smoothStatusStore","optional","target","setState","animatorRef","animatorPartRef","partChanged","current"],"sources":["../../../src/utils/smooth/useSmooth.ts"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useMemo, useRef, useState } from \"react\";\nimport { useAui, useAuiState } from \"@assistant-ui/store\";\nimport type {\n MessagePartStatus,\n ReasoningMessagePart,\n TextMessagePart,\n MessagePartState,\n} from \"@assistant-ui/core\";\nimport { useCallbackRef } from \"@radix-ui/react-use-callback-ref\";\nimport { useSmoothStatusStore } from \"./SmoothContext\";\nimport { writableStore } from \"../../context/ReadonlyStore\";\n\n/**\n * Tuning options for the smooth text streaming animation.\n */\nexport type SmoothOptions = {\n /**\n * Target time in milliseconds to drain the backlog of unrevealed\n * characters. Larger values reveal long backlogs more gradually.\n * @default 250\n */\n drainMs?: number | undefined;\n /**\n * Maximum time in milliseconds between revealed characters, i.e. the\n * slowest reveal rate when the backlog is short.\n * @default 5\n */\n maxCharIntervalMs?: number | undefined;\n /**\n * Maximum number of characters revealed per animation frame.\n * @default Infinity\n */\n maxCharsPerFrame?: number | undefined;\n};\n\nconst DEFAULT_DRAIN_MS = 250;\nconst DEFAULT_MAX_CHAR_INTERVAL_MS = 5;\n\nclass TextStreamAnimator {\n private animationFrameId: number | null = null;\n private lastUpdateTime: number = Date.now();\n\n public targetText: string = \"\";\n public drainMs: number = DEFAULT_DRAIN_MS;\n public maxCharIntervalMs: number = DEFAULT_MAX_CHAR_INTERVAL_MS;\n public maxCharsPerFrame: number = Infinity;\n\n constructor(\n public currentText: string,\n private setText: (newText: string) => void,\n ) {}\n\n start() {\n if (this.animationFrameId !== null) return;\n this.lastUpdateTime = Date.now();\n this.animate();\n }\n\n stop() {\n if (this.animationFrameId !== null) {\n cancelAnimationFrame(this.animationFrameId);\n this.animationFrameId = null;\n }\n }\n\n private animate = () => {\n const currentTime = Date.now();\n const deltaTime = currentTime - this.lastUpdateTime;\n let timeToConsume = deltaTime;\n\n const remainingChars = this.targetText.length - this.currentText.length;\n const baseTimePerChar = Math.min(\n this.maxCharIntervalMs,\n this.drainMs / remainingChars,\n );\n\n const frameLimit = Math.min(remainingChars, this.maxCharsPerFrame);\n let charsToAdd = 0;\n while (timeToConsume >= baseTimePerChar && charsToAdd < frameLimit) {\n charsToAdd++;\n timeToConsume -= baseTimePerChar;\n }\n // A cap-limited frame must not bank its surplus time, or the next\n // frame would burst past the cap.\n if (charsToAdd === frameLimit && frameLimit === this.maxCharsPerFrame) {\n timeToConsume = 0;\n }\n\n if (charsToAdd !== remainingChars) {\n this.animationFrameId = requestAnimationFrame(this.animate);\n } else {\n this.animationFrameId = null;\n }\n if (charsToAdd === 0) return;\n\n this.currentText = this.targetText.slice(\n 0,\n this.currentText.length + charsToAdd,\n );\n this.lastUpdateTime = currentTime - timeToConsume;\n this.setText(this.currentText);\n };\n}\n\nconst SMOOTH_STATUS: MessagePartStatus = Object.freeze({\n type: \"running\",\n});\n\nconst positiveOr = (value: number | undefined, fallback: number): number =>\n value !== undefined && value > 0 ? value : fallback;\n\n/**\n * Animates streamed message part text with a typewriter-style reveal.\n *\n * Takes the current part state and a `smooth` argument: `false` disables,\n * `true` uses the default rate, and a {@link SmoothOptions} object tunes\n * the reveal. Returns the part state with `text` replaced by the revealed\n * prefix and `status` reporting `running` until the reveal catches up.\n *\n * @example\n * ```tsx\n * const { text, status } = useSmooth(useMessagePartText(), {\n * drainMs: 500,\n * maxCharsPerFrame: 30,\n * });\n * ```\n */\nexport const useSmooth = (\n state: MessagePartState & (TextMessagePart | ReasoningMessagePart),\n smooth: boolean | SmoothOptions = false,\n): MessagePartState & (TextMessagePart | ReasoningMessagePart) => {\n const { text } = state;\n const options =\n typeof smooth === \"object\" && smooth !== null ? smooth : undefined;\n const enabled = smooth !== false && smooth !== null;\n const drainMs = positiveOr(options?.drainMs, DEFAULT_DRAIN_MS);\n const maxCharIntervalMs = positiveOr(\n options?.maxCharIntervalMs,\n DEFAULT_MAX_CHAR_INTERVAL_MS,\n );\n const maxCharsPerFrame = positiveOr(options?.maxCharsPerFrame, Infinity);\n\n const [displayedText, setDisplayedText] = useState(\n state.status.type === \"running\" ? \"\" : text,\n );\n\n // Render-phase resync on part flip or text discontinuity, so the\n // first paint after a thread switch never shows the previous\n // part's text (#4051). `displayedText` is already a prefix of\n // `text` during normal streaming, so use it as the previous-text\n // reference instead of carrying separate state — avoids the\n // double render per streaming token. Read part identity through\n // `useAuiState` so we actually subscribe to its changes instead\n // of relying on a render-time proxy reference that may be stable\n // across thread swaps.\n const aui = useAui();\n const part = useAuiState(() => aui.part());\n const [prevPart, setPrevPart] = useState(part);\n if (part !== prevPart || !text.startsWith(displayedText)) {\n setPrevPart(part);\n setDisplayedText(state.status.type === \"running\" ? \"\" : text);\n }\n\n const smoothStatusStore = useSmoothStatusStore({ optional: true });\n const setText = useCallbackRef((text: string) => {\n setDisplayedText(text);\n if (smoothStatusStore) {\n const target =\n displayedText !== text || state.status.type === \"running\"\n ? SMOOTH_STATUS\n : state.status;\n writableStore(smoothStatusStore).setState(target, true);\n }\n });\n\n // TODO this is hacky\n useEffect(() => {\n if (smoothStatusStore) {\n const target =\n enabled && (displayedText !== text || state.status.type === \"running\")\n ? SMOOTH_STATUS\n : state.status;\n writableStore(smoothStatusStore).setState(target, true);\n }\n }, [smoothStatusStore, enabled, text, displayedText, state.status]);\n\n const [animatorRef] = useState<TextStreamAnimator>(\n new TextStreamAnimator(displayedText, setText),\n );\n\n useEffect(() => {\n animatorRef.drainMs = drainMs;\n animatorRef.maxCharIntervalMs = maxCharIntervalMs;\n animatorRef.maxCharsPerFrame = maxCharsPerFrame;\n }, [animatorRef, drainMs, maxCharIntervalMs, maxCharsPerFrame]);\n\n const animatorPartRef = useRef(part);\n useEffect(() => {\n if (!enabled) {\n animatorRef.stop();\n return;\n }\n\n // Discontinuity: part flipped, or new text breaks continuation\n // of the animator's current target. Either case requires\n // resetting the cursor — without the part check, a new part\n // whose text happens to share a prefix with the previous target\n // would keep the stale cursor and flicker.\n const partChanged = animatorPartRef.current !== part;\n animatorPartRef.current = part;\n if (partChanged || !text.startsWith(animatorRef.targetText)) {\n if (state.status.type === \"running\") {\n animatorRef.currentText = \"\";\n animatorRef.targetText = text;\n animatorRef.start();\n } else {\n animatorRef.currentText = text;\n animatorRef.targetText = text;\n animatorRef.stop();\n }\n return;\n }\n\n animatorRef.targetText = text;\n animatorRef.start();\n }, [animatorRef, enabled, text, state.status.type, part]);\n\n useEffect(() => {\n return () => {\n animatorRef.stop();\n };\n }, [animatorRef]);\n\n return useMemo(\n () =>\n enabled\n ? {\n ...state,\n text: displayedText,\n status: text === displayedText ? state.status : SMOOTH_STATUS,\n }\n : state,\n [enabled, displayedText, state, text],\n );\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"useSmooth.js","names":["useEffect","useMemo","useRef","useState","useAui","useAuiState","MessagePartStatus","ReasoningMessagePart","TextMessagePart","MessagePartState","useCallbackRef","useSmoothStatusStore","writableStore","SmoothOptions","drainMs","maxCharIntervalMs","maxCharsPerFrame","minCommitMs","DEFAULT_DRAIN_MS","DEFAULT_MAX_CHAR_INTERVAL_MS","TextStreamAnimator","animationFrameId","lastUpdateTime","Date","now","lastCommitTime","targetText","Infinity","constructor","currentText","setText","newText","start","animate","stop","cancelAnimationFrame","currentTime","deltaTime","timeToConsume","remainingChars","length","baseTimePerChar","Math","min","frameLimit","charsToAdd","requestAnimationFrame","slice","isComplete","SMOOTH_STATUS","Object","freeze","type","positiveOr","value","fallback","undefined","useSmooth","state","smooth","text","options","enabled","displayedText","setDisplayedText","status","aui","part","prevPart","setPrevPart","startsWith","smoothStatusStore","optional","target","setState","animatorRef","animatorPartRef","partChanged","current"],"sources":["../../../src/utils/smooth/useSmooth.ts"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useMemo, useRef, useState } from \"react\";\nimport { useAui, useAuiState } from \"@assistant-ui/store\";\nimport type {\n MessagePartStatus,\n ReasoningMessagePart,\n TextMessagePart,\n MessagePartState,\n} from \"@assistant-ui/core\";\nimport { useCallbackRef } from \"@radix-ui/react-use-callback-ref\";\nimport { useSmoothStatusStore } from \"./SmoothContext\";\nimport { writableStore } from \"../../context/ReadonlyStore\";\n\n/**\n * Tuning options for the smooth text streaming animation.\n */\nexport type SmoothOptions = {\n /**\n * Target time in milliseconds to drain the backlog of unrevealed\n * characters. Larger values reveal long backlogs more gradually.\n * @default 250\n */\n drainMs?: number | undefined;\n /**\n * Maximum time in milliseconds between revealed characters, i.e. the\n * slowest reveal rate when the backlog is short.\n * @default 5\n */\n maxCharIntervalMs?: number | undefined;\n /**\n * Maximum number of characters revealed per animation frame.\n * @default Infinity\n */\n maxCharsPerFrame?: number | undefined;\n /**\n * Minimum time in milliseconds between committed updates. The reveal keeps\n * advancing every frame, but the visible text (and the downstream re-render\n * and markdown re-parse it triggers) is committed at most once per interval.\n * The final frame always commits. `0` commits every frame.\n * @default 0\n */\n minCommitMs?: number | undefined;\n};\n\nconst DEFAULT_DRAIN_MS = 250;\nconst DEFAULT_MAX_CHAR_INTERVAL_MS = 5;\n\nclass TextStreamAnimator {\n private animationFrameId: number | null = null;\n private lastUpdateTime: number = Date.now();\n public lastCommitTime: number = 0;\n\n public targetText: string = \"\";\n public drainMs: number = DEFAULT_DRAIN_MS;\n public maxCharIntervalMs: number = DEFAULT_MAX_CHAR_INTERVAL_MS;\n public maxCharsPerFrame: number = Infinity;\n public minCommitMs: number = 0;\n\n constructor(\n public currentText: string,\n private setText: (newText: string) => void,\n ) {}\n\n start() {\n if (this.animationFrameId !== null) return;\n this.lastUpdateTime = Date.now();\n this.animate();\n }\n\n stop() {\n if (this.animationFrameId !== null) {\n cancelAnimationFrame(this.animationFrameId);\n this.animationFrameId = null;\n }\n }\n\n private animate = () => {\n const currentTime = Date.now();\n const deltaTime = currentTime - this.lastUpdateTime;\n let timeToConsume = deltaTime;\n\n const remainingChars = this.targetText.length - this.currentText.length;\n const baseTimePerChar = Math.min(\n this.maxCharIntervalMs,\n this.drainMs / remainingChars,\n );\n\n const frameLimit = Math.min(remainingChars, this.maxCharsPerFrame);\n let charsToAdd = 0;\n while (timeToConsume >= baseTimePerChar && charsToAdd < frameLimit) {\n charsToAdd++;\n timeToConsume -= baseTimePerChar;\n }\n // A cap-limited frame must not bank its surplus time, or the next\n // frame would burst past the cap.\n if (charsToAdd === frameLimit && frameLimit === this.maxCharsPerFrame) {\n timeToConsume = 0;\n }\n\n if (charsToAdd !== remainingChars) {\n this.animationFrameId = requestAnimationFrame(this.animate);\n } else {\n this.animationFrameId = null;\n }\n if (charsToAdd === 0) return;\n\n this.currentText = this.targetText.slice(\n 0,\n this.currentText.length + charsToAdd,\n );\n this.lastUpdateTime = currentTime - timeToConsume;\n\n const isComplete = charsToAdd === remainingChars;\n if (isComplete || currentTime - this.lastCommitTime >= this.minCommitMs) {\n this.lastCommitTime = currentTime;\n this.setText(this.currentText);\n }\n };\n}\n\nconst SMOOTH_STATUS: MessagePartStatus = Object.freeze({\n type: \"running\",\n});\n\nconst positiveOr = (value: number | undefined, fallback: number): number =>\n value !== undefined && value > 0 ? value : fallback;\n\n/**\n * Animates streamed message part text with a typewriter-style reveal.\n *\n * Takes the current part state and a `smooth` argument: `false` disables,\n * `true` uses the default rate, and a {@link SmoothOptions} object tunes\n * the reveal. Returns the part state with `text` replaced by the revealed\n * prefix and `status` reporting `running` until the reveal catches up.\n *\n * @example\n * ```tsx\n * const { text, status } = useSmooth(useMessagePartText(), {\n * drainMs: 500,\n * maxCharsPerFrame: 30,\n * });\n * ```\n */\nexport const useSmooth = (\n state: MessagePartState & (TextMessagePart | ReasoningMessagePart),\n smooth: boolean | SmoothOptions = false,\n): MessagePartState & (TextMessagePart | ReasoningMessagePart) => {\n const { text } = state;\n const options =\n typeof smooth === \"object\" && smooth !== null ? smooth : undefined;\n const enabled = smooth !== false && smooth !== null;\n const drainMs = positiveOr(options?.drainMs, DEFAULT_DRAIN_MS);\n const maxCharIntervalMs = positiveOr(\n options?.maxCharIntervalMs,\n DEFAULT_MAX_CHAR_INTERVAL_MS,\n );\n const maxCharsPerFrame = positiveOr(options?.maxCharsPerFrame, Infinity);\n const minCommitMs = positiveOr(options?.minCommitMs, 0);\n\n const [displayedText, setDisplayedText] = useState(\n state.status.type === \"running\" ? \"\" : text,\n );\n\n // Render-phase resync on part flip or text discontinuity, so the\n // first paint after a thread switch never shows the previous\n // part's text (#4051). `displayedText` is already a prefix of\n // `text` during normal streaming, so use it as the previous-text\n // reference instead of carrying separate state — avoids the\n // double render per streaming token. Read part identity through\n // `useAuiState` so we actually subscribe to its changes instead\n // of relying on a render-time proxy reference that may be stable\n // across thread swaps.\n const aui = useAui();\n const part = useAuiState(() => aui.part());\n const [prevPart, setPrevPart] = useState(part);\n if (part !== prevPart || !text.startsWith(displayedText)) {\n setPrevPart(part);\n setDisplayedText(state.status.type === \"running\" ? \"\" : text);\n }\n\n const smoothStatusStore = useSmoothStatusStore({ optional: true });\n const setText = useCallbackRef((text: string) => {\n setDisplayedText(text);\n if (smoothStatusStore) {\n const target =\n displayedText !== text || state.status.type === \"running\"\n ? SMOOTH_STATUS\n : state.status;\n writableStore(smoothStatusStore).setState(target, true);\n }\n });\n\n // TODO this is hacky\n useEffect(() => {\n if (smoothStatusStore) {\n const target =\n enabled && (displayedText !== text || state.status.type === \"running\")\n ? SMOOTH_STATUS\n : state.status;\n writableStore(smoothStatusStore).setState(target, true);\n }\n }, [smoothStatusStore, enabled, text, displayedText, state.status]);\n\n const [animatorRef] = useState<TextStreamAnimator>(\n new TextStreamAnimator(displayedText, setText),\n );\n\n useEffect(() => {\n animatorRef.drainMs = drainMs;\n animatorRef.maxCharIntervalMs = maxCharIntervalMs;\n animatorRef.maxCharsPerFrame = maxCharsPerFrame;\n animatorRef.minCommitMs = minCommitMs;\n }, [animatorRef, drainMs, maxCharIntervalMs, maxCharsPerFrame, minCommitMs]);\n\n const animatorPartRef = useRef(part);\n useEffect(() => {\n if (!enabled) {\n animatorRef.stop();\n return;\n }\n\n // Discontinuity: part flipped, or new text breaks continuation\n // of the animator's current target. Either case requires\n // resetting the cursor — without the part check, a new part\n // whose text happens to share a prefix with the previous target\n // would keep the stale cursor and flicker.\n const partChanged = animatorPartRef.current !== part;\n animatorPartRef.current = part;\n if (partChanged || !text.startsWith(animatorRef.targetText)) {\n if (state.status.type === \"running\") {\n animatorRef.currentText = \"\";\n animatorRef.targetText = text;\n animatorRef.lastCommitTime = 0;\n animatorRef.start();\n } else {\n animatorRef.currentText = text;\n animatorRef.targetText = text;\n animatorRef.stop();\n }\n return;\n }\n\n animatorRef.targetText = text;\n animatorRef.start();\n }, [animatorRef, enabled, text, state.status.type, part]);\n\n useEffect(() => {\n return () => {\n animatorRef.stop();\n };\n }, [animatorRef]);\n\n return useMemo(\n () =>\n enabled\n ? {\n ...state,\n text: displayedText,\n status: text === displayedText ? state.status : SMOOTH_STATUS,\n }\n : state,\n [enabled, displayedText, state, text],\n );\n};\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@assistant-ui/react",
3
- "version": "0.14.20",
3
+ "version": "0.14.22",
4
4
  "description": "Open-source TypeScript/React library for building production-grade AI chat experiences",
5
5
  "keywords": [
6
6
  "ai",
@@ -55,9 +55,9 @@
55
55
  ],
56
56
  "sideEffects": false,
57
57
  "dependencies": {
58
- "@assistant-ui/core": "^0.2.16",
58
+ "@assistant-ui/core": "^0.2.17",
59
59
  "@assistant-ui/store": "^0.2.18",
60
- "@assistant-ui/tap": "^0.9.1",
60
+ "@assistant-ui/tap": "^0.9.2",
61
61
  "@radix-ui/primitive": "^1.1.4",
62
62
  "@radix-ui/react-compose-refs": "^1.1.3",
63
63
  "@radix-ui/react-context": "^1.1.4",
@@ -98,7 +98,7 @@
98
98
  "react-dom": "^19.2.7",
99
99
  "vitest": "^4.1.8",
100
100
  "@assistant-ui/vite": "0.0.5",
101
- "@assistant-ui/x-buildutils": "0.0.14"
101
+ "@assistant-ui/x-buildutils": "0.0.15"
102
102
  },
103
103
  "publishConfig": {
104
104
  "access": "public",
@@ -55,11 +55,12 @@ export function createContextStoreHook<T, K extends keyof T & string>(
55
55
  selector = param.selector;
56
56
  }
57
57
 
58
- const store = useStoreStoreHook({
58
+ const useStore = useStoreStoreHook({
59
59
  optional,
60
60
  } as any) as UseBoundStore<ReadonlyStore<StateType>>;
61
- if (!store) return null;
62
- return selector ? store(selector) : store();
61
+ if (!useStore) return null;
62
+ // oxlint-disable-next-line react-hooks/rules-of-hooks -- optional context returns before calling the dynamic Zustand hook
63
+ return selector ? useStore(selector) : useStore();
63
64
  }
64
65
 
65
66
  // Return an object with keys based on contextKey