1import { useComposerHandleContext } from "../../composerHandleContext";
2
3const COMPOSER_FLOATING_LAYER_SELECTOR = [
4 '[data-composer-drawer-layer="true"]',
5 '[data-chat-composer-floating-layer="true"]',
6].join(",");
7
8export const composerFloatingLayerProps = {
9 "data-chat-composer-floating-layer": "true",
10} as const;
11
12export function useComposerMenuProps() {
13 const composerRef = useComposerHandleContext();
14
15 return {
16 ...composerFloatingLayerProps,
17 finalFocus: composerRef
18 ? () => {
19 const activeElement = document.activeElement;
20 if (activeElement !== document.body && !isInsideComposerFloatingLayer(activeElement)) {
21 return false;
22 }
23 composerRef.current?.focusAtEnd();
24 return false;
25 }
26 : undefined,
27 };
28}
29
30export function isInsideComposerFloatingLayer(target: EventTarget | null): boolean {
31 return target instanceof Element && target.closest(COMPOSER_FLOATING_LAYER_SELECTOR) !== null;
32}
33
34// Banners, the approval row, and the tasks badge dock above the surface. A
35// pointer or focus landing on one of them acts on that control and must not
36// expand a resting or collapsed composer.
37export function isInsideCollapsedComposerControls(target: EventTarget | null): boolean {
38 return (
39 target instanceof Element &&
40 target.closest('[data-chat-composer-collapsed-controls="true"]') !== null
41 );
42}
43
44export function isInsideRestingComposerControlScope(target: EventTarget | null): boolean {
45 return (
46 target instanceof Element &&
47 (target.closest('[data-chat-composer-resting-controls="true"]') !== null ||
48 target.closest('[data-chat-composer-resting-images="true"]') !== null ||
49 target.closest("[data-composer-context-control]") !== null ||
50 isInsideComposerFloatingLayer(target))
51 );
52}