Skip to content

Commit f03351e

Browse files
authored
Merge pull request #1114 from hiTyagi/hiTyagi/prefers-reduced-motion-support
2 parents 1de46ea + ac2a716 commit f03351e

File tree

2 files changed

+137
-7
lines changed

2 files changed

+137
-7
lines changed

addon/modifiers/scroll-into-view.js

+11-7
Original file line numberDiff line numberDiff line change
@@ -45,18 +45,22 @@ function scrollIntoView(element, positional, named = {}) {
4545

4646
shouldScrollPromise.then((shouldScrollValue) => {
4747
if (shouldScrollValue && element && !hasBeenRemoved) {
48+
let { behavior = 'auto' } = options || {};
49+
behavior =
50+
behavior === 'smooth' &&
51+
window.matchMedia(`(prefers-reduced-motion: reduce)`).matches
52+
? 'instant'
53+
: behavior;
4854
if (
4955
options?.topOffset === undefined &&
5056
options?.leftOffset === undefined
5157
) {
52-
element.scrollIntoView(options);
58+
element.scrollIntoView({
59+
...options,
60+
...(options?.behavior && { behavior }),
61+
});
5362
} else {
54-
const {
55-
behavior = 'auto',
56-
topOffset,
57-
leftOffset,
58-
scrollContainerId,
59-
} = options;
63+
const { topOffset, leftOffset, scrollContainerId } = options;
6064

6165
let scrollContainer, left, top;
6266
if (scrollContainerId !== undefined) {

tests/integration/modifiers/scroll-into-view-test.js

+126
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ module('Integration | Modifier | scroll-into-view', function (hooks) {
88
setupRenderingTest(hooks);
99

1010
hooks.beforeEach(function () {
11+
window.matchMedia = sinon.stub().returns({ matches: false });
1112
this.scrollIntoViewSpy = sinon.spy(Element.prototype, 'scrollIntoView');
1213
});
1314

@@ -366,4 +367,129 @@ module('Integration | Modifier | scroll-into-view', function (hooks) {
366367
.isFocused('First focusable element has focus');
367368
});
368369
});
370+
371+
module('prefers-reduced-motion', function (motionHooks) {
372+
motionHooks.beforeEach(function () {
373+
this.scrollToSpyMotion = sinon.spy(window, 'scrollTo');
374+
375+
this.smoothOptions = { behavior: 'smooth' };
376+
this.instantOptions = { behavior: 'instant' };
377+
});
378+
379+
test('it handles prefers-reduced-motion setting enabled', async function (assert) {
380+
window.matchMedia = sinon.stub().returns({ matches: true });
381+
382+
await render(
383+
hbs`<div {{scroll-into-view options=this.smoothOptions shouldScroll=true}}></div>`,
384+
);
385+
386+
assert.ok(this.scrollIntoViewSpy.called, 'scrollIntoView was called');
387+
388+
assert.deepEqual(
389+
this.scrollIntoViewSpy.args[0][0],
390+
this.instantOptions,
391+
'scrollIntoView was called with correct params',
392+
);
393+
});
394+
395+
test('it handles prefers-reduced-motion setting disabled and request behavior smooth', async function (assert) {
396+
window.matchMedia = sinon.stub().returns({ matches: false });
397+
398+
await render(
399+
hbs`<div {{scroll-into-view options=this.smoothOptions shouldScroll=true}}></div>`,
400+
);
401+
402+
assert.ok(this.scrollIntoViewSpy.called, 'scrollIntoView was called');
403+
404+
assert.deepEqual(
405+
this.scrollIntoViewSpy.args[0][0],
406+
this.smoothOptions,
407+
'scrollIntoView was called with correct params',
408+
);
409+
});
410+
411+
test('it handles prefers-reduced-motion setting disabled and request behavior instant', async function (assert) {
412+
window.matchMedia = sinon.stub().returns({ matches: false });
413+
414+
await render(
415+
hbs`<div {{scroll-into-view options=this.instantOptions shouldScroll=true}}></div>`,
416+
);
417+
418+
assert.ok(this.scrollIntoViewSpy.called, 'scrollIntoView was called');
419+
420+
assert.deepEqual(
421+
this.scrollIntoViewSpy.args[0][0],
422+
this.instantOptions,
423+
'scrollIntoView was called with correct params',
424+
);
425+
});
426+
427+
test('it does not set behavior when not passed as option', async function (assert) {
428+
window.matchMedia = sinon.stub().returns({ matches: true });
429+
this.options = { test: 'test' };
430+
431+
await render(
432+
hbs`<div {{scroll-into-view options=this.options shouldScroll=true}}></div>`,
433+
);
434+
435+
assert.ok(this.scrollIntoViewSpy.called, 'scrollIntoView was called');
436+
437+
assert.deepEqual(
438+
this.scrollIntoViewSpy.args[0][0],
439+
this.options,
440+
'scrollIntoView was called with correct params',
441+
);
442+
});
443+
444+
test('it does not override default behavior when not passed as option with offset', async function (assert) {
445+
window.matchMedia = sinon.stub().returns({ matches: true });
446+
this.options = { test: 'test', topOffset: 50 };
447+
448+
await render(
449+
hbs`<div {{scroll-into-view options=this.options shouldScroll=true}}></div>`,
450+
);
451+
452+
assert.strictEqual(
453+
this.scrollToSpyMotion.args[0][0].behavior,
454+
'auto',
455+
'scrollTo was called with correct behavior',
456+
);
457+
});
458+
459+
test('it handles prefers-reduced-motion setting enabled with offset present', async function (assert) {
460+
window.matchMedia = sinon.stub().returns({ matches: true });
461+
this.options = {
462+
...this.smoothOptions,
463+
topOffset: 50,
464+
};
465+
466+
await render(
467+
hbs`<div {{scroll-into-view shouldScroll=true options=this.options}}></div>`,
468+
);
469+
470+
assert.strictEqual(
471+
this.scrollToSpyMotion.args[0][0].behavior,
472+
this.instantOptions.behavior,
473+
'scrollTo was called with correct behavior',
474+
);
475+
});
476+
477+
test('it handles prefers-reduced-motion setting disabled with offset present', async function (assert) {
478+
window.matchMedia = sinon.stub().returns({ matches: false });
479+
this.options = {
480+
...this.smoothOptions,
481+
topOffset: 50,
482+
};
483+
484+
await render(
485+
hbs`<div {{scroll-into-view shouldScroll=true options=this.options}}></div>`,
486+
);
487+
488+
assert.strictEqual(
489+
this.scrollToSpyMotion.args[0][0].behavior,
490+
this.smoothOptions.behavior,
491+
'scrollTo was called with correct behavior',
492+
);
493+
});
494+
});
369495
});

0 commit comments

Comments
 (0)