Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add new configurations to FloatingUI component #237

Merged
merged 6 commits into from
Jun 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 13 additions & 3 deletions web/app/components/floating-u-i/content.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ interface FloatingUIContentSignature {
Args: {
anchor: HTMLElement;
id: string;
placement?: Placement;
// TODO: Move null logic to a parent component.
placement?: Placement | null;
renderOut?: boolean;
offset?: OffsetOptions;
};
Expand All @@ -41,10 +42,19 @@ export default class FloatingUIContent extends Component<FloatingUIContentSignat
@action didInsert(e: HTMLElement) {
this._content = e;

if (this.args.placement === null) {
this.content.removeAttribute("data-floating-ui-placement");
this.content.classList.add("non-floating-content");
this.cleanup = () => {};
return;
}

let updatePosition = async () => {
let placement = this.args.placement || "bottom-start";

computePosition(this.args.anchor, this.content, {
platform: platform,
placement: this.args.placement || "bottom-start",
platform,
placement: placement as Placement,
middleware: [offset(this.offset), flip(), shift()],
}).then(({ x, y, placement }) => {
this.content.setAttribute("data-floating-ui-placement", placement);
Expand Down
10 changes: 7 additions & 3 deletions web/app/components/floating-u-i/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@ interface FloatingUIComponentSignature {
Element: HTMLDivElement;
Args: {
renderOut?: boolean;
placement?: Placement;
// TODO: Move null logic to a parent component.
placement?: Placement | null;
disableClose?: boolean;
offset?: OffsetOptions;
};
Blocks: {
Expand All @@ -38,7 +40,7 @@ export default class FloatingUIComponent extends Component<FloatingUIComponentSi

@tracked _anchor: HTMLElement | null = null;
@tracked content: HTMLElement | null = null;
@tracked contentIsShown: boolean = false;
@tracked contentIsShown: boolean = this.args.disableClose || false;

get anchor() {
assert("_anchor must exist", this._anchor);
Expand All @@ -65,7 +67,9 @@ export default class FloatingUIComponent extends Component<FloatingUIComponentSi
}

@action hideContent() {
this.contentIsShown = false;
if (this.args.disableClose !== true) {
this.contentIsShown = false;
}
}
}

Expand Down
8 changes: 5 additions & 3 deletions web/app/styles/components/floating-u-i/content.scss
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
.hermes-floating-ui-content {
@apply absolute;
&:not(.non-floating-content) {
@apply absolute;

/* These positioning styles are overwritten by FloatingUI,
/* These positioning styles are overwritten by FloatingUI,
* but they ensure that the tooltip isn't added to the bottom of the page,
* where it could cause a reflow. This is especially important because
* the Google Docs iframe responds to layout changes and might
* otherwise jitter when a tooltip opened.
*/
@apply top-0 left-0;
@apply top-0 left-0;
}
}
88 changes: 67 additions & 21 deletions web/tests/integration/components/floating-u-i/content-test.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,32 @@
import { module, test, todo } from "qunit";
import { setupRenderingTest } from "ember-qunit";
import { render } from "@ember/test-helpers";
import { TestContext, render } from "@ember/test-helpers";
import { hbs } from "ember-cli-htmlbars";
import htmlElement from "hermes/utils/html-element";
import { OffsetOptions } from "@floating-ui/dom";

const DEFAULT_CONTENT_OFFSET = 5;
const CONTENT_SELECTOR = ".hermes-floating-ui-content";

interface FloatingUIComponentTestContext extends TestContext {
renderOut?: boolean;
offset?: OffsetOptions;
}

module("Integration | Component | floating-u-i/content", function (hooks) {
setupRenderingTest(hooks);

test("it can be rendered inline or outside", async function (assert) {
this.set("renderOut", undefined);
test("it can be rendered inline or outside", async function (this: FloatingUIComponentTestContext, assert) {
this.set("renderOut", false);

await render(hbs`
{{! @glint-nocheck: not typesafe yet }}
await render<FloatingUIComponentTestContext>(hbs`
<div class="anchor">
Attach here
</div>

<div class="container">
<FloatingUI::Content
@id="1"
@anchor={{html-element '.anchor'}}
@renderOut={{this.renderOut}}
>
Expand All @@ -29,21 +36,21 @@ module("Integration | Component | floating-u-i/content", function (hooks) {
`);

assert
.dom(".container .hermes-floating-ui-content")
.dom(`.container ${CONTENT_SELECTOR}`)
.exists("content is rendered inline by default");

this.set("renderOut", true);

assert
.dom(".container .hermes-floating-ui-content")
.dom(`.container ${CONTENT_SELECTOR}`)
.doesNotExist("content is rendered outside its container");

assert
.dom(".ember-application .hermes-floating-ui-content")
.dom(`.ember-application ${CONTENT_SELECTOR}`)
.exists("content is rendered in the root element");
});

test("it is positioned by floating-ui", async function (assert) {
test("it is positioned by floating-ui", async function (this: FloatingUIComponentTestContext, assert) {
let contentWidth = 0;
let anchorWidth = 0;
let contentLeft = 0;
Expand All @@ -63,15 +70,15 @@ module("Integration | Component | floating-u-i/content", function (hooks) {

// Center the anchor so the content can be flexibly positioned

await render(hbs`
{{! @glint-nocheck: not typesafe yet }}
await render<FloatingUIComponentTestContext>(hbs`
<div class="grid place-items-center w-full h-full">
<div>
<div class="anchor" style="width: 100px">
Attach
</div>
<FloatingUI::Content
style="width: 100px"
@id="1"
@anchor={{html-element '.anchor'}}
@placement="left"
>
Expand All @@ -82,7 +89,7 @@ module("Integration | Component | floating-u-i/content", function (hooks) {
`);

let anchor = htmlElement(".anchor");
let content = htmlElement(".hermes-floating-ui-content");
let content = htmlElement(CONTENT_SELECTOR);

setVariables(anchor, content);

Expand All @@ -100,15 +107,15 @@ module("Integration | Component | floating-u-i/content", function (hooks) {

this.clearRender();

await render(hbs`
{{! @glint-nocheck: not typesafe yet }}
await render<FloatingUIComponentTestContext>(hbs`
<div class="grid place-items-center w-full h-full">
<div>
<div class="anchor" style="width: 100px">
Attach
</div>
<FloatingUI::Content
style="width: 100px"
@id="1"
@anchor={{html-element '.anchor'}}
@placement="right"
>
Expand All @@ -119,7 +126,7 @@ module("Integration | Component | floating-u-i/content", function (hooks) {
`);

anchor = htmlElement(".anchor");
content = htmlElement(".hermes-floating-ui-content");
content = htmlElement(CONTENT_SELECTOR);

setVariables(anchor, content);

Expand All @@ -130,16 +137,16 @@ module("Integration | Component | floating-u-i/content", function (hooks) {
);
});

test("it can use a custom offset", async function (assert) {
test("it can use a custom offset", async function (this: FloatingUIComponentTestContext, assert) {
await render(hbs`
{{! @glint-nocheck: not typesafe yet }}
<div class="grid place-items-center w-full h-full">
<div>
<div class="anchor" style="width: 100px">
Attach
</div>
<FloatingUI::Content
style="width: 100px"
@id="1"
@anchor={{html-element '.anchor'}}
@placement="left"
>
Expand All @@ -150,7 +157,7 @@ module("Integration | Component | floating-u-i/content", function (hooks) {
`);

let anchor = htmlElement(".anchor");
let content = htmlElement(".hermes-floating-ui-content");
let content = htmlElement(CONTENT_SELECTOR);
let contentWidth = content.offsetWidth;
let contentRight = content.offsetLeft + contentWidth;
let anchorLeft = anchor.offsetLeft;
Expand All @@ -165,15 +172,15 @@ module("Integration | Component | floating-u-i/content", function (hooks) {
this.clearRender();
this.set("offset", 10);

await render(hbs`
{{! @glint-nocheck: not typesafe yet }}
await render<FloatingUIComponentTestContext>(hbs`
<div class="grid place-items-center w-full h-full">
<div>
<div class="anchor" style="width: 100px">
Attach
</div>
<FloatingUI::Content
style="width: 100px"
@id="1"
@anchor={{html-element '.anchor'}}
@placement="left"
@offset={{this.offset}}
Expand All @@ -185,7 +192,7 @@ module("Integration | Component | floating-u-i/content", function (hooks) {
`);

anchor = htmlElement(".anchor");
content = htmlElement(".hermes-floating-ui-content");
content = htmlElement(CONTENT_SELECTOR);
contentWidth = content.offsetWidth;
contentRight = content.offsetLeft + contentWidth;
anchorLeft = anchor.offsetLeft;
Expand All @@ -197,6 +204,45 @@ module("Integration | Component | floating-u-i/content", function (hooks) {
);
});

test("it can ignore dynamic positioning", async function (assert) {
await render(hbs`
<div class="anchor">
Attach
</div>
<FloatingUI::Content
@id="1"
@anchor={{html-element '.anchor'}}
@placement={{null}}
>
Content
</FloatingUI::Content>
`);

assert
.dom(CONTENT_SELECTOR)
.doesNotHaveAttribute("data-floating-ui-placement");

const content = htmlElement(CONTENT_SELECTOR);

assert.true(
content.classList.contains("non-floating-content"),
"content has the `non-floating-content` class"
);

assert.true(
getComputedStyle(content).position === "static",
"content is static"
);

const inlineStyle = content.getAttribute("style");

assert.strictEqual(
inlineStyle,
null,
"content not positioned by floatingUI"
);
});

todo("it runs a cleanup function on teardown", async function (assert) {
assert.ok(false);
});
Expand Down
30 changes: 29 additions & 1 deletion web/tests/integration/components/floating-u-i/index-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ module("Integration | Component | floating-u-i/index", function (hooks) {
this.set("renderOut", undefined);

await render(hbs`
{{! @glint-nocheck: not typesafe yet }}
<FloatingUI>
<:anchor as |f|>
<Action
Expand Down Expand Up @@ -62,4 +61,33 @@ module("Integration | Component | floating-u-i/index", function (hooks) {
.dom(".content")
.doesNotExist("the API is also available to the content block");
});

test("the close action can be disabled", async function (assert) {
await render(hbs`
<FloatingUI @disableClose={{true}}>
<:anchor as |f|>
<Action
class="open-button"
{{on "click" f.showContent}}
{{did-insert f.registerAnchor}}
>
Open
</Action>
</:anchor>
<:content as |f|>
<Action {{on "click" f.hideContent}} class="close-button">
Close
</Action>
</:content>
</FloatingUI>
`);

await click(".open-button");

assert.dom(".close-button").exists();

await click(".close-button");

assert.dom(".close-button").exists('the "close" action was disabled');
});
});