-
Notifications
You must be signed in to change notification settings - Fork 842
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Feature/1067 focus trap initialfocus #1099
Changes from 2 commits
a6e417d
2f31fb1
2f5c40b
810eca4
874b62a
80e9b1a
dcf8b16
979004f
2540817
8ce0fd1
0326123
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -22,6 +22,7 @@ export class EuiModal extends Component { | |
const { | ||
className, | ||
children, | ||
initialFocus, | ||
onClose, // eslint-disable-line no-unused-vars | ||
...rest | ||
} = this.props; | ||
|
@@ -32,6 +33,7 @@ export class EuiModal extends Component { | |
<FocusTrap | ||
focusTrapOptions={{ | ||
fallbackFocus: () => this.modal, | ||
initialFocus: initialFocus, | ||
}} | ||
> | ||
{ | ||
|
@@ -65,4 +67,10 @@ EuiModal.propTypes = { | |
className: PropTypes.string, | ||
children: PropTypes.node, | ||
onClose: PropTypes.func.isRequired, | ||
/** specifies what element should initially have focus; Can be a DOM node, or a selector string (which will be passed to document.querySelector() to find the DOM node), or a function that returns a DOM node. */ | ||
initialFocus: PropTypes.oneOfType([ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. General question; do you always type check properties you don't actually use? You're not using that prop, so you don't really care what it is. You're just passing this into There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Couple of reasons:
|
||
PropTypes.instanceOf(HTMLElement), | ||
PropTypes.func, | ||
PropTypes.string, | ||
]), | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -33,11 +33,12 @@ export class EuiOverlayMask extends Component { | |
} | ||
this.overlayMaskNode.setAttribute(key, rest[key]); | ||
}); | ||
|
||
document.body.appendChild(this.overlayMaskNode); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just curious, why did you move this into the constructor? Does this merit a comment to explain why it's better than being in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Given this React DOM tree:
React will mount the tree into the document and then call Because of this, to maintain & allow natural assumptions we build about React's lifecycle, it's best practice to always best to add the custom DOM elements in the constructor. |
||
} | ||
|
||
componentDidMount() { | ||
document.body.classList.add('euiBody-hasOverlayMask'); | ||
document.body.appendChild(this.overlayMaskNode); | ||
} | ||
|
||
componentWillUnmount() { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -76,6 +76,13 @@ const DEFAULT_POPOVER_STYLES = { | |
|
||
const GROUP_NUMERIC = /^([\d.]+)/; | ||
|
||
function getElementFromInitialFocus(initialFocus) { | ||
const initialFocusType = typeof initialFocus; | ||
if (initialFocusType === 'string') return document.querySelector(initialFocus); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Bah! This is how the upstream library works too, it just happened that the thing I tried in the modal happened to be the only item. I still can't get things to focus correctly in the popover, but There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I can't figure out why, but popover focus does not work. The props look right, the node exists, it says it's calling ...but the node definitely does not get focused: It should look like this when that input is focused: There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Yep, that's why :) I agree with your thoughts on this, I wish the upstream library didn't search the entire DOM. Is your element const origfocus = HTMLElement.prototype.focus;
HTMLElement.prototype.focus = function() {
const style = window.getComputedStyle(this);
console.log(this); // what element is being focused
console.log(style.display, style.visibility);
origfocus.call(this);
}; Adding a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Nope, it's just an input in the popover content. We wrap popover, but primarily only so we can set |
||
if (initialFocusType === 'function') return initialFocus(); | ||
return initialFocus; | ||
} | ||
|
||
export class EuiPopover extends Component { | ||
static getDerivedStateFromProps(nextProps, prevState) { | ||
if (prevState.prevProps.isOpen && !nextProps.isOpen) { | ||
|
@@ -139,10 +146,18 @@ export class EuiPopover extends Component { | |
} | ||
|
||
// Otherwise let's focus the first tabbable item and expedite input from the user. | ||
const tabbableItems = tabbable(this.panel); | ||
if (tabbableItems.length) { | ||
tabbableItems[0].focus(); | ||
let focusTarget; | ||
|
||
if (this.props.initialFocus != null) { | ||
focusTarget = getElementFromInitialFocus(this.props.initialFocus); | ||
} else { | ||
const tabbableItems = tabbable(this.panel); | ||
if (tabbableItems.length) { | ||
focusTarget = tabbableItems[0]; | ||
} | ||
} | ||
|
||
if (focusTarget != null) focusTarget.focus(); | ||
}); | ||
} | ||
|
||
|
@@ -193,7 +208,9 @@ export class EuiPopover extends Component { | |
}, 250); | ||
} | ||
|
||
this.updateFocus(); | ||
this.updateFocus( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It looks like There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good catch! It did something at one point during my refactoring :) |
||
!prevProps.isOpen && this.props.isOpen ? this.props.initialFocus : null | ||
); | ||
} | ||
|
||
componentWillUnmount() { | ||
|
@@ -307,6 +324,7 @@ export class EuiPopover extends Component { | |
panelClassName, | ||
panelPaddingSize, | ||
popoverRef, | ||
initialFocus, // eslint-disable-line no-unused-vars | ||
...rest | ||
} = this.props; | ||
|
||
|
@@ -436,6 +454,12 @@ EuiPopover.propTypes = { | |
]), | ||
/** When `true`, the popover's position is re-calculated when the user scrolls, this supports having fixed-position popover anchors. */ | ||
repositionOnScroll: PropTypes.bool, | ||
/** specifies what element should initially have focus; Can be a DOM node, or a selector string (which will be passed to document.querySelector() to find the DOM node), or a function that returns a DOM node. */ | ||
initialFocus: PropTypes.oneOfType([ | ||
PropTypes.instanceOf(HTMLElement), | ||
PropTypes.func, | ||
PropTypes.string, | ||
]), | ||
}; | ||
|
||
EuiPopover.defaultProps = { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Minor nit but I think you could just leave off the assignment:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, that should be an eslint rule...