diff --git a/.changeset/violet-ducks-refuse.md b/.changeset/violet-ducks-refuse.md new file mode 100644 index 00000000000..154e9f1750d --- /dev/null +++ b/.changeset/violet-ducks-refuse.md @@ -0,0 +1,5 @@ +--- +'@siemens/ix': patch +--- + +fix(core/group): remove max-width restriction diff --git a/.changeset/wicked-avocados-glow.md b/.changeset/wicked-avocados-glow.md new file mode 100644 index 00000000000..75b3e8856ca --- /dev/null +++ b/.changeset/wicked-avocados-glow.md @@ -0,0 +1,5 @@ +--- +'@siemens/ix': minor +--- + +feat(core/group): handle preventDefault for events diff --git a/packages/core/component-doc.json b/packages/core/component-doc.json index ad18af664df..4c763c577b8 100644 --- a/packages/core/component-doc.json +++ b/packages/core/component-doc.json @@ -7212,7 +7212,13 @@ "styles": [], "slots": [], "parts": [], - "listeners": [] + "listeners": [ + { + "event": "selectedChanged", + "capture": false, + "passive": false + } + ] }, { "dirPath": "src/components/group", diff --git a/packages/core/src/components/group/group.scss b/packages/core/src/components/group/group.scss index b33249d5a31..27eba82117a 100644 --- a/packages/core/src/components/group/group.scss +++ b/packages/core/src/components/group/group.scss @@ -18,7 +18,8 @@ display: flex; flex-direction: column; position: relative; - max-width: 19.75rem; + width: 19.75rem; + min-width: 12rem; border-color: var(--theme-group-item--border-color); .group-header { diff --git a/packages/core/src/components/group/group.tsx b/packages/core/src/components/group/group.tsx index a2ede2781e0..48a39d8b50c 100644 --- a/packages/core/src/components/group/group.tsx +++ b/packages/core/src/components/group/group.tsx @@ -14,8 +14,10 @@ import { EventEmitter, h, Host, + Listen, Prop, State, + Watch, } from '@stencil/core'; import { createMutationObserver } from '../utils/mutation-observer'; import { hasSlottedElements } from '../utils/shadow-dom'; @@ -87,6 +89,13 @@ export class Group { private observer: MutationObserver = null!; + @Watch('selected') + selectedChanged(newSelected: boolean) { + if (newSelected === false) { + this.changeItemIndex(); + } + } + get dropdownItems() { return Array.from( this.hostElement.querySelectorAll('ix-group-dropdown-item') @@ -104,38 +113,59 @@ export class Group { } private onExpandClick(event: Event) { + const oldCollapsed = this.collapsed; this.collapsed = !this.collapsed; - - this.collapsedChanged.emit(this.collapsed); + const { defaultPrevented } = this.collapsedChanged.emit(this.collapsed); event.stopPropagation(); + + if (defaultPrevented) { + this.collapsed = oldCollapsed; + } } private onHeaderClick(event: Event) { - this.setGroupSelection(!this.selected); - if (this.suppressHeaderSelection) { this.onExpandClick(event); + return; } + + this.changeHeaderSelection(!this.selected); + this.changeItemIndex(); } - private onItemClick(index?: number) { - const newIndex = index === this.index ? undefined : index; - this.selectItem.emit(newIndex); + private changeHeaderSelection(newSelection: boolean) { + const oldIsHeaderSelected = this.selected; + const newIsHeaderSelected = newSelection; + this.selected = newIsHeaderSelected; + const { defaultPrevented } = this.selectGroup.emit(newIsHeaderSelected); - this.index = newIndex; + if (defaultPrevented) { + this.selected = oldIsHeaderSelected; + return; + } + } - if (this.index !== undefined && this.index >= 0) { - this.itemSelected = true; - } else this.itemSelected = false; + private changeItemIndex(index?: number) { + const oldIndex = this.index; + const newIndex = index === this.index ? undefined : index; - this.setGroupSelection(false); - } + if (this.index === newIndex) { + return; + } - private setGroupSelection(selection: boolean) { - if (!this.suppressHeaderSelection) { - this.selected = selection; - this.selectGroup.emit(this.selected); + this.index = newIndex; + const { defaultPrevented } = this.selectItem.emit(newIndex); + if (defaultPrevented) { + this.index = oldIndex; + return; } + + const items = this.groupItems; + items.forEach((item, i) => { + item.selected = i === this.index; + }); + + this.itemSelected = items.some((item) => item.selected); } private onSlotChange() { @@ -150,12 +180,6 @@ export class Group { componentWillRender() { this.groupItems.forEach((item, index) => { - if (this.selected === true) { - item.selected = false; - this.index = undefined; - this.itemSelected = false; - return; - } item.selected = index === this.index; item.index = index; }); @@ -165,26 +189,12 @@ export class Group { this.observer = createMutationObserver(() => { this.slotSize = this.groupItems.length; }); - if (!this.groupContent) { return; } - this.observer.observe(this.groupContent, { childList: true, }); - - this.groupContent?.addEventListener( - 'selectedChanged', - (evt: CustomEvent) => { - if (evt.detail.suppressSelection) { - evt.stopPropagation(); - return; - } - - this.onItemClick(evt.detail.index); - } - ); } disconnectedCallback() { @@ -193,6 +203,15 @@ export class Group { } } + @Listen('selectedChanged') + onItemClicked(event: CustomEvent) { + if (event.target instanceof HTMLElement) { + const item = event.target as HTMLIxGroupItemElement; + const index = this.groupItems.indexOf(item); + this.changeItemIndex(index); + } + } + render() { return ( diff --git a/packages/core/src/components/group/test/group.ct.ts b/packages/core/src/components/group/test/group.ct.ts index d4a070d446d..80d32d810ff 100644 --- a/packages/core/src/components/group/test/group.ct.ts +++ b/packages/core/src/components/group/test/group.ct.ts @@ -82,7 +82,7 @@ test('suppress selection should not stop event propagation', async ({ await expect(groupItem).toHaveText('Item 1Clicked'); }); -test('prevent default', async ({ mount, page }) => { +test('item prevent default selection item event', async ({ mount, page }) => { await mount(` Item 1 @@ -97,9 +97,53 @@ test('prevent default', async ({ mount, page }) => { await expect(group).toHaveClass(/hydrated/); await group.evaluate((item) => { - item.addEventListener('selectedChanged', (e) => e.preventDefault()); + item.addEventListener('selectItem', (e) => e.preventDefault()); }); await groupItem.click(); - await expect(groupItem).not.toHaveClass('/hydrated selected'); + await expect(groupItem).not.toHaveClass(/hydrated selected/); +}); + +test('group header prevent default collapse/expand', async ({ + mount, + page, +}) => { + await mount(` + + Item 1 + Item 2 + + `); + const group = page.locator('ix-group'); + const expandIcon = group.getByTestId('expand-collapsed-icon'); + + await group.evaluate((item) => { + item.addEventListener('collapsedChanged', (e) => e.preventDefault()); + }); + + await expandIcon.click(); + + await expect(group).toHaveAttribute('collapsed'); +}); + +test('group header prevent default selection event', async ({ + mount, + page, +}) => { + await mount(` + + Item 1 + Item 2 + + `); + const group = page.locator('ix-group'); + const groupHeader = group.locator('.group-header'); + + await group.evaluate((item) => { + item.addEventListener('selectGroup', (e) => e.preventDefault()); + }); + + await groupHeader.click(); + + await expect(group).not.toHaveAttribute('selected'); }); diff --git a/packages/core/src/tests/group/adapt-width/index.html b/packages/core/src/tests/group/adapt-width/index.html new file mode 100644 index 00000000000..a0f46e5c7e9 --- /dev/null +++ b/packages/core/src/tests/group/adapt-width/index.html @@ -0,0 +1,74 @@ + + + + + + + Stencil Component Starter + + + + + + + + + + + + + + Long long long long long longLong long long long long longLong long long + long long longLong long long long long longLong long long long long long + + + + + + + + Long long long long long longLong long long long long longLong long long + long long longLong long long long long longLong long long long long long + + + + + + + + + diff --git a/packages/core/src/tests/group/group.e2e.ts b/packages/core/src/tests/group/group.e2e.ts index b5928b1ea0c..5bc28ebd462 100644 --- a/packages/core/src/tests/group/group.e2e.ts +++ b/packages/core/src/tests/group/group.e2e.ts @@ -38,10 +38,20 @@ regressionTest.describe('group', () => { regressionTest('item selected', async ({ page }) => { await page.goto('group/basic'); - await page.locator('.btn-expand-header ix-icon').click(); - await page.locator('text=Example text 1').first().click(); - await page.locator('text=Example text 2').first().hover(); + await page.getByTestId('expand-collapsed-icon').click(); + await page.locator('text=Example text 1').click(); + await page.locator('text=Example text 2').hover(); expect(await page.screenshot({ fullPage: true })).toMatchSnapshot(); }); + + regressionTest('adapt-width', async ({ page }) => { + await page.goto('group/adapt-width'); + const children = await page.locator('ix-group').all(); + for (const child of children) { + const groupExpand = child.getByTestId('expand-collapsed-icon'); + await groupExpand.click(); + } + expect(await page.screenshot({ fullPage: true })).toMatchSnapshot(); + }); }); diff --git a/packages/core/src/tests/group/group.e2e.ts-snapshots/group-adapt-width-1-chromium---theme-classic-dark-linux.png b/packages/core/src/tests/group/group.e2e.ts-snapshots/group-adapt-width-1-chromium---theme-classic-dark-linux.png new file mode 100644 index 00000000000..f8a8d49c6dd Binary files /dev/null and b/packages/core/src/tests/group/group.e2e.ts-snapshots/group-adapt-width-1-chromium---theme-classic-dark-linux.png differ diff --git a/packages/core/src/tests/group/group.e2e.ts-snapshots/group-adapt-width-1-chromium---theme-classic-light-linux.png b/packages/core/src/tests/group/group.e2e.ts-snapshots/group-adapt-width-1-chromium---theme-classic-light-linux.png new file mode 100644 index 00000000000..660b59bc5ea Binary files /dev/null and b/packages/core/src/tests/group/group.e2e.ts-snapshots/group-adapt-width-1-chromium---theme-classic-light-linux.png differ