From a22debe79987f128f7010f4bbcf881291734e036 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Louren=C3=A7o?= Date: Tue, 14 Jan 2025 13:37:02 +0000 Subject: [PATCH 1/3] Protect connectedCallback of segment-button for when segment-content has not yet been created --- .../segment-button/segment-button.tsx | 59 ++++++++++++++++--- .../segment-view/test/basic/index.html | 29 +++++++++ 2 files changed, 81 insertions(+), 7 deletions(-) diff --git a/core/src/components/segment-button/segment-button.tsx b/core/src/components/segment-button/segment-button.tsx index e670643066c..078f14fb3b4 100644 --- a/core/src/components/segment-button/segment-button.tsx +++ b/core/src/components/segment-button/segment-button.tsx @@ -67,7 +67,51 @@ export class SegmentButton implements ComponentInterface, ButtonInterface { this.updateState(); } - connectedCallback() { + private getNextSiblingOfType(element: Element): T | null { + let sibling = element.nextSibling; + while (sibling) { + if (sibling.nodeType === Node.ELEMENT_NODE && (sibling as T) !== null) { + return sibling as T; + } + sibling = sibling.nextSibling; + } + return null; + } + + private waitForSegmentContent(ionSegment: HTMLIonSegmentElement | null, contentId: string): Promise { + return new Promise((resolve, reject) => { + if (!ionSegment) { + reject(new Error(`Segment not found when looking for Segment Content`)); + } + + let timeoutId: any = null; + let animationFrameId: number; + + const check = () => { + const segmentView = this.getNextSiblingOfType(ionSegment!); // Skip the text nodes + const segmentContent = segmentView?.querySelector( + `ion-segment-content[id="${contentId}"]` + ) as HTMLIonSegmentContentElement | null; + if (segmentContent) { + clearTimeout(timeoutId); // Clear the timeout if the segmentContent is found + cancelAnimationFrame(animationFrameId); + resolve(segmentContent); + } else { + animationFrameId = requestAnimationFrame(check); // Keep checking on the next animation frame + } + }; + + check(); + + // Set a timeout to reject the promise + timeoutId = setTimeout(() => { + cancelAnimationFrame(animationFrameId); + reject(new Error(`Unable to find Segment Content with id="${contentId} within 1000 ms`)); + }, 1000); + }); + } + + async connectedCallback() { const segmentEl = (this.segmentEl = this.el.closest('ion-segment')); if (segmentEl) { this.updateState(); @@ -78,12 +122,13 @@ export class SegmentButton implements ComponentInterface, ButtonInterface { // Return if there is no contentId defined if (!this.contentId) return; - // Attempt to find the Segment Content by its contentId - const segmentContent = document.getElementById(this.contentId) as HTMLIonSegmentContentElement | null; - - // If no associated Segment Content exists, log an error and return - if (!segmentContent) { - console.error(`Segment Button: Unable to find Segment Content with id="${this.contentId}".`); + let segmentContent; + try { + // Attempt to find the Segment Content by its contentId + segmentContent = await this.waitForSegmentContent(segmentEl, this.contentId); + } catch (error) { + // If no associated Segment Content exists, log an error and return + console.error('Segment Button: ', (error as Error).message); return; } diff --git a/core/src/components/segment-view/test/basic/index.html b/core/src/components/segment-view/test/basic/index.html index 69d36d4a6c0..faf8eb8d1ec 100644 --- a/core/src/components/segment-view/test/basic/index.html +++ b/core/src/components/segment-view/test/basic/index.html @@ -123,6 +123,8 @@ + + @@ -158,6 +160,33 @@ segment.value = undefined; }); } + + async function addSegmentButtonAndContent() { + const segment = document.querySelector('ion-segment'); + const segmentView = document.querySelector('ion-segment-view'); + + const newButton = document.createElement('ion-segment-button'); + newButton.setAttribute('content-id', 'new'); + newButton.setAttribute('value', 'new'); + newButton.innerHTML = 'New Button'; + + segment.appendChild(newButton); + + setTimeout(() => { + // Timeout to test waitForSegmentContent() in segment-button + const newContent = document.createElement('ion-segment-content'); + newContent.setAttribute('id', 'new'); + newContent.innerHTML = 'New Content'; + + segmentView.appendChild(newContent); + + // Necessary timeout to ensure the value is set after the content is added. + // Otherwise, the transition is unsuccessful and the content is not shown. + setTimeout(() => { + segment.setAttribute('value', 'new'); + }, 200); + }, 200); + } From 2acc6337336ff79b0a759fb28c3edc14da17cdb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Louren=C3=A7o?= Date: Tue, 14 Jan 2025 17:03:03 +0000 Subject: [PATCH 2/3] Adding support for several new buttons in testing page --- core/src/components/segment-view/test/basic/index.html | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/core/src/components/segment-view/test/basic/index.html b/core/src/components/segment-view/test/basic/index.html index faf8eb8d1ec..78dec1d9ff9 100644 --- a/core/src/components/segment-view/test/basic/index.html +++ b/core/src/components/segment-view/test/basic/index.html @@ -166,8 +166,9 @@ const segmentView = document.querySelector('ion-segment-view'); const newButton = document.createElement('ion-segment-button'); - newButton.setAttribute('content-id', 'new'); - newButton.setAttribute('value', 'new'); + const newId = `new-${Date.now()}`; + newButton.setAttribute('content-id', newId); + newButton.setAttribute('value', newId); newButton.innerHTML = 'New Button'; segment.appendChild(newButton); @@ -175,7 +176,7 @@ setTimeout(() => { // Timeout to test waitForSegmentContent() in segment-button const newContent = document.createElement('ion-segment-content'); - newContent.setAttribute('id', 'new'); + newContent.setAttribute('id', newId); newContent.innerHTML = 'New Content'; segmentView.appendChild(newContent); @@ -183,7 +184,7 @@ // Necessary timeout to ensure the value is set after the content is added. // Otherwise, the transition is unsuccessful and the content is not shown. setTimeout(() => { - segment.setAttribute('value', 'new'); + segment.setAttribute('value', newId); }, 200); }, 200); } From 010908bfffcb23ffa2d120c223ad92c19ce17a13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Louren=C3=A7o?= Date: Wed, 15 Jan 2025 12:11:35 +0000 Subject: [PATCH 3/3] CR --- .../components/segment-button/segment-button.tsx | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/core/src/components/segment-button/segment-button.tsx b/core/src/components/segment-button/segment-button.tsx index 078f14fb3b4..d4a5d74266d 100644 --- a/core/src/components/segment-button/segment-button.tsx +++ b/core/src/components/segment-button/segment-button.tsx @@ -80,15 +80,16 @@ export class SegmentButton implements ComponentInterface, ButtonInterface { private waitForSegmentContent(ionSegment: HTMLIonSegmentElement | null, contentId: string): Promise { return new Promise((resolve, reject) => { - if (!ionSegment) { - reject(new Error(`Segment not found when looking for Segment Content`)); - } - - let timeoutId: any = null; + let timeoutId: NodeJS.Timeout | undefined = undefined; let animationFrameId: number; const check = () => { - const segmentView = this.getNextSiblingOfType(ionSegment!); // Skip the text nodes + if (!ionSegment) { + reject(new Error(`Segment not found when looking for Segment Content`)); + return; + } + + const segmentView = this.getNextSiblingOfType(ionSegment); // Skip the text nodes const segmentContent = segmentView?.querySelector( `ion-segment-content[id="${contentId}"]` ) as HTMLIonSegmentContentElement | null;