-
Notifications
You must be signed in to change notification settings - Fork 424
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
Add modifier to filter keyboard events. #442
Changes from all commits
aad454a
2c9769d
c2e30a4
49503b7
18d6fa2
10ee3de
ff8cf8b
2fe8852
5693106
cf5473c
9d8fa19
4e13612
2a0bcdf
4e55fea
3818734
c8e4a41
53e1cdf
c3262cc
e0fdab6
44a7b4a
a4ce7fe
b97faa9
9c31747
d5cb12e
59bcdb4
c1d2d6a
fa297c5
15f09c9
630a685
a3ad75c
1b59313
1226501
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 |
---|---|---|
|
@@ -65,11 +65,71 @@ input type=submit | click | |
select | change | ||
textarea | input | ||
|
||
|
||
## KeyboardEvent Filter | ||
|
||
There may be cases where [KeyboardEvent](https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent) Actions should only call the Controller method when certain keystrokes are used. | ||
|
||
You can install an event listener that responds only to the `Escape` key by adding `.esc` to the event name of the action descriptor, as in the following example. | ||
|
||
```html | ||
<div data-controller="modal" | ||
data-action="keydown.esc->modal#close" tabindex="0"> | ||
</div> | ||
``` | ||
|
||
This will only work if the event being fired is a keyboard event. | ||
|
||
The correspondence between these filter and keys is shown below. | ||
|
||
Filter | Key Name | ||
-------- | -------- | ||
enter | Enter | ||
tab | Tab | ||
esc | Escape | ||
space | " " | ||
up | ArrowUp | ||
down | ArrowDown | ||
left | ArrowLeft | ||
right | ArrowRight | ||
home | Home | ||
end | End | ||
[a-z] | [a-z] | ||
[0-9] | [0-9] | ||
|
||
If you need to support other keys, you can customize the modifiers using a custom schema. | ||
|
||
```javascript | ||
import { Application, defaultSchema } from "@hotwired/stimulus" | ||
|
||
const customSchema = { | ||
...defaultSchema, | ||
keyMappings: { ...defaultSchema.keyMappings, at: "@" }, | ||
} | ||
|
||
const app = Application.start(document.documentElement, customSchema) | ||
``` | ||
|
||
If you want to subscribe to a compound filter using a modifier key, you can write it like `ctrl+a`. | ||
|
||
```html | ||
<div data-action="keydown.ctrl+a->listbox#selectAll" role="option" tabindex="0">...</div> | ||
``` | ||
|
||
NakajimaTakuya marked this conversation as resolved.
Show resolved
Hide resolved
|
||
The list of supported modifier keys is shown below. | ||
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. Is this last section needed? This seems to conflict with the table above. 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. @lb- 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. sorry, my bad - I was confused, I will suggest a table approach in a min if that is ok. 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. Certainly it would be easier to use a table structure and include explanations, given the existence of meta keys, etc. |
||
|
||
| Modifier | Notes | | ||
| -------- | ------------------ | | ||
| `alt` | `option` on MacOS | | ||
| `ctrl` | | | ||
| `meta` | Command key on MacOS | | ||
| `shift` | | | ||
|
||
### Global Events | ||
|
||
Sometimes a controller needs to listen for events dispatched on the global `window` or `document` objects. | ||
|
||
You can append `@window` or `@document` to the event name in an action descriptor to install the event listener on `window` or `document`, respectively, as in the following example: | ||
You can append `@window` or `@document` to the event name (along with any filter modifer) in an action descriptor to install the event listener on `window` or `document`, respectively, as in the following example: | ||
|
||
<meta data-controller="callout" data-callout-text-value="resize@window"> | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
import { Controller } from "@hotwired/stimulus" | ||
|
||
export default class extends Controller { | ||
static targets = [ "tab", "tabpanel" ] | ||
static classes = [ "current" ] | ||
static values = { index: { default: 0, type: Number } } | ||
|
||
next() { | ||
if (this.indexValue < this.lastIndex) { | ||
this.indexValue++ | ||
return | ||
} | ||
this.indexValue = 0 | ||
} | ||
|
||
previous() { | ||
if (this.indexValue > 0) { | ||
this.indexValue-- | ||
return | ||
} | ||
this.indexValue = this.lastIndex | ||
} | ||
|
||
open(evt) { | ||
this.indexValue = this.tabTargets.indexOf(evt.currentTarget) | ||
} | ||
|
||
get lastIndex() { | ||
return this.tabTargets.length - 1 | ||
} | ||
|
||
indexValueChanged(current, old) { | ||
let panels = this.tabpanelTargets | ||
let tabs = this.tabTargets | ||
|
||
if (old != null) { | ||
panels[old].classList.remove(...this.currentClasses) | ||
tabs[old].tabIndex = -1 | ||
} | ||
panels[current].classList.add(...this.currentClasses) | ||
tabs[current].tabIndex = 0 | ||
tabs[current].focus() | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
<%- include("layout/head") %> | ||
|
||
<div data-controller="tabs" data-tabs-current-class="tabpanel--current" aria-label="example"> | ||
<p>This tabbed interface is operated by focusing on a button and pressing the left and right keys.</p> | ||
<div role="tablist"> | ||
<button | ||
id="tab1" | ||
role="tab" | ||
tabindex="0" | ||
data-action="keydown.left->tabs#previous keydown.right->tabs#next click->tabs#open" | ||
data-tabs-target="tab" | ||
aria-controls="panel1" | ||
>tab1</button> | ||
<button | ||
id="tab2" | ||
role="tab" | ||
tabindex="0" | ||
data-action="keydown.left->tabs#previous keydown.right->tabs#next click->tabs#open" | ||
data-tabs-target="tab" | ||
aria-controls="panel2" | ||
>tab2</button> | ||
</div> | ||
|
||
<div | ||
id="panel1" | ||
role="tabpanel" | ||
tabindex="0" | ||
data-tabs-target="tabpanel" | ||
class="tabpanel tabpanel--current" | ||
aria-labelledby="tab1" | ||
>🐵</div> | ||
<div | ||
id="panel2" | ||
role="tabpanel" | ||
tabindex="0" | ||
data-tabs-target="tabpanel" | ||
class="tabpanel" | ||
aria-labelledby="tab2" | ||
>🙈</div> | ||
</div> | ||
|
||
<%- include("layout/tail") %> |
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.
A suggestion for refinement - using backticks on the code bits.
OR - if the side borders is not really what we do (by the looks of things).