Skip to content

Commit

Permalink
👶 mia (make internet accessible)
Browse files Browse the repository at this point in the history
  • Loading branch information
provok-me committed Dec 21, 2021
1 parent 98e24cf commit 434aab6
Show file tree
Hide file tree
Showing 9 changed files with 357 additions and 1 deletion.
2 changes: 1 addition & 1 deletion packages/eva/__tests__/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ beforeEach(() => {
});

afterEach(() => {
// Resetting Alice instance values before next test.
// Resetting Eva instance values before next test.
global.innerWidth = 0;
global.dispatchEvent(new Event('resize'));
eva.destroy();
Expand Down
2 changes: 2 additions & 0 deletions packages/eva/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,7 @@ export class Eva {
* @author Alphability <[email protected]>
* @memberof Eva
*/

public destroy(): void {
this._detachListeners();

Expand All @@ -180,6 +181,7 @@ export class Eva {
* @type {Calvin}
* @memberof Eva
*/

get view(): Calvin {
return Eva._reactor;
}
Expand Down
11 changes: 11 additions & 0 deletions packages/mia/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# MIA

> MIA: Make Internet Accessible
## Installation

```sh
yarn add @endgame/mia
# or
npm i -S @endgame/mia
```
114 changes: 114 additions & 0 deletions packages/mia/__tests__/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import { Mia } from '../src/index';

const mia = new Mia();

// Use modern in order to run time out functions
jest.useFakeTimers('modern');

const buttonId = 'button-id';
const focusBlacklistElementId = 'focus-blacklist-element-id';

beforeAll(() => {
// Add button to the DOM
const button = global.document.createElement('button');
button.id = buttonId;
global.document.body.appendChild(button);

// Add p to the DOM
const p = global.document.createElement('p');
p.id = focusBlacklistElementId;
global.document.body.appendChild(p);
});

beforeEach(() => {
// Init before each test
mia.initialize();
});

afterEach(() => {
// Resetting Mia instance values before next test.
const button = global.document.getElementById(buttonId);
if (button) {
button.dispatchEvent(new Event('blur'));

jest.runOnlyPendingTimers();
}

// Destroy before each new test
mia.destroy();
});

describe('Success cases', () => {
test('Document element should have an a11y class', () => {
const { documentElement } = global.document;

expect(documentElement.classList.contains('a11y')).toStrictEqual(true);
});

test("Document element shouldn't have an a11y class", () => {
const { documentElement } = global.document;

mia.destroy();

expect(documentElement.classList.contains('a11y')).toStrictEqual(false);
});

test('It should have focusActive === true', () => {
const button = global.document.getElementById(buttonId);
if (button) {
// Trigger the button keyup event.
button.dispatchEvent(
new KeyboardEvent('keyup', {
key: 'Tab',
bubbles: true,
})
);

expect(mia.reactor.data.focusActive).toStrictEqual(true);
}
});

test('It should have focusActive === true even if Mia is initialized two times', () => {
const button = global.document.getElementById(buttonId);
if (button) {
// Trigger the button keyup event.
button.dispatchEvent(
new KeyboardEvent('keyup', {
key: 'Tab',
bubbles: true,
})
);

mia.initialize();

expect(mia.reactor.data.focusActive).toStrictEqual(true);
}
});

test('It should have focusActive === false if target is not whitelisted', () => {
const p = global.document.getElementById(focusBlacklistElementId);
if (p) {
// Trigger the paragraph keyup event.
p.dispatchEvent(
new KeyboardEvent('keyup', {
key: 'Tab',
bubbles: true,
})
);

expect(mia.reactor.data.focusActive).toStrictEqual(false);
}
});

test('It should have focusActive === false if the key pressed is not Tab', () => {
// Trigger the document keyup event.
global.document.dispatchEvent(
new KeyboardEvent('keyup', {
key: 'a',
bubbles: true,
})
);

expect(mia.reactor.data.focusActive).toStrictEqual(false);
});
});
11 changes: 11 additions & 0 deletions packages/mia/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/* eslint-disable */
const base = require('../../jest.config.base');
const pkg = require('./package.json');
/* eslint-enable */

module.exports = {
...base,
testEnvironment: 'jsdom',
name: pkg.name,
displayName: pkg.name,
};
26 changes: 26 additions & 0 deletions packages/mia/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"name": "@endgame/mia",
"version": "1.0.7",
"description": "Make Internet Accessible",
"repository": "MBDW-Studio/endgame",
"author": "Mental Breakdown <[email protected]> (https://mentalbreakdown.studio)",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"files": [
"dist/**/*.js",
"dist/**/*.js.map",
"dist/**/*.d.ts"
],
"scripts": {
"watch": "tsc -w",
"test": "jest __tests__ --collectCoverage --runInBand",
"test:watch": "jest __tests__ --watchAll --runInBand"
},
"dependencies": {
"@endgame/calvin": "^1.0.7"
},
"publishConfig": {
"access": "public"
},
"license": "MIT"
}
4 changes: 4 additions & 0 deletions packages/mia/package.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export default {
build: true,
giveExecutionRights: false,
};
174 changes: 174 additions & 0 deletions packages/mia/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
import { Calvin } from '@endgame/calvin';

export class Mia {
/**
* @description The focus switch delay in ms.
* @author Alphability <[email protected]>
* @static
* @memberof Mia
*/

static _focusEndDelay = 500;

/**
* @description Object allowing the use of reactive data.
* Storing a11y state values.
* @author Alphability <[email protected]>
* @static
* @type {Calvin}
* @memberof Mia
*/

static _reactor: Calvin = new Calvin({ focusActive: false });

/**
* @description The focus event's last target.
* @author Alphability <[email protected]>
* @private
* @type {(HTMLElement | null)}
* @memberof Mia
*/

private _target: HTMLElement | null = null;

/**
* @description Boolean ensuring that we can't initialize multiple a11y listeners.
* @author Alphability <[email protected]>
* @private
* @memberof Mia
*/

private _isInitialized = false;

/**
* Creates an instance of Mia.
* @author Alphability <[email protected]>
* @memberof Mia
*/

constructor() {
this._handleFocus = this._handleFocus.bind(this);
this._removeFocus = this._removeFocus.bind(this);
}

/**
* @description Removing the focus on the last target.
* @author Alphability <[email protected]>
* @private
* @param {(Event | undefined)} [event=undefined]
* @memberof Mia
*/

private _removeFocus(event: Event | undefined = undefined): void {
document.documentElement.classList.remove('visible-focus-style');

if (event && event.target) {
const target = event.target as HTMLElement;
setTimeout(() => {
if (target === this._target) {
Mia._reactor.data.focusActive = false;
}
}, Mia._focusEndDelay);
}
}

/**
* @description Adding focus on the new target.
* @author Alphability <[email protected]>
* @private
* @param {(EventTarget | null)} target
* @return {*} {void}
* @memberof Mia
*/
private _handleNewTarget(target: EventTarget | null): void {
if (!target) {
return;
}

// Registering new target
this._target = <HTMLElement>target;

const nameLowercased = this._target.nodeName.toLowerCase();
if (
nameLowercased === 'input' ||
nameLowercased === 'select' ||
nameLowercased === 'textarea' ||
nameLowercased === 'a' ||
nameLowercased === 'button'
) {
if (!Mia._reactor.data.focusActive) {
Mia._reactor.data.focusActive = true;
}

this._target.addEventListener('blur', this._removeFocus, false);
document.documentElement.classList.add('visible-focus-style');
} else {
this._removeFocus();
}
}

/**
* @description Handling global HTML elements focus.
* @author Alphability <[email protected]>
* @private
* @param {KeyboardEvent} { key, target }
* @return {*} {void}
* @memberof Mia
*/

private _handleFocus({ key, target }: KeyboardEvent): void {
if (!key || key !== 'Tab') {
return;
}

// âš¡ Avoid memory leak by removing old listeners before registering new ones
if (this._target) {
this._target.removeEventListener('blur', this._removeFocus, false);
}

this._handleNewTarget(target);
}

/**
* @description Initializing the a11y abilities.
* @author Alphability <[email protected]>
* @memberof Mia
*/

public initialize(): void {
// No multiple init
// Avoid having multiple listeners at the same time.
if (this._isInitialized) {
return;
}

this._isInitialized = true;

document.documentElement.classList.add('a11y');
document.addEventListener('keyup', this._handleFocus, false);
}

/**
* @description Destroying the listeners.
* @author Alphability <[email protected]>
* @memberof Mia
*/

public destroy(): void {
document.removeEventListener('keyup', this._handleFocus, false);
document.documentElement.classList.remove('a11y');

this._isInitialized = false;
}

/**
* @description Reactive properties object's getter.
* @readonly
* @type {Calvin}
* @memberof Mia
*/

get reactor(): Calvin {
return Mia._reactor;
}
}
14 changes: 14 additions & 0 deletions packages/mia/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"generated": true,
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"outDir": "./dist",
"rootDir": "src",
},
"include": [
"src/**/*",
],
"exclude": [
"./dist"
],
}

0 comments on commit 434aab6

Please sign in to comment.