New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

@lion/overlays

Package Overview
Dependencies
Maintainers
1
Versions
128
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@lion/overlays - npm Package Compare versions

Comparing version 0.16.4 to 0.16.5

11

CHANGELOG.md

@@ -6,2 +6,13 @@ # Change Log

## [0.16.5](https://github.com/ing-bank/lion/compare/@lion/overlays@0.16.4...@lion/overlays@0.16.5) (2020-06-23)
### Bug Fixes
* **overlays:** accessibility attrs setup/teardown ([dfe1905](https://github.com/ing-bank/lion/commit/dfe1905e7c61007decb27da4dc30ea17fb1de1b1))
## [0.16.4](https://github.com/ing-bank/lion/compare/@lion/overlays@0.16.3...@lion/overlays@0.16.4) (2020-06-18)

@@ -8,0 +19,0 @@

40

docs/40-system-configuration.md

@@ -64,2 +64,40 @@ [//]: # 'AUTO INSERT HEADER PREPUBLISH'

## isTooltip (placementMode: 'local')
As specified in the [overlay rationale](/?path=/docs/overlays-system-rationale--page) there are only two official types of overlays: dialogs and tooltips. And their main differences are:
- Dialogs have a modal option, tooltips don’t
- Dialogs have interactive content, tooltips don’t
- Dialogs are opened via regular buttons (click/space/enter), tooltips act on focus/mouseover
Since most overlays have interactive content the default is set to dialogs. To get a tooltip, you can add `isTooltip` to the config object. This only works for local placement and it also needs to have `handlesAccessibility` activated to work.
```js preview-story
export const isTooltip = () => {
function showTooltip() {
const tooltip = document.querySelector('#tooltip');
tooltip.opened = true;
}
function hideTooltip() {
const tooltip = document.querySelector('#tooltip');
tooltip.opened = false;
}
return html`
<demo-overlay-system
id="tooltip"
.config=${{ placementMode: 'local', isTooltip: true, handlesAccessibility: true }}
>
<button slot="invoker" @mouseenter="${showTooltip}" @mouseleave="${hideTooltip}">
Hover me to open the tooltip!
</button>
<div slot="content" class="demo-overlay">
Hello!
</div>
</demo-overlay-system>
`;
};
```
## trapsKeyboardFocus

@@ -299,3 +337,3 @@

For locally DOM positioned overlays that position themselves relative to their invoker, we use <a href="https://popper.js.org/" target="_blank">Popper.js</a> for positioning.
For locally DOM positioned overlays that position themselves relative to their invoker, we use [Popper.js](https://popper.js.org/) for positioning.

@@ -302,0 +340,0 @@ > In Popper, `contentNode` is often referred to as `popperElement`, and `invokerNode` is often referred to as the `referenceElement`.

4

package.json
{
"name": "@lion/overlays",
"version": "0.16.4",
"version": "0.16.5",
"description": "Overlays System using lit-html for rendering",

@@ -46,3 +46,3 @@ "license": "MIT",

},
"gitHead": "8958b2fa02d3b0c39120d405c5284aa01990a524"
"gitHead": "a217b8a1286477157ce6c7a8c5495158537e798d"
}

@@ -103,2 +103,3 @@ import '@lion/core/src/differentKeyEventNamesShimIE.js';

isTooltip: false,
invokerRelation: 'description',
handlesUserInteraction: false,

@@ -138,3 +139,3 @@ handlesAccessibility: false,

this._contentId = `overlay-content--${Math.random().toString(36).substr(2, 10)}`;
this.__originalAttrs = new Map();
if (this._defaultConfig.contentNode) {

@@ -241,15 +242,29 @@ if (!this._defaultConfig.contentNode.isConnected) {

if (!newConfig.placementMode) {
throw new Error('You need to provide a .placementMode ("global"|"local")');
throw new Error(
'[OverlayController] You need to provide a .placementMode ("global"|"local")',
);
}
if (!['global', 'local'].includes(newConfig.placementMode)) {
throw new Error(
`"${newConfig.placementMode}" is not a valid .placementMode, use ("global"|"local")`,
`[OverlayController] "${newConfig.placementMode}" is not a valid .placementMode, use ("global"|"local")`,
);
}
if (!newConfig.contentNode) {
throw new Error('You need to provide a .contentNode');
throw new Error('[OverlayController] You need to provide a .contentNode');
}
if (this.__isContentNodeProjected && !newConfig.contentWrapperNode) {
throw new Error('You need to provide a .contentWrapperNode when .contentNode is projected');
throw new Error(
'[OverlayController] You need to provide a .contentWrapperNode when .contentNode is projected',
);
}
if (newConfig.isTooltip && newConfig.placementMode !== 'local') {
throw new Error(
'[OverlayController] .isTooltip should be configured with .placementMode "local"',
);
}
if (newConfig.isTooltip && !newConfig.handlesAccessibility) {
throw new Error(
'[OverlayController] .isTooltip only takes effect when .handlesAccessibility is enabled',
);
}
// if (newConfig.popperConfig.modifiers.arrow && !newConfig.contentWrapperNode) {

@@ -263,5 +278,2 @@ // throw new Error('You need to provide a .contentWrapperNode when Popper arrow is enabled');

this.__initConnectionTarget();
if (this.handlesAccessibility) {
this.__initAccessibility({ cfgToAdd });
}

@@ -346,25 +358,56 @@ if (this.placementMode === 'local') {

__initAccessibility() {
// TODO: remove a11y attributes on teardown
if (!this.contentNode.id) {
this.contentNode.setAttribute('id', this._contentId);
}
if (this.isTooltip) {
if (this.invokerNode) {
this.invokerNode.setAttribute(
this.invokerRelation === 'label' ? 'aria-labelledby' : 'aria-describedby',
this._contentId,
);
__setupTeardownAccessibility({ phase }) {
if (phase === 'init') {
this.__storeOriginalAttrs(this.contentNode, ['role', 'id']);
this.__storeOriginalAttrs(this.invokerNode, [
'aria-expanded',
'aria-labelledby',
'aria-describedby',
]);
if (!this.contentNode.id) {
this.contentNode.setAttribute('id', this._contentId);
}
this.contentNode.setAttribute('role', 'tooltip');
} else {
if (this.invokerNode) {
this.invokerNode.setAttribute('aria-expanded', this.isShown);
if (this.isTooltip) {
if (this.invokerNode) {
this.invokerNode.setAttribute(
this.invokerRelation === 'label' ? 'aria-labelledby' : 'aria-describedby',
this._contentId,
);
}
this.contentNode.setAttribute('role', 'tooltip');
} else {
if (this.invokerNode) {
this.invokerNode.setAttribute('aria-expanded', this.isShown);
}
if (!this.contentNode.role) {
this.contentNode.setAttribute('role', 'dialog');
}
}
if (!this.contentNode.role) {
this.contentNode.setAttribute('role', 'dialog');
}
} else if (phase === 'teardown') {
this.__restorOriginalAttrs();
}
}
__storeOriginalAttrs(node, attrs) {
const attrMap = {};
attrs.forEach(attrName => {
attrMap[attrName] = node.getAttribute(attrName);
});
this.__originalAttrs.set(node, attrMap);
}
__restorOriginalAttrs() {
for (const [node, attrMap] of this.__originalAttrs) {
Object.entries(attrMap).forEach(([attrName, value]) => {
if (value !== null) {
node.setAttribute(attrName, value);
} else {
node.removeAttribute(attrName);
}
});
}
this.__originalAttrs.clear();
}
get isShown() {

@@ -780,2 +823,5 @@ return Boolean(this._contentWrapperNode.style.display !== 'none');

_handleAccessibility({ phase }) {
if (phase === 'init' || phase === 'teardown') {
this.__setupTeardownAccessibility({ phase });
}
if (this.invokerNode && !this.isTooltip) {

@@ -782,0 +828,0 @@ this.invokerNode.setAttribute('aria-expanded', phase === 'show');

@@ -38,2 +38,3 @@ /**

* element.
* @property {'label'|'description'} [invokerRelation='description']
* @property {boolean} [handlesAccessibility]

@@ -40,0 +41,0 @@ * For non `isTooltip`:

@@ -21,3 +21,3 @@ /* eslint-disable no-new */

placementMode: 'global',
contentNode: fixtureSync(html` <div>my content</div> `),
contentNode: fixtureSync(html`<div>my content</div>`),
});

@@ -27,3 +27,3 @@

placementMode: 'local',
contentNode: fixtureSync(html` <div>my content</div> `),
contentNode: fixtureSync(html`<div>my content</div>`),
invokerNode: fixtureSync(html`

@@ -139,3 +139,3 @@ <div role="button" style="width: 100px; height: 20px;">Invoker</div>

...withLocalTestConfig(),
invokerNode: await fixture(html` <button>Invoker</button> `),
invokerNode: await fixture(html`<button>Invoker</button>`),
});

@@ -299,3 +299,3 @@ expect(ctrl._renderTarget).to.be.undefined;

const elOutside = await fixture(html` <button>click me</button> `);
const elOutside = await fixture(html`<button>click me</button>`);
const input1 = ctrl.contentNode.querySelectorAll('input')[0];

@@ -315,3 +315,3 @@ const input2 = ctrl.contentNode.querySelectorAll('input')[1];

it('allows to move the focus outside of the overlay if trapsKeyboardFocus is disabled', async () => {
const contentNode = await fixture(html` <div><input /></div> `);
const contentNode = await fixture(html`<div><input /></div>`);

@@ -324,6 +324,6 @@ const ctrl = new OverlayController({

// add element to dom to allow focus
await fixture(html` ${ctrl.content} `);
await fixture(html`${ctrl.content}`);
await ctrl.show();
const elOutside = await fixture(html` <input /> `);
const elOutside = await fixture(html`<input />`);
const input = ctrl.contentNode.querySelector('input');

@@ -533,3 +533,3 @@

it('works with 3rd party code using "event.stopPropagation()" on capture phase', async () => {
const invokerNode = await fixture(html` <div role="button">Invoker</div> `);
const invokerNode = await fixture(html`<div role="button">Invoker</div>`);
const contentNode = await fixture('<div>Content</div>');

@@ -1021,3 +1021,3 @@ const ctrl = new OverlayController({

...withLocalTestConfig(),
contentNode: await fixture(html` <div>content1</div> `),
contentNode: await fixture(html`<div>content1</div>`),
});

@@ -1030,3 +1030,3 @@ await ctrl.show(); // Popper adds inline styles

placementMode: 'local',
contentNode: await fixture(html` <div>content2</div> `),
contentNode: await fixture(html`<div>content2</div>`),
});

@@ -1037,3 +1037,3 @@ expect(ctrl.contentNode.textContent).to.include('content2');

it('respects the initial config provided to new OverlayController(initialConfig)', async () => {
const contentNode = fixtureSync(html` <div>my content</div> `);
const contentNode = fixtureSync(html`<div>my content</div>`);

@@ -1058,3 +1058,3 @@ const ctrl = new OverlayController({

it.skip('allows for updating viewport config placement only, while keeping the content shown', async () => {
const contentNode = fixtureSync(html` <div>my content</div> `);
const contentNode = fixtureSync(html`<div>my content</div>`);

@@ -1085,3 +1085,3 @@ const ctrl = new OverlayController({

describe('Accessibility', () => {
it('adds and removes [aria-expanded] on invoker', async () => {
it('synchronizes [aria-expanded] on invoker', async () => {
const invokerNode = await fixture('<div role="button">invoker</div>');

@@ -1247,2 +1247,62 @@ const ctrl = new OverlayController({

});
describe('Teardown', () => {
it('restores [role] on dialog content', async () => {
const invokerNode = await fixture('<div role="button">invoker</div>');
const ctrl = new OverlayController({
...withLocalTestConfig(),
handlesAccessibility: true,
invokerNode,
});
expect(ctrl.contentNode.getAttribute('role')).to.equal('dialog');
ctrl.teardown();
expect(ctrl.contentNode.getAttribute('role')).to.equal(null);
});
it('restores [role] on tooltip content', async () => {
const invokerNode = await fixture('<div role="button">invoker</div>');
const contentNode = await fixture('<div role="presentation">content</div>');
const ctrl = new OverlayController({
...withLocalTestConfig(),
handlesAccessibility: true,
isTooltip: true,
invokerNode,
contentNode,
});
expect(contentNode.getAttribute('role')).to.equal('tooltip');
ctrl.teardown();
expect(contentNode.getAttribute('role')).to.equal('presentation');
});
it('restores [aria-describedby] on content', async () => {
const invokerNode = await fixture('<div role="button">invoker</div>');
const contentNode = await fixture('<div role="presentation">content</div>');
const ctrl = new OverlayController({
...withLocalTestConfig(),
handlesAccessibility: true,
isTooltip: true,
invokerNode,
contentNode,
});
expect(invokerNode.getAttribute('aria-describedby')).to.equal(contentNode.id);
ctrl.teardown();
expect(invokerNode.getAttribute('aria-describedby')).to.equal(null);
});
it('restores [aria-labelledby] on content', async () => {
const invokerNode = await fixture('<div role="button">invoker</div>');
const contentNode = await fixture('<div role="presentation">content</div>');
const ctrl = new OverlayController({
...withLocalTestConfig(),
handlesAccessibility: true,
isTooltip: true,
invokerNode,
contentNode,
invokerRelation: 'label',
});
expect(invokerNode.getAttribute('aria-labelledby')).to.equal(contentNode.id);
ctrl.teardown();
expect(invokerNode.getAttribute('aria-labelledby')).to.equal(null);
});
});
});

@@ -1260,3 +1320,3 @@ });

});
}).to.throw('You need to provide a .placementMode ("global"|"local")');
}).to.throw('[OverlayController] You need to provide a .placementMode ("global"|"local")');
});

@@ -1269,3 +1329,5 @@

});
}).to.throw('"invalid" is not a valid .placementMode, use ("global"|"local")');
}).to.throw(
'[OverlayController] "invalid" is not a valid .placementMode, use ("global"|"local")',
);
});

@@ -1278,3 +1340,3 @@

});
}).to.throw('You need to provide a .contentNode');
}).to.throw('[OverlayController] You need to provide a .contentNode');
});

@@ -1303,5 +1365,37 @@

});
}).to.throw('You need to provide a .contentWrapperNode when .contentNode is projected');
}).to.throw(
'[OverlayController] You need to provide a .contentWrapperNode when .contentNode is projected',
);
});
it('throws if placementMode is global for a tooltip', async () => {
const contentNode = document.createElement('div');
document.body.appendChild(contentNode);
expect(() => {
new OverlayController({
placementMode: 'global',
contentNode,
isTooltip: true,
handlesAccessibility: true,
});
}).to.throw(
'[OverlayController] .isTooltip should be configured with .placementMode "local"',
);
});
it('throws if handlesAccessibility is false for a tooltip', async () => {
const contentNode = document.createElement('div');
document.body.appendChild(contentNode);
expect(() => {
new OverlayController({
placementMode: 'local',
contentNode,
isTooltip: true,
handlesAccessibility: false,
});
}).to.throw(
'[OverlayController] .isTooltip only takes effect when .handlesAccessibility is enabled',
);
});
});
});
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc