oddbird / popover-polyfill

Polyfills the HTML popover attribute and showPopover/hidePopover/togglePopover methods onto HTMLElement, as well as the popovertarget and popovertargetaction attributes on <button> elements.
https://popover.oddbird.net
BSD 3-Clause "New" or "Revised" License
269 stars 14 forks source link

Is there a way to detect if the Popover API is polyfilled ot not? #189

Closed didoo closed 8 months ago

didoo commented 8 months ago

Quick question: is there a way to detect in code if the Popover API methods are polyfilled ot not? I looked into the src code but seems there's no way to differentiate or have a "flag" that can be used to detect this.

jgerigmeyer commented 8 months ago

@didoo That's a great question. Right now if you're using the default build (or a CDN) you're correct that there's not a way to detect directly whether the polyfill is used or not -- at least without something hacky like checking document.adoptedStyleSheets or looking for class :popover-open on an opened popover or something.

You can indirectly use the same logic that the polyfill uses to check for support, either by importing the isSupported function, or replicating it in your own code. Either of these should work:

import { apply, isSupported } from '@oddbird/popover-polyfill/fn';

const usePolyfill = !isSupported();
if (usePolyfill) {
  apply();
}
const isPolyfilled = !(typeof HTMLElement !== 'undefined' &&
  typeof HTMLElement.prototype === 'object' &&
  'popover' in HTMLElement.prototype);

I'm not opposed to adding a "flag" that indicates when the polyfill has been applied. Do you have a suggestion for how that could be communicated?

didoo commented 8 months ago

Ok, here's the reason why I need to know if the API has been polyfilled or not (which is not the same thing as being "supported", which is what the isSupported() method does).

Let's imagine you have two instances (A1 and A2) of the same component A, in the same page The component A is implemented in a way that checks if isSupported() is false, in which case it invokes the apply() method, polyfilling the API.

What happens is that when the instance A1 is rendered, isSupported() is false, so the apply() method is invoked. But when comes to the instance of A2 to be rendered, now the API has been polyfilled, and now the isSupported() call returns true. Which is correct, because now it is supported.

The problem I have is that I may want to do something specific if the Popover API is not "native" but polyfilled. In my specific case, I want to use a fixed position strategy with Floating UI, to avoid issues in Firefox with the position of the popover (it's mentioned in the caveats of the library, when a parent has CSS position relative).

This means that only the first popover in the page will have a fixed position (because I am checking via isSupported), while the other ones have an incorrect position (they have an absolute position).

Now, the question is: how do we register a "flag" that we can use to detect if the Popover API has been polyfilled or not? At the moment I don't have many ideas, the only thing that I can think of is to register an extra property like this, within the apply method:

Object.defineProperties(HTMLElement.prototype, {
  popoverIsPolyfilled: {
    value: true,
    writable: false,
    enumerable: false,
    configurable: true,
  },
});

and expose a new public method like this:

export function isPolyfilled() {
  return (
    typeof HTMLElement !== 'undefined' &&
    typeof HTMLElement.prototype === 'object' &&
    'popoverIsPolyfilled' in HTMLElement.prototype
  );
}

(in this file https://github.com/oddbird/popover-polyfill/blob/main/src/popover.ts)

I know it's not great, but I can't think of other alternatives (I don't think it's possible to add an extra property here https://github.com/oddbird/popover-polyfill/blob/main/src/popover.ts#L155-L156 and then read it, right?)

If you want I can open a PR to start the conversation in a more tangible way.

jgerigmeyer commented 8 months ago

@keithamus or @mirisuzanne, I'm curious what you think about this API question. In brief, what's the best way to tell users when popover has been polyfilled?

mirisuzanne commented 8 months ago

I think a public method sounds like a good place to start. We could also consider adding a class/attribute to the root element. I imagine that could be useful for CSS in some cases, but maybe not as essential? Once we have a public method, adding your own attribute is simpler.

keithamus commented 8 months ago

We've done this when we need to check:

const isNativePopover = /native code/.test(document.body.showPopover?.toString())

But I am also open to adding an API within the polyfill. I'd encourage us to make sure it doesn't return false positives when multiple versions of the polyfill are loaded, though (which is why we go with the above route).

didoo commented 8 months ago

Would @keithamus solution work/be acceptable for you @mirisuzanne @jgerigmeyer? If so, I can open a PR for the change.

jgerigmeyer commented 8 months ago

@didoo Yes, I think that makes sense. Thanks!

didoo commented 8 months ago

I have opened #193.

jgerigmeyer commented 8 months ago

Included in v0.4.1 release.