Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions src/BaseSelect/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,14 @@ export interface BaseSelectProps
popupRender?: (menu: React.ReactElement) => React.ReactElement;
popupAlign?: AlignType;

/**
* Get dynamic popup offset based on input element.
* Useful for positioning dropdown relative to cursor in multi-line textarea.
* @param inputElement - The input/textarea element
* @returns [x, y] offset array or null to use default positioning
*/
getPopupOffset?: (inputElement: HTMLElement) => [number, number] | null;

placement?: Placement;
builtinPlacements?: BuildInPlacements;
getPopupContainer?: RenderDOMFunc;
Expand Down Expand Up @@ -299,6 +307,7 @@ const BaseSelect = React.forwardRef<BaseSelectRef, BaseSelectProps>((props, ref)
popupMatchSelectWidth,
popupRender,
popupAlign,
getPopupOffset,
placement,
builtinPlacements,
getPopupContainer,
Expand Down Expand Up @@ -789,6 +798,7 @@ const BaseSelect = React.forwardRef<BaseSelectRef, BaseSelectProps>((props, ref)
popupMatchSelectWidth={popupMatchSelectWidth}
popupRender={popupRender}
popupAlign={popupAlign}
getPopupOffset={getPopupOffset}
placement={placement}
builtinPlacements={builtinPlacements}
getPopupContainer={getPopupContainer}
Expand Down
34 changes: 30 additions & 4 deletions src/SelectTrigger.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@ export interface SelectTriggerProps {
popupRender?: (menu: React.ReactElement) => React.ReactElement;
getPopupContainer?: RenderDOMFunc;
popupAlign: AlignType;
/** Get dynamic popup offset based on input element for textarea cursor positioning */
getPopupOffset?: (inputElement: HTMLElement) => [number, number] | null;
empty: boolean;

onPopupVisibleChange?: (visible: boolean) => void;
Expand Down Expand Up @@ -100,6 +102,7 @@ const SelectTrigger: React.ForwardRefRenderFunction<RefTriggerProps, SelectTrigg
popupMatchSelectWidth,
popupRender,
popupAlign,
getPopupOffset,
getPopupContainer,
empty,
onPopupVisibleChange,
Expand All @@ -122,10 +125,33 @@ const SelectTrigger: React.ForwardRefRenderFunction<RefTriggerProps, SelectTrigg
popupNode = popupRender(popupElement);
}

const mergedBuiltinPlacements = React.useMemo(
() => builtinPlacements || getBuiltInPlacements(popupMatchSelectWidth),
[builtinPlacements, popupMatchSelectWidth],
);
const mergedBuiltinPlacements = React.useMemo(() => {
const base = builtinPlacements || getBuiltInPlacements(popupMatchSelectWidth);

if (!getPopupOffset) {
return base;
}

// Apply dynamic offset to placements
const dynamicPlacements: Record<string, AlignType> = {};
Object.keys(base).forEach((key) => {
const placement = base[key];
dynamicPlacements[key] = {
...placement,
offset: (node: HTMLElement) => {
// Get the offset from getPopupOffset callback
const customOffset = getPopupOffset(node);
if (customOffset) {
return customOffset;
}
// Default offset from base placement
return placement.offset as [number, number];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The offset property in AlignType is optional. If placement.offset is undefined (e.g., if a custom builtinPlacements prop is provided without an offset for a specific placement), then placement.offset as [number, number] would return undefined. This could lead to unexpected behavior or runtime errors in the underlying Trigger component, which likely expects a [number, number] tuple if an offset is provided. Consider using the nullish coalescing operator (??) to provide a default offset like [0, 0] if placement.offset is undefined.

Suggested change
return placement.offset as [number, number];
return placement.offset ?? [0, 0];

},
};
});

return dynamicPlacements;
}, [builtinPlacements, popupMatchSelectWidth, getPopupOffset]);

// ===================== Motion ======================
const mergedTransitionName = animation ? `${popupPrefixCls}-${animation}` : transitionName;
Expand Down
Loading