Skip to content

Commit

Permalink
refactor(mon-pix): extract auto-scroll logic in service
Browse files Browse the repository at this point in the history
  • Loading branch information
dlahaye committed Jul 5, 2024
1 parent 0527fa9 commit 338a71f
Show file tree
Hide file tree
Showing 9 changed files with 169 additions and 32 deletions.
16 changes: 5 additions & 11 deletions mon-pix/app/components/module/grain.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { action } from '@ember/object';
import { service } from '@ember/service';
import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';

import ModulePassage from './passage';

export default class ModuleGrain extends Component {
@service modulixAutoScroll;

grain = this.args.grain;

static AVAILABLE_ELEMENT_TYPES = ['text', 'image', 'video', 'qcu', 'qcm', 'qrocm'];
Expand Down Expand Up @@ -129,19 +130,12 @@ export default class ModuleGrain extends Component {
}

@action
focusAndScroll(element) {
focusAndScroll(htmlElement) {
if (!this.args.hasJustAppeared) {
return;
}

element.focus({ preventScroll: true });

const newGrainY = element.getBoundingClientRect().top + window.scrollY;
const userPrefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)');
window.scroll({
top: newGrainY - ModulePassage.SCROLL_OFFSET_PX,
behavior: userPrefersReducedMotion.matches ? 'instant' : 'smooth',
});
this.modulixAutoScroll.focusAndScroll(htmlElement);
}

@action
Expand Down
6 changes: 5 additions & 1 deletion mon-pix/app/components/module/passage.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@
<h1>{{@module.title}}</h1>
</div>

<div class="module-passage__content" aria-live="assertive" {{did-insert this.setGrainScrollOffsetCssProperty}}>
<div
class="module-passage__content"
aria-live="assertive"
{{did-insert this.modulixAutoScroll.setHTMLElementScrollOffsetCssProperty}}
>
{{#each this.grainsToDisplay as |grain index|}}
<Module::Grain
@grain={{grain}}
Expand Down
20 changes: 7 additions & 13 deletions mon-pix/app/components/module/passage.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,18 @@ export default class ModulePassage extends Component {
@service router;
@service metrics;
@service store;
@service modulixAutoScroll;

displayableGrains = this.args.module.grains.filter((grain) => ModuleGrain.getSupportedComponents(grain).length > 0);
@tracked grainsToDisplay = this.displayableGrains.length > 0 ? [this.displayableGrains[0]] : [];

static SCROLL_OFFSET_PX = 70;

@action
setGrainScrollOffsetCssProperty(element) {
element.style.setProperty('--scroll-offset', `${ModulePassage.SCROLL_OFFSET_PX}px`);
hasGrainJustAppeared(index) {
if (this.grainsToDisplay.length === 1) {
return false;
}

return this.grainsToDisplay.length - 1 === index;
}

get hasNextGrain() {
Expand Down Expand Up @@ -92,15 +95,6 @@ export default class ModulePassage extends Component {
return this.args.module.transitionTexts.find((transition) => transition.grainId === grainId);
}

@action
hasGrainJustAppeared(index) {
if (this.grainsToDisplay.length === 1) {
return false;
}

return this.grainsToDisplay.length - 1 === index;
}

@action
terminateModule() {
this.args.passage.terminate();
Expand Down
17 changes: 16 additions & 1 deletion mon-pix/app/components/module/step.gjs
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
import { action } from '@ember/object';
import { service } from '@ember/service';
import Component from '@glimmer/component';
import { t } from 'ember-intl';
import Element from 'mon-pix/components/module/element';
import ModuleGrain from 'mon-pix/components/module/grain';

import didInsert from '../../modifiers/modifier-did-insert';

export default class ModulixStep extends Component {
@service modulixAutoScroll;

get displayableElements() {
return this.args.step.elements.filter((element) => ModuleGrain.AVAILABLE_ELEMENT_TYPES.includes(element.type));
}
Expand All @@ -12,9 +18,18 @@ export default class ModulixStep extends Component {
return this.displayableElements.length > 0;
}

@action
focusAndScroll(htmlElement) {
if (!this.args.hasJustAppeared) {
return;
}

this.modulixAutoScroll.focusAndScroll(htmlElement);
}

<template>
{{#if this.hasDisplayableElements}}
<section class="stepper__step">
<section class="stepper__step" tabindex="-1" {{didInsert this.focusAndScroll}}>
<h3
class="stepper-step__position"
aria-label="{{t 'pages.modulix.stepper.step.position' currentStep=@currentStep totalSteps=@totalSteps}}"
Expand Down
21 changes: 20 additions & 1 deletion mon-pix/app/components/module/stepper.gjs
Original file line number Diff line number Diff line change
@@ -1,20 +1,34 @@
import PixButton from '@1024pix/pix-ui/components/pix-button';
import { action } from '@ember/object';
import { service } from '@ember/service';
import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
import { t } from 'ember-intl';
import ModuleGrain from 'mon-pix/components/module/grain';
import Step from 'mon-pix/components/module/step';
import { inc } from 'mon-pix/helpers/inc';

import didInsert from '../../modifiers/modifier-did-insert';

export default class ModulixStepper extends Component {
@service modulixAutoScroll;

displayableSteps = this.args.steps.filter((step) =>
step.elements.some((element) => ModuleGrain.AVAILABLE_ELEMENT_TYPES.includes(element.type)),
);

@tracked
stepsToDisplay = [this.displayableSteps[0]];

@action
hasStepJustAppeared(index) {
if (this.stepsToDisplay.length === 1) {
return false;
}

return this.stepsToDisplay.length - 1 === index;
}

get hasDisplayableSteps() {
return this.displayableSteps.length > 0;
}
Expand Down Expand Up @@ -56,7 +70,11 @@ export default class ModulixStepper extends Component {
}

<template>
<div class="stepper">
<div
class="stepper"
aria-live="assertive"
{{didInsert this.modulixAutoScroll.setHTMLElementScrollOffsetCssProperty}}
>
{{#if this.hasDisplayableSteps}}
{{#each this.stepsToDisplay as |step index|}}
<Step
Expand All @@ -66,6 +84,7 @@ export default class ModulixStepper extends Component {
@submitAnswer={{@submitAnswer}}
@retryElement={{@retryElement}}
@getLastCorrectionForElement={{@getLastCorrectionForElement}}
@hasJustAppeared={{this.hasStepJustAppeared index}}
/>
{{/each}}
{{#if this.shouldDisplayNextButton}}
Expand Down
41 changes: 41 additions & 0 deletions mon-pix/app/services/modulix-auto-scroll.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { action } from '@ember/object';
import Service from '@ember/service';

export default class ModulixAutoScroll extends Service {
#SCROLL_OFFSET_PX = 70;

@action
setHTMLElementScrollOffsetCssProperty(htmlElement) {
htmlElement.style.setProperty('--scroll-offset', `${this.#SCROLL_OFFSET_PX}px`);
}

focusAndScroll(
htmlElement,
{ scroll, areUserPrefersMatches, getWindowScrollY } = {
scroll: this.#scroll,
areUserPrefersMatches: this.#areUserPrefersMatches,
getWindowScrollY: this.#getWindowScrollY,
},
) {
htmlElement.focus({ preventScroll: true });

const elementY = htmlElement.getBoundingClientRect().top + getWindowScrollY();
scroll({
top: elementY - this.#SCROLL_OFFSET_PX,
behavior: areUserPrefersMatches() ? 'instant' : 'smooth',
});
}

#scroll(args) {
window.scroll(args);
}

#areUserPrefersMatches() {
const userPrefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)');
return userPrefersReducedMotion.matches;
}

#getWindowScrollY() {
return window.scrollY;
}
}
6 changes: 1 addition & 5 deletions mon-pix/app/styles/components/module/_passage.scss
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,7 @@
margin: 0 auto;

.auto-scroll {
min-height: calc(100vh - var(--scroll-offset));

@supports (min-height: 100dvh) {
min-height: calc(100dvh - var(--scroll-offset));
}
min-height: var(--scroll-offset);
}

h3,
Expand Down
6 changes: 6 additions & 0 deletions mon-pix/app/styles/components/module/_stepper.scss
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@
&__step {
margin-bottom: var(--pix-spacing-6x);

&--active {
@extend .auto-scroll;

outline: none;
}

&:last-child {
margin-bottom: 0;
}
Expand Down
68 changes: 68 additions & 0 deletions mon-pix/tests/unit/services/modulix-auto-scroll_test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { setupTest } from 'ember-qunit';
import { module, test } from 'qunit';
import sinon from 'sinon';

module('Unit | Services | Module | ModulixAutoScroll', function (hooks) {
setupTest(hooks);

module('#setHTMLElementScrollOffsetCssProperty', function () {
test('should set --scroll-offset to the given html element', function (assert) {
// given
const modulixAutoScrollService = this.owner.lookup('service:modulix-auto-scroll');
const htmlElement = document.createElement('div');

// when
modulixAutoScrollService.setHTMLElementScrollOffsetCssProperty(htmlElement);

// then
assert.strictEqual(htmlElement.style.cssText, '--scroll-offset: 70px;');
});
});

module('#focusAndScroll', function () {
test('should call focus on given html element', function (assert) {
// given
const modulixAutoScrollService = this.owner.lookup('service:modulix-auto-scroll');
const htmlElement = document.createElement('div');
htmlElement.focus = sinon.stub();

// when
modulixAutoScrollService.focusAndScroll(htmlElement);

// then
sinon.assert.calledWith(htmlElement.focus, { preventScroll: true });
assert.ok(true);
});

test('should call scroll to the given html element', function (assert) {
// given
const scrollOffset = 70;
const htmlElementBoundingClientRectTop = 20;
const windowScrollY = 5;
const calculatedElementY = htmlElementBoundingClientRectTop + windowScrollY;
const scrollToTop = calculatedElementY - scrollOffset;

const modulixAutoScrollService = this.owner.lookup('service:modulix-auto-scroll');
const htmlElement = document.createElement('div');
htmlElement.getBoundingClientRect = sinon.stub().returns({
top: htmlElementBoundingClientRectTop,
});
const scrollStub = sinon.stub();
const areUserPrefersMatchesStub = sinon.stub();
areUserPrefersMatchesStub.returns(true);
const getWindowScrollYStub = sinon.stub();
getWindowScrollYStub.returns(windowScrollY);

// when
modulixAutoScrollService.focusAndScroll(htmlElement, {
scroll: scrollStub,
areUserPrefersMatches: areUserPrefersMatchesStub,
getWindowScrollY: getWindowScrollYStub,
});

// then
sinon.assert.calledWith(scrollStub, { top: scrollToTop, behavior: 'instant' });
assert.ok(true);
});
});
});

0 comments on commit 338a71f

Please sign in to comment.