diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index 56849b9..2777730 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -1,6 +1,6 @@ name: Node CI -on: [push] +on: [push, pull_request] jobs: build: runs-on: macos-latest diff --git a/README.md b/README.md index 909e2cd..ec2c204 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,16 @@ import '@github/typing-effect-element' ``` +## Accessibility + +This component detects whether `prefers-reduced-motion` is set on the window: + +```js +window.matchMedia('(prefers-reduced-motion)').matches === true +``` + +If this evaluates to true, any content lines provided will be appended immediately rather than being typed out with a delay. + ## Browser support Browsers without native [custom element support][support] require a [polyfill][]. diff --git a/examples/index.html b/examples/index.html index d6f3b87..9855aaa 100644 --- a/examples/index.html +++ b/examples/index.html @@ -11,7 +11,7 @@ diff --git a/karma.config.cjs b/karma.config.cjs index d18e564..a74e945 100644 --- a/karma.config.cjs +++ b/karma.config.cjs @@ -1,4 +1,4 @@ -module.exports = function(config) { +module.exports = function (config) { config.set({ frameworks: ['mocha', 'chai'], files: [ diff --git a/package-lock.json b/package-lock.json index aca66c9..5c8d4fc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@github/typing-effect-element", - "version": "0.0.2", + "version": "0.1.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@github/typing-effect-element", - "version": "0.0.2", + "version": "0.1.0", "license": "MIT", "devDependencies": { "@github/prettier-config": "0.0.4", diff --git a/package.json b/package.json index 6c6f282..fe1278c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@github/typing-effect-element", - "version": "0.0.2", + "version": "0.1.0", "description": "A custom element that shows text as if it were being typed.", "main": "dist/index.js", "module": "dist/index.js", diff --git a/src/index.ts b/src/index.ts index 8dcd117..36a2089 100644 --- a/src/index.ts +++ b/src/index.ts @@ -39,8 +39,15 @@ class TypingEffectElement extends HTMLElement { } } + get prefersReducedMotion(): boolean { + return window.matchMedia('(prefers-reduced-motion)').matches + } + get characterDelay(): number { - return Math.max(Math.min(0, Math.floor(Number(this.getAttribute('data-character-delay'))), 2_147_483_647)) || 40 + if (this.prefersReducedMotion) { + return 0 + } + return Math.max(0, Math.min(Math.floor(Number(this.getAttribute('data-character-delay'))), 2_147_483_647)) || 40 } set characterDelay(value: number) { @@ -51,7 +58,10 @@ class TypingEffectElement extends HTMLElement { } get lineDelay(): number { - return Math.max(Math.min(0, Math.floor(Number(this.getAttribute('data-line-delay'))), 2_147_483_647)) || 40 + if (this.prefersReducedMotion) { + return 0 + } + return Math.max(0, Math.min(Math.floor(Number(this.getAttribute('data-line-delay'))), 2_147_483_647)) || 40 } set lineDelay(value: number) { @@ -82,12 +92,16 @@ async function typeLines( lineDelay: number ): Promise { for (let lineIndex = 0; lineIndex < lines.length; lineIndex++) { - for (const character of lines[lineIndex].split('')) { - await wait(characterDelay) - contentElement.innerHTML += character + if (characterDelay === 0) { + contentElement.append(lines[lineIndex]) + } else { + for (const character of lines[lineIndex].split('')) { + await wait(characterDelay) + contentElement.innerHTML += character + } } - await wait(lineDelay) + if (lineDelay !== 0) await wait(lineDelay) if (lineIndex < lines.length - 1) contentElement.append(document.createElement('br')) } } diff --git a/test/test.js b/test/test.js index 384aa84..33a283c 100644 --- a/test/test.js +++ b/test/test.js @@ -68,6 +68,21 @@ describe('typing-effect', function () { }) describe('delay attributes', function () { + let realMatchMedia + before(() => { + realMatchMedia = window.matchMedia + window.matchMedia = mediaString => { + if (mediaString === '(prefers-reduced-motion)') { + return {matches: false} + } + return realMatchMedia(mediaString) + } + }) + + after(() => { + window.matchMedia = realMatchMedia + }) + it('uses defaults when no delays specified', function () { const typingEffectElement = document.createElement('typing-effect') document.body.append(typingEffectElement) @@ -75,6 +90,45 @@ describe('typing-effect', function () { assert.equal(typingEffectElement.characterDelay, 40) assert.equal(typingEffectElement.lineDelay, 40) }) + + it('uses specified delay attributes instead of using defaults', function () { + const characterDelay = 20 + const lineDelay = 20 + document.body.innerHTML = ` + + + ` + const typingEffectElement = document.querySelector('typing-effect') + + assert.equal(typingEffectElement.characterDelay, characterDelay) + assert.equal(typingEffectElement.lineDelay, lineDelay) + }) + }) + + describe('a11y considerations', function () { + let realMatchMedia + before(() => { + realMatchMedia = window.matchMedia + window.matchMedia = mediaString => { + if (mediaString === '(prefers-reduced-motion)') { + return {matches: true} + } + return realMatchMedia(mediaString) + } + }) + + after(() => { + window.matchMedia = realMatchMedia + }) + + it('sets delay to 0 when media query matches (prefers-reduced-motion)', function () { + const typingEffectElement = document.createElement('typing-effect') + document.body.append(typingEffectElement) + + assert.equal(window.matchMedia('(prefers-reduced-motion)').matches, true) + assert.equal(typingEffectElement.characterDelay, 0) + assert.equal(typingEffectElement.lineDelay, 0) + }) }) })