Skip to content

_⚠️ Potential issue_ Overriding of keydown events #1510

@kotAPI

Description

@kotAPI

⚠️ Potential issue

onKeyDown is overridden by consumer props and may override Floater’s handlers — compose instead

Spreading getFloatingProps() first, then defining onKeyDown, and finally spreading {...props} means a consumer-provided onKeyDown will replace your Escape/Tab logic; likewise, your handler may override any internal getFloatingProps().onKeyDown. This can break closing on Escape and focus trapping.

Refactor to compose handlers via getFloatingProps(userProps) and remove the trailing {...props}:

-                    {...getFloatingProps()}
+                    {...getFloatingProps({
+                        ...props,
+                        onKeyDown: (e: React.KeyboardEvent<HTMLDivElement>) => {
+                            // Run consumer first
+                            (props as any)?.onKeyDown?.(e);
+                            if (e.defaultPrevented) return;
+                            // Escape to close
+                            if (e.key === 'Escape') {
+                                e.preventDefault();
+                                e.stopPropagation();
+                                handleOpenChange(false);
+                                return;
+                            }
+                            // Tab trap (see fix below for 0 focusable elements)
+                            if (e.key === 'Tab') {
+                                const focusableSelector = [
+                                    'button:not([disabled])','[href]','input:not([disabled])',
+                                    'select:not([disabled])','textarea:not([disabled])',
+                                    '[tabindex]:not([tabindex="-1"])'
+                                ].join(',');
+                                const focusableElements = Array.from(
+                                    contentRef.current?.querySelectorAll(focusableSelector) || []
+                                ) as HTMLElement[];
+                                if (focusableElements.length === 0) {
+                                    e.preventDefault();
+                                    (contentRef.current as HTMLElement | null)?.focus();
+                                    return;
+                                }
+                                const firstElement = focusableElements[0];
+                                const lastElement = focusableElements[focusableElements.length - 1];
+                                if (e.shiftKey) {
+                                    if (document.activeElement === firstElement) {
+                                        e.preventDefault();
+                                        lastElement.focus();
+                                    }
+                                } else {
+                                    if (document.activeElement === lastElement) {
+                                        e.preventDefault();
+                                        firstElement.focus();
+                                    }
+                                }
+                            }
+                        }
+                    })}
-                    onKeyDown={(e) => {
-                        // Handle escape key
-                        if (e.key === 'Escape') {
-                            e.preventDefault();
-                            e.stopPropagation();
-                            handleOpenChange(false);
-                        }
-                        // Handle tab trapping
-                        if (e.key === 'Tab') {
-                          ...
-                        }
-                    }}
-                    {...props}

Also applies to: 94-101, 136-138

🤖 Prompt for AI Agents
In src/core/primitives/Dialog/fragments/DialogPrimitiveContent.tsx around lines
86-87 (and likewise update lines 94-101 and 136-138), the current prop spread
order lets consumer onKeyDown override Floater handlers and vice-versa; fix by
passing the consumer props into getFloatingProps (e.g., call
getFloatingProps(props) / getFloatingProps(userProps)) instead of spreading
getFloatingProps() first and then {...props}, and remove the trailing {...props}
spread so event handlers are composed by getFloatingProps rather than
overwritten.

Originally posted by @coderabbitai[bot] in #1508 (comment)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions