diff --git a/vitest.setup.jsdom.ts b/vitest.setup.jsdom.ts index 228df6a..36b8174 100644 --- a/vitest.setup.jsdom.ts +++ b/vitest.setup.jsdom.ts @@ -61,3 +61,48 @@ if (typeof PointerEvent === 'undefined') { // jsdom lacks pointer capture HTMLElement.prototype.setPointerCapture = vi.fn(); HTMLElement.prototype.releasePointerCapture = vi.fn(); + +// jsdom lacks the Popover API. Minimal shim: methods toggle an internal flag, +// dispatch a `toggle` event ({ oldState, newState }), and make +// matches(':popover-open') reflect the flag so components can sync state. +if (typeof HTMLElement.prototype.showPopover !== 'function') { + const openFlag = new WeakSet(); + + const fireToggle = (el: HTMLElement, oldState: string, newState: string) => { + const event = new Event('toggle') as Event & { oldState: string; newState: string }; + event.oldState = oldState; + event.newState = newState; + el.dispatchEvent(event); + }; + + HTMLElement.prototype.showPopover = function showPopover(this: HTMLElement) { + if (openFlag.has(this)) { + return; + } + openFlag.add(this); + fireToggle(this, 'closed', 'open'); + }; + HTMLElement.prototype.hidePopover = function hidePopover(this: HTMLElement) { + if (!openFlag.has(this)) { + return; + } + openFlag.delete(this); + fireToggle(this, 'open', 'closed'); + }; + HTMLElement.prototype.togglePopover = function togglePopover(this: HTMLElement) { + if (openFlag.has(this)) { + this.hidePopover(); + return !openFlag.has(this); + } + this.showPopover(); + return openFlag.has(this); + }; + + const originalMatches = Element.prototype.matches; + Element.prototype.matches = function matches(this: Element, selector: string): boolean { + if (selector === ':popover-open') { + return this instanceof HTMLElement && openFlag.has(this); + } + return originalMatches.call(this, selector); + } as typeof Element.prototype.matches; +}