Skip to content

Commit

Permalink
feat: support hiding via inert attribute
Browse files Browse the repository at this point in the history
  • Loading branch information
theKashey committed Aug 21, 2022
1 parent 9317455 commit d5b9999
Show file tree
Hide file tree
Showing 12 changed files with 287 additions and 145 deletions.
2 changes: 1 addition & 1 deletion .husky/pre-commit
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"

lint-staged
yarn lint-staged
2 changes: 1 addition & 1 deletion .size.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@
{
"name": "dist/es2015/index.js",
"passed": true,
"size": 454
"size": 599
}
]
69 changes: 53 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,41 +1,78 @@
aria-hidden
====
# aria-hidden

[![NPM](https://nodei.co/npm/aria-hidden.png?downloads=true&stars=true)](https://nodei.co/npm/aria-hidden/)

Hides from ARIA everything, except provided node.
Hides from ARIA everything, except provided node(s).

Helps to isolate modal dialogs and focused task - the content will be not accessible using
accesible tools.
accessible tools.

Now with [HTML inert](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/inert) support

# API
Just call `hideOthers` with DOM-node you want to keep, and it will hide everything else.
targetNode could be placed anywhere - its siblings would be hidden, but its parents - not.

Just call `hideOthers` with DOM-node you want to keep, and it will _hide_ everything else.
`targetNode` could be placed anywhere - its siblings would be hidden, but it and its parents - not.

> "hidden" in terms or `aria-hidden`
```js
import {hideOthers} from 'aria-hidden';
import { hideOthers } from 'aria-hidden';

const undo = hideOthers(DOMnode);
const undo = hideOthers(exceptThisDOMnode);
// everything else is "aria-hidden"

// undo changes
undo();
// all changes undone
```

you also may limit the effect by providing top level node as a second paramiter
you also may limit the effect spread by providing top level node as a second parameter

```js
hideOthers(targetNode, parentNode);
hideOthers(anotherNode, document.getElementById('app'));
// parentNode defaults to document.body
// keep only `anotherNode` node visible in #app
// the rest of document will be untouched
hideOthers(anotherNode, document.getElementById('app'));
```

> `parentNode` defaults to document.body
# Inert

While `aria-hidden` played important role in the past and will play in the future - the main
use case always was around isolating content and making elements "transparent" not only for aria, but for
user interaction as well.

This is why you might consider using `inertOthers`

```tsx
import { hideOthers, inertOthers, supportsInert } from 'aria-hidden';

// focus on element mean "hide others". Ideally disable interactions
const focusOnElement = (node) => (supportsInert() ? inertOthers(node) : hideOthers(node));
```

the same function as above is already contructed and exported as

```tsx
import { suppressOthers } from 'aria-hidden';

suppressOthers([keepThisNode, andThis]);
```

# Inspiration

Based on [smooth-ui](https://github.com/smooth-code/smooth-ui) modal dialogs.

# See also

- [inert](https://github.com/WICG/inert) - The HTML attribute/property to mark parts of the DOM tree as "inert".
- [react-focus-lock](https://github.com/theKashey/react-focus-lock) to lock Focus inside modal.
- [react-scroll-lock](https://github.com/theKashey/react-scroll-lock) to disable page scroll while modal is opened.

# Size
# Size

Code is 30 lines long

# Licence
MIT
# Licence

MIT
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`Specs handles aria-live 1`] = `"<div><div><div><div>kept</div><div>hidden</div><div aria-live=\\"polite\\">not-hidden life</div><div aria-live=\\"off\\">hidden life</div></div><div>not-hidden</div></div></div>"`;
exports[`Specs hides cross 1`] = `"<div><div><div data-aria-hidden=\\"true\\" aria-hidden=\\"true\\">hide me 1</div><div>not me 2</div><div data-aria-hidden=\\"true\\" aria-hidden=\\"true\\"><div>not me 3</div></div><div data-aria-hidden=\\"true\\" aria-hidden=\\"true\\">hide me 4</div><svg data-aria-hidden=\\"true\\" aria-hidden=\\"true\\"><text>svg</text></svg><div aria-hidden=\\"true\\" data-aria-hidden=\\"true\\">I am already hidden! 5</div></div><div>dont touch me 6</div></div>"`;
exports[`Specs hides cross 2`] = `"<div><div><div data-aria-hidden=\\"true\\" aria-hidden=\\"true\\">hide me 1</div><div data-aria-hidden=\\"true\\" aria-hidden=\\"true\\">not me 2</div><div data-aria-hidden=\\"true\\" aria-hidden=\\"true\\"><div>not me 3</div></div><div data-aria-hidden=\\"true\\" aria-hidden=\\"true\\">hide me 4</div><svg data-aria-hidden=\\"true\\" aria-hidden=\\"true\\"><text>svg</text></svg><div aria-hidden=\\"true\\" data-aria-hidden=\\"true\\">I am already hidden! 5</div></div><div>dont touch me 6</div></div>"`;
Expand Down
19 changes: 2 additions & 17 deletions __tests__/index.tsx → __tests__/aria.spec.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { render } from '@testing-library/react';
import * as React from 'react';

import { RefObject } from 'react';

import { hideOthers } from '../src';
import { getNearestAttribute } from './utils';

describe('Specs', () => {
const factory = () => {
Expand Down Expand Up @@ -43,20 +42,6 @@ describe('Specs', () => {
};
};

const getNearestAttribute = (node: Element, name: string, parent: RefObject<Element>): any => {
const attr = node.getAttribute(name);

if (attr) {
return attr;
}

if (node === parent.current || !node.parentNode) {
return null;
}

return getNearestAttribute(node.parentNode as any, name, parent);
};

it('hides single', () => {
const { base, parent, target1, target2, targetOutside1, wrapper } = factory();

Expand Down Expand Up @@ -157,7 +142,7 @@ describe('Specs', () => {
expect(getNearestAttribute(root.querySelector('#to-be-hidden')!, 'aria-hidden', { current: root })).toBe('true');

expect(wrapper.baseElement.innerHTML).toMatchInlineSnapshot(
`"<div><div><div><div id=\\"to-be-hidden\\" data-aria-hidden=\\"true\\" aria-hidden=\\"true\\">hidden</div><div aria-live=\\"polite\\">not-hidden life</div><div aria-live=\\"off\\">hidden life</div></div><div>not-hidden</div></div></div>"`
`"<div><div><div><div id=\\"to-be-hidden\\" data-aria-hidden=\\"true\\" aria-hidden=\\"true\\">hidden</div><div aria-live=\\"polite\\">not-hidden life</div><div aria-live=\\"off\\">hidden life</div></div><div>not-hidden</div></div></div>"`
);

unhide();
Expand Down
35 changes: 35 additions & 0 deletions __tests__/inert.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { render } from '@testing-library/react';
import * as React from 'react';

import { inertOthers, supportsInert } from '../src';
import { getNearestAttribute } from './utils';

describe('inert', () => {
it('supports in jsdom', () => {
// not yet
expect(supportsInert()).toBeFalsy();
});

it('handles inert', () => {
const wrapper = render(
<div>
<div>
<div id="to-be-hidden">hidden</div>
</div>
<div id="not-to-be-hidden">not-hidden</div>
</div>
);
const root = wrapper.baseElement.firstElementChild!;

const unhide = inertOthers(root.firstElementChild!.lastElementChild!, root as any);

expect(getNearestAttribute(root.querySelector('#to-be-hidden')!, 'inert', { current: root })).toBe('true');
expect(getNearestAttribute(root.querySelector('#not-to-be-hidden')!, 'inert', { current: root })).toBe(null);

expect(wrapper.baseElement.innerHTML).toMatchInlineSnapshot(
`"<div><div><div data-inert-ed=\\"true\\" inert=\\"true\\"><div id=\\"to-be-hidden\\">hidden</div></div><div id=\\"not-to-be-hidden\\">not-hidden</div></div></div>"`
);

unhide();
});
});
11 changes: 11 additions & 0 deletions __tests__/ssr.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/**
* @jest-environment node
*/

import { supportsInert } from '../src';

describe('smoke ssr', () => {
it('do not throw', () => {
expect(supportsInert()).toBeFalsy();
});
});
16 changes: 16 additions & 0 deletions __tests__/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { RefObject } from 'react';

export const getNearestAttribute = (node: Element, name: string, parent: RefObject<Element> | Element): any => {
const attr = node.getAttribute(name);

if (attr) {
return attr;
}

// @ts-ignore
if (node === parent || node === parent.current || !node.parentNode) {
return null;
}

return getNearestAttribute(node.parentNode as any, name, parent);
};
2 changes: 2 additions & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
module.exports = {
preset: 'ts-jest',
testMatch: ['**/?(*.)+(spec|test).[jt]s?(x)'],
testPathIgnorePatterns: ['<rootDir>/dist/', '<rootDir>/node_modules/'],
};
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"version": "1.1.3",
"description": "Cast aria-hidden to everything, except...",
"main": "dist/es5/index.js",
"sideEffects": false,
"scripts": {
"test": "jest",
"dev": "lib-builder dev",
Expand Down Expand Up @@ -76,4 +77,4 @@
"semi": true,
"singleQuote": true
}
}
}
Loading

0 comments on commit d5b9999

Please sign in to comment.