Skip to content

Commit 11f81f3

Browse files
authored
feat: Add getRawInputElement (#639)
* feat: Add raw customize * feat: Support dropdown * test: Add test case
1 parent caf04cb commit 11f81f3

File tree

5 files changed

+118
-50
lines changed

5 files changed

+118
-50
lines changed

examples/custom-selector.tsx

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
/* eslint-disable no-console */
2+
import React from 'react';
3+
import Select from '../src';
4+
import '../assets/index.less';
5+
6+
export default () => {
7+
return (
8+
<Select
9+
getRawInputElement={() => <span>Content</span>}
10+
mode="multiple"
11+
options={[{ value: 'light' }, { value: 'bamboo' }]}
12+
allowClear
13+
placeholder="2333"
14+
/>
15+
);
16+
};
17+
/* eslint-enable */

src/SelectTrigger.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ export interface SelectTriggerProps {
6767
empty: boolean;
6868

6969
getTriggerDOMNode: () => HTMLElement;
70+
onPopupVisibleChange?: (visible: boolean) => void;
7071
}
7172

7273
const SelectTrigger: React.RefForwardingComponent<RefTriggerProps, SelectTriggerProps> = (
@@ -91,6 +92,7 @@ const SelectTrigger: React.RefForwardingComponent<RefTriggerProps, SelectTrigger
9192
getPopupContainer,
9293
empty,
9394
getTriggerDOMNode,
95+
onPopupVisibleChange,
9496
...restProps
9597
} = props;
9698

@@ -129,8 +131,8 @@ const SelectTrigger: React.RefForwardingComponent<RefTriggerProps, SelectTrigger
129131
return (
130132
<Trigger
131133
{...restProps}
132-
showAction={[]}
133-
hideAction={[]}
134+
showAction={onPopupVisibleChange ? ['click'] : []}
135+
hideAction={onPopupVisibleChange ? ['click'] : []}
134136
popupPlacement={direction === 'rtl' ? 'bottomRight' : 'bottomLeft'}
135137
builtinPlacements={builtInPlacements}
136138
prefixCls={dropdownPrefixCls}
@@ -144,6 +146,7 @@ const SelectTrigger: React.RefForwardingComponent<RefTriggerProps, SelectTrigger
144146
})}
145147
popupStyle={popupStyle}
146148
getTriggerDOMNode={getTriggerDOMNode}
149+
onPopupVisibleChange={onPopupVisibleChange}
147150
>
148151
{children}
149152
</Trigger>

src/generate.tsx

Lines changed: 78 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import * as React from 'react';
1111
import { useState, useRef, useEffect, useMemo } from 'react';
1212
import KeyCode from 'rc-util/lib/KeyCode';
1313
import isMobile from 'rc-util/lib/isMobile';
14+
import { composeRef } from 'rc-util/lib/ref';
1415
import classNames from 'classnames';
1516
import useMergedState from 'rc-util/lib/hooks/useMergedState';
1617
import type { ScrollTo } from 'rc-virtual-list/lib/List';
@@ -133,6 +134,8 @@ export interface SelectProps<OptionsType extends object[], ValueType> extends Re
133134
backfill?: boolean;
134135
/** @private Internal usage. Do not use in your production. */
135136
getInputElement?: () => JSX.Element;
137+
/** @private Internal usage. Do not use in your production. */
138+
getRawInputElement?: () => JSX.Element;
136139
optionLabelProp?: string;
137140
maxTagTextLength?: number;
138141
maxTagCount?: number | 'responsive';
@@ -289,6 +292,7 @@ export default function generateSelector<
289292
backfill,
290293
tabIndex,
291294
getInputElement,
295+
getRawInputElement,
292296
getPopupContainer,
293297

294298
// Dropdown
@@ -635,9 +639,13 @@ export default function generateSelector<
635639
};
636640

637641
// ============================= Input ==============================
638-
// Only works in `combobox` or `rc-cascader`
642+
// Only works in `combobox`
639643
const customizeInputElement: React.ReactElement =
640-
(typeof getInputElement === 'function' && getInputElement()) || null;
644+
(mode === 'combobox' && typeof getInputElement === 'function' && getInputElement()) || null;
645+
646+
// Used for customize replacement for `rc-cascader`
647+
const customizeRawInputElement: React.ReactElement =
648+
typeof getRawInputElement === 'function' && getRawInputElement();
641649

642650
// ============================== Open ==============================
643651
const [innerOpen, setInnerOpen] = useMergedState<boolean>(undefined, {
@@ -666,6 +674,14 @@ export default function generateSelector<
666674
}
667675
};
668676

677+
// Used for raw custom input trigger
678+
let onTriggerVisibleChange: null | ((newOpen: boolean) => void);
679+
if (customizeRawInputElement) {
680+
onTriggerVisibleChange = (newOpen: boolean) => {
681+
onToggleOpen(newOpen);
682+
};
683+
}
684+
669685
useSelectTriggerControl(
670686
[containerRef.current, triggerRef.current && triggerRef.current.getPopupElement()],
671687
triggerOpen,
@@ -931,8 +947,8 @@ export default function generateSelector<
931947

932948
useLayoutEffect(() => {
933949
if (triggerOpen) {
934-
const newWidth = Math.ceil(containerRef.current.offsetWidth);
935-
if (containerWidth !== newWidth) {
950+
const newWidth = Math.ceil(containerRef.current?.offsetWidth);
951+
if (containerWidth !== newWidth && !Number.isNaN(newWidth)) {
936952
setContainerWidth(newWidth);
937953
}
938954
}
@@ -1034,6 +1050,63 @@ export default function generateSelector<
10341050
[`${prefixCls}-show-search`]: mergedShowSearch,
10351051
});
10361052

1053+
const selectorNode = (
1054+
<SelectTrigger
1055+
ref={triggerRef}
1056+
disabled={disabled}
1057+
prefixCls={prefixCls}
1058+
visible={triggerOpen}
1059+
popupElement={popupNode}
1060+
containerWidth={containerWidth}
1061+
animation={animation}
1062+
transitionName={transitionName}
1063+
dropdownStyle={dropdownStyle}
1064+
dropdownClassName={dropdownClassName}
1065+
direction={direction}
1066+
dropdownMatchSelectWidth={dropdownMatchSelectWidth}
1067+
dropdownRender={dropdownRender}
1068+
dropdownAlign={dropdownAlign}
1069+
getPopupContainer={getPopupContainer}
1070+
empty={!mergedOptions.length}
1071+
getTriggerDOMNode={() => selectorDomRef.current}
1072+
onPopupVisibleChange={onTriggerVisibleChange}
1073+
>
1074+
{customizeRawInputElement ? (
1075+
React.cloneElement(customizeRawInputElement, {
1076+
ref: composeRef(selectorDomRef, customizeRawInputElement.props.ref),
1077+
})
1078+
) : (
1079+
<Selector
1080+
{...props}
1081+
domRef={selectorDomRef}
1082+
prefixCls={prefixCls}
1083+
inputElement={customizeInputElement}
1084+
ref={selectorRef}
1085+
id={mergedId}
1086+
showSearch={mergedShowSearch}
1087+
mode={mode}
1088+
accessibilityIndex={accessibilityIndex}
1089+
multiple={isMultiple}
1090+
tagRender={tagRender}
1091+
values={displayValues}
1092+
open={mergedOpen}
1093+
onToggleOpen={onToggleOpen}
1094+
searchValue={mergedSearchValue}
1095+
activeValue={activeValue}
1096+
onSearch={triggerSearch}
1097+
onSearchSubmit={onSearchSubmit}
1098+
onSelect={onInternalSelectionSelect}
1099+
tokenWithEnter={tokenWithEnter}
1100+
/>
1101+
)}
1102+
</SelectTrigger>
1103+
);
1104+
1105+
// Render raw
1106+
if (customizeRawInputElement) {
1107+
return selectorNode;
1108+
}
1109+
10371110
return (
10381111
<div
10391112
className={mergedClassName}
@@ -1060,48 +1133,7 @@ export default function generateSelector<
10601133
{`${mergedRawValue.join(', ')}`}
10611134
</span>
10621135
)}
1063-
<SelectTrigger
1064-
ref={triggerRef}
1065-
disabled={disabled}
1066-
prefixCls={prefixCls}
1067-
visible={triggerOpen}
1068-
popupElement={popupNode}
1069-
containerWidth={containerWidth}
1070-
animation={animation}
1071-
transitionName={transitionName}
1072-
dropdownStyle={dropdownStyle}
1073-
dropdownClassName={dropdownClassName}
1074-
direction={direction}
1075-
dropdownMatchSelectWidth={dropdownMatchSelectWidth}
1076-
dropdownRender={dropdownRender}
1077-
dropdownAlign={dropdownAlign}
1078-
getPopupContainer={getPopupContainer}
1079-
empty={!mergedOptions.length}
1080-
getTriggerDOMNode={() => selectorDomRef.current}
1081-
>
1082-
<Selector
1083-
{...props}
1084-
domRef={selectorDomRef}
1085-
prefixCls={prefixCls}
1086-
inputElement={customizeInputElement}
1087-
ref={selectorRef}
1088-
id={mergedId}
1089-
showSearch={mergedShowSearch}
1090-
mode={mode}
1091-
accessibilityIndex={accessibilityIndex}
1092-
multiple={isMultiple}
1093-
tagRender={tagRender}
1094-
values={displayValues}
1095-
open={mergedOpen}
1096-
onToggleOpen={onToggleOpen}
1097-
searchValue={mergedSearchValue}
1098-
activeValue={activeValue}
1099-
onSearch={triggerSearch}
1100-
onSearchSubmit={onSearchSubmit}
1101-
onSelect={onInternalSelectionSelect}
1102-
tokenWithEnter={tokenWithEnter}
1103-
/>
1104-
</SelectTrigger>
1136+
{selectorNode}
11051137

11061138
{arrowNode}
11071139
{clearNode}

tests/Select.test.tsx

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -801,6 +801,24 @@ describe('Select.Basic', () => {
801801
expect(onCompositionEnd).toHaveBeenCalled();
802802
});
803803

804+
it('getRawInputElement for rc-cascader', () => {
805+
const wrapper = mount(
806+
<Select
807+
getRawInputElement={() => <span className="bamboo" />}
808+
options={[
809+
{
810+
label: <span className="little" />,
811+
value: 'little',
812+
},
813+
]}
814+
open
815+
/>,
816+
);
817+
818+
expect(wrapper.exists('.bamboo')).toBeTruthy();
819+
expect(wrapper.exists('.little')).toBeTruthy();
820+
});
821+
804822
describe('propTypes', () => {
805823
let errorSpy;
806824

typings/index.d.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,3 @@ declare module 'rc-menu';
77
declare module 'rc-util/lib/Children/toArray';
88

99
declare module 'dom-scroll-into-view';
10-
11-
declare module 'rc-trigger';

0 commit comments

Comments
 (0)