what-input 5.2.12 → 6.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +248 -59
- package/dist/what-input.d.mts +88 -0
- package/dist/what-input.d.ts +88 -0
- package/dist/what-input.js +296 -512
- package/dist/what-input.min.js +17 -2
- package/dist/what-input.min.js.map +7 -1
- package/dist/what-input.mjs +284 -0
- package/package.json +51 -30
- package/.browserslistrc +0 -5
- package/.editorconfig +0 -12
- package/.eslintrc +0 -12
- package/.nvmrc +0 -1
- package/.prettierrc +0 -4
- package/.stylelintrc +0 -9
- package/Gulpfile.js +0 -178
- package/src/images/select-arrow.svg +0 -1
- package/src/markup/index.html +0 -141
- package/src/scripts/what-input.d.ts +0 -15
- package/src/scripts/what-input.js +0 -482
- package/src/styles/_html.scss +0 -49
- package/src/styles/_layout.scss +0 -229
- package/src/styles/_mixins.scss +0 -70
- package/src/styles/_variables.scss +0 -2
- package/src/styles/index.scss +0 -51
package/README.md
CHANGED
|
@@ -2,21 +2,66 @@
|
|
|
2
2
|
|
|
3
3
|
**A global utility for tracking the current input method (mouse, keyboard or touch).**
|
|
4
4
|
|
|
5
|
-
## _What Input_ is now
|
|
5
|
+
## _What Input_ is now v6
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
Rebuilt for the modern web: Pointer Events, `KeyboardEvent.key`, TypeScript, native ES modules, and an `EventTarget` API. The outputs are the same as v5, so it's a drop-in upgrade for existing sites. See the [changelog](#changelog) for the few things that changed.
|
|
8
8
|
|
|
9
|
-
_What Input_ adds data attributes to the
|
|
9
|
+
_What Input_ adds data attributes to the `<html>` element based on the type of input being used. It also exposes a simple API that can be used for scripting interactions.
|
|
10
10
|
|
|
11
11
|
## How it works
|
|
12
12
|
|
|
13
|
-
_What Input_
|
|
13
|
+
_What Input_ listens on the `window` (in the capture phase, so local `stopPropagation()` calls don't hide anything) for:
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
- `pointerdown` – a direct interaction. `pointerType` decides whether it's `mouse` or `touch` (`pen` is reported as `touch`).
|
|
16
|
+
- `keydown` / `keyup` – a direct `keyboard` interaction, unless the key is on the ignore list (modifiers by default).
|
|
17
|
+
- `pointermove` / `wheel` – an _indirect_ signal that a mouse is present.
|
|
18
|
+
- `focusin` / `focusout` – which element currently has focus.
|
|
16
19
|
|
|
17
|
-
|
|
20
|
+
It then sets or updates these attributes on `<html>`:
|
|
18
21
|
|
|
19
|
-
|
|
22
|
+
| Attribute | Values | Updated by |
|
|
23
|
+
| ------------------ | ------------------------------------------- | ------------------------------------------------------ |
|
|
24
|
+
| `data-whatinput` | `initial`, `mouse`, `keyboard`, `touch` | `pointerdown`, `keydown`, `keyup` |
|
|
25
|
+
| `data-whatintent` | `initial`, `mouse`, `keyboard`, `touch` | everything above plus `pointermove` and `wheel` |
|
|
26
|
+
| `data-whatelement` | the focused element's tag name, e.g. `a` | `focusin` / `focusout` |
|
|
27
|
+
| `data-whatclasses` | the focused element's classes, e.g. `a,b,c` | `focusin` / `focusout` (only when the element has any) |
|
|
28
|
+
|
|
29
|
+
_What Input does not make assumptions about the input environment before the page is interacted with._ However, the `pointermove` and `wheel` events are used to set `data-whatintent="mouse"` to indicate that a mouse is being used _indirectly_.
|
|
30
|
+
|
|
31
|
+
### Interacting with forms
|
|
32
|
+
|
|
33
|
+
Since interacting with a form _always_ requires use of the keyboard, _What Input_ uses the `data-whatintent` attribute to display a "buffered" version of input events while `<input>`s, `<select>`s, `<textarea>`s, `contenteditable` elements, [form-associated custom elements](https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals) and `<button>`s inside a `<form>` are being interacted with (i.e. a mouse user's `data-whatintent` will be preserved as `mouse` while typing). `data-whatinput` always reflects the actual last input.
|
|
34
|
+
|
|
35
|
+
Checkboxes and radio buttons are toggled rather than typed into, so pressing a key on one _does_ switch the intent to `keyboard`.
|
|
36
|
+
|
|
37
|
+
The same buffering can be opted into for any other element by adding a `data-whatfreeze` attribute to it or to a container. This is handy for custom controls that are operated with the arrow keys, like a slider or a video scrubber, where pressing a key is not a change of intent:
|
|
38
|
+
|
|
39
|
+
```html
|
|
40
|
+
<div class="scrubber" data-whatfreeze>
|
|
41
|
+
<div class="scrubber-handle" role="slider" tabindex="0" ...></div>
|
|
42
|
+
</div>
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Device hint
|
|
46
|
+
|
|
47
|
+
Before anyone interacts with the page, `data-whatinput` and `data-whatintent` are `initial` on purpose. When a best guess is still useful (sizing hit targets on first paint, say), the `data-whathint` attribute carries what the device's _primary_ pointer suggests, read from the [`pointer` interaction media feature](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/pointer):
|
|
48
|
+
|
|
49
|
+
| `data-whathint` | Media query | Typically |
|
|
50
|
+
| --------------- | ------------------- | ---------------------------- |
|
|
51
|
+
| `touch` | `(pointer: coarse)` | phones, tablets |
|
|
52
|
+
| `mouse` | `(pointer: fine)` | desktops, laptops, trackpads |
|
|
53
|
+
| `unknown` | neither | keyboard-only devices, TVs |
|
|
54
|
+
|
|
55
|
+
The hint follows the device live (a mouse paired with a tablet, a keyboard detached from one) and is never persisted. It is only a hint: an iPad with a trackpad still reports `touch`, and a touch-screen laptop reports `mouse`. `data-whatinput` and `data-whatintent` never use it.
|
|
56
|
+
|
|
57
|
+
### Isn't this what `:focus-visible` is for?
|
|
58
|
+
|
|
59
|
+
Partly. [`:focus-visible`](https://developer.mozilla.org/en-US/docs/Web/CSS/:focus-visible) is a great default for focus rings and you should use it. _What Input_ is still useful when you want to:
|
|
60
|
+
|
|
61
|
+
- style anything other than focus (hover affordances, hit targets, tooltips) based on how the page is being used;
|
|
62
|
+
- know the input method from JavaScript;
|
|
63
|
+
- tell `mouse` and `touch` apart;
|
|
64
|
+
- remember the input method across page loads so the first render is already right.
|
|
20
65
|
|
|
21
66
|
## Demo
|
|
22
67
|
|
|
@@ -24,35 +69,31 @@ Check out the demo to see _What Input_ in action.
|
|
|
24
69
|
|
|
25
70
|
https://ten1seven.github.io/what-input
|
|
26
71
|
|
|
27
|
-
### Interacting with Forms
|
|
28
|
-
|
|
29
|
-
Since interacting with a form _always_ requires use of the keyboard, _What Input_ uses the `data-whatintent` attribute to display a "buffered" version of input events while form `<input>`s, `<select>`s, and `<textarea>`s are being interacted with (i.e. mouse user's `data-whatintent` will be preserved as `mouse` while typing).
|
|
30
|
-
|
|
31
72
|
## Installing
|
|
32
73
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
Install via Yarn:
|
|
74
|
+
Install via npm (or your package manager of choice):
|
|
36
75
|
|
|
37
76
|
```shell
|
|
38
|
-
|
|
77
|
+
npm install what-input
|
|
39
78
|
```
|
|
40
79
|
|
|
41
|
-
|
|
80
|
+
Or load it from a CDN:
|
|
42
81
|
|
|
43
|
-
```
|
|
44
|
-
|
|
82
|
+
```html
|
|
83
|
+
<script src="https://unpkg.com/what-input@6"></script>
|
|
45
84
|
```
|
|
46
85
|
|
|
86
|
+
Or download `dist/what-input.js` and include it directly.
|
|
87
|
+
|
|
47
88
|
## Usage
|
|
48
89
|
|
|
49
|
-
Include the script directly in your project.
|
|
90
|
+
Include the script directly in your project. It exposes a global `whatInput`.
|
|
50
91
|
|
|
51
92
|
```html
|
|
52
93
|
<script src="path/to/what-input.js"></script>
|
|
53
94
|
```
|
|
54
95
|
|
|
55
|
-
Or
|
|
96
|
+
Or import it. The package ships as an ES module (`dist/what-input.mjs`), as a UMD build (`dist/what-input.js`, which also covers CommonJS and AMD) and with TypeScript declarations.
|
|
56
97
|
|
|
57
98
|
```javascript
|
|
58
99
|
import 'what-input'
|
|
@@ -63,28 +104,16 @@ import whatInput from 'what-input'
|
|
|
63
104
|
|
|
64
105
|
// or
|
|
65
106
|
|
|
66
|
-
require('what-input')
|
|
67
|
-
|
|
68
|
-
// or
|
|
69
|
-
|
|
70
|
-
var whatInput = require('what-input')
|
|
71
|
-
|
|
72
|
-
// or
|
|
73
|
-
|
|
74
|
-
requirejs.config({
|
|
75
|
-
paths: {
|
|
76
|
-
whatInput: 'path/to/what-input',
|
|
77
|
-
},
|
|
78
|
-
})
|
|
79
|
-
|
|
80
|
-
require(['whatInput'], function () {})
|
|
107
|
+
const whatInput = require('what-input')
|
|
81
108
|
```
|
|
82
109
|
|
|
83
110
|
_What Input_ will start doing its thing while you do yours.
|
|
84
111
|
|
|
85
|
-
|
|
112
|
+
> **Bundlers:** importing the module has the side effect of starting _What Input_. The package declares `"sideEffects": true` so a bare `import 'what-input'` is never tree-shaken away.
|
|
113
|
+
|
|
114
|
+
### Default behavior
|
|
86
115
|
|
|
87
|
-
#### Persisting
|
|
116
|
+
#### Persisting input/intent across pages
|
|
88
117
|
|
|
89
118
|
By default, _What Input_ uses [session storage](https://developer.mozilla.org/en-US/docs/Web/API/Window/sessionStorage) to persist the input and intent values across pages. The benefit is that once a visitor has interacted with the page, subsequent pages won't have to wait for interactions to know the input method.
|
|
90
119
|
|
|
@@ -110,7 +139,7 @@ Session storage can be cleared at any time with:
|
|
|
110
139
|
whatInput.clearStorage()
|
|
111
140
|
```
|
|
112
141
|
|
|
113
|
-
### Basic
|
|
142
|
+
### Basic styling
|
|
114
143
|
|
|
115
144
|
```css
|
|
116
145
|
/*
|
|
@@ -123,13 +152,27 @@ whatInput.clearStorage()
|
|
|
123
152
|
}
|
|
124
153
|
```
|
|
125
154
|
|
|
126
|
-
|
|
155
|
+
A more modern take pairs _What Input_ with `:focus-visible` and only reaches for the data attributes where the browser heuristic isn't enough:
|
|
156
|
+
|
|
157
|
+
```css
|
|
158
|
+
:focus-visible {
|
|
159
|
+
outline: 2px solid currentColor;
|
|
160
|
+
outline-offset: 2px;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/* bigger hit areas while a touch screen is being used */
|
|
164
|
+
[data-whatintent='touch'] .toolbar button {
|
|
165
|
+
min-block-size: 44px;
|
|
166
|
+
}
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
**Note:** If you remove outlines with `outline: none;`, be sure to provide clear visual `:focus` styles so the user can see which element they are on at any time for greater accessibility. Visit [W3C's WCAG 2.2 2.4.7 Guideline](https://www.w3.org/WAI/WCAG22/Understanding/focus-visible.html) to learn more.
|
|
127
170
|
|
|
128
171
|
### Scripting
|
|
129
172
|
|
|
130
|
-
#### Current
|
|
173
|
+
#### Current input
|
|
131
174
|
|
|
132
|
-
Ask _What Input_ what the current input method is. This works best if asked after the events _What Input_ is bound to (`
|
|
175
|
+
Ask _What Input_ what the current input method is. This works best if asked after the events _What Input_ is bound to (`pointerdown` and `keydown`).
|
|
133
176
|
|
|
134
177
|
```javascript
|
|
135
178
|
whatInput.ask() // returns `mouse`, `keyboard` or `touch`
|
|
@@ -143,7 +186,7 @@ myButton.addEventListener('click', () => {
|
|
|
143
186
|
})
|
|
144
187
|
```
|
|
145
188
|
|
|
146
|
-
If it's necessary to know if `
|
|
189
|
+
If it's necessary to know if `pointermove` is being used, use the `'intent'` option. For example:
|
|
147
190
|
|
|
148
191
|
```javascript
|
|
149
192
|
/*
|
|
@@ -161,47 +204,61 @@ whatInput.ask() // returns `keyboard` because the keyboard was the last direct p
|
|
|
161
204
|
whatInput.ask('intent') // returns `mouse` because mouse movement was the most recent action detected
|
|
162
205
|
```
|
|
163
206
|
|
|
164
|
-
|
|
207
|
+
Since `click` events are `PointerEvent`s in current browsers, a single click handler can also read `event.pointerType` directly: it's `mouse`, `pen` or `touch`, and empty when the click came from the keyboard.
|
|
208
|
+
|
|
209
|
+
#### Device hint
|
|
210
|
+
|
|
211
|
+
```javascript
|
|
212
|
+
whatInput.ask('hint') // returns `mouse`, `touch` or `unknown`
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
#### Current element
|
|
165
216
|
|
|
166
217
|
Ask _What Input_ the currently focused DOM element.
|
|
167
218
|
|
|
168
219
|
```javascript
|
|
169
|
-
whatInput.element() // returns a string, like `input
|
|
220
|
+
whatInput.element() // returns a string, like `input`, or null
|
|
170
221
|
```
|
|
171
222
|
|
|
172
|
-
#### Ignore
|
|
223
|
+
#### Ignore keys
|
|
173
224
|
|
|
174
|
-
Set a custom
|
|
225
|
+
Set a custom list of keys that will be ignored (will not switch over to `keyboard`) when pressed. Keys can be [`KeyboardEvent.key`](https://developer.mozilla.org/en-US/docs/Web/API/UI_Events/Keyboard_event_key_values) values, [`KeyboardEvent.code`](https://developer.mozilla.org/en-US/docs/Web/API/UI_Events/Keyboard_event_code_values) values or legacy [keycodes](https://www.toptal.com/developers/keycode). _A custom list will overwrite the default values._
|
|
175
226
|
|
|
176
227
|
```javascript
|
|
177
228
|
/*
|
|
178
229
|
* default ignored keys:
|
|
179
|
-
*
|
|
180
|
-
*
|
|
181
|
-
*
|
|
182
|
-
*
|
|
183
|
-
*
|
|
230
|
+
* 'Shift'
|
|
231
|
+
* 'Control'
|
|
232
|
+
* 'Alt'
|
|
233
|
+
* 'Meta' // Windows key / Apple cmd
|
|
234
|
+
* 'ContextMenu' // Windows menu key
|
|
184
235
|
*/
|
|
185
236
|
|
|
186
|
-
whatInput.ignoreKeys([
|
|
237
|
+
whatInput.ignoreKeys(['Shift', 'CapsLock', 'F11'])
|
|
238
|
+
|
|
239
|
+
// legacy keycodes still work
|
|
240
|
+
whatInput.ignoreKeys([16, 20, 122])
|
|
187
241
|
```
|
|
188
242
|
|
|
189
|
-
#### Specific
|
|
243
|
+
#### Specific keys
|
|
190
244
|
|
|
191
|
-
Set a custom
|
|
245
|
+
Set a custom list of keys that will trigger the keyboard pressed intent (will not switch to `keyboard` unless these keys are pressed). _This overrides ignoreKeys._
|
|
192
246
|
|
|
193
247
|
```javascript
|
|
194
248
|
// only listen to tab keyboard press
|
|
195
|
-
whatInput.specificKeys([
|
|
249
|
+
whatInput.specificKeys(['Tab'])
|
|
250
|
+
|
|
251
|
+
// go back to using the ignore list
|
|
252
|
+
whatInput.specificKeys([])
|
|
196
253
|
```
|
|
197
254
|
|
|
198
|
-
#### Custom
|
|
255
|
+
#### Custom callbacks
|
|
199
256
|
|
|
200
257
|
Fire a function when the input or intent changes.
|
|
201
258
|
|
|
202
259
|
```javascript
|
|
203
260
|
// create a function to be fired
|
|
204
|
-
|
|
261
|
+
const myFunction = (type) => {
|
|
205
262
|
console.log(type)
|
|
206
263
|
}
|
|
207
264
|
|
|
@@ -211,16 +268,148 @@ whatInput.registerOnChange(myFunction, 'intent')
|
|
|
211
268
|
// fire `myFunction` when the input changes
|
|
212
269
|
whatInput.registerOnChange(myFunction, 'input')
|
|
213
270
|
|
|
271
|
+
// fire `myFunction` when the device hint changes
|
|
272
|
+
whatInput.registerOnChange(myFunction, 'hint')
|
|
273
|
+
|
|
214
274
|
// remove custom event
|
|
215
275
|
whatInput.unRegisterOnChange(myFunction)
|
|
216
276
|
```
|
|
217
277
|
|
|
278
|
+
#### Events
|
|
279
|
+
|
|
280
|
+
`whatInput` is an [`EventTarget`](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget). It dispatches `input`, `intent` and `hint` [`CustomEvent`](https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent)s whose `detail` is the new value, so all the usual listener options (`once`, `signal`, ...) are available.
|
|
281
|
+
|
|
282
|
+
```javascript
|
|
283
|
+
whatInput.addEventListener('intent', (event) => {
|
|
284
|
+
console.log(event.detail) // `mouse`, `keyboard` or `touch`
|
|
285
|
+
})
|
|
286
|
+
|
|
287
|
+
// listen until a component unmounts
|
|
288
|
+
const controller = new AbortController()
|
|
289
|
+
|
|
290
|
+
whatInput.addEventListener('input', update, { signal: controller.signal })
|
|
291
|
+
|
|
292
|
+
controller.abort()
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
In browsers that ship the [Observable API](https://developer.mozilla.org/en-US/docs/Web/API/Observable), `whatInput.when('intent')` works too, for free.
|
|
296
|
+
|
|
297
|
+
#### Tearing down
|
|
298
|
+
|
|
299
|
+
Stop listening, remove the `data-what*` attributes and drop every callback. Mostly useful in tests and when an app is unmounted.
|
|
300
|
+
|
|
301
|
+
```javascript
|
|
302
|
+
whatInput.destroy()
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
### TypeScript
|
|
306
|
+
|
|
307
|
+
Types ship with the package.
|
|
308
|
+
|
|
309
|
+
```typescript
|
|
310
|
+
import whatInput, { type InputMethod, type Strategy } from 'what-input'
|
|
311
|
+
|
|
312
|
+
const current: InputMethod = whatInput.ask('intent')
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
### Server-side rendering
|
|
316
|
+
|
|
317
|
+
_What Input_ can be imported in Node, workers and other environments without a DOM. It won't observe anything, `ask()` always answers `initial` and `element()` always answers `null`.
|
|
318
|
+
|
|
319
|
+
### Shadow DOM
|
|
320
|
+
|
|
321
|
+
_What Input_ reports focus inside open shadow roots and knows when a keypress lands in a field inside one. The attributes still live on `<html>`, and selectors in the main document don't reach into a shadow tree, so style from inside the component instead. `:host-context()` works in Chromium and Safari; for every browser, mirror the state onto the host with the events:
|
|
322
|
+
|
|
323
|
+
```javascript
|
|
324
|
+
class MyField extends HTMLElement {
|
|
325
|
+
connectedCallback() {
|
|
326
|
+
this.controller = new AbortController()
|
|
327
|
+
|
|
328
|
+
whatInput.addEventListener(
|
|
329
|
+
'intent',
|
|
330
|
+
(event) => this.setAttribute('data-whatintent', event.detail),
|
|
331
|
+
{ signal: this.controller.signal },
|
|
332
|
+
)
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
disconnectedCallback() {
|
|
336
|
+
this.controller.abort()
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
```css
|
|
342
|
+
/* inside the shadow root */
|
|
343
|
+
:host([data-whatintent='mouse']) :focus {
|
|
344
|
+
outline: none;
|
|
345
|
+
}
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
### Testing
|
|
349
|
+
|
|
350
|
+
The state only changes in response to real events, which is also the easiest way to drive it in tests. In a DOM test environment that implements `PointerEvent` (jsdom 22+, happy-dom, every browser):
|
|
351
|
+
|
|
352
|
+
```javascript
|
|
353
|
+
window.dispatchEvent(new PointerEvent('pointerdown', { pointerType: 'touch' }))
|
|
354
|
+
whatInput.ask() // `touch`
|
|
355
|
+
|
|
356
|
+
window.dispatchEvent(new KeyboardEvent('keydown', { key: 'Tab' }))
|
|
357
|
+
whatInput.ask() // `keyboard`
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
Or mock the module entirely:
|
|
361
|
+
|
|
362
|
+
```javascript
|
|
363
|
+
vi.mock('what-input', () => ({ default: { ask: () => 'touch' } }))
|
|
364
|
+
```
|
|
365
|
+
|
|
218
366
|
## Compatibility
|
|
219
367
|
|
|
220
|
-
_What Input_ works in all
|
|
368
|
+
_What Input_ v6 works in all evergreen browsers: Chrome and Edge 90+, Firefox 90+ and Safari 15+ (2021 and newer). It relies on Pointer Events, `AbortSignal` support in `addEventListener()`, `EventTarget` subclassing and class private fields, none of which are polyfilled.
|
|
369
|
+
|
|
370
|
+
If you need to support older browsers, stay on [v5](https://github.com/ten1seven/what-input/tree/v5.2.12).
|
|
371
|
+
|
|
372
|
+
## Contributing
|
|
373
|
+
|
|
374
|
+
```shell
|
|
375
|
+
npm install # install dependencies
|
|
376
|
+
npm run dev # run the demo with hot reloading
|
|
377
|
+
npm test # build the library and run the tests
|
|
378
|
+
npm run lint # prettier and type checks
|
|
379
|
+
npm run build # build the library and the demo site
|
|
380
|
+
```
|
|
221
381
|
|
|
222
382
|
## Changelog
|
|
223
383
|
|
|
384
|
+
### v6.0.0
|
|
385
|
+
|
|
386
|
+
A rewrite in TypeScript on modern browser APIs. The data attributes, the session storage keys, the API and the `dist/what-input.js` file path are unchanged, so for most sites this is a drop-in upgrade.
|
|
387
|
+
|
|
388
|
+
- **Breaking:** Drops support for Internet Explorer and other legacy browsers. The build targets ES2022 and is no longer transpiled.
|
|
389
|
+
- **Breaking:** Detection is built on Pointer Events only. The `mousedown`, `mousemove`, `touchstart`, `touchend`, `MSPointerDown`, `MSPointerMove`, `mousewheel` and `DOMMouseScroll` listeners (and the 200ms touch-versus-mouse buffer they required) are gone.
|
|
390
|
+
- **Breaking:** `clearStorage()` now removes only _What Input_'s own `what-input` and `what-intent` keys instead of wiping the entire session storage.
|
|
391
|
+
- **Breaking:** `pointerdown` now updates the intent as well as the input, even while a form field has focus. Only keyboard use inside form fields is buffered.
|
|
392
|
+
- **Breaking:** `unRegisterOnChange(fn)` removes `fn` from both the `input` and `intent` lists.
|
|
393
|
+
- **Breaking:** The `InputMethod` type no longer includes `pointer`, which was never actually reported.
|
|
394
|
+
- **Breaking:** Requires Node 20+ for development. The build uses esbuild, the demo uses Vite and the tests use Vitest; Gulp, Webpack, Babel, Sass and Yarn are gone.
|
|
395
|
+
- **Added:** A `data-whathint` attribute, `ask('hint')` and a `hint` event report what the device's primary pointer suggests (`mouse`, `touch` or `unknown`) from the `pointer` interaction media feature, before any interaction and live as devices are attached or removed. The existing attributes never use it.
|
|
396
|
+
- **Added:** A `data-whatfreeze` attribute opts any element (or container) into the same intent buffering that form fields get, for custom controls driven by the arrow keys ([#123](https://github.com/ten1seven/what-input/issues/123)).
|
|
397
|
+
- **Added:** An ES module build (`dist/what-input.mjs`) with a proper `exports` map, `module` and `unpkg` fields. The UMD build (`dist/what-input.js`) and the minified build remain for `<script>` tags, CommonJS and AMD.
|
|
398
|
+
- **Added:** `whatInput` is an `EventTarget` and dispatches `input` and `intent` events.
|
|
399
|
+
- **Added:** `whatInput.destroy()` tears everything down (listeners are bound with an `AbortController`).
|
|
400
|
+
- **Added:** `ignoreKeys()` and `specificKeys()` accept `KeyboardEvent.key` and `KeyboardEvent.code` strings alongside legacy keycodes. The defaults are now key names.
|
|
401
|
+
- **Added:** Typing inside `contenteditable` elements and form-associated custom elements no longer changes the intent, just like form fields.
|
|
402
|
+
- **Fixed:** Pressing a key on a checkbox or radio button switches the intent to `keyboard`; they are toggled, not typed into ([#128](https://github.com/ten1seven/what-input/issues/128)).
|
|
403
|
+
- **Fixed:** Long-presses in Firefox for Android no longer flash `data-whatintent="mouse"`, because the emulated mouse events are no longer listened to ([#94](https://github.com/ten1seven/what-input/issues/94)).
|
|
404
|
+
- **Added:** Focus inside open shadow roots is reported for the inner element (via `composedPath()`), and the active-element check follows shadow roots when deciding whether a keypress is text entry.
|
|
405
|
+
- **Added:** Persisted session storage values are validated before they are used.
|
|
406
|
+
- **Changed:** Type declarations are generated from the source and shipped in `dist/`.
|
|
407
|
+
- **Changed:** `pointerdown` and `keydown` are observed in the capture phase on `window`, so `whatInput.ask()` is already up to date inside any element's own event handler, even one that calls `preventDefault()` or `stopPropagation()` ([#80](https://github.com/ten1seven/what-input/issues/80)).
|
|
408
|
+
- **Changed:** When the script runs after `DOMContentLoaded` (dynamic `import()`, late-loaded scripts) the initial state is published immediately instead of never.
|
|
409
|
+
- **Changed:** An interaction that happens before `DOMContentLoaded` is no longer overwritten by the persisted value.
|
|
410
|
+
- **Fixed:** `data-whatclasses` separates _all_ classes with commas (`a,b,c`); previously only the first space was replaced.
|
|
411
|
+
- **Fixed:** `data-whatclasses` is removed when focus moves to an element without classes.
|
|
412
|
+
|
|
224
413
|
### v5.2.12
|
|
225
414
|
|
|
226
415
|
- **Fixed:** Improved detection and respect for of `data-whatpersist` before `DOMContentLoaded`. Fix via [FanFataL](https://github.com/FanFataL).
|
|
@@ -310,7 +499,7 @@ _What Input_ works in all modern browsers.
|
|
|
310
499
|
|
|
311
500
|
## Acknowledgments
|
|
312
501
|
|
|
313
|
-
Special thanks to [Viget](
|
|
502
|
+
Special thanks to [Viget](https://viget.com/) for their encouragement and commitment to open source projects. Visit [code.viget.com](https://code.viget.com/) to see more projects from [Viget](https://viget.com).
|
|
314
503
|
|
|
315
504
|
Thanks to [mAAdhaTTah](https://github.com/mAAdhaTTah) for the initial conversion to Webpack. Thanks to [greypants](https://github.com/greypants) for adding TypeScript definitions.
|
|
316
505
|
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What Input?
|
|
3
|
+
*
|
|
4
|
+
* A global utility for tracking the current input method (mouse, keyboard or
|
|
5
|
+
* touch). It writes `data-whatinput`, `data-whatintent`, `data-whatelement`
|
|
6
|
+
* and `data-whatclasses` attributes to the `<html>` element and exposes a
|
|
7
|
+
* small API for scripting against the same information.
|
|
8
|
+
*/
|
|
9
|
+
/** The input methods What Input can report. */
|
|
10
|
+
export type InputMethod = 'initial' | 'mouse' | 'keyboard' | 'touch';
|
|
11
|
+
/**
|
|
12
|
+
* `input` is the last *direct* interaction (`pointerdown`, `keydown`).
|
|
13
|
+
* `intent` also includes *indirect* signals (`pointermove`, `wheel`) and is
|
|
14
|
+
* preserved while typing into form fields.
|
|
15
|
+
*/
|
|
16
|
+
export type Strategy = 'input' | 'intent';
|
|
17
|
+
/**
|
|
18
|
+
* What the device's *primary* pointer suggests before (or regardless of) any
|
|
19
|
+
* interaction, read from the `pointer` interaction media feature. It is a
|
|
20
|
+
* hint only: `data-whatinput` and `data-whatintent` never use it.
|
|
21
|
+
*/
|
|
22
|
+
export type Hint = 'mouse' | 'touch' | 'unknown';
|
|
23
|
+
/** A callback registered with `registerOnChange()`. */
|
|
24
|
+
export type ChangeCallback = (type: InputMethod) => void;
|
|
25
|
+
/** A callback registered with `registerOnChange(callback, 'hint')`. */
|
|
26
|
+
export type HintCallback = (hint: Hint) => void;
|
|
27
|
+
type Callback = ChangeCallback | HintCallback;
|
|
28
|
+
/**
|
|
29
|
+
* A key to ignore or require. Strings are matched against `KeyboardEvent.key`
|
|
30
|
+
* and `KeyboardEvent.code` (`'Shift'`, `'Tab'`, `'KeyA'`); numbers are matched
|
|
31
|
+
* against the legacy `KeyboardEvent.keyCode` for backwards compatibility.
|
|
32
|
+
*/
|
|
33
|
+
export type KeyMatcher = string | number;
|
|
34
|
+
/** The events dispatched by the `whatInput` EventTarget. */
|
|
35
|
+
export interface WhatInputEventMap {
|
|
36
|
+
input: CustomEvent<InputMethod>;
|
|
37
|
+
intent: CustomEvent<InputMethod>;
|
|
38
|
+
hint: CustomEvent<Hint>;
|
|
39
|
+
}
|
|
40
|
+
declare class WhatInput extends EventTarget {
|
|
41
|
+
#private;
|
|
42
|
+
constructor();
|
|
43
|
+
/**
|
|
44
|
+
* The current input method.
|
|
45
|
+
*
|
|
46
|
+
* - `'input'` (default): the same value as the `data-whatinput` attribute
|
|
47
|
+
* - `'intent'`: the same value as the `data-whatintent` attribute
|
|
48
|
+
* - `'hint'`: the same value as the `data-whathint` attribute
|
|
49
|
+
*/
|
|
50
|
+
ask(strategy?: Strategy): InputMethod;
|
|
51
|
+
ask(strategy: 'hint'): Hint;
|
|
52
|
+
/** The tag name of the currently focused element, or `null`. */
|
|
53
|
+
element(): string | null;
|
|
54
|
+
/**
|
|
55
|
+
* Replaces the list of keys that are ignored (do not switch to `keyboard`)
|
|
56
|
+
* when pressed. Defaults to the modifier keys.
|
|
57
|
+
*/
|
|
58
|
+
ignoreKeys(keys: readonly KeyMatcher[]): void;
|
|
59
|
+
/**
|
|
60
|
+
* Replaces the list of keys that are the *only* keys that switch to
|
|
61
|
+
* `keyboard`. When set, this overrides `ignoreKeys()`. Pass an empty array
|
|
62
|
+
* to restore the `ignoreKeys()` behavior.
|
|
63
|
+
*/
|
|
64
|
+
specificKeys(keys: readonly KeyMatcher[]): void;
|
|
65
|
+
/** Fires `callback` whenever the input (default), intent or hint changes. */
|
|
66
|
+
registerOnChange(callback: ChangeCallback, strategy?: Strategy): void;
|
|
67
|
+
registerOnChange(callback: HintCallback, strategy: 'hint'): void;
|
|
68
|
+
/** Removes a callback added with `registerOnChange()`. */
|
|
69
|
+
unRegisterOnChange(callback: Callback): void;
|
|
70
|
+
/** Removes the persisted input and intent from session storage. */
|
|
71
|
+
clearStorage(): void;
|
|
72
|
+
/**
|
|
73
|
+
* Stops listening, removes the `data-what*` attributes from `<html>` and
|
|
74
|
+
* drops every registered callback. Useful for tests and app teardown.
|
|
75
|
+
*/
|
|
76
|
+
destroy(): void;
|
|
77
|
+
}
|
|
78
|
+
/** Typed listeners for the `input`, `intent` and `hint` events (types only). */
|
|
79
|
+
interface WhatInput {
|
|
80
|
+
addEventListener<K extends keyof WhatInputEventMap>(type: K, listener: ((this: WhatInput, event: WhatInputEventMap[K]) => void) | null, options?: boolean | AddEventListenerOptions): void;
|
|
81
|
+
addEventListener(type: string, listener: EventListenerOrEventListenerObject | null, options?: boolean | AddEventListenerOptions): void;
|
|
82
|
+
removeEventListener<K extends keyof WhatInputEventMap>(type: K, listener: ((this: WhatInput, event: WhatInputEventMap[K]) => void) | null, options?: boolean | EventListenerOptions): void;
|
|
83
|
+
removeEventListener(type: string, listener: EventListenerOrEventListenerObject | null, options?: boolean | EventListenerOptions): void;
|
|
84
|
+
}
|
|
85
|
+
export type { WhatInput };
|
|
86
|
+
/** The shared What Input instance. It starts observing as soon as it is imported. */
|
|
87
|
+
declare const whatInput: WhatInput;
|
|
88
|
+
export default whatInput;
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What Input?
|
|
3
|
+
*
|
|
4
|
+
* A global utility for tracking the current input method (mouse, keyboard or
|
|
5
|
+
* touch). It writes `data-whatinput`, `data-whatintent`, `data-whatelement`
|
|
6
|
+
* and `data-whatclasses` attributes to the `<html>` element and exposes a
|
|
7
|
+
* small API for scripting against the same information.
|
|
8
|
+
*/
|
|
9
|
+
/** The input methods What Input can report. */
|
|
10
|
+
export type InputMethod = 'initial' | 'mouse' | 'keyboard' | 'touch';
|
|
11
|
+
/**
|
|
12
|
+
* `input` is the last *direct* interaction (`pointerdown`, `keydown`).
|
|
13
|
+
* `intent` also includes *indirect* signals (`pointermove`, `wheel`) and is
|
|
14
|
+
* preserved while typing into form fields.
|
|
15
|
+
*/
|
|
16
|
+
export type Strategy = 'input' | 'intent';
|
|
17
|
+
/**
|
|
18
|
+
* What the device's *primary* pointer suggests before (or regardless of) any
|
|
19
|
+
* interaction, read from the `pointer` interaction media feature. It is a
|
|
20
|
+
* hint only: `data-whatinput` and `data-whatintent` never use it.
|
|
21
|
+
*/
|
|
22
|
+
export type Hint = 'mouse' | 'touch' | 'unknown';
|
|
23
|
+
/** A callback registered with `registerOnChange()`. */
|
|
24
|
+
export type ChangeCallback = (type: InputMethod) => void;
|
|
25
|
+
/** A callback registered with `registerOnChange(callback, 'hint')`. */
|
|
26
|
+
export type HintCallback = (hint: Hint) => void;
|
|
27
|
+
type Callback = ChangeCallback | HintCallback;
|
|
28
|
+
/**
|
|
29
|
+
* A key to ignore or require. Strings are matched against `KeyboardEvent.key`
|
|
30
|
+
* and `KeyboardEvent.code` (`'Shift'`, `'Tab'`, `'KeyA'`); numbers are matched
|
|
31
|
+
* against the legacy `KeyboardEvent.keyCode` for backwards compatibility.
|
|
32
|
+
*/
|
|
33
|
+
export type KeyMatcher = string | number;
|
|
34
|
+
/** The events dispatched by the `whatInput` EventTarget. */
|
|
35
|
+
export interface WhatInputEventMap {
|
|
36
|
+
input: CustomEvent<InputMethod>;
|
|
37
|
+
intent: CustomEvent<InputMethod>;
|
|
38
|
+
hint: CustomEvent<Hint>;
|
|
39
|
+
}
|
|
40
|
+
declare class WhatInput extends EventTarget {
|
|
41
|
+
#private;
|
|
42
|
+
constructor();
|
|
43
|
+
/**
|
|
44
|
+
* The current input method.
|
|
45
|
+
*
|
|
46
|
+
* - `'input'` (default): the same value as the `data-whatinput` attribute
|
|
47
|
+
* - `'intent'`: the same value as the `data-whatintent` attribute
|
|
48
|
+
* - `'hint'`: the same value as the `data-whathint` attribute
|
|
49
|
+
*/
|
|
50
|
+
ask(strategy?: Strategy): InputMethod;
|
|
51
|
+
ask(strategy: 'hint'): Hint;
|
|
52
|
+
/** The tag name of the currently focused element, or `null`. */
|
|
53
|
+
element(): string | null;
|
|
54
|
+
/**
|
|
55
|
+
* Replaces the list of keys that are ignored (do not switch to `keyboard`)
|
|
56
|
+
* when pressed. Defaults to the modifier keys.
|
|
57
|
+
*/
|
|
58
|
+
ignoreKeys(keys: readonly KeyMatcher[]): void;
|
|
59
|
+
/**
|
|
60
|
+
* Replaces the list of keys that are the *only* keys that switch to
|
|
61
|
+
* `keyboard`. When set, this overrides `ignoreKeys()`. Pass an empty array
|
|
62
|
+
* to restore the `ignoreKeys()` behavior.
|
|
63
|
+
*/
|
|
64
|
+
specificKeys(keys: readonly KeyMatcher[]): void;
|
|
65
|
+
/** Fires `callback` whenever the input (default), intent or hint changes. */
|
|
66
|
+
registerOnChange(callback: ChangeCallback, strategy?: Strategy): void;
|
|
67
|
+
registerOnChange(callback: HintCallback, strategy: 'hint'): void;
|
|
68
|
+
/** Removes a callback added with `registerOnChange()`. */
|
|
69
|
+
unRegisterOnChange(callback: Callback): void;
|
|
70
|
+
/** Removes the persisted input and intent from session storage. */
|
|
71
|
+
clearStorage(): void;
|
|
72
|
+
/**
|
|
73
|
+
* Stops listening, removes the `data-what*` attributes from `<html>` and
|
|
74
|
+
* drops every registered callback. Useful for tests and app teardown.
|
|
75
|
+
*/
|
|
76
|
+
destroy(): void;
|
|
77
|
+
}
|
|
78
|
+
/** Typed listeners for the `input`, `intent` and `hint` events (types only). */
|
|
79
|
+
interface WhatInput {
|
|
80
|
+
addEventListener<K extends keyof WhatInputEventMap>(type: K, listener: ((this: WhatInput, event: WhatInputEventMap[K]) => void) | null, options?: boolean | AddEventListenerOptions): void;
|
|
81
|
+
addEventListener(type: string, listener: EventListenerOrEventListenerObject | null, options?: boolean | AddEventListenerOptions): void;
|
|
82
|
+
removeEventListener<K extends keyof WhatInputEventMap>(type: K, listener: ((this: WhatInput, event: WhatInputEventMap[K]) => void) | null, options?: boolean | EventListenerOptions): void;
|
|
83
|
+
removeEventListener(type: string, listener: EventListenerOrEventListenerObject | null, options?: boolean | EventListenerOptions): void;
|
|
84
|
+
}
|
|
85
|
+
export type { WhatInput };
|
|
86
|
+
/** The shared What Input instance. It starts observing as soon as it is imported. */
|
|
87
|
+
declare const whatInput: WhatInput;
|
|
88
|
+
export default whatInput;
|