Skip to content

Commit a22debe

Browse files
Protect connectedCallback of segment-button for when segment-content has not yet been created
1 parent b0c5555 commit a22debe

File tree

2 files changed

+81
-7
lines changed

2 files changed

+81
-7
lines changed

core/src/components/segment-button/segment-button.tsx

Lines changed: 52 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,51 @@ export class SegmentButton implements ComponentInterface, ButtonInterface {
6767
this.updateState();
6868
}
6969

70-
connectedCallback() {
70+
private getNextSiblingOfType<T extends Element>(element: Element): T | null {
71+
let sibling = element.nextSibling;
72+
while (sibling) {
73+
if (sibling.nodeType === Node.ELEMENT_NODE && (sibling as T) !== null) {
74+
return sibling as T;
75+
}
76+
sibling = sibling.nextSibling;
77+
}
78+
return null;
79+
}
80+
81+
private waitForSegmentContent(ionSegment: HTMLIonSegmentElement | null, contentId: string): Promise<HTMLElement> {
82+
return new Promise((resolve, reject) => {
83+
if (!ionSegment) {
84+
reject(new Error(`Segment not found when looking for Segment Content`));
85+
}
86+
87+
let timeoutId: any = null;
88+
let animationFrameId: number;
89+
90+
const check = () => {
91+
const segmentView = this.getNextSiblingOfType<HTMLIonSegmentViewElement>(ionSegment!); // Skip the text nodes
92+
const segmentContent = segmentView?.querySelector(
93+
`ion-segment-content[id="${contentId}"]`
94+
) as HTMLIonSegmentContentElement | null;
95+
if (segmentContent) {
96+
clearTimeout(timeoutId); // Clear the timeout if the segmentContent is found
97+
cancelAnimationFrame(animationFrameId);
98+
resolve(segmentContent);
99+
} else {
100+
animationFrameId = requestAnimationFrame(check); // Keep checking on the next animation frame
101+
}
102+
};
103+
104+
check();
105+
106+
// Set a timeout to reject the promise
107+
timeoutId = setTimeout(() => {
108+
cancelAnimationFrame(animationFrameId);
109+
reject(new Error(`Unable to find Segment Content with id="${contentId} within 1000 ms`));
110+
}, 1000);
111+
});
112+
}
113+
114+
async connectedCallback() {
71115
const segmentEl = (this.segmentEl = this.el.closest('ion-segment'));
72116
if (segmentEl) {
73117
this.updateState();
@@ -78,12 +122,13 @@ export class SegmentButton implements ComponentInterface, ButtonInterface {
78122
// Return if there is no contentId defined
79123
if (!this.contentId) return;
80124

81-
// Attempt to find the Segment Content by its contentId
82-
const segmentContent = document.getElementById(this.contentId) as HTMLIonSegmentContentElement | null;
83-
84-
// If no associated Segment Content exists, log an error and return
85-
if (!segmentContent) {
86-
console.error(`Segment Button: Unable to find Segment Content with id="${this.contentId}".`);
125+
let segmentContent;
126+
try {
127+
// Attempt to find the Segment Content by its contentId
128+
segmentContent = await this.waitForSegmentContent(segmentEl, this.contentId);
129+
} catch (error) {
130+
// If no associated Segment Content exists, log an error and return
131+
console.error('Segment Button: ', (error as Error).message);
87132
return;
88133
}
89134

core/src/components/segment-view/test/basic/index.html

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,8 @@
123123
<button class="expand" onClick="changeSegmentContent()">Change Segment Content</button>
124124

125125
<button class="expand" onClick="clearSegmentValue()">Clear Segment Value</button>
126+
127+
<button class="expand" onClick="addSegmentButtonAndContent()">Add New Segment Button & Content</button>
126128
</ion-content>
127129

128130
<ion-footer>
@@ -158,6 +160,33 @@
158160
segment.value = undefined;
159161
});
160162
}
163+
164+
async function addSegmentButtonAndContent() {
165+
const segment = document.querySelector('ion-segment');
166+
const segmentView = document.querySelector('ion-segment-view');
167+
168+
const newButton = document.createElement('ion-segment-button');
169+
newButton.setAttribute('content-id', 'new');
170+
newButton.setAttribute('value', 'new');
171+
newButton.innerHTML = '<ion-label>New Button</ion-label>';
172+
173+
segment.appendChild(newButton);
174+
175+
setTimeout(() => {
176+
// Timeout to test waitForSegmentContent() in segment-button
177+
const newContent = document.createElement('ion-segment-content');
178+
newContent.setAttribute('id', 'new');
179+
newContent.innerHTML = 'New Content';
180+
181+
segmentView.appendChild(newContent);
182+
183+
// Necessary timeout to ensure the value is set after the content is added.
184+
// Otherwise, the transition is unsuccessful and the content is not shown.
185+
setTimeout(() => {
186+
segment.setAttribute('value', 'new');
187+
}, 200);
188+
}, 200);
189+
}
161190
</script>
162191
</ion-app>
163192
</body>

0 commit comments

Comments
 (0)