# Popover Attribute Polyfill [![Build Status](https://github.com/oddbird/popover-polyfill/actions/workflows/test.yml/badge.svg)](https://github.com/oddbird/popover-polyfill/actions/workflows/test.yml) [![npm version](https://badge.fury.io/js/@oddbird%2Fpopover-polyfill.svg)](https://badge.fury.io/js/@oddbird%2Fpopover-polyfill) [![Netlify Status](https://api.netlify.com/api/v1/badges/35bc7ba7-97a2-4e41-93ed-5141988adb1e/deploy-status)](https://app.netlify.com/sites/popover-polyfill/deploys) - [Demo](https://popover.oddbird.net/) - [Explainer](https://open-ui.org/components/popover.research.explainer/) This polyfills the HTML `popover` attribute and `showPopover`/`hidePopover`/`togglePopover` methods onto HTMLElement, as well as the `popovertarget` and `popovertargetaction` attributes on Button elements. ## Polyfill Installation ### Download a copy The simplest, recommended way to install the polyfill is to copy it into your project. Download `popover.js` (or `popover.min.js`) [from unpkg.com](https://unpkg.com/browse/@oddbird/popover-polyfill/dist/) and add it to the appropriate directory in your project. Then, include it where necessary with a ` ``` Or without [JavaScript modules](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules): ```html ``` Note that the JS will inject CSS styles into your document (or ShadowRoot). ### With npm For more advanced configuration, you can install with [npm](https://www.npmjs.com/): ```sh npm install @oddbird/popover-polyfill ``` After installing, you’ll need to use appropriate tooling to use `node_modules/@oddbird/popover-polyfill/dist/popover.js`. For most tooling such as Vite, Webpack, and Parcel, that will look like this: ```js import '@oddbird/popover-polyfill'; ``` If you want to manually apply the polyfill, you can instead import the `isSupported` and `apply` functions directly from `node_modules/@oddbird/popover-polyfill/dist/popover-fn.js` file. With most tooling: ```js import { apply, isSupported } from '@oddbird/popover-polyfill/fn'; ``` Or in CommonJS environments: ```js const { apply, isSupported } = require('@oddbird/popover-polyfill/fn'); ``` An `isPolyfilled` function is also available, to detect if the Popover methods have been polyfilled: ```js import { isPolyfilled } from '@oddbird/popover-polyfill/fn'; ``` ### Via CDN For prototyping or testing, you can use the npm package via a Content Delivery Network. Avoid using JavaScript CDNs in production, for [many good reasons](https://blog.wesleyac.com/posts/why-not-javascript-cdn) such as performance and robustness. ```html ``` ## Usage After installation the polyfill will automatically add the correct methods and attributes to the HTMLElement class. ## Caveats This polyfill is not a perfect replacement for the native behavior; there are some caveats which will need accommodations: - A native `popover` has a `:popover-open` pseudo selector when in the open state. Pseudo selectors cannot be polyfilled within CSS, and so instead the polyfill will add the `.\:popover-open` CSS class to any open popover. In other words a popover in the open state will have `class=":popover-open"`. In CSS the `:` character must be escaped with a backslash. - The `:popover-open` selector within JavaScript methods has been polyfilled, so both `.querySelector(':popover-open')` _and_ `.querySelector('.\:popover-open')` will work to select the same element. `matches` and `closest` have also been patched, so `.matches(':popover-open')` will work the same as `.matches('.\:popover-open')`. - Using native `:popover-open` in CSS that does not support native `popover` results in an invalid selector, and so the entire declaration is thrown away. This is important because if you intend to style a popover using `.\:popover-open` it will need to be a separate declaration. For example, `[popover]:popover-open, [popover].\:popover-open` will not work. - Native `popover` elements use the `:top-layer` pseudo element which gets placed above all other elements on the page, regardless of overflow or z-index. This is not possible to polyfill, and so this library simply sets a really high `z-index`. This means if a popover is within an element that has `overflow:` or `position:` CSS, then there will be visual differences between the polyfill and the native behavior. - Native _invokers_ (that is, buttons or inputs using the `popovertarget` attribute) on `popover=auto` will render in the accessibility tree as elements with `expanded`. The only way to do this in the polyfill is setting the `aria-expanded` attribute on those elements. This _may_ impact mutation observers or frameworks which do DOM diffing, or it may interfere with other code which sets `aria-expanded` on elements. - The polyfill uses `adoptedStyleSheets` to inject CSS onto the page (and each Shadow DOM). If it can't use that it'll generate a `