Skip to content

Commit

Permalink
fix(tooltip): ensure delayed and self-managed tooltips do not disrupt…
Browse files Browse the repository at this point in the history
… the page layout
  • Loading branch information
Westbrook committed Nov 30, 2021
1 parent 423d93e commit 0f43b25
Show file tree
Hide file tree
Showing 5 changed files with 74 additions and 21 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@
"cssnano/**/postcss-calc": "7.0.0",
"rollup-plugin-workbox": "6.1.1"
},
"customElements": "projects/documentation/custom-elements.json",
"customElements": ".storybook/custom-elements.json",
"workspaces": [
"packages/*",
"projects/*"
Expand Down
49 changes: 36 additions & 13 deletions packages/tooltip/src/Tooltip.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,14 @@ import { openOverlay } from '@spectrum-web-components/overlay/src/loader.js';

import tooltipStyles from './tooltip.css.js';

export class TooltipProxy extends HTMLElement {
disconnectedCallback(): void {
this.dispatchEvent(new Event('disconnected'));
}
}

customElements.define('tooltip-proxy', TooltipProxy);

/**
* @element sp-tooltip
*
Expand Down Expand Up @@ -103,23 +111,29 @@ export class Tooltip extends SpectrumElement {
event.detail.overlayContentTipElement = this.tipElement;
}

private _proxy?: HTMLElement;
private _proxy!: HTMLElement;

private generateProxy(): void {
if (!this._proxy) {
this._proxy = document.createElement('tooltip-proxy');
this._proxy.id = this._tooltipId;
this._proxy.hidden = true;
this._proxy.slot = 'hidden-tooltip-content';
this._proxy.setAttribute('role', 'tooltip');
this._proxy.addEventListener('disconnected', this.closeOverlay);
}
}

public overlayWillOpenCallback({
trigger,
}: {
trigger: HTMLElement;
}): void {
this.setAttribute('aria-hidden', 'true');
if (!this._proxy) {
this._proxy = document.createElement('span');
this._proxy.textContent = this.textContent;
this._proxy.id = this._tooltipId;
this._proxy.hidden = true;
this._proxy.setAttribute('role', 'tooltip');
}
this.generateProxy();
this._proxy.textContent = this.textContent;
trigger.setAttribute('aria-describedby', this._tooltipId);
trigger.insertAdjacentElement('beforebegin', this._proxy);
this.insertAdjacentElement('beforebegin', this._proxy);
}

public overlayOpenCancelledCallback({
Expand All @@ -137,10 +151,7 @@ export class Tooltip extends SpectrumElement {
}

private removeProxy(): void {
if (this._proxy) {
this._proxy.remove();
this._proxy = undefined;
}
this._proxy.remove();
}

private closeOverlayCallback?: Promise<() => void>;
Expand All @@ -167,14 +178,25 @@ export class Tooltip extends SpectrumElement {
delete this.closeOverlayCallback;
};

private previousSlot?: string;

private manageTooltip(): void {
const parentElement = this.parentElement as HTMLElement;
if (this.selfManaged) {
if (this.slot) {
this.previousSlot = this.slot;
}
this.slot = 'self-managed-tooltip';
parentElement.addEventListener('pointerenter', this.openOverlay);
parentElement.addEventListener('focusin', this.openOverlay);
parentElement.addEventListener('pointerleave', this.closeOverlay);
parentElement.addEventListener('focusout', this.closeOverlay);
} else {
if (this.previousSlot) {
this.slot = this.previousSlot;
} else if (this.slot === 'self-managed-tooltip') {
this.removeAttribute('slot');
}
parentElement.removeEventListener('pointerenter', this.openOverlay);
parentElement.removeEventListener('focusin', this.openOverlay);
parentElement.removeEventListener(
Expand All @@ -201,6 +223,7 @@ export class Tooltip extends SpectrumElement {
this.closeOverlay();
}
}
this.generateProxy();
super.update(changed);
}

Expand Down
4 changes: 0 additions & 4 deletions packages/tooltip/src/tooltip.css
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,6 @@ governing permissions and limitations under the License.
border: none;
}

:host([self-managed]:not([aria-hidden])) {
display: none;
}

:host([placement*='right']) #tip,
:host([placement*='left']) #tip,
:host([placement*='bottom']) #tip {
Expand Down
6 changes: 5 additions & 1 deletion packages/tooltip/stories/tooltip.stories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,10 @@ const overlayStyles = html`
flex: none;
margin: 24px 0;
}
.self-managed:nth-child(3) {
margin-left: 50px;
}
</style>
`;

Expand Down Expand Up @@ -304,7 +308,7 @@ export const selfManaged = ({
delayed,
}: Properties): TemplateResult => html`
${overlayStyles}
<sp-action-button>
<sp-action-button class="self-managed">
This is a button.
<sp-tooltip
self-managed
Expand Down
34 changes: 32 additions & 2 deletions packages/tooltip/test/tooltip.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,15 +48,15 @@ describe('Tooltip', () => {
const el = button.querySelector('sp-tooltip') as Tooltip;

await elementUpdated(el);
await expect(el).to.be.accessible();
await expect(button).to.be.accessible();

const opened = oneEvent(button, 'sp-opened');
button.focus();
await opened;
await elementUpdated(el);

expect(el.open).to.be.true;
await expect(el).to.be.accessible();
await expect(button).to.be.accessible();

const closed = oneEvent(button, 'sp-closed');
button.blur();
Expand All @@ -65,6 +65,36 @@ describe('Tooltip', () => {

expect(el.open).to.be.false;
});
it('cleans up when self manages', async () => {
const button = await fixture<Button>(
html`
<sp-button>
This is a button.
<sp-tooltip self-managed>Help text.</sp-tooltip>
</sp-button>
`
);

const el = button.querySelector('sp-tooltip') as Tooltip;

await elementUpdated(el);

const opened = oneEvent(button, 'sp-opened');
button.focus();
await opened;
await elementUpdated(el);

expect(el.open).to.be.true;
let activeOverlay = document.querySelector('active-overlay');
expect(activeOverlay).to.not.be.null;

const closed = oneEvent(button, 'sp-closed');
button.remove();
await closed;

activeOverlay = document.querySelector('active-overlay');
expect(activeOverlay).to.be.null;
});
it('accepts variants', async () => {
const el = await fixture<Tooltip>(
html`
Expand Down

0 comments on commit 0f43b25

Please sign in to comment.