1 | import { __rest } from "tslib";
|
2 | import * as React from 'react';
|
3 | import styles from '@patternfly/react-styles/css/components/Select/select';
|
4 | import buttonStyles from '@patternfly/react-styles/css/components/Button/button';
|
5 | import { css } from '@patternfly/react-styles';
|
6 | import CaretDownIcon from '@patternfly/react-icons/dist/esm/icons/caret-down-icon';
|
7 | import { SelectVariant, SelectFooterTabbableItems } from './selectConstants';
|
8 | import { findTabbableElements } from '../../helpers/util';
|
9 | import { KeyTypes } from '../../helpers/constants';
|
10 | export 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 |
|
37 | if (tabbableItems.length <= 0) {
|
38 | onToggle(false, event);
|
39 | onClose();
|
40 | this.toggle.current.focus();
|
41 | return;
|
42 | }
|
43 | else {
|
44 |
|
45 | const currentElementIndex = tabbableItems.findIndex((item) => item === document.activeElement);
|
46 | if (currentElementIndex === -1) {
|
47 | if (event.shiftKey) {
|
48 | if (variant !== 'checkbox') {
|
49 |
|
50 | onToggle(false, event);
|
51 | onClose();
|
52 | this.toggle.current.focus();
|
53 | }
|
54 | }
|
55 | else {
|
56 |
|
57 | tabbableItems[0].focus();
|
58 | return;
|
59 | }
|
60 | }
|
61 |
|
62 | if (event.shiftKey) {
|
63 |
|
64 | if (currentElementIndex === 0) {
|
65 | moveFocusToLastMenuItem();
|
66 | event.preventDefault();
|
67 | }
|
68 | return;
|
69 | }
|
70 |
|
71 | if (currentElementIndex + 1 < tabbableItems.length) {
|
72 | tabbableItems[currentElementIndex + 1].focus();
|
73 | }
|
74 | else {
|
75 |
|
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 |
|
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 |
|
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 |
|
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 |
|
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 | }
|
185 | SelectToggle.displayName = 'SelectToggle';
|
186 | SelectToggle.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 |
|
\ | No newline at end of file |