Skip to content

Commit

Permalink
Merge pull request #557 from st-h/auto-first-responder
Browse files Browse the repository at this point in the history
Add disableOnInputFields option, which allows to automatically disable keyboard events on all input fields
  • Loading branch information
lukemelia authored Jan 18, 2022
2 parents 6007a12 + c7f3c30 commit 01551b3
Show file tree
Hide file tree
Showing 5 changed files with 208 additions and 2 deletions.
4 changes: 4 additions & 0 deletions addon-test-support/test-helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ export function keyDown(keyCombo) {
return keyEvent(keyCombo, 'keydown');
}

export function keyDownWithElement(keyCombo, element) {
return keyEvent(keyCombo, 'keydown', element);
}

export function keyUp(keyCombo) {
return keyEvent(keyCombo, 'keyup');
}
Expand Down
13 changes: 13 additions & 0 deletions addon/services/keyboard.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ export default class KeyboardService extends Service {
getOwner(this).resolveRegistration('config:environment') || {};
let emberKeyboardConfig = config.emberKeyboard || {};

if (emberKeyboardConfig.disableOnInputFields) {
this._disableOnInput = true;
}

this._listeners = emberKeyboardConfig.listeners || [
'keyUp',
'keyDown',
Expand All @@ -65,6 +69,15 @@ export default class KeyboardService extends Service {

@action
_respond(event) {
if (this._disableOnInput && event.target) {
const tag = event.target.tagName;
const isContentEditable =
event.target.getAttribute &&
event.target.getAttribute('contenteditable') != null;
if (isContentEditable || tag === 'TEXTAREA' || tag === 'INPUT') {
return;
}
}
run(() => {
let { firstResponders, normalResponders } = this;
handleKeyEventWithPropagation(event, {
Expand Down
178 changes: 178 additions & 0 deletions tests/acceptance/disable-on-input-fields-config-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
import { module, test } from 'qunit';
import { setupApplicationTest } from 'ember-qunit';
import { click, fillIn, blur, visit } from '@ember/test-helpers';
import { gte } from 'ember-compatibility-helpers';
import config from 'dummy/config/environment';

import {
mouseDown,
keyUp,
keyDown,
touchStart,
keyDownWithElement,
} from 'ember-keyboard/test-support/test-helpers';

import { hook } from '../helpers/hook';

import {
getValues,
getMouseValues,
getTouchValues,
} from '../helpers/get-values';

module('Acceptance | disableOnInputFields config', function (hooks) {
setupApplicationTest(hooks);

hooks.beforeEach(() => {
config.emberKeyboard.disableOnInputFields = true;
});

if (gte('3.10.0')) {
test('test event does not propagate on input field', async function (assert) {
assert.expect(1);

await visit('/test-scenario');

await keyDownWithElement('ArrowRight', '[data-test-input-field]');
assert.deepEqual(getValues(), [0, 0, 0], 'responders do not respond');
});

test('test standard functionality', async function (assert) {
assert.expect(8);

await visit('/test-scenario');

await mouseDown('left');
assert.deepEqual(getMouseValues(), [1], 'left mouse');

await mouseDown('right');
assert.deepEqual(getMouseValues(), [11], 'right mouse');

await mouseDown('middle');
assert.deepEqual(getMouseValues(), [1], 'middle mouse');

await touchStart();
assert.deepEqual(getTouchValues(), [1], 'touch event');

await keyDown('ArrowRight');
assert.deepEqual(getValues(), [1, 1, 1], 'equal responders all respond');

await click(
`${hook('counter-first')} ${hook('counter-activated-toggle')}`
);

await keyDown('ArrowRight');
assert.deepEqual(
getValues(),
[1, 2, 2],
'deactivating a responder removes it from the stack'
);

await keyDown('ArrowRight+ctrl+shift');
assert.deepEqual(getValues(), [1, 102, 102], 'modifier keys work');

await keyUp('KeyR');
assert.deepEqual(getValues(), [1, 0, 0], 'keyUp works');
});

test('test event propagation', async function (assert) {
assert.expect(6);

await visit('/test-scenario');
await keyDown('ArrowRight');
assert.deepEqual(getValues(), [1, 1, 1], 'equal responders all respond');

await fillIn(
`${hook('counter-first')} ${hook('counter-priority-input')}`,
'1'
);
await blur(`${hook('counter-first')} ${hook('counter-priority-input')}`);

await keyDown('ArrowRight');
assert.deepEqual(
getValues(),
[2, 2, 2],
'highest responder responds first, lower responders follow'
);

await fillIn(
`${hook('counter-second')} ${hook('counter-priority-input')}`,
'1'
);
await blur(`${hook('counter-second')} ${hook('counter-priority-input')}`);
await click(
`${hook('counter-first')} ${hook(
'counter-stop-immediate-propagation-toggle'
)}`
);

await keyDown('ArrowRight');
assert.deepEqual(
getValues(),
[3, 2, 3],
'highest responder responds first and stops immediate propagation, lower responders follow'
);

await click(
`${hook('counter-first')} ${hook(
'counter-stop-immediate-propagation-toggle'
)}`
);
await click(
`${hook('counter-first')} ${hook('counter-stop-propagation-toggle')}`
);

await keyDown('ArrowRight');
assert.deepEqual(
getValues(),
[4, 3, 3],
'highest responders responds first and block propagation to lower priority responders'
);

await click(
`${hook('counter-first')} ${hook('counter-activated-toggle')}`
);

await keyDown('ArrowRight');
assert.deepEqual(
getValues(),
[4, 4, 4],
'deactivating a responder removes it from the stack, deactivated responders do not block propagation'
);

await fillIn(
`${hook('counter-first')} ${hook('counter-priority-input')}`,
'2'
);
await blur(`${hook('counter-first')} ${hook('counter-priority-input')}`);
await click(
`${hook('counter-first')} ${hook('counter-stop-propagation-toggle')}`
);
await click(
`${hook('counter-first')} ${hook('counter-activated-toggle')}`
);
await click(
`${hook('counter-first')} ${hook('counter-first-responder-toggle')}`
);
await click(
`${hook('counter-second')} ${hook('counter-first-responder-toggle')}`
);
await click(
`${hook('counter-second')} ${hook(
'counter-stop-immediate-propagation-toggle'
)}`
);

await click(
`${hook('counter-third')} ${hook('counter-first-responder-toggle')}`
);

await keyDown('ArrowRight');
assert.deepEqual(
getValues(),
[5, 5, 4],
'first responders get called in priority order.'
);
});
}
});
3 changes: 3 additions & 0 deletions tests/dummy/app/templates/test-scenario/index.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,6 @@
@stopPropagationToggle={{true}}
data-test-touch-start-counter
/>

<label for="data-test-input-field">input field</label>
<input id="data-test-input-field" data-test-input-field/>
12 changes: 10 additions & 2 deletions tests/dummy/app/templates/usage.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -277,8 +277,16 @@ triggerOnAlphaNumeric(event) {
### `Ember.TextField` && `Ember.TextArea`
To prevent `ember-keyboard` from responding to keystrokes while an input/textarea is focused,
you may use the `on-key` modifier with regular `<input>` and `<textarea>` elements and invoke
To prevent `ember-keyboard` from responding to keystrokes while an input/textarea/contenteditable is focused,
you can set disableOnInputFields in config/environment.json.
```javascript
emberKeyboard: {
disableOnInputFields: true
},
```
Alernatively you may use the `on-key` modifier with regular `<input>` and `<textarea>` elements and invoke
`ekEvent.stopPropagation()` and/or `ekEvent.stopImmediatePropagation()`.
This ensures that whenever an input is focused, other key responders will not fire.
Expand Down

0 comments on commit 01551b3

Please sign in to comment.