Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/eight-coats-read.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@spectrum-web-components/overlay': patch
---

Guards `OverlayTriggerDirective`'s `reconnected()` implementation so it only calls `init()` after an overlay instance exists. This prevents type errors during the reconnected flow when `listenerHost` would otherwise be unset because the overlay is not yet ready.
9 changes: 9 additions & 0 deletions 1st-gen/packages/overlay/src/overlay-trigger-directive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,15 @@ export class OverlayTriggerDirective extends SlottableRequestDirective {
insertionEl.insertAdjacentElement(where, this.overlay);
}
}

override reconnected(): void {
// Do not re-initialize until an overlay instance exists.
// The overlay-ready callback is responsible for wiring the initial listener.
if (!this.overlay) {
return;
}
this.init();
}
}

export const trigger = directive(OverlayTriggerDirective);
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ import './overlay-story-components.js';
import { tooltip } from '@spectrum-web-components/tooltip/src/tooltip-directive.js';
import { ifDefined } from '@spectrum-web-components/base/src/directives.js';
import { state } from '@spectrum-web-components/base/src/decorators.js';
import { cache } from 'lit/directives/cache.js';

const storyStyles = html`
<style>
Expand Down Expand Up @@ -314,7 +315,7 @@ class ManagedOverlayTrigger extends LitElement {
Create Overlay Render Button And Open Overlay
</sp-button>

${this.isRenderOverlay ? this.renderOverlayButton() : html``}
${cache(this.isRenderOverlay ? this.renderOverlayButton() : html``)}
`;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,13 @@
* governing permissions and limitations under the License.
*/

import { elementUpdated, expect, fixture, oneEvent } from '@open-wc/testing';
import {
elementUpdated,
expect,
fixture,
nextFrame,
oneEvent,
} from '@open-wc/testing';
import { html, TemplateResult } from '@spectrum-web-components/base';
import { stub } from 'sinon';
import { trigger } from '@spectrum-web-components/overlay/src/overlay-trigger-directive.js';
Expand All @@ -20,6 +26,9 @@ import '@spectrum-web-components/dialog/sp-dialog.js';
import '@spectrum-web-components/slider/sp-slider.js';
import '@spectrum-web-components/tooltip/sp-tooltip.js';
import { Overlay } from '@spectrum-web-components/overlay/src/Overlay.js';
import { LitElement } from '@spectrum-web-components/base';
import { cache } from 'lit/directives/cache.js';
import { Button } from '@spectrum-web-components/button';

describe('Overlay trigger directive', () => {
describe('dev mode', () => {
Expand Down Expand Up @@ -88,4 +97,81 @@ describe('Overlay trigger directive', () => {
});
});
});

it('does not throw when directive reconnects before overlay exists', async function () {
// Define a test component that uses cache() to trigger reconnection.
class CachedOverlayTriggerTest extends LitElement {
private show = true;

protected override render(): TemplateResult {
const cachedButton = html`
<sp-button
${trigger(
() => html`
<sp-popover>
<sp-dialog no-divider>
Cached popover
</sp-dialog>
</sp-popover>
`,
{ triggerInteraction: 'click' }
)}
>
Cached Trigger
</sp-button>
`;

return html`
<sp-button
@click=${() => {
this.show = !this.show;
this.requestUpdate();
}}
>
Toggle cached host
</sp-button>
${cache(this.show ? cachedButton : html``)}
`;
}
}
customElements.define(
'cached-overlay-trigger-test',
CachedOverlayTriggerTest
);

const wrapper = await fixture<CachedOverlayTriggerTest>(html`
<cached-overlay-trigger-test></cached-overlay-trigger-test>
`);
await elementUpdated(wrapper);

const shadowRoot = wrapper.shadowRoot as ShadowRoot;
const toggleButton = shadowRoot.querySelector('sp-button') as Button;
expect(toggleButton).to.exist;

// Get the cached trigger button (initially visible).
let cachedTrigger = shadowRoot.querySelectorAll(
'sp-button'
)[1] as Button;
expect(cachedTrigger, 'cached trigger should exist initially').to.exist;

// Toggle off to disconnect the cached button.
toggleButton.click();
await nextFrame();
await elementUpdated(wrapper);

// Toggle back on to reconnect the cached button.
toggleButton.click();
await nextFrame();
await elementUpdated(wrapper);

// Get the reconnected cached trigger button.
cachedTrigger = shadowRoot.querySelectorAll('sp-button')[1] as Button;
expect(cachedTrigger, 'cached trigger should exist after toggle').to
.exist;

// Now click the cached trigger to open the overlay.
const opened = oneEvent(cachedTrigger, 'sp-opened');
cachedTrigger.click();
await opened;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we add some assertions after this to confirm the popover is rendered?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

still missing the assertions for popover being open

});
});
Loading