stencil icon indicating copy to clipboard operation
stencil copied to clipboard

bug: Accessing an IonList's children property always results in an empty collection

Open ErikOnBike opened this issue 7 months ago • 12 comments

Prerequisites

Stencil Version

v4.36.1

Current Behavior

When an <ion-list> element is asked for its children, an empty collection is answered (in all situations). The internal property __children correctly recognises the children present.

Expected Behavior

The actual children (as present in the property __children) should be answered.

System Info


Steps to Reproduce

  • Go to IonList page of the Ionic components: https://ionicframework.com/docs/api/list
  • Open dev console
  • Enter: document.body.querySelector("ion-list").children
  • Result: empty collection
  • Enter document.body.querySelector("ion-list").__children
  • Result: non-empty collection

Code Reproduction URL

(see reproduction steps above)

Additional Information

I think the recent updates wrt patching components (https://github.com/stenciljs/core/issues/6349) incorrectly concludes that the IonList element has slots. The IonList has a render function on some template, but it does not contain an explicit slot element.

ErikOnBike avatar Aug 08 '25 12:08 ErikOnBike

hey @ErikOnBike - thanks for raising. Yeah - this is valid I think hasRenderFn was used as a proxy for 'component with internals that should not be exposed' which in this case doesn't work. Something like hasSlot (which doesn't currently exist) would be better.

johnjenkins avatar Aug 08 '25 20:08 johnjenkins

Probably related (and already present in earlier versions of Ionic), when executing the following code the childNodes is an empty Array. The __childNodes is a NodeList with a single item. The later is correct, the first not (I presume).

let label = document.createElement("ion-label");
label.textContent = "Hello world";
console.log(label.childNodes); // Empty Array
console.log(label.__childNodes); // NodeList with a Text Node

ErikOnBike avatar Sep 21 '25 14:09 ErikOnBike

Hey @ErikOnBike - hm this sounds like a different issue, ion label does have a slot - so this looks to be more of an issue with the patched textContent. Don’t suppose you’d mind raising another ticket?

johnjenkins avatar Sep 21 '25 16:09 johnjenkins

NP I'll add another issue.

ErikOnBike avatar Sep 22 '25 04:09 ErikOnBike

Thx for looking into this. @johnjenkins

ErikOnBike avatar Sep 22 '25 04:09 ErikOnBike

Hello @johnjenkins, what do you think should be done to fix this issue ?

I think some patches like insertBefore or appendChild need to be applied whether the component has a slot or not. So wrapping all patches under a hasSlot condition might break those methods. However, some patches like textContent or childNodes may not be needed if there is no slot ? (I don't know what is the expected behavior here).

I see on the components ion-input and ion-alert (both scoped) that the properties textContent and childNodes are empty. In my case, I am building custom elements using stencil and since v0.36.0 my tests with testing-library/jest-dom fail because of the empty textContent.

Thank you for your feedback

jsulpis avatar Nov 12 '25 14:11 jsulpis

hi @jsulpis! Can you say more re the use case around:

I think some patches like insertBefore or appendChild need to be applied whether the component has a slot or not.

The main / usual reason people use the ...SlotFix... patches is for use with frameworks; you slot some nodes in your vue template or react jsx and those frameworks (under-the-hood) use methods like insertBefore / appendChild to render the output. Without the patches, those nodes will either be dumped at the top of your component or throw an error.

Additionally, using something like Next / Nuxt - they compare SSRd html against client-side output using things like childNodes / children and when they find your component's internals will throw a resolution error.

I see on the components ion-input and ion-alert (both scoped) that the properties textContent and childNodes are empty....

Are you rendering them with properties e.g. <ion-input label="..."> ? If so, could you not test against the attribute or property? Alternatively, there's nothing stopping you querying the component internals (document.querySelector('ion-input .label-text')). This is analogous to if the component was rendered with a shadowRoot - you'd either have to do myCmp.shadowRoot.querySelector(...) or test against something else.

If you were to render content via slots instead (https://ionicframework.com/docs/api/input#label-slot-experimental) then you get the expected result.

Image

(from https://ionicframework.com/docs/usage/v8/input/label-slot/demo.html?ionic:mode=ios)

johnjenkins avatar Nov 12 '25 16:11 johnjenkins

My use case for the patches is exactly what you said, I generate Angular component wrappers and I need the patches to avoid errors in Angular. And that's why I think all non-shadow components need those patches.

I did not render the Ionic components, I inspected the DOM on the documentation. And I see that on components with shadow DOM, the textContent and childNodes properties are correct, so I think there is an issue with the patches on non-shadow components (at least those without a slot).

Image

jsulpis avatar Nov 12 '25 16:11 jsulpis

My use case for the patches is exactly what you said, I generate Angular component wrappers and I need the patches to avoid errors in Angular. And that's why I think all non-shadow components need those patches.

If there are no slots, it wouldn't be valid to render something within your component tags (i.e. to slot something) in Angular - so I don't think any patches would be necessary?

And I see that on components with shadow DOM, the textContent and childNodes properties are correct

Sorry, I'm confused - can you give me an example of a shadowRoot enabled component exposing component internals?

Your screenshot shows the desired output as far as I can see. The example is rendered with something like:

<ion-item label="Input with placeholder" placeholder="Enter company name"></ion-item>

Which in-turn renders internal markup including a label and wrappers etc; there are no public nodes or text content.

If this were rendered with a shadowRoot textContent and childNodes would be empty too.

In the example I posted previously, the example is generated like:

  <ion-input label-placement="floating" value="[email protected]">
    <div slot="label">Email <ion-text color="danger">(Required)</ion-text></div>
  </ion-input>

With this setup, the label is slotted so the element is part of the public DOM; textContent outputs the text of the slotted label

johnjenkins avatar Nov 12 '25 17:11 johnjenkins

Aah ok sorry I was wrong, I understand now. Indeed the patch of insertBefore and appendChild don't seem necessary if there is no slot, and I understand that it's the expected behavior to have empty textContent and childNodes on components that don't have a slot. It's a little ennoying when using testing-library because they encourage to test what users actually see (i.e. textContent), rather than attributes and properties. But I can understand that you want to mimic the shadow DOM behavior, so I will see if I can update my tests.

One last thing I don't understand though : I see that ion-list doesn't have a slot element in its template, but in the doc it receives a list of ion-item as children. Is this a correct behavior to pass children to a component without a slot ? What is the expected behavior then, in terms of patching (textContent, insertBefore etc) ?

jsulpis avatar Nov 13 '25 10:11 jsulpis

No problem - it's not obvious stuff :)

I'd say that component is - strictly speaking - implemented incorrectly. In reality though, there is no internal markup to 'protect' so patches aren't necessary - if it was patched correctly it should return the same result as not being patched

johnjenkins avatar Nov 13 '25 11:11 johnjenkins

OK and if I understand correctly the patch is applied on the ionList whereas is shouldn't. It is looking for a slotted content which doesn't exist, so the textContent and childNodes are incorrectly empty, hence this issue.

Everything seems clear now, thank you for your time and patience 🙏

jsulpis avatar Nov 13 '25 13:11 jsulpis

@johnjenkins thx for fixing this! ❤️

ErikOnBike avatar Dec 16 '25 07:12 ErikOnBike