Skip to content
Closed
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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ React.render(c, container);
|optionLabelProp | which prop value of option will render as content of select | String | 'value' |
|defaultValue | initial selected option(s) | String/Array<String> | - |
|value | current selected option(s) | String/Array<String>/{key:String, label:React.Node}/Array<{key, label}> | - |
|firstActiveValue | first active value when there is no value | String/Array<String> | - |
|labelInValue| whether to embed label in value, see above value type | Bool | false |
|onChange | called when select an option or input value change(combobox) | function(value) | - |
|onSearch | called when input changed | function | - |
Expand Down
1 change: 1 addition & 0 deletions examples/single.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ class Test extends React.Component {
optionLabelProp="children"
optionFilterProp="text"
onChange={this.onChange}
firstActiveValue="2"
>
<Option value="01" text="jack" title="jack">
<b
Expand Down
30 changes: 22 additions & 8 deletions src/DropdownMenu.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import PropTypes from 'prop-types';
import toArray from 'rc-util/lib/Children/toArray';
import Menu from 'rc-menu';
import scrollIntoView from 'dom-scroll-into-view';
import { getSelectKeys, preventDefaultEvent } from './util';
import { getSelectKeys, preventDefaultEvent, saveRef } from './util';

export default class DropdownMenu extends React.Component {
static propTypes= {
Expand All @@ -21,6 +21,11 @@ export default class DropdownMenu extends React.Component {
visible: PropTypes.bool,
}

constructor(props) {
super(props);
this.saveMenuRef = saveRef.bind(this, 'menuInstance');
}

componentWillMount() {
this.lastInputValue = this.props.inputValue;
}
Expand Down Expand Up @@ -50,10 +55,17 @@ export default class DropdownMenu extends React.Component {
scrollActiveItemToView = () => {
// scroll into view
const itemComponent = findDOMNode(this.firstActiveItem);
const props = this.props;

if (itemComponent) {
scrollIntoView(itemComponent, findDOMNode(this.refs.menu), {
const scrollIntoViewOpts = {
onlyScrollIfNeeded: true,
});
};
if ((!props.value || props.value.length === 0) && props.firstActiveValue) {
scrollIntoViewOpts.alignWithTop = true;
}

scrollIntoView(itemComponent, findDOMNode(this.menuInstance), scrollIntoViewOpts);
}
}

Expand All @@ -63,7 +75,7 @@ export default class DropdownMenu extends React.Component {
menuItems,
defaultActiveFirstOption, value,
prefixCls, multiple,
onMenuSelect, inputValue,
onMenuSelect, inputValue, firstActiveValue,
} = props;
if (menuItems && menuItems.length) {
const menuProps = {};
Expand All @@ -78,15 +90,16 @@ export default class DropdownMenu extends React.Component {
const activeKeyProps = {};

let clonedMenuItems = menuItems;
if (selectedKeys.length) {
if (selectedKeys.length || firstActiveValue) {
if (props.visible && !this.lastVisible) {
activeKeyProps.activeKey = selectedKeys[0];
activeKeyProps.activeKey = selectedKeys[0] || firstActiveValue;
}
let foundFirst = false;
// set firstActiveItem via cloning menus
// for scroll into view
const clone = (item) => {
if (!foundFirst && selectedKeys.indexOf(item.key) !== -1) {
if ((!foundFirst && selectedKeys.indexOf(item.key) !== -1)
|| (!foundFirst && !selectedKeys.length && firstActiveValue.indexOf(item.key) !== -1)) {
foundFirst = true;
return cloneElement(item, {
ref: (ref) => {
Expand All @@ -106,13 +119,14 @@ export default class DropdownMenu extends React.Component {
});
}


// clear activeKey when inputValue change
if (inputValue !== this.lastInputValue) {
activeKeyProps.activeKey = '';
}

return (<Menu
ref="menu"
ref={this.saveMenuRef}
style={this.props.dropdownMenuStyle}
defaultActiveFirst={defaultActiveFirstOption}
{...activeKeyProps}
Expand Down
32 changes: 16 additions & 16 deletions src/Select.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
UNSELECTABLE_ATTRIBUTE, UNSELECTABLE_STYLE,
preventDefaultEvent, findFirstMenuItem,
includesSeparators, splitBySeparators,
findIndexInValueByLabel, defaultFilterFn,
findIndexInValueByLabel, defaultFilterFn, saveRef,
} from './util';
import SelectTrigger from './SelectTrigger';
import { SelectPropTypes } from './PropTypes';
Expand All @@ -21,10 +21,6 @@ import warning from 'warning';
function noop() {
}

function saveRef(name, component) {
this[name] = component;
}

function chaining(...fns) {
return function (...args) { // eslint-disable-line
for (let i = 0; i < fns.length; i++) {
Expand Down Expand Up @@ -78,6 +74,9 @@ constructor(props) {
}
this.saveInputRef = saveRef.bind(this, 'inputInstance');
this.saveInputMirrorRef = saveRef.bind(this, 'inputMirrorInstance');
this.saveRootRef = saveRef.bind(this, 'rootInstance');
this.saveSelectionRef = saveRef.bind(this, 'selectionInstance');
this.saveTriggerRef = saveRef.bind(this, 'triggerInstance');
let open = props.open;
if (open === undefined) {
open = props.defaultOpen;
Expand Down Expand Up @@ -216,7 +215,7 @@ constructor(props) {
}

if (state.open) {
const menu = this.refs.trigger.getInnerMenu();
const menu = this.triggerInstance.getInnerMenu();
if (menu && menu.onKeyDown(event)) {
event.preventDefault();
event.stopPropagation();
Expand Down Expand Up @@ -362,7 +361,7 @@ constructor(props) {
}

onChoiceAnimationLeave =() => {
this.refs.trigger.refs.trigger.forcePopupAlign();
this.triggerInstance.triggerInstance.forcePopupAlign();
}

getLabelBySingleValue = (children, value) => {
Expand Down Expand Up @@ -505,11 +504,11 @@ constructor(props) {
}

getPopupDOMNode=() => {
return this.refs.trigger.getPopupDOMNode();
return this.triggerInstance.getPopupDOMNode();
}

getPopupMenuComponent=() => {
return this.refs.trigger.getInnerMenu();
return this.triggerInstance.getInnerMenu();
}

setOpenState = (open, needFocus) => {
Expand Down Expand Up @@ -600,12 +599,12 @@ constructor(props) {
}

updateFocusClassName = () => {
const { refs, props } = this;
const { rootInstance, props } = this;
// avoid setState and its side effect
if (this._focused) {
classes(refs.root).add(`${props.prefixCls}-focused`);
classes(rootInstance).add(`${props.prefixCls}-focused`);
} else {
classes(refs.root).remove(`${props.prefixCls}-focused`);
classes(rootInstance).remove(`${props.prefixCls}-focused`);
}
}

Expand All @@ -619,7 +618,7 @@ constructor(props) {
this._focused = true;
}
} else {
const selection = this.refs.selection;
const selection = this.selectionInstance;
if (activeElement !== selection) {
selection.focus();
this._focused = true;
Expand Down Expand Up @@ -1031,21 +1030,22 @@ constructor(props) {
visible={open}
inputValue={state.inputValue}
value={state.value}
firstActiveValue={props.firstActiveValue}
onDropdownVisibleChange={this.onDropdownVisibleChange}
getPopupContainer={props.getPopupContainer}
onMenuSelect={this.onMenuSelect}
onMenuDeselect={this.onMenuDeselect}
ref="trigger"
ref={this.saveTriggerRef}
>
<div
style={props.style}
ref="root"
ref={this.saveRootRef}
onBlur={this.onOuterBlur}
onFocus={this.onOuterFocus}
className={classnames(rootCls)}
>
<div
ref="selection"
ref={this.saveSelectionRef}
key="selection"
className={`${prefixCls}-selection
${prefixCls}-selection--${multiple ? 'multiple' : 'single'}`}
Expand Down
22 changes: 13 additions & 9 deletions src/SelectTrigger.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import PropTypes from 'prop-types';
import classnames from 'classnames';
import DropdownMenu from './DropdownMenu';
import ReactDOM from 'react-dom';
import { isSingleMode } from './util';
import { isSingleMode, saveRef } from './util';

Trigger.displayName = 'Trigger';

Expand Down Expand Up @@ -45,6 +45,13 @@ export default class SelectTrigger extends React.Component {
children: PropTypes.any,
}

constructor(props) {
super(props);

this.saveMenuRef = saveRef.bind(this, 'popupMenu');
this.saveTriggerRef = saveRef.bind(this, 'triggerInstance');
}

componentDidUpdate() {
const { visible, dropdownMatchSelectWidth } = this.props;
if (visible) {
Expand All @@ -57,22 +64,23 @@ export default class SelectTrigger extends React.Component {
}

getInnerMenu = () => {
return this.popupMenu && this.popupMenu.refs.menu;
return this.popupMenu && this.popupMenu.menuInstance;
}

getPopupDOMNode = () => {
return this.refs.trigger.getPopupDomNode();
return this.triggerInstance.getPopupDomNode();
}

getDropdownElement = (newProps) => {
const props = this.props;
return (<DropdownMenu
ref={this.saveMenu}
ref={this.saveMenuRef}
{...newProps}
prefixCls={this.getDropdownPrefixCls()}
onMenuSelect={props.onMenuSelect}
onMenuDeselect={props.onMenuDeselect}
value={props.value}
firstActiveValue={props.firstActiveValue}
defaultActiveFirstOption={props.defaultActiveFirstOption}
dropdownMenuStyle={props.dropdownMenuStyle}
/>);
Expand All @@ -91,10 +99,6 @@ export default class SelectTrigger extends React.Component {
return `${this.props.prefixCls}-dropdown`;
}

saveMenu = (menu) => {
this.popupMenu = menu;
}

render() {
const { onPopupFocus, ...props } = this.props;
const { multiple, visible, inputValue, dropdownAlign,
Expand Down Expand Up @@ -122,7 +126,7 @@ export default class SelectTrigger extends React.Component {
return (<Trigger {...props}
showAction={disabled ? [] : ['click']}
hideAction={hideAction}
ref="trigger"
ref={this.saveTriggerRef}
popupPlacement="bottomLeft"
builtinPlacements={BUILT_IN_PLACEMENTS}
prefixCls={dropdownPrefixCls}
Expand Down
4 changes: 4 additions & 0 deletions src/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -141,3 +141,7 @@ export function splitBySeparators(string, separators) {
export function defaultFilterFn(input, child) {
return String(getPropValue(child, this.props.optionFilterProp)).indexOf(input) > -1;
}

export function saveRef(name, component) {
this[name] = component;
}
53 changes: 53 additions & 0 deletions tests/DropdownMenu.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,59 @@ describe('DropdownMenu', () => {
expect(wrapper.find('Menu').props().activeKey).toBe('');
});

it('save firstActiveValue', () => {
const menuItems = [
<MenuItem key="1">1</MenuItem>,
<MenuItem key="2">2</MenuItem>,
];

const wrapper = mount(
<DropdownMenu
menuItems={menuItems}
firstActiveValue={'2'}
/>
);

expect(wrapper.instance().firstActiveItem.props.children).toBe('2');
});

it('set firstActiveValue key to menu', () => {
const menuItems = [
<MenuItem key="1">1</MenuItem>,
<MenuItem key="2">2</MenuItem>,
];

const wrapper = mount(
<DropdownMenu
menuItems={menuItems}
firstActiveValue={'2'}
/>
);

wrapper.setProps({ visible: true });
expect(wrapper.find('Menu').props().activeKey).toBe('2');

wrapper.setProps({ inputValue: 'foo' });
expect(wrapper.find('Menu').props().activeKey).toBe('');
});

it('save value not firstActiveValue', () => {
const menuItems = [
<MenuItem key="1">1</MenuItem>,
<MenuItem key="2">2</MenuItem>,
];

const wrapper = mount(
<DropdownMenu
value={[{ key: '1' }]}
menuItems={menuItems}
firstActiveValue={'2'}
/>
);

expect(wrapper.instance().firstActiveItem.props.children).toBe('1');
});

it('save visible and inputValue when update', () => {
const wrapper = mount(
<DropdownMenu />
Expand Down
1 change: 1 addition & 0 deletions tests/__snapshots__/SelectTrigger.spec.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ exports[`SelectTrigger renders correctly 1`] = `
<DropdownMenu
defaultActiveFirstOption={undefined}
dropdownMenuStyle={undefined}
firstActiveValue={undefined}
inputValue={undefined}
menuItems={undefined}
multiple={undefined}
Expand Down
12 changes: 12 additions & 0 deletions tests/util.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
splitBySeparators,
getValuePropValue,
defaultFilterFn,
saveRef,
} from '../src/util';

describe('includesSeparators', () => {
Expand Down Expand Up @@ -94,3 +95,14 @@ describe('defaultFilterFn', () => {
expect(defaultFilterFn.call(testerInstance, 'wrong-val', child)).toBe(false);
});
});

describe('saveRef', () => {
const mock = {};
const saveTestRef = saveRef.bind(mock, 'testInstance');

it('adds a property with the given name to context', () => {
expect(mock.testInstance).toBe(undefined);
saveTestRef('bar');
expect(mock.testInstance).toBe('bar');
});
});