UNPKG

11.3 kBJavaScriptView Raw
1import { __rest } from "tslib";
2import * as React from 'react';
3import styles from '@patternfly/react-styles/css/components/Select/select';
4import buttonStyles from '@patternfly/react-styles/css/components/Button/button';
5import { css } from '@patternfly/react-styles';
6import CaretDownIcon from '@patternfly/react-icons/dist/esm/icons/caret-down-icon';
7import { SelectVariant, SelectFooterTabbableItems } from './selectConstants';
8import { findTabbableElements } from '../../helpers/util';
9import { KeyTypes } from '../../helpers/constants';
10export class SelectToggle extends React.Component {
11 constructor(props) {
12 super(props);
13 this.onDocClick = (event) => {
14 const { parentRef, menuRef, footerRef, isOpen, onToggle, onClose } = this.props;
15 const clickedOnToggle = parentRef && parentRef.current && parentRef.current.contains(event.target);
16 const clickedWithinMenu = menuRef && menuRef.current && menuRef.current.contains && menuRef.current.contains(event.target);
17 const clickedWithinFooter = footerRef && footerRef.current && footerRef.current.contains && footerRef.current.contains(event.target);
18 if (isOpen && !(clickedOnToggle || clickedWithinMenu || clickedWithinFooter)) {
19 onToggle(false, event);
20 onClose();
21 }
22 };
23 this.handleGlobalKeys = (event) => {
24 const { parentRef, menuRef, hasFooter, footerRef, isOpen, variant, onToggle, onClose, moveFocusToLastMenuItem } = this.props;
25 const escFromToggle = parentRef && parentRef.current && parentRef.current.contains(event.target);
26 const escFromWithinMenu = menuRef && menuRef.current && menuRef.current.contains && menuRef.current.contains(event.target);
27 if (isOpen &&
28 event.key === KeyTypes.Tab &&
29 (variant === SelectVariant.typeahead || variant === SelectVariant.typeaheadMulti)) {
30 this.props.handleTypeaheadKeys('tab', event.shiftKey);
31 event.preventDefault();
32 return;
33 }
34 if (isOpen && event.key === KeyTypes.Tab && hasFooter) {
35 const tabbableItems = findTabbableElements(footerRef, SelectFooterTabbableItems);
36 // If no tabbable item in footer close select
37 if (tabbableItems.length <= 0) {
38 onToggle(false, event);
39 onClose();
40 this.toggle.current.focus();
41 return;
42 }
43 else {
44 // if current element is not in footer, tab to first tabbable element in footer, or close if shift clicked
45 const currentElementIndex = tabbableItems.findIndex((item) => item === document.activeElement);
46 if (currentElementIndex === -1) {
47 if (event.shiftKey) {
48 if (variant !== 'checkbox') {
49 // only close non checkbox variation on shift clicked
50 onToggle(false, event);
51 onClose();
52 this.toggle.current.focus();
53 }
54 }
55 else {
56 // tab to footer
57 tabbableItems[0].focus();
58 return;
59 }
60 }
61 // Current element is in footer.
62 if (event.shiftKey) {
63 // Move focus back to menu if current tab index is 0
64 if (currentElementIndex === 0) {
65 moveFocusToLastMenuItem();
66 event.preventDefault();
67 }
68 return;
69 }
70 // Tab to next element in footer or close if there are none
71 if (currentElementIndex + 1 < tabbableItems.length) {
72 tabbableItems[currentElementIndex + 1].focus();
73 }
74 else {
75 // no more footer items close menu
76 onToggle(false, event);
77 onClose();
78 this.toggle.current.focus();
79 }
80 event.preventDefault();
81 return;
82 }
83 }
84 if (isOpen &&
85 (event.key === KeyTypes.Escape || event.key === KeyTypes.Tab) &&
86 (escFromToggle || escFromWithinMenu)) {
87 onToggle(false, event);
88 onClose();
89 this.toggle.current.focus();
90 }
91 };
92 this.onKeyDown = (event) => {
93 const { isOpen, onToggle, variant, onClose, onEnter, handleTypeaheadKeys } = this.props;
94 if (variant === SelectVariant.typeahead || variant === SelectVariant.typeaheadMulti) {
95 if (event.key === KeyTypes.ArrowDown || event.key === KeyTypes.ArrowUp) {
96 handleTypeaheadKeys((event.key === KeyTypes.ArrowDown && 'down') || (event.key === KeyTypes.ArrowUp && 'up'));
97 event.preventDefault();
98 }
99 else if (event.key === KeyTypes.Enter) {
100 if (isOpen) {
101 handleTypeaheadKeys('enter');
102 }
103 else {
104 onToggle(!isOpen, event);
105 }
106 }
107 }
108 if (variant === SelectVariant.typeahead ||
109 variant === SelectVariant.typeaheadMulti ||
110 (event.key === KeyTypes.Tab && !isOpen) ||
111 (event.key !== KeyTypes.Enter && event.key !== KeyTypes.Space)) {
112 return;
113 }
114 event.preventDefault();
115 if ((event.key === KeyTypes.Tab || event.key === KeyTypes.Enter || event.key === KeyTypes.Space) && isOpen) {
116 onToggle(!isOpen, event);
117 onClose();
118 this.toggle.current.focus();
119 }
120 else if ((event.key === KeyTypes.Enter || event.key === KeyTypes.Space) && !isOpen) {
121 onToggle(!isOpen, event);
122 onEnter();
123 }
124 };
125 const { variant } = props;
126 const isTypeahead = variant === SelectVariant.typeahead || variant === SelectVariant.typeaheadMulti;
127 this.toggle = isTypeahead ? React.createRef() : React.createRef();
128 }
129 componentDidMount() {
130 document.addEventListener('click', this.onDocClick, { capture: true });
131 document.addEventListener('touchstart', this.onDocClick);
132 document.addEventListener('keydown', this.handleGlobalKeys);
133 }
134 componentWillUnmount() {
135 document.removeEventListener('click', this.onDocClick);
136 document.removeEventListener('touchstart', this.onDocClick);
137 document.removeEventListener('keydown', this.handleGlobalKeys);
138 }
139 render() {
140 /* eslint-disable @typescript-eslint/no-unused-vars */
141 const _a = this.props, { className, children, isOpen, isActive, isPlain, isDisabled, hasPlaceholderStyle, variant, onToggle, onEnter, onClose, onBlur, onClickTypeaheadToggleButton, handleTypeaheadKeys, moveFocusToLastMenuItem, parentRef, menuRef, id, type, hasClearButton, 'aria-labelledby': ariaLabelledBy, 'aria-label': ariaLabel, hasFooter, footerRef } = _a, props = __rest(_a, ["className", "children", "isOpen", "isActive", "isPlain", "isDisabled", "hasPlaceholderStyle", "variant", "onToggle", "onEnter", "onClose", "onBlur", "onClickTypeaheadToggleButton", "handleTypeaheadKeys", "moveFocusToLastMenuItem", "parentRef", "menuRef", "id", "type", "hasClearButton", 'aria-labelledby', 'aria-label', "hasFooter", "footerRef"]);
142 /* eslint-enable @typescript-eslint/no-unused-vars */
143 const isTypeahead = variant === SelectVariant.typeahead || variant === SelectVariant.typeaheadMulti || hasClearButton;
144 const toggleProps = {
145 id,
146 'aria-labelledby': ariaLabelledBy,
147 'aria-expanded': isOpen,
148 'aria-haspopup': (variant !== SelectVariant.checkbox && 'listbox') || null
149 };
150 return (React.createElement(React.Fragment, null,
151 !isTypeahead && (React.createElement("button", Object.assign({}, props, toggleProps, { ref: this.toggle, type: type, className: css(styles.selectToggle, hasPlaceholderStyle && styles.modifiers.placeholder, isDisabled && styles.modifiers.disabled, isPlain && styles.modifiers.plain, isActive && styles.modifiers.active, className), "aria-label": ariaLabel, onBlur: onBlur,
152 // eslint-disable-next-line @typescript-eslint/no-unused-vars
153 onClick: event => {
154 onToggle(!isOpen, event);
155 if (isOpen) {
156 onClose();
157 }
158 }, onKeyDown: this.onKeyDown, disabled: isDisabled }),
159 children,
160 React.createElement("span", { className: css(styles.selectToggleArrow) },
161 React.createElement(CaretDownIcon, null)))),
162 isTypeahead && (React.createElement("div", Object.assign({}, props, { ref: this.toggle, className: css(styles.selectToggle, hasPlaceholderStyle && styles.modifiers.placeholder, isDisabled && styles.modifiers.disabled, isPlain && styles.modifiers.plain, isTypeahead && styles.modifiers.typeahead, className), onBlur: onBlur,
163 // eslint-disable-next-line @typescript-eslint/no-unused-vars
164 onClick: event => {
165 if (!isDisabled) {
166 onToggle(!isOpen, event);
167 if (isOpen) {
168 onClose();
169 }
170 }
171 }, onKeyDown: this.onKeyDown }),
172 children,
173 React.createElement("button", Object.assign({}, toggleProps, { type: type, className: css(buttonStyles.button, styles.selectToggleButton, styles.modifiers.plain), "aria-label": ariaLabel, onClick: event => {
174 onToggle(!isOpen, event);
175 if (isOpen) {
176 onClose();
177 }
178 onClickTypeaheadToggleButton();
179 } }, ((variant === SelectVariant.typeahead || variant === SelectVariant.typeaheadMulti) && {
180 tabIndex: -1
181 }), { disabled: isDisabled }),
182 React.createElement(CaretDownIcon, { className: css(styles.selectToggleArrow) }))))));
183 }
184}
185SelectToggle.displayName = 'SelectToggle';
186SelectToggle.defaultProps = {
187 className: '',
188 isOpen: false,
189 isActive: false,
190 isPlain: false,
191 isDisabled: false,
192 hasPlaceholderStyle: false,
193 hasClearButton: false,
194 hasFooter: false,
195 variant: 'single',
196 'aria-labelledby': '',
197 'aria-label': '',
198 type: 'button',
199 onToggle: () => { },
200 onEnter: () => { },
201 onClose: () => { },
202 onClickTypeaheadToggleButton: () => { }
203};
204//# sourceMappingURL=SelectToggle.js.map
\No newline at end of file