Skip to content

Commit d44888c

Browse files
authored
fix(tabs): Nested tabs selection behavior (#1459)
1 parent 2724d89 commit d44888c

File tree

6 files changed

+75
-13
lines changed

6 files changed

+75
-13
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
88
### Added
99
- Carousel component select method overload accepting index [#1457](https://github.com/IgniteUI/igniteui-webcomponents/issues/1457)
1010

11+
### Fixed
12+
- Tabs - nested tabs selection [#713](https://github.com/IgniteUI/igniteui-webcomponents/issues/713)
13+
1114
## [5.1.1] - 2024-10-28
1215
### Fixed
1316
- Library - internal import path for styles and public exports for themes

src/components/button-group/button-group.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { watch } from '../common/decorators/watch.js';
1010
import { registerComponent } from '../common/definitions/register.js';
1111
import type { Constructor } from '../common/mixins/constructor.js';
1212
import { EventEmitterMixin } from '../common/mixins/event-emitter.js';
13-
import { findElementFromEventPath } from '../common/util.js';
13+
import { findElementFromEventPath, last } from '../common/util.js';
1414
import { styles } from './themes/group.base.css.js';
1515
import { all } from './themes/group.js';
1616
import { styles as shared } from './themes/shared/group/group.common.css.js';
@@ -62,7 +62,7 @@ export default class IgcButtonGroupComponent extends EventEmitterMixin<
6262

6363
const buttons = this.toggleButtons;
6464
const idx = buttons.indexOf(
65-
added.length ? added.at(-1)! : attributes.at(-1)!
65+
added.length ? last(added).node : last(attributes)
6666
);
6767

6868
for (const [i, button] of buttons.entries()) {

src/components/carousel/carousel.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -160,8 +160,9 @@ export default class IgcCarouselComponent extends EventEmitterMixin<
160160
if (activeSlides.length <= 1) {
161161
return;
162162
}
163-
164-
const idx = this.slides.indexOf(last(added.length ? added : attributes));
163+
const idx = this.slides.indexOf(
164+
added.length ? last(added).node : last(attributes)
165+
);
165166

166167
for (const [i, slide] of this.slides.entries()) {
167168
if (slide.active && i !== idx) {

src/components/common/controllers/mutation-observer.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,14 +29,15 @@ type MutationControllerCallback<T> = (
2929
* an array of selector strings or a predicate function.
3030
*/
3131
type MutationControllerFilter<T> = string[] | ((node: T) => boolean);
32+
type MutationDOMChange<T> = { target: Element; node: T };
3233

3334
type MutationChange<T> = {
3435
/** Elements that have attribute(s) changes. */
3536
attributes: T[];
3637
/** Elements that have been added. */
37-
added: T[];
38+
added: MutationDOMChange<T>[];
3839
/** Elements that have been removed. */
39-
removed: T[];
40+
removed: MutationDOMChange<T>[];
4041
};
4142

4243
export type MutationControllerParams<T> = {
@@ -110,10 +111,14 @@ class MutationController<T> implements ReactiveController {
110111
);
111112
} else if (record.type === 'childList') {
112113
changes.added.push(
113-
...mutationFilter(Array.from(record.addedNodes) as T[], filter)
114+
...mutationFilter(Array.from(record.addedNodes) as T[], filter).map(
115+
(node) => ({ target: record.target as Element, node })
116+
)
114117
);
115118
changes.removed.push(
116-
...mutationFilter(Array.from(record.removedNodes) as T[], filter)
119+
...mutationFilter(Array.from(record.removedNodes) as T[], filter).map(
120+
(node) => ({ target: record.target as Element, node })
121+
)
117122
);
118123
}
119124
}

src/components/tabs/tabs.spec.ts

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,17 @@ import {
2323
homeKey,
2424
spaceBar,
2525
} from '../common/controllers/key-bindings.js';
26+
import { first, last } from '../common/util.js';
2627
import { simulateClick, simulateKeyboard } from '../common/utils.spec.js';
2728

2829
describe('Tabs component', () => {
2930
// Helper functions
3031
const getTabs = (tabs: IgcTabsComponent) =>
31-
Array.from(tabs.querySelectorAll(IgcTabComponent.tagName));
32+
Array.from(
33+
tabs.querySelectorAll<IgcTabComponent>(
34+
`:scope > ${IgcTabComponent.tagName}`
35+
)
36+
);
3237

3338
const getPanels = (tabs: IgcTabsComponent) =>
3439
Array.from(tabs.querySelectorAll(IgcTabPanelComponent.tagName));
@@ -670,4 +675,42 @@ describe('Tabs component', () => {
670675
expect(() => tabs.appendChild(tab)).not.to.throw();
671676
});
672677
});
678+
679+
describe('issue-713', () => {
680+
it('Nested tabs selection', async () => {
681+
const tabs = await fixture<IgcTabsComponent>(html`
682+
<igc-tabs>
683+
<igc-tab>1</igc-tab>
684+
<igc-tab>2</igc-tab>
685+
<igc-tab-panel>
686+
Panel 1
687+
<igc-tabs>
688+
<igc-tab>1.1</igc-tab>
689+
<igc-tab selected>1.2</igc-tab>
690+
<igc-tab-panel>Panel 1.1</igc-tab-panel>
691+
<igc-tab-panel>Panel 1.2</igc-tab-panel>
692+
</igc-tabs>
693+
</igc-tab-panel>
694+
<igc-tab-panel>Panel 2</igc-tab-panel>
695+
</igc-tabs>
696+
`);
697+
698+
const nestedTabs = tabs.querySelector(IgcTabsComponent.tagName)!;
699+
700+
expect(getSelectedTab(tabs).textContent).to.equal('1');
701+
expect(getSelectedTab(nestedTabs).textContent).to.equal('1.2');
702+
703+
simulateClick(first(getTabs(nestedTabs)));
704+
await elementUpdated(tabs);
705+
706+
expect(getSelectedTab(tabs).textContent).to.equal('1');
707+
expect(getSelectedTab(nestedTabs).textContent).to.equal('1.1');
708+
709+
simulateClick(last(getTabs(tabs)));
710+
await elementUpdated(tabs);
711+
712+
expect(getSelectedTab(tabs).textContent).to.equal('2');
713+
expect(getSelectedTab(nestedTabs).textContent).to.equal('1.1');
714+
});
715+
});
673716
});

src/components/tabs/tabs.ts

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -121,17 +121,27 @@ export default class IgcTabsComponent extends EventEmitterMixin<
121121
private _mutationCallback({
122122
changes: { attributes, added, removed },
123123
}: MutationControllerParams<IgcTabComponent>) {
124-
this.setSelectedTab(attributes.find((tab) => tab.selected));
124+
const ownAttributes = attributes.filter(
125+
(tab) => tab.closest(this.tagName) === this
126+
);
127+
const ownAdded = added.filter(
128+
({ target }) => target.closest(this.tagName) === this
129+
);
130+
const ownRemoved = removed.filter(
131+
({ target }) => target.closest(this.tagName) === this
132+
);
133+
134+
this.setSelectedTab(ownAttributes.find((tab) => tab.selected));
125135

126-
if (removed.length || added.length) {
127-
for (const tab of removed) {
136+
if (ownRemoved.length || ownAdded.length) {
137+
for (const { node: tab } of ownRemoved) {
128138
this.resizeObserver?.unobserve(tab);
129139
if (tab.selected && tab === this.activeTab) {
130140
this.activeTab = undefined;
131141
}
132142
}
133143

134-
for (const tab of added) {
144+
for (const { node: tab } of ownAdded) {
135145
this.resizeObserver?.observe(tab);
136146
if (tab.selected) {
137147
this.setSelectedTab(tab);

0 commit comments

Comments
 (0)