Skip to content

Commit be2cad5

Browse files
committed
fix(tabs): Internal DOM reference checks in observers
Check for the existence of the tab header strip and indicator, if applicable, before trying to manipulate DOM state inside the observers callbacks.
1 parent cad4a59 commit be2cad5

File tree

1 file changed

+53
-43
lines changed

1 file changed

+53
-43
lines changed

src/components/tabs/tab-dom.ts

Lines changed: 53 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -22,15 +22,15 @@ class TabsHelpers {
2222
/**
2323
* Returns the DOM container holding the tabs headers.
2424
*/
25-
public get container(): HTMLElement {
26-
return this._container.value!;
25+
public get container(): HTMLElement | undefined {
26+
return this._container.value;
2727
}
2828

2929
/**
3030
* Returns the selected indicator DOM element.
3131
*/
32-
public get indicator(): HTMLElement {
33-
return this._indicator.value!;
32+
public get indicator(): HTMLElement | undefined {
33+
return this._indicator.value;
3434
}
3535

3636
/**
@@ -82,7 +82,9 @@ class TabsHelpers {
8282
public setStyleProperties(): void {
8383
this._styleProperties = {
8484
'--_tabs-count': this._host.tabs.length.toString(),
85-
'--_ig-tabs-width': `${this.container.getBoundingClientRect().width}px`,
85+
'--_ig-tabs-width': this.container
86+
? `${this.container.getBoundingClientRect().width}px`
87+
: '',
8688
};
8789
this._host.requestUpdate();
8890
}
@@ -91,66 +93,74 @@ class TabsHelpers {
9193
* Sets the type of the `scroll-snap-align` CSS property for the tabs header strip.
9294
*/
9395
public setScrollSnap(type?: 'start' | 'end'): void {
94-
this.container.style.setProperty('--_ig-tab-snap', type || 'unset');
96+
if (this.container) {
97+
this.container.style.setProperty('--_ig-tab-snap', type || 'unset');
98+
}
9599
}
96100

97101
/**
98102
* Scrolls the tabs header strip in the given direction with `scroll-snap-align` set.
99103
*/
100104
public scrollTabs(direction: 'start' | 'end'): void {
101-
const factor = isLTR(this._host) ? 1 : -1;
102-
const amount =
103-
direction === 'start'
104-
? -TabsHelpers.SCROLL_AMOUNT
105-
: TabsHelpers.SCROLL_AMOUNT;
106-
107-
this.setScrollSnap(direction);
108-
this.container.scrollBy({ left: factor * amount, behavior: 'smooth' });
105+
if (this.container) {
106+
const factor = isLTR(this._host) ? 1 : -1;
107+
const amount =
108+
direction === 'start'
109+
? -TabsHelpers.SCROLL_AMOUNT
110+
: TabsHelpers.SCROLL_AMOUNT;
111+
112+
this.setScrollSnap(direction);
113+
this.container.scrollBy({ left: factor * amount, behavior: 'smooth' });
114+
}
109115
}
110116

111117
/**
112118
* Updates the state of the tabs header strip scroll buttons - visibility and active state.
113119
* Triggers an update cycle (rerender) of the `igc-tabs` component.
114120
*/
115121
public setScrollButtonState(): void {
116-
const { scrollLeft, scrollWidth, clientWidth } = this.container;
122+
if (this.container) {
123+
const { scrollLeft, scrollWidth, clientWidth } = this.container;
117124

118-
this._hasScrollButtons = scrollWidth > clientWidth;
119-
this._scrollButtonsDisabled = {
120-
start: scrollLeft === 0,
121-
end: Math.abs(Math.abs(scrollLeft) + clientWidth - scrollWidth) <= 1,
122-
};
125+
this._hasScrollButtons = scrollWidth > clientWidth;
126+
this._scrollButtonsDisabled = {
127+
start: scrollLeft === 0,
128+
end: Math.abs(Math.abs(scrollLeft) + clientWidth - scrollWidth) <= 1,
129+
};
123130

124-
this._host.requestUpdate();
131+
this._host.requestUpdate();
132+
}
125133
}
126134

127135
/**
128136
* Updates the indicator DOM element styles based on the current "active" tab.
129137
*/
130138
public async setIndicator(active?: IgcTabComponent): Promise<void> {
131-
const styles = {
132-
visibility: active ? 'visible' : 'hidden',
133-
} satisfies Partial<CSSStyleDeclaration>;
134-
135-
await this._host.updateComplete;
136-
137-
if (active) {
138-
const tabHeader = getTabHeader(active);
139-
const { width } = tabHeader.getBoundingClientRect();
140-
141-
const offset = this._isLeftToRight
142-
? tabHeader.offsetLeft - this.container.offsetLeft
143-
: width +
144-
tabHeader.offsetLeft -
145-
this.container.getBoundingClientRect().width;
146-
147-
Object.assign(styles, {
148-
width: `${width}px`,
149-
transform: `translateX(${offset}px)`,
150-
});
139+
if (this.container && this.indicator) {
140+
const styles = {
141+
visibility: active ? 'visible' : 'hidden',
142+
} satisfies Partial<CSSStyleDeclaration>;
143+
144+
await this._host.updateComplete;
145+
146+
if (active) {
147+
const tabHeader = getTabHeader(active);
148+
const { width } = tabHeader.getBoundingClientRect();
149+
150+
const offset = this._isLeftToRight
151+
? tabHeader.offsetLeft - this.container.offsetLeft
152+
: width +
153+
tabHeader.offsetLeft -
154+
this.container.getBoundingClientRect().width;
155+
156+
Object.assign(styles, {
157+
width: `${width}px`,
158+
transform: `translateX(${offset}px)`,
159+
});
160+
}
161+
162+
Object.assign(this.indicator.style, styles);
151163
}
152-
153-
Object.assign(this.indicator.style, styles);
154164
}
155165
}
156166

0 commit comments

Comments
 (0)