diff --git a/docs-app/app/templates/5-floaty-bits/floating-ui.gjs.md b/docs-app/app/templates/5-floaty-bits/floating-ui.gjs.md
index ff3f898e3..76d603831 100644
--- a/docs-app/app/templates/5-floaty-bits/floating-ui.gjs.md
+++ b/docs-app/app/templates/5-floaty-bits/floating-ui.gjs.md
@@ -1,13 +1,19 @@
# Floating UI
-The `FloatingUI` component provides a wrapper for using [Floating UI](https://floating-ui.com/), for associating a floating element to an anchor element (such as for menus, popovers, etc).
-
-The usage of a 3rd-party library will be removed when [CSS Anchor Positioning](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_anchor_positioning) lands and is widely supported (This component and modifier will still exist for the purpose of wiring up the ids between anchor and target).
+**Higher-level components use CSS Anchor Positioning.** ``, `
+The `FloatingUI` component provides a wrapper for using [Floating UI](https://floating-ui.com/), for associating a floating element to an anchor element (such as for menus, popovers, etc).
+
Several of Floating UI's functions and [middleware](https://floating-ui.com/docs/middleware) are used to create an experience out of the box that is useful and expected.
See Floating UI's [documentation](https://floating-ui.com/docs/getting-started) for more information on any of the following included functionality.
diff --git a/docs-app/app/templates/5-floaty-bits/popover.gjs.md b/docs-app/app/templates/5-floaty-bits/popover.gjs.md
index 8906b3ced..e0d6e6fbb 100644
--- a/docs-app/app/templates/5-floaty-bits/popover.gjs.md
+++ b/docs-app/app/templates/5-floaty-bits/popover.gjs.md
@@ -1,12 +1,12 @@
# Popover
-Popovers are built with [Floating UI][docs-floating-ui], a set of utilities for making floating elements relate to each other with minimal configuration. The `` component uses the [Popover API](https://developer.mozilla.org/en-US/docs/Web/API/Popover_API) for layering, which totally solves z-index and overflow clipping issues, no portals needed.
+Popovers use [CSS Anchor Positioning][docs-anchor] for positioning floating elements relative to their anchor, with automatic flip fallbacks via `position-try-fallbacks` and viewport-aware visibility via `position-visibility`. No JavaScript positioning library needed.
+
+The `` component uses the [Popover API](https://developer.mozilla.org/en-US/docs/Web/API/Popover_API) for layering, which totally solves z-index and overflow clipping issues — no portals needed.
One thing to note is that the position of the popover can _escape_ the boundary of a [ShadowDom][docs-shadow-dom] -- all demos on this docs site for `ember-primitives` use a `ShadowDom` to allow for isolated CSS usage within the demos.
-[docs-floating-ui]: /5-floaty-bits/floating-ui.md
-[docs-floating]: https://floating-ui.com/
-[docs-popper]: https://popper.js.org/
+[docs-anchor]: https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_anchor_positioning
[docs-shadow-dom]: https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_shadow_DOM
@@ -376,6 +376,29 @@ The `@inline` argument has been removed. All popover content now renders inline
+
```
+### Remove Floating UI–specific arguments
+
+Positioning is now handled by [CSS Anchor Positioning][docs-anchor] instead of Floating UI. The arguments that mapped onto Floating UI middleware no longer exist on `` (or ``):
+
+- `@flipOptions` — flip behavior is now provided by `position-try-fallbacks: flip-block`.
+- `@middleware` — there is no JS middleware pipeline anymore.
+- `@shiftOptions` — the browser handles shifting via `position-area`.
+- `@strategy` — the component sets `position: fixed` itself; there is no `absolute` strategy to opt into.
+
+`@offsetOptions` and `@placement` continue to work the same way.
+
+```diff
+
+```
+
### CSS considerations
The `popover` HTML attribute has a browser UA stylesheet that adds default `border`, `padding`, and `overflow` styles to elements with `[popover]`. The component resets `overflow: visible` automatically so arrows aren't clipped, but you may need to set `border: none` on your floating content if you don't want the browser's default `[popover]` border:
diff --git a/ember-primitives/src/anchor-position.ts b/ember-primitives/src/anchor-position.ts
new file mode 100644
index 000000000..14afb257a
--- /dev/null
+++ b/ember-primitives/src/anchor-position.ts
@@ -0,0 +1,3 @@
+export { attachArrow } from './anchor-position/modifier.ts';
+export type { OffsetOptions, Placement, PlacementConfig } from './anchor-position/placement.ts';
+export { anchorPositionStyle, PLACEMENT_CONFIG } from './anchor-position/placement.ts';
diff --git a/ember-primitives/src/anchor-position/modifier.ts b/ember-primitives/src/anchor-position/modifier.ts
new file mode 100644
index 000000000..80c4f90ec
--- /dev/null
+++ b/ember-primitives/src/anchor-position/modifier.ts
@@ -0,0 +1,43 @@
+import { modifier as eModifier } from 'ember-modifier';
+
+import { PLACEMENT_CONFIG } from './placement.ts';
+
+import type { Placement } from './placement.ts';
+
+/**
+ * Positions an arrow element on the side of its anchor-positioned parent
+ * facing the reference element, centered along the cross-axis.
+ *
+ * Example:
+ * ```gjs
+ * import { attachArrow } from 'ember-primitives/anchor-position';
+ *
+ *
+ *
+ *
+ * ```
+ */
+export const attachArrow = eModifier<{
+ Element: Element;
+ Args: {
+ Named: {
+ placement: Placement;
+ };
+ };
+}>((el, _: [], { placement }) => {
+ if (!(el instanceof HTMLElement)) return;
+
+ const config = PLACEMENT_CONFIG[placement];
+
+ el.style.setProperty('position', 'absolute');
+
+ if (config.arrowIsVertical) {
+ el.style.setProperty('left', '50%');
+ el.style.setProperty('translate', '-50% 0');
+ } else {
+ el.style.setProperty('top', '50%');
+ el.style.setProperty('translate', '0 -50%');
+ }
+
+ el.style.setProperty(config.arrowSide, '-4px');
+});
diff --git a/ember-primitives/src/anchor-position/placement.ts b/ember-primitives/src/anchor-position/placement.ts
new file mode 100644
index 000000000..d1589b647
--- /dev/null
+++ b/ember-primitives/src/anchor-position/placement.ts
@@ -0,0 +1,180 @@
+import { htmlSafe } from '@ember/template';
+
+import type { SafeString } from '@ember/template';
+
+export type Placement =
+ | 'top'
+ | 'top-start'
+ | 'top-end'
+ | 'bottom'
+ | 'bottom-start'
+ | 'bottom-end'
+ | 'left'
+ | 'left-start'
+ | 'left-end'
+ | 'right'
+ | 'right-start'
+ | 'right-end';
+
+export type OffsetOptions = number | { mainAxis?: number; crossAxis?: number };
+
+export interface PlacementConfig {
+ positionArea: string;
+ selfProp: string;
+ selfValue: string;
+ offsetMargin: string;
+ crossOffsetMargin: string;
+ arrowSide: string;
+ arrowIsVertical: boolean;
+}
+
+/**
+ * Static lookup for CSS Anchor Positioning properties per placement.
+ *
+ * The position-area grid has the anchor element at center.
+ * For -start/-end variants, we use span-right/span-left (or span-bottom/span-top)
+ * to create an area starting at the anchor's edge, then align within that area.
+ */
+export const PLACEMENT_CONFIG: Record = {
+ top: {
+ positionArea: 'top',
+ selfProp: 'justify-self',
+ selfValue: 'anchor-center',
+ offsetMargin: 'margin-bottom',
+ crossOffsetMargin: 'margin-left',
+ arrowSide: 'bottom',
+ arrowIsVertical: true,
+ },
+ 'top-start': {
+ positionArea: 'top span-right',
+ selfProp: 'justify-self',
+ selfValue: 'start',
+ offsetMargin: 'margin-bottom',
+ crossOffsetMargin: 'margin-left',
+ arrowSide: 'bottom',
+ arrowIsVertical: true,
+ },
+ 'top-end': {
+ positionArea: 'top span-left',
+ selfProp: 'justify-self',
+ selfValue: 'end',
+ offsetMargin: 'margin-bottom',
+ crossOffsetMargin: 'margin-left',
+ arrowSide: 'bottom',
+ arrowIsVertical: true,
+ },
+ bottom: {
+ positionArea: 'bottom',
+ selfProp: 'justify-self',
+ selfValue: 'anchor-center',
+ offsetMargin: 'margin-top',
+ crossOffsetMargin: 'margin-left',
+ arrowSide: 'top',
+ arrowIsVertical: true,
+ },
+ 'bottom-start': {
+ positionArea: 'bottom span-right',
+ selfProp: 'justify-self',
+ selfValue: 'start',
+ offsetMargin: 'margin-top',
+ crossOffsetMargin: 'margin-left',
+ arrowSide: 'top',
+ arrowIsVertical: true,
+ },
+ 'bottom-end': {
+ positionArea: 'bottom span-left',
+ selfProp: 'justify-self',
+ selfValue: 'end',
+ offsetMargin: 'margin-top',
+ crossOffsetMargin: 'margin-left',
+ arrowSide: 'top',
+ arrowIsVertical: true,
+ },
+ left: {
+ positionArea: 'left',
+ selfProp: 'align-self',
+ selfValue: 'anchor-center',
+ offsetMargin: 'margin-right',
+ crossOffsetMargin: 'margin-top',
+ arrowSide: 'right',
+ arrowIsVertical: false,
+ },
+ 'left-start': {
+ positionArea: 'left span-bottom',
+ selfProp: 'align-self',
+ selfValue: 'start',
+ offsetMargin: 'margin-right',
+ crossOffsetMargin: 'margin-top',
+ arrowSide: 'right',
+ arrowIsVertical: false,
+ },
+ 'left-end': {
+ positionArea: 'left span-top',
+ selfProp: 'align-self',
+ selfValue: 'end',
+ offsetMargin: 'margin-right',
+ crossOffsetMargin: 'margin-top',
+ arrowSide: 'right',
+ arrowIsVertical: false,
+ },
+ right: {
+ positionArea: 'right',
+ selfProp: 'align-self',
+ selfValue: 'anchor-center',
+ offsetMargin: 'margin-left',
+ crossOffsetMargin: 'margin-top',
+ arrowSide: 'left',
+ arrowIsVertical: false,
+ },
+ 'right-start': {
+ positionArea: 'right span-bottom',
+ selfProp: 'align-self',
+ selfValue: 'start',
+ offsetMargin: 'margin-left',
+ crossOffsetMargin: 'margin-top',
+ arrowSide: 'left',
+ arrowIsVertical: false,
+ },
+ 'right-end': {
+ positionArea: 'right span-top',
+ selfProp: 'align-self',
+ selfValue: 'end',
+ offsetMargin: 'margin-left',
+ crossOffsetMargin: 'margin-top',
+ arrowSide: 'left',
+ arrowIsVertical: false,
+ },
+};
+
+/**
+ * Build the inline CSS that positions a floating element relative to its
+ * anchor using CSS Anchor Positioning.
+ *
+ * The returned string covers placement (`position-area`), self-alignment,
+ * fallback flipping (`position-try-fallbacks`), and viewport-aware visibility
+ * (`position-visibility`). Optional `mainAxis` / `crossAxis` offsets are
+ * expressed as margins in the appropriate direction for the chosen placement.
+ */
+export function anchorPositionStyle(
+ placement: Placement,
+ anchorName: string,
+ offsetOptions?: OffsetOptions
+): SafeString {
+ const config = PLACEMENT_CONFIG[placement];
+
+ const offsetOpts = offsetOptions ?? 0;
+ const mainAxis = typeof offsetOpts === 'number' ? offsetOpts : (offsetOpts?.mainAxis ?? 0);
+ const crossAxis = typeof offsetOpts === 'number' ? 0 : (offsetOpts?.crossAxis ?? 0);
+
+ let style = `position: fixed; inset: auto; overflow: visible; border: none; position-anchor: ${anchorName}; position-area: ${config.positionArea}; ${config.selfProp}: ${config.selfValue}; width: max-content; margin: 0; position-try-fallbacks: flip-block; position-visibility: anchors-visible`;
+
+ if (mainAxis) {
+ style += `; ${config.offsetMargin}: ${mainAxis}px`;
+ }
+
+ if (crossAxis) {
+ style += `; ${config.crossOffsetMargin}: ${crossAxis}px`;
+ }
+
+ return htmlSafe(style);
+}
diff --git a/ember-primitives/src/components/menu.gts b/ember-primitives/src/components/menu.gts
index 2d0aa08f1..e56f957c1 100644
--- a/ember-primitives/src/components/menu.gts
+++ b/ember-primitives/src/components/menu.gts
@@ -338,15 +338,7 @@ export class Menu extends Component {
{{#let (IsOpen) (TriggerElement) as |isOpen triggerEl|}}
-
+
{{#let
(modifier
trigger
diff --git a/ember-primitives/src/components/popover.gts b/ember-primitives/src/components/popover.gts
index cd593f27a..18b9ad63f 100644
--- a/ember-primitives/src/components/popover.gts
+++ b/ember-primitives/src/components/popover.gts
@@ -1,40 +1,28 @@
+import Component from "@glimmer/component";
import { hash } from "@ember/helper";
+import { guidFor } from "@ember/object/internals";
-import { arrow } from "@floating-ui/dom";
import { element } from "ember-element-helper";
import { modifier as eModifier } from "ember-modifier";
-import { cell } from "ember-resources";
-import { FloatingUI } from "../floating-ui.ts";
+import { attachArrow } from "../anchor-position/modifier.ts";
+import { anchorPositionStyle } from "../anchor-position/placement.ts";
-import type { Signature as FloatingUiComponentSignature } from "../floating-ui/component.ts";
-import type { Signature as HookSignature } from "../floating-ui/modifier.ts";
+import type { OffsetOptions, Placement } from "../anchor-position/placement.ts";
import type { TOC } from "@ember/component/template-only";
-import type { ElementContext, Middleware } from "@floating-ui/dom";
+import type { SafeString } from "@ember/template";
import type { ModifierLike, WithBoundArgs } from "@glint/template";
export interface Signature {
Args: {
/**
- * See the Floating UI's [flip docs](https://floating-ui.com/docs/flip) for possible values.
- *
- * This argument is forwarded to the `` component.
- */
- flipOptions?: HookSignature["Args"]["Named"]["flipOptions"];
- /**
- * Array of one or more objects to add to Floating UI's list of [middleware](https://floating-ui.com/docs/middleware)
- *
- * This argument is forwarded to the `` component.
- */
- middleware?: HookSignature["Args"]["Named"]["middleware"];
- /**
- * See the Floating UI's [offset docs](https://floating-ui.com/docs/offset) for possible values.
- *
- * This argument is forwarded to the `` component.
+ * Offset distance between the reference and floating elements.
+ * Can be a number (pixels) or an object with `mainAxis` and/or `crossAxis` values.
*/
- offsetOptions?: HookSignature["Args"]["Named"]["offsetOptions"];
+ offsetOptions?: OffsetOptions;
/**
- * One of the possible [`placements`](https://floating-ui.com/docs/computeposition#placement). The default is 'bottom'.
+ * Where to place the floating element relative to its reference element.
+ * The default is 'bottom'.
*
* Possible values are
* - top
@@ -43,82 +31,66 @@ export interface Signature {
* - left
*
* And may optionally have `-start` or `-end` added to adjust position along the side.
- *
- * This argument is forwarded to the `` component.
- */
- placement?: `${"top" | "bottom" | "left" | "right"}${"" | "-start" | "-end"}`;
- /**
- * See the Floating UI's [shift docs](https://floating-ui.com/docs/shift) for possible values.
- *
- * This argument is forwarded to the `` component.
*/
- shiftOptions?: HookSignature["Args"]["Named"]["shiftOptions"];
- /**
- * CSS position property, either `fixed` or `absolute`.
- *
- * Pros and cons of each strategy are explained on [Floating UI's Docs](https://floating-ui.com/docs/computePosition#strategy)
- *
- * This argument is forwarded to the `` component.
- */
- strategy?: HookSignature["Args"]["Named"]["strategy"];
+ placement?: Placement;
};
Blocks: {
default: [
{
- reference: FloatingUiComponentSignature["Blocks"]["default"][0];
- setReference: FloatingUiComponentSignature["Blocks"]["default"][2]["setReference"];
- Content: WithBoundArgs;
- data: FloatingUiComponentSignature["Blocks"]["default"][2]["data"];
+ reference: ModifierLike<{ Element: HTMLElement | SVGElement }>;
+ setReference: (element: HTMLElement | SVGElement) => void;
+ Content: WithBoundArgs;
+ data: undefined;
arrow: ModifierLike<{ Element: HTMLElement }>;
},
];
};
}
-const showPopover = eModifier<{ Element: Element }>((element) => {
- const el = element as HTMLElement;
+function getElementTag(tagName: undefined | string) {
+ return tagName || "div";
+}
- // Reset [popover] UA overflow default that clips arrows positioned outside
- el.style.setProperty("overflow", "visible");
+const showPopover = eModifier<{ Element: Element }>((el) => {
+ const popoverEl = el as HTMLElement;
// Don't promote to top layer if already inside a popover — the parent
// popover already handles layering. Adding both to the top layer causes
// stacking issues where the parent renders on top of the child.
- if (el.parentElement?.closest("[popover]")) {
- el.removeAttribute("popover");
+ if (popoverEl.parentElement?.closest("[popover]")) {
+ popoverEl.removeAttribute("popover");
// ;
-interface AttachArrowSignature {
- Element: HTMLElement;
+const applyReference = eModifier<{
+ Element: HTMLElement | SVGElement;
Args: {
- Named: {
- arrowElement: ReturnType;
- data:
- | undefined
- | {
- placement: string;
- middlewareData?: {
- arrow?: { x?: number; y?: number };
- };
- };
- };
+ Positional: [setRef: (element: HTMLElement | SVGElement) => void];
};
-}
-
-const arrowSides = {
- top: "bottom",
- right: "left",
- bottom: "top",
- left: "right",
-};
-
-type Direction = "top" | "bottom" | "left" | "right";
-type Placement = `${Direction}${"" | "-start" | "-end"}`;
-
-const attachArrow: ModifierLike = eModifier(
- (element, _: [], named) => {
- if (element === named.arrowElement.current) {
- if (!named.data) return;
- if (!named.data.middlewareData) return;
-
- const { arrow } = named.data.middlewareData;
- const { placement } = named.data;
-
- if (!arrow) return;
- if (!placement) return;
-
- const { x: arrowX, y: arrowY } = arrow;
- const otherSide = (placement as Placement).split("-")[0] as Direction;
- const staticSide = arrowSides[otherSide];
-
- Object.assign(named.arrowElement.current.style, {
- left: arrowX != null ? `${arrowX}px` : "",
- top: arrowY != null ? `${arrowY}px` : "",
- right: "",
- bottom: "",
- [staticSide]: "-4px",
- });
-
- return;
- }
+}>((element, [setRef]) => {
+ setRef(element);
+});
- void (async () => {
- await Promise.resolve();
- named.arrowElement.set(element);
- })();
- },
-);
+/**
+ * Popover component using CSS Anchor Positioning for placement.
+ *
+ * Positions a floating element relative to a reference element using the native
+ * [CSS Anchor Positioning](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_anchor_positioning)
+ * API, with automatic flip fallbacks via `position-try-fallbacks` and viewport-aware
+ * visibility via `position-visibility`.
+ *
+ * Example usage:
+ * ```gjs
+ * import { Popover } from 'ember-primitives';
+ *
+ *
+ *
+ *
+ * Floating content
+ *
+ *
+ * ```
+ */
+export class Popover extends Component {
+ anchorName = `--ep-${guidFor(this)}`;
+ data = undefined;
-const ArrowElement: () => ReturnType> = () => cell();
+ get placement(): Placement {
+ return this.args.placement ?? "bottom";
+ }
-function maybeAddArrow(middleware: Middleware[] | undefined, element: Element | undefined) {
- const result = [...(middleware || [])];
+ setReference = (element: HTMLElement | SVGElement) => {
+ element.style.setProperty("anchor-name", this.anchorName);
+ };
- if (element) {
- result.push(arrow({ element }));
+ get contentStyle(): SafeString {
+ return anchorPositionStyle(this.placement, this.anchorName, this.args.offsetOptions);
}
- return result;
-}
-
-function flipOptions(options: HookSignature["Args"]["Named"]["flipOptions"]) {
- return {
- elementContext: "reference" as ElementContext,
- ...options,
- };
+
+ {{#let
+ (modifier applyReference this.setReference) (modifier attachArrow placement=this.placement)
+ as |referenceModifier arrowModifier|
+ }}
+ {{yield
+ (hash
+ reference=referenceModifier
+ setReference=this.setReference
+ Content=(component Content style=this.contentStyle)
+ data=this.data
+ arrow=arrowModifier
+ )
+ }}
+ {{/let}}
+
}
-export const Popover: TOC =
- {{#let (ArrowElement) as |arrowElement|}}
-
- {{#let (modifier attachArrow arrowElement=arrowElement data=extra.data) as |arrow|}}
- {{yield
- (hash
- reference=reference
- setReference=extra.setReference
- Content=(component Content floating=floating)
- data=extra.data
- arrow=arrow
- )
- }}
- {{/let}}
-
- {{/let}}
-;
-
export default Popover;
diff --git a/packages/docs-support/src/site-css/shell.css b/packages/docs-support/src/site-css/shell.css
index 2c821c4c1..90b9000b0 100644
--- a/packages/docs-support/src/site-css/shell.css
+++ b/packages/docs-support/src/site-css/shell.css
@@ -10,6 +10,17 @@
background: transparent;
}
+ /**
+ * ember-mobile-menu sets will-change: transform on the content wrapper,
+ * which changes the containing block for position: fixed descendants.
+ * This breaks CSS Anchor Positioning's position-try-fallbacks (flip)
+ * and position-visibility (hide on scroll), which need the viewport
+ * as the containing block. Override it here.
+ */
+ .mobile-menu-wrapper__content {
+ will-change: auto;
+ }
+
aside,
aside nav {
background: white;
diff --git a/test-app/tests/popover/rendering-test.gts b/test-app/tests/popover/rendering-test.gts
new file mode 100644
index 000000000..051319a01
--- /dev/null
+++ b/test-app/tests/popover/rendering-test.gts
@@ -0,0 +1,141 @@
+import { render } from '@ember/test-helpers';
+import { module, test } from 'qunit';
+import { setupRenderingTest } from 'ember-qunit';
+
+import { Popover } from 'ember-primitives';
+
+module('Rendering | popover', function (hooks) {
+ setupRenderingTest(hooks);
+
+ test('it renders with CSS Anchor Positioning styles', async function (assert) {
+ await render(
+
+
+
Anchor
+ Floating content
+
+
+ );
+
+ assert.dom('#reference').exists();
+ assert.dom('#reference').hasAttribute('style');
+
+ const referenceEl = document.querySelector('#reference') as HTMLElement;
+
+ assert.ok(
+ referenceEl.style.getPropertyValue('anchor-name').startsWith('--ep-'),
+ 'reference element has anchor-name set'
+ );
+
+ const floatingEl = document.querySelector('#floating') as HTMLElement;
+
+ assert.strictEqual(
+ floatingEl.style.getPropertyValue('position'),
+ 'fixed',
+ 'floating element has position: fixed'
+ );
+ assert.ok(
+ floatingEl.style.getPropertyValue('position-anchor').startsWith('--ep-'),
+ 'floating element has position-anchor set'
+ );
+ assert.strictEqual(
+ floatingEl.style.getPropertyValue('position-area'),
+ 'bottom',
+ 'floating element has correct position-area for placement=bottom'
+ );
+ assert.strictEqual(
+ floatingEl.style.getPropertyValue('margin-top'),
+ '8px',
+ 'floating element has correct offset margin'
+ );
+ assert.strictEqual(
+ floatingEl.style.getPropertyValue('position-try-fallbacks'),
+ 'flip-block',
+ 'floating element has flip fallback'
+ );
+ });
+
+ test('placement top-end uses correct position-area', async function (assert) {
+ await render(
+
+
+
Anchor
+ Content
+
+
+ );
+
+ const floatingEl = document.querySelector('#floating') as HTMLElement;
+
+ // Browser may normalize "top span-left" to "span-left top"
+ const positionArea = floatingEl.style.getPropertyValue('position-area');
+ const parts = positionArea.split(' ').sort();
+
+ assert.deepEqual(
+ parts,
+ ['span-left', 'top'],
+ `top-end maps to position-area containing top and span-left, got: ${positionArea}`
+ );
+ assert.strictEqual(
+ floatingEl.style.getPropertyValue('justify-self'),
+ 'end',
+ 'top-end uses justify-self: end'
+ );
+ });
+
+ test('placement left uses correct position-area', async function (assert) {
+ await render(
+
+
+