Skip to content

Commit

Permalink
fix(core/dropdown): remove shadow-dom workaround (#777)
Browse files Browse the repository at this point in the history
  • Loading branch information
danielleroux authored Sep 25, 2023
1 parent b38f3c0 commit 03a3099
Show file tree
Hide file tree
Showing 8 changed files with 250 additions and 74 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,7 @@ import { Component } from '@angular/core';
template: `
<ix-button #trigger>Open</ix-button>
<ix-dropdown [ixDropdownTrigger]="trigger">
<ix-dropdown-item #submenu label="Submenu">
<ix-icon name="chevron-right-small" size="24"></ix-icon>
</ix-dropdown-item>
<ix-dropdown-item #submenu label="Submenu"></ix-dropdown-item>
<ix-dropdown-item icon="star" label="Item 1"></ix-dropdown-item>
<ix-dropdown-item icon="document" label="Item 2"></ix-dropdown-item>
<ix-dropdown-item icon="bulb" label="Item 3"></ix-dropdown-item>
Expand Down
90 changes: 52 additions & 38 deletions packages/core/src/components/dropdown/dropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -147,14 +147,14 @@ export class Dropdown {
return Array.from(this.hostElement.querySelectorAll('ix-dropdown-item'));
}

get slotElement() {
return this.hostElement.shadowRoot.querySelector('slot');
}

private addEventListenersFor(triggerEvent: DropdownTriggerEvent) {
switch (triggerEvent) {
case 'click':
if (this.closeBehavior === 'outside') {
this.triggerElement.addEventListener('click', this.openBind);
} else {
this.triggerElement.addEventListener('click', this.toggleBind);
}
this.triggerElement.addEventListener('click', this.openBind);
break;

case 'hover':
Expand Down Expand Up @@ -277,8 +277,13 @@ export class Dropdown {
@Listen('click', {
target: 'window',
})
clickOutside(event: Event) {
clickOutside(event: PointerEvent) {
const target = event.target as HTMLElement;

if (event.defaultPrevented) {
return;
}

if (
this.show === false ||
this.closeBehavior === false ||
Expand All @@ -287,24 +292,27 @@ export class Dropdown {
) {
return;
}

const clickInsideDropdown = this.isClickInsideDropdown(event);

switch (this.closeBehavior) {
case 'outside':
if (!this.dropdownRef.contains(target)) {
this.close(event);
if (!clickInsideDropdown || this.anchor === target) {
this.close();
}
break;
case 'inside':
if (this.dropdownRef.contains(target) && this.hostElement !== target) {
this.close(event);
if (clickInsideDropdown && this.hostElement !== target) {
this.close();
}
break;
case 'both':
if (this.hostElement !== target) {
this.close(event);
this.close();
}
break;
default:
this.close(event);
this.close();
}
}

Expand All @@ -330,42 +338,40 @@ export class Dropdown {
return true;
}

private toggle(event?: Event) {
event?.preventDefault();
private toggle(event: Event) {
event.preventDefault();

if (this.isNestedDropdown(event.target as HTMLElement)) {
event?.stopPropagation();
event.stopPropagation();
}

this.show = !this.show;
this.showChanged.emit(this.show);
const { defaultPrevented } = this.showChanged.emit(this.show);

if (!defaultPrevented) {
this.show = !this.show;
}
}

private open(event?: Event) {
event?.preventDefault();
private open(event: Event) {
event.preventDefault();

if (this.isNestedDropdown(event.target as HTMLElement)) {
event?.stopPropagation();
event.stopPropagation();
}

this.show = true;
this.showChanged.emit(true);
}
const { defaultPrevented } = this.showChanged.emit(true);

private close(event?: Event) {
if (event?.defaultPrevented) {
const target = event.target as HTMLElement;
if (
target.contains(this.anchorElement) ||
target.contains(this.triggerElement) ||
target.shadowRoot.contains(this.anchorElement) ||
target.shadowRoot.contains(this.triggerElement)
)
return;
if (!defaultPrevented) {
this.show = true;
}
}

private close() {
const { defaultPrevented } = this.showChanged.emit(false);

this.show = false;
this.showChanged.emit(false);
if (!defaultPrevented) {
this.show = false;
}
}

private async applyDropdownPosition() {
Expand Down Expand Up @@ -440,9 +446,7 @@ export class Dropdown {
}

async componentDidLoad() {
if (this.trigger) {
this.changedTrigger(this.trigger, null);
}
this.changedTrigger(this.trigger, null);
}

async componentDidRender() {
Expand All @@ -459,6 +463,16 @@ export class Dropdown {
}
}

private isClickInsideDropdown(event: PointerEvent) {
const rect = this.dropdownRef.getBoundingClientRect();
return (
rect.top <= event.clientY &&
event.clientY <= rect.top + rect.height &&
rect.left <= event.clientX &&
event.clientX <= rect.left + rect.width
);
}

disconnectedCallback() {
if (this.autoUpdateCleanup) {
this.autoUpdateCleanup();
Expand Down
101 changes: 101 additions & 0 deletions packages/core/src/components/dropdown/test/dropdown.ct.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/*
* SPDX-FileCopyrightText: 2023 Siemens AG
*
* SPDX-License-Identifier: MIT
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

/*
* SPDX-FileCopyrightText: 2023 Siemens AG
*
* SPDX-License-Identifier: MIT
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import { expect, Locator } from '@playwright/test';
import { test } from '@utils/test';

test('renders', async ({ mount, page }) => {
await mount(`
<ix-split-button label="Test 1">
<ix-dropdown-item>Test 1</ix-dropdown-item>
</ix-split-button>
<ix-split-button label="Test 2">
<ix-dropdown-item>Test 1</ix-dropdown-item>
</ix-split-button>
<ix-group header="Title" sub-header="Subtitle">
<ix-dropdown slot="dropdown">
<ix-dropdown-item label="Item 1" icon="pin" />
<ix-dropdown-item label="Item 2" icon="star" />
<ix-dropdown-item label="Item 3" icon="heart" />
<ix-dropdown-item label="Item 4" icon="cogwheel" />
</ix-dropdown>
</ix-group>
<ix-group header="Title" sub-header="Subtitle">
<ix-dropdown slot="dropdown">
<ix-dropdown-item label="Item 1" icon="pin" />
<ix-dropdown-item label="Item 2" icon="star" />
<ix-dropdown-item label="Item 3" icon="heart" />
<ix-dropdown-item label="Item 4" icon="cogwheel" />
</ix-dropdown>
</ix-group>
`);

const sb1 = page.locator('ix-split-button').nth(0);
const sb2 = page.locator('ix-split-button').nth(1);

const g1 = page.locator('ix-group').nth(0);
const g2 = page.locator('ix-group').nth(1);

const sb1Dropdown = sb1.locator('ix-dropdown');
const sb2Dropdown = sb2.locator('ix-dropdown');
const g1Dropdown = g1.locator('ix-dropdown');
const g2Dropdown = g2.locator('ix-dropdown');

await sb1
.getByRole('button')
.filter({ hasText: 'context-menu' })
.first()
.click();

await expectToBeVisible(
[sb1Dropdown, sb2Dropdown, g1Dropdown, g2Dropdown],
0
);

await sb2
.getByRole('button')
.filter({ hasText: 'context-menu' })
.first()
.click();

await expectToBeVisible(
[sb1Dropdown, sb2Dropdown, g1Dropdown, g2Dropdown],
1
);

await g2.getByRole('button').filter({ hasText: 'context-menu' }).click();

await expectToBeVisible(
[sb1Dropdown, sb2Dropdown, g1Dropdown, g2Dropdown],
3
);
});

function expectToBeVisible(elements: Locator[], index: number) {
return Promise.all(
elements.map(async (element, i) => {
let ef = expect(element);
if (i !== index) {
ef = ef.not;
}
await ef.toBeVisible();
})
);
}
47 changes: 47 additions & 0 deletions packages/core/src/components/pagination/test/pagination.ct.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
* SPDX-FileCopyrightText: 2023 Siemens AG
*
* SPDX-License-Identifier: MIT
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import { expect } from '@playwright/test';
import { test } from '@utils/test';

test('renders', async ({ mount, page }) => {
await mount(`
<ix-pagination>
</ix-pagination>
`);
const element = page.locator('ix-pagination');

await expect(element).toHaveClass(/hydrated/);
});

test('advanced', async ({ mount, page }) => {
await mount(`
<ix-pagination advanced>
</ix-pagination>
`);
const element = page.locator('ix-pagination[advanced]');

await expect(element).toHaveClass(/hydrated/);
});

test('open show number of page dropdown', async ({ mount, page }) => {
await mount(`
<ix-pagination advanced>
</ix-pagination>
`);
const element = page.locator('ix-pagination[advanced]');

await element
.getByRole('button')
.filter({ hasText: 'chevron-down-small' })
.click();

const dropdown = element.locator('ix-dropdown');

await expect(dropdown).toBeVisible();
});
Loading

0 comments on commit 03a3099

Please sign in to comment.