{
+ el?.setAttribute('style', `position-anchor: --${anchorId}`);
+ if(typeof ref === 'function') ref(el);
+ else if(ref) ref.current = el;
+ }}
className={`anchored-box${visible ? ' active' : ''} ${className}`}
- style={{ positionAnchor: `--${anchorId}` }} // setting anchor properties here allows greater recyclability.
{...props}
>
{children}
diff --git a/client/components/anchorPositioningPolyfill.js b/client/components/anchorPositioningPolyfill.js
new file mode 100644
index 000000000..dd0ffd59a
--- /dev/null
+++ b/client/components/anchorPositioningPolyfill.js
@@ -0,0 +1,29 @@
+import './anchorPositioningPolyfill.less';
+
+let polyfillPromise;
+
+const supportsAnchorPositioning = ()=>'anchorName' in document.documentElement.style;
+
+const afterInitialRender = ()=>new Promise((resolve)=>{
+ requestAnimationFrame(()=>{
+ requestAnimationFrame(resolve);
+ });
+});
+
+export const bootstrapAnchorPositioningPolyfill = ()=>{
+ if(supportsAnchorPositioning()) return Promise.resolve(false);
+ if(polyfillPromise) return polyfillPromise;
+
+ polyfillPromise = afterInitialRender()
+ .then(async ()=>{
+ const { default: polyfill } = await import('@oddbird/css-anchor-positioning/fn');
+ await polyfill();
+ return true;
+ })
+ .catch((error)=>{
+ polyfillPromise = undefined;
+ throw error;
+ });
+
+ return polyfillPromise;
+};
\ No newline at end of file
diff --git a/client/components/anchorPositioningPolyfill.less b/client/components/anchorPositioningPolyfill.less
new file mode 100644
index 000000000..93cb1df55
--- /dev/null
+++ b/client/components/anchorPositioningPolyfill.less
@@ -0,0 +1,15 @@
+.hb-anchor-source {
+ anchor-name: var(--hb-anchor-name);
+}
+
+.hb-anchor-target {
+ position-anchor: var(--hb-position-anchor);
+}
+
+.hb-position-area {
+ position-area: var(--hb-position-area);
+}
+
+.hb-position-try-fallbacks {
+ position-try-fallbacks: var(--hb-position-try-fallbacks);
+}
\ No newline at end of file
diff --git a/client/components/dropdown/dropdown.jsx b/client/components/dropdown/dropdown.jsx
index 0520b996d..bff8efec1 100644
--- a/client/components/dropdown/dropdown.jsx
+++ b/client/components/dropdown/dropdown.jsx
@@ -13,7 +13,7 @@
*/
import './dropdown.less';
-import React, { useEffect } from 'react';
+import React, { useEffect, useRef } from 'react';
import _ from 'lodash';
// use react context to keep track of the menu depth (menus in menus)
@@ -27,6 +27,16 @@ const Dropdown = ({ groupName, className = null, icon, children, color = null, c
// A menu is a submenu if depth > 0
const isSubMenu = depth > 0;
+ const wrapperRef = useRef(null);
+ const menuRef = useRef(null);
+
+ // Use setAttribute instead of the React style prop because React silently strips unknown CSS
+ // properties (like anchor-name) from inline styles in browsers that don't support them.
+ // setAttribute writes raw CSS text that the anchor positioning polyfill can read.
+ useEffect(()=>{
+ wrapperRef.current?.setAttribute('style', `anchor-name: ${anchorName}`);
+ menuRef.current?.setAttribute('style', `position-anchor: ${anchorName}`);
+ }, [anchorName]);
// hide popover with click inside iframe (not supported by light dismiss)
useEffect(()=>{
@@ -59,7 +69,7 @@ const Dropdown = ({ groupName, className = null, icon, children, color = null, c
};
return (
-