UNPKG

11 kBJavaScriptView Raw
1import _extends from "@babel/runtime-corejs2/helpers/esm/extends";
2import _objectWithoutPropertiesLoose from "@babel/runtime-corejs2/helpers/esm/objectWithoutPropertiesLoose";
3import _inheritsLoose from "@babel/runtime-corejs2/helpers/esm/inheritsLoose";
4import _assertThisInitialized from "@babel/runtime-corejs2/helpers/esm/assertThisInitialized";
5import classNames from 'classnames';
6import activeElement from 'dom-helpers/activeElement';
7import contains from 'dom-helpers/query/contains';
8import keycode from 'keycode';
9import React, { cloneElement } from 'react';
10import PropTypes from 'prop-types';
11import ReactDOM from 'react-dom';
12import all from 'prop-types-extra/lib/all';
13import elementType from 'prop-types-extra/lib/elementType';
14import isRequiredForA11y from 'prop-types-extra/lib/isRequiredForA11y';
15import uncontrollable from 'uncontrollable';
16import warning from 'warning';
17import ButtonGroup from './ButtonGroup';
18import DropdownMenu from './DropdownMenu';
19import DropdownToggle from './DropdownToggle';
20import { bsClass as setBsClass, prefix } from './utils/bootstrapUtils';
21import createChainedFunction from './utils/createChainedFunction';
22import { exclusiveRoles, requiredRoles } from './utils/PropTypes';
23import ValidComponentChildren from './utils/ValidComponentChildren';
24var TOGGLE_ROLE = DropdownToggle.defaultProps.bsRole;
25var MENU_ROLE = DropdownMenu.defaultProps.bsRole;
26var propTypes = {
27 /**
28 * The menu will open above the dropdown button, instead of below it.
29 */
30 dropup: PropTypes.bool,
31
32 /**
33 * An html id attribute, necessary for assistive technologies, such as screen readers.
34 * @type {string|number}
35 * @required
36 */
37 id: isRequiredForA11y(PropTypes.oneOfType([PropTypes.string, PropTypes.number])),
38 componentClass: elementType,
39
40 /**
41 * The children of a Dropdown may be a `<Dropdown.Toggle>` or a `<Dropdown.Menu>`.
42 * @type {node}
43 */
44 children: all(requiredRoles(TOGGLE_ROLE, MENU_ROLE), exclusiveRoles(MENU_ROLE)),
45
46 /**
47 * Whether or not component is disabled.
48 */
49 disabled: PropTypes.bool,
50
51 /**
52 * Align the menu to the right side of the Dropdown toggle
53 */
54 pullRight: PropTypes.bool,
55
56 /**
57 * Whether or not the Dropdown is visible.
58 *
59 * @controllable onToggle
60 */
61 open: PropTypes.bool,
62 defaultOpen: PropTypes.bool,
63
64 /**
65 * A callback fired when the Dropdown wishes to change visibility. Called with the requested
66 * `open` value, the DOM event, and the source that fired it: `'click'`,`'keydown'`,`'rootClose'`, or `'select'`.
67 *
68 * ```js
69 * function(Boolean isOpen, Object event, { String source }) {}
70 * ```
71 * @controllable open
72 */
73 onToggle: PropTypes.func,
74
75 /**
76 * A callback fired when a menu item is selected.
77 *
78 * ```js
79 * (eventKey: any, event: Object) => any
80 * ```
81 */
82 onSelect: PropTypes.func,
83
84 /**
85 * If `'menuitem'`, causes the dropdown to behave like a menu item rather than
86 * a menu button.
87 */
88 role: PropTypes.string,
89
90 /**
91 * Which event when fired outside the component will cause it to be closed
92 *
93 * *Note: For custom dropdown components, you will have to pass the
94 * `rootCloseEvent` to `<RootCloseWrapper>` in your custom dropdown menu
95 * component ([similarly to how it is implemented in `<Dropdown.Menu>`](https://github.com/react-bootstrap/react-bootstrap/blob/v0.31.5/src/DropdownMenu.js#L115-L119)).*
96 */
97 rootCloseEvent: PropTypes.oneOf(['click', 'mousedown']),
98
99 /**
100 * @private
101 */
102 onMouseEnter: PropTypes.func,
103
104 /**
105 * @private
106 */
107 onMouseLeave: PropTypes.func
108};
109var defaultProps = {
110 componentClass: ButtonGroup
111};
112
113var Dropdown =
114/*#__PURE__*/
115function (_React$Component) {
116 _inheritsLoose(Dropdown, _React$Component);
117
118 function Dropdown(props, context) {
119 var _this;
120
121 _this = _React$Component.call(this, props, context) || this;
122 _this.handleClick = _this.handleClick.bind(_assertThisInitialized(_assertThisInitialized(_this)));
123 _this.handleKeyDown = _this.handleKeyDown.bind(_assertThisInitialized(_assertThisInitialized(_this)));
124 _this.handleClose = _this.handleClose.bind(_assertThisInitialized(_assertThisInitialized(_this)));
125 _this._focusInDropdown = false;
126 _this.lastOpenEventType = null;
127 return _this;
128 }
129
130 var _proto = Dropdown.prototype;
131
132 _proto.componentDidMount = function componentDidMount() {
133 this.focusNextOnOpen();
134 };
135
136 _proto.componentWillUpdate = function componentWillUpdate(nextProps) {
137 if (!nextProps.open && this.props.open) {
138 this._focusInDropdown = contains(ReactDOM.findDOMNode(this.menu), activeElement(document));
139 }
140 };
141
142 _proto.componentDidUpdate = function componentDidUpdate(prevProps) {
143 var open = this.props.open;
144 var prevOpen = prevProps.open;
145
146 if (open && !prevOpen) {
147 this.focusNextOnOpen();
148 }
149
150 if (!open && prevOpen) {
151 // if focus hasn't already moved from the menu let's return it
152 // to the toggle
153 if (this._focusInDropdown) {
154 this._focusInDropdown = false;
155 this.focus();
156 }
157 }
158 };
159
160 _proto.focus = function focus() {
161 var toggle = ReactDOM.findDOMNode(this.toggle);
162
163 if (toggle && toggle.focus) {
164 toggle.focus();
165 }
166 };
167
168 _proto.focusNextOnOpen = function focusNextOnOpen() {
169 var menu = this.menu;
170
171 if (!menu || !menu.focusNext) {
172 return;
173 }
174
175 if (this.lastOpenEventType === 'keydown' || this.props.role === 'menuitem') {
176 menu.focusNext();
177 }
178 };
179
180 _proto.handleClick = function handleClick(event) {
181 if (this.props.disabled) {
182 return;
183 }
184
185 this.toggleOpen(event, {
186 source: 'click'
187 });
188 };
189
190 _proto.handleClose = function handleClose(event, eventDetails) {
191 if (!this.props.open) {
192 return;
193 }
194
195 this.toggleOpen(event, eventDetails);
196 };
197
198 _proto.handleKeyDown = function handleKeyDown(event) {
199 if (this.props.disabled) {
200 return;
201 }
202
203 switch (event.keyCode) {
204 case keycode.codes.down:
205 if (!this.props.open) {
206 this.toggleOpen(event, {
207 source: 'keydown'
208 });
209 } else if (this.menu.focusNext) {
210 this.menu.focusNext();
211 }
212
213 event.preventDefault();
214 break;
215
216 case keycode.codes.esc:
217 case keycode.codes.tab:
218 this.handleClose(event, {
219 source: 'keydown'
220 });
221 break;
222
223 default:
224 }
225 };
226
227 _proto.toggleOpen = function toggleOpen(event, eventDetails) {
228 var open = !this.props.open;
229
230 if (open) {
231 this.lastOpenEventType = eventDetails.source;
232 }
233
234 if (this.props.onToggle) {
235 this.props.onToggle(open, event, eventDetails);
236 }
237 };
238
239 _proto.renderMenu = function renderMenu(child, _ref) {
240 var _this2 = this;
241
242 var id = _ref.id,
243 onSelect = _ref.onSelect,
244 rootCloseEvent = _ref.rootCloseEvent,
245 props = _objectWithoutPropertiesLoose(_ref, ["id", "onSelect", "rootCloseEvent"]);
246
247 var ref = function ref(c) {
248 _this2.menu = c;
249 };
250
251 if (typeof child.ref === 'string') {
252 process.env.NODE_ENV !== "production" ? warning(false, 'String refs are not supported on `<Dropdown.Menu>` components. ' + 'To apply a ref to the component use the callback signature:\n\n ' + 'https://facebook.github.io/react/docs/more-about-refs.html#the-ref-callback-attribute') : void 0;
253 } else {
254 ref = createChainedFunction(child.ref, ref);
255 }
256
257 return cloneElement(child, _extends({}, props, {
258 ref: ref,
259 labelledBy: id,
260 bsClass: prefix(props, 'menu'),
261 onClose: createChainedFunction(child.props.onClose, this.handleClose),
262 onSelect: createChainedFunction(child.props.onSelect, onSelect, function (key, event) {
263 return _this2.handleClose(event, {
264 source: 'select'
265 });
266 }),
267 rootCloseEvent: rootCloseEvent
268 }));
269 };
270
271 _proto.renderToggle = function renderToggle(child, props) {
272 var _this3 = this;
273
274 var ref = function ref(c) {
275 _this3.toggle = c;
276 };
277
278 if (typeof child.ref === 'string') {
279 process.env.NODE_ENV !== "production" ? warning(false, 'String refs are not supported on `<Dropdown.Toggle>` components. ' + 'To apply a ref to the component use the callback signature:\n\n ' + 'https://facebook.github.io/react/docs/more-about-refs.html#the-ref-callback-attribute') : void 0;
280 } else {
281 ref = createChainedFunction(child.ref, ref);
282 }
283
284 return cloneElement(child, _extends({}, props, {
285 ref: ref,
286 bsClass: prefix(props, 'toggle'),
287 onClick: createChainedFunction(child.props.onClick, this.handleClick),
288 onKeyDown: createChainedFunction(child.props.onKeyDown, this.handleKeyDown)
289 }));
290 };
291
292 _proto.render = function render() {
293 var _classes,
294 _this4 = this;
295
296 var _this$props = this.props,
297 Component = _this$props.componentClass,
298 id = _this$props.id,
299 dropup = _this$props.dropup,
300 disabled = _this$props.disabled,
301 pullRight = _this$props.pullRight,
302 open = _this$props.open,
303 onSelect = _this$props.onSelect,
304 role = _this$props.role,
305 bsClass = _this$props.bsClass,
306 className = _this$props.className,
307 rootCloseEvent = _this$props.rootCloseEvent,
308 children = _this$props.children,
309 props = _objectWithoutPropertiesLoose(_this$props, ["componentClass", "id", "dropup", "disabled", "pullRight", "open", "onSelect", "role", "bsClass", "className", "rootCloseEvent", "children"]);
310
311 delete props.onToggle;
312 var classes = (_classes = {}, _classes[bsClass] = true, _classes.open = open, _classes.disabled = disabled, _classes);
313
314 if (dropup) {
315 classes[bsClass] = false;
316 classes.dropup = true;
317 } // This intentionally forwards bsSize and bsStyle (if set) to the
318 // underlying component, to allow it to render size and style variants.
319
320
321 return React.createElement(Component, _extends({}, props, {
322 className: classNames(className, classes)
323 }), ValidComponentChildren.map(children, function (child) {
324 switch (child.props.bsRole) {
325 case TOGGLE_ROLE:
326 return _this4.renderToggle(child, {
327 id: id,
328 disabled: disabled,
329 open: open,
330 role: role,
331 bsClass: bsClass
332 });
333
334 case MENU_ROLE:
335 return _this4.renderMenu(child, {
336 id: id,
337 open: open,
338 pullRight: pullRight,
339 bsClass: bsClass,
340 onSelect: onSelect,
341 rootCloseEvent: rootCloseEvent
342 });
343
344 default:
345 return child;
346 }
347 }));
348 };
349
350 return Dropdown;
351}(React.Component);
352
353Dropdown.propTypes = propTypes;
354Dropdown.defaultProps = defaultProps;
355setBsClass('dropdown', Dropdown);
356var UncontrolledDropdown = uncontrollable(Dropdown, {
357 open: 'onToggle'
358});
359UncontrolledDropdown.Toggle = DropdownToggle;
360UncontrolledDropdown.Menu = DropdownMenu;
361export default UncontrolledDropdown;
\No newline at end of file