ember-focus v0.2.0
ember-focus
Ember implementation to handle focus: management, trapping, throwing, catching, releasing, guiding, announcing
Compatibility
- Ember.js v3.16 or above
- Ember CLI v2.13 or above
- Node.js v10 or above
Installation
ember install ember-focus
Usage
ember-focus
provides modifiers to be used in your components and applications.
focus service
NOTE:
These modifiers leverage a focus-service
to communicate between themselves. However, the service is a private API and its internals are implementation details which may change at any time. Do not use it directly. Instead, only manage focus via the modifiers.
focus modifier
<FancyInput
{{focus when=this.shouldFocus onSelector='input'}}
/>
trap-focus modifier
<FancyTextArea {{trap-focus}}></FancyTextArea>
default focus
NOTE:
These modifiers DO NOT leverage the focus-service
to communicate between themselves. Instead, browser default focus calls are made and the service has no awareness of previous focus history
trap-arrows
<MyAwesomeContainer {{trap-arrows}}>
<button ...>
<button ...>
</MyAwesomeContainer>
API
Modifier: focus
Set focus on an element.
By default, this focuses the element the modifier is installed on as soon as it is rendered.
When the element is destroyed, the modifier returns focus to whatever had it prior to the modified-element taking it.
<label>
Name:
<input
value={{this.value}}
id="name"
{{ember-focus$focus}}
{{on "input" this.updateValue}}
/>
</label>
However, this also supports a variety of alternatives:
You can specify when to focus the element:
<label> Name: <input value={{this.value}} id="name" {{ember-focus$focus when=(eq this.inputToFocus "name")}} {{on "input" this.updateValue "name"}} /> </label>
As with element destruction, focus returns to the previous element any time
when
is set tofalse
.You can specify a selector that is a child of the element, if working with a component that accepts modifiers and which provides information about its internals to make this kind of thing safe:
<FancyInput @label="Name" @value={{this.value}} @id="name" {{ember-focus$focus onSelector="input"}} {{on "input" this.updateValue "name"}} />
You can specify to set the cursor at the end of any text which is already populating an input:
<label> Name: <input value={{this.value}} id="name" {{ember-focus$focus withCursorAtEnd=true}} {{on "input" this.updateValue "name"}} /> </label>
You can specify an alternative element to return focus when the targeted element is destroyed or
when
is set tofalse
:<label> Name: <input value={{this.value}} id="name" {{ember-focus$focus returnTo=this.anotherSelectorValue}} {{on "input" this.updateValue "name"}} /> </label>
You can prevent auto-scrolling to the element:
<label> Name: <input value={{this.value}} id="name" {{ember-focus$focus preventScroll=true}} {{on "input" this.updateValue "name"}} /> </label>
You can optionally disable use of the
focus-manager
service andforget
where focus was<label> Name: <input value={{this.value}} id="name" {{ember-focus$focus forget=true}} {{on "input" this.updateValue "name"}} /> </label>
You can combine any or all of these features:
<FancyInput @label="Name" @value={{this.value}} @id="name" {{ember-focus$focus when=(eq this.inputToFocus "name") onSelector="input" withCursorAtEnd=true returnTo=this.anotherSelectorValue preventScroll=true forget=true }} {{on "input" this.updateValue "name"}} />
Arguments
Name | Type | Required | Default | Description |
---|---|---|---|---|
when | boolean | optional | true | whether the element should focus. Default true . |
onSelector | string | optional | undefined | an optional query select to target an inner child of the element |
returnTo | string | optional | undefined | an optional query select to return focus to instead of the default |
withCursorAtEnd | boolean | optional | false | only for input/textarea element: whether we should put cursor at end of input after focus |
preventScroll | boolean | optional | false | bool prop for focus call |
forget | boolean | optional | false | if true, do not use the focus-manager service and forget where focus came from |
focus: example
<FancyInput
@label="Name"
@value={{this.value}}
@id="name"
{{focus
when=this.shouldFocus
onSelector='input'
returnTo='nav'
preventScroll=true
withCursorAtEnd=true
forget=true}}
/>
Modifier: trap-focus
Traps focus within an element.
trap-focus: example
<FancyTextArea {{trap-focus}}></FancyTextArea>
Modifier: trap-arrows
Traps arrow navigation within a given element, leaving tab to default behavior. Using arrow keys to navigate will wrap within the element, and skip disabled elements, appropriately.
trap-arrows: example
With no setup, up/down arrow keys will can be used within a given element:
<MyAwesomeContainer {{trap-arrows}}>
<button type="button">Button A</button>
<button type="button">Button B</button>
</MyAwesomeContainer>
Alternatively, you can set it up for horizontal use, and left/right arrow keys will now respond to focusing the previous/next element within the given element.
<MyAwesomeContainer {{trap-arrows orientation='horizontal'}}>
<button type="button">Button A</button>
<button type="button">Button B</button>
</MyAwesomeContainer>
By default, all focusable elements (a hefty list of elements that are non-disabled
, with a tabindex > -1
) within the attached element are cycled through.
If, for any reason you need to override the default list, you can pass in an optional position-parameter as the selector
:
<MyAwesomeContainer {{trap-arrows '[role=tab]'}}>
<button type="button">Button A</button>
<button type="button" role="tab">Button B</button>
<button type="button">Button C</button>
<button type="button" role="tab">Button D</button>
</MyAwesomeContainer>
More Examples
Combining the focus
modifier with the trap-focus
and handle-arrow-navigation-focus
modifiers.
<FancyTextArea
{{trap-focus}}
{{focus when=this.shouldFocus}}
></FancyTextArea>
<MenuContainer
{{handle-arrow-navigation-focus}}
{{focus when=this.shouldFocus}}
></MenuContainer>
Accessibility Considerations
- Non-natively focusable elements need a tabindex
If you want to set focus on an element which is not naturally focusable, you will need to make the element focusable using
tabindex
(eg,tabindex="-1"
). - Be predictable; don't be unexpected Focus should only ever be explicitly requested and/or managed through direct user action. It should be discernable and predictable.
- Trapping focus requires an escape route
If you implement some component to
{{trap-focus}}
, don't forget to create a mechanism to bail out! Often times theEscape
key or some sort ofCancel button
. A common example is the modal dialog pattern.
Contributing
See the Contributing guide for details.
License
This project is licensed under the MIT License.
3 years ago