Skip to content

Commit

Permalink
fix(core): dropdown positioning (#1681)
Browse files Browse the repository at this point in the history
Co-authored-by: Daniel Leroux <daniel.leroux@siemens.com>
  • Loading branch information
jul-lam and danielleroux authored Feb 12, 2025
1 parent 6fd44e0 commit 5b56d90
Show file tree
Hide file tree
Showing 7 changed files with 108 additions and 45 deletions.
5 changes: 5 additions & 0 deletions .changeset/heavy-cups-peel.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@siemens/ix": patch
---

Update `@floating-ui/dom` dependency to fix a wrong placement of the `ix-dropdown` if the dropdown is placed inside a `dialog`-element with animations in certain environments.
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,11 @@ import {
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { IxModule } from '@siemens/ix-angular';
import { By } from '@angular/platform-browser';
import { waitForHydration } from '../utils/wait-for-hydration';
import { checkForError } from '../utils/check-for-error';

@Component({
selector: 'ix-example-form-select',
selector: 'ix-example-select-form',
template: `
<form [formGroup]="form">
<ix-select formControlName="select">
Expand All @@ -35,7 +37,7 @@ import { By } from '@angular/platform-browser';
</ix-select>
`,
})
class SelectComponent {
class SelectFormComponent {
public form = new FormGroup({ select: new FormControl('1') });
value = '3';
selection = ['3', '4', '5'];
Expand All @@ -46,45 +48,19 @@ class SelectComponent {
}
}

function waitForHydration(element: HTMLElement, timeout = 5000): Promise<void> {
return new Promise((resolve, reject) => {
const interval = 50;
let elapsedTime = 0;

const checkClass = () => {
if (element.classList.contains('hydrated')) {
resolve();
} else if (elapsedTime >= timeout) {
reject(new Error(`Timeout waiting for hydration`));
} else {
elapsedTime += interval;
setTimeout(checkClass, interval);
}
};

checkClass();
});
}

function checkForError(consoleSpy: jasmine.Spy, errorName: string): boolean {
return consoleSpy.calls
.allArgs()
.some((arg) => arg[0].toString().includes(errorName));
}

describe('SelectFormComponent', () => {
let component: SelectComponent;
let fixture: ComponentFixture<SelectComponent>;
let component: SelectFormComponent;
let fixture: ComponentFixture<SelectFormComponent>;
let consoleSpy: jasmine.Spy;

beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [SelectComponent],
declarations: [SelectFormComponent],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
imports: [FormsModule, ReactiveFormsModule, IxModule.forRoot()],
}).compileComponents();

fixture = TestBed.createComponent(SelectComponent);
fixture = TestBed.createComponent(SelectFormComponent);
component = fixture.componentInstance;

consoleSpy = spyOn(console, 'error').and.callThrough();
Expand All @@ -98,7 +74,9 @@ describe('SelectFormComponent', () => {
});

it('should change the form control value', async () => {
const select = fixture.debugElement.query(By.css('ix-select[formControlName="select"]'));
const select = fixture.debugElement.query(
By.css('ix-select[formControlName="select"]')
);

await waitForHydration(select.nativeElement);

Expand All @@ -110,7 +88,9 @@ describe('SelectFormComponent', () => {
});

it('should change the input value and check for errors', async () => {
const select = fixture.debugElement.query(By.css('ix-select:not([formControlName="select"])'));
const select = fixture.debugElement.query(
By.css('ix-select:not([formControlName="select"])')
);

await waitForHydration(select.nativeElement);

Expand Down
8 changes: 8 additions & 0 deletions packages/angular-test-app/src/test/utils/check-for-error.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export function checkForError(
consoleSpy: jasmine.Spy,
errorName: string
): boolean {
return consoleSpy.calls
.allArgs()
.some((arg) => arg[0].toString().includes(errorName));
}
22 changes: 22 additions & 0 deletions packages/angular-test-app/src/test/utils/wait-for-hydration.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
export function waitForHydration(
element: HTMLElement,
timeout = 5000
): Promise<void> {
return new Promise((resolve, reject) => {
const interval = 50;
let elapsedTime = 0;

const checkClass = () => {
if (element.classList.contains('hydrated')) {
resolve();
} else if (elapsedTime >= timeout) {
reject(new Error(`Timeout waiting for hydration`));
} else {
elapsedTime += interval;
setTimeout(checkClass, interval);
}
};

checkClass();
});
}
2 changes: 1 addition & 1 deletion packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
"pkg": "pnpm pack"
},
"dependencies": {
"@floating-ui/dom": "^1.6.10",
"@floating-ui/dom": "^1.6.13",
"@stencil/core": "~4.17.0",
"@types/luxon": "^3.3.7",
"animejs": "~3.2.1",
Expand Down
48 changes: 48 additions & 0 deletions packages/core/src/components/dropdown/test/dropdown.ct.ts
Original file line number Diff line number Diff line change
Expand Up @@ -694,3 +694,51 @@ test.describe('A11y', () => {
});
});
});

test('Dropdown works in floating-ui', async ({ mount, page }) => {
await mount(`
<style>
.dialog {
animation: fade-in 0.2s forwards;
overflow: visible;
}
@keyframes fade-in {
0% {
opacity: 0;
transform: translate(0, -50px);
}
100% {
opacity: 1;
transform: translate(0, 0);
}
}
</style>
<dialog id="dialog" class="dialog">
<ix-button id="trigger">Open</ix-button>
<ix-dropdown id="dropdown" trigger="trigger">
<ix-dropdown-item label="Item 1"></ix-dropdown-item>
<ix-dropdown-item label="Item 2"></ix-dropdown-item>
</ix-dropdown>
</dialog>
`);

await page.evaluate(() => {
const dialog = document.getElementById('dialog') as HTMLDialogElement;
dialog.showModal();
});

const trigger = page.locator('#trigger');
await trigger.click();

const dropdown = page.locator('#dropdown');

const dropdownRect = (await dropdown.boundingBox())!;
const triggerRect = (await trigger.boundingBox())!;

expect(Math.round(dropdownRect.x)).toBe(Math.round(triggerRect.x));
expect(Math.round(dropdownRect.y)).toBe(
Math.round(triggerRect.y + triggerRect.height)
);
});
20 changes: 10 additions & 10 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 5b56d90

Please sign in to comment.