Skip to content

ROU-12937: Fix Carousel removeChild NotFoundError on data refresh - #1185

Merged
gnbm merged 9 commits into
devfrom
ROU-12937
Jul 29, 2026
Merged

ROU-12937: Fix Carousel removeChild NotFoundError on data refresh#1185
gnbm merged 9 commits into
devfrom
ROU-12937

Conversation

@gnbm

@gnbm gnbm commented Jul 28, 2026

Copy link
Copy Markdown
Collaborator

What was happening

On O11 and ODC, a Carousel with bare <img> slides crashed with NotFoundError: Failed to execute 'removeChild' on 'Node' (OS-CLRT-60500) when a ParameterOnChange action re-executed an aggregate feeding the carousel (and an IF around it).

Regressed in v2.29.0 by commit c30702391 (PR #1138, ROU-12567): _prepareCarouselItems() started reparenting bare <img> slides into a newly created wrapper <div>:

if (item.tagName === 'IMG') {
    const wrapper = document.createElement(...Div);
    item.replaceWith(wrapper);   // moves a platform-owned node
    wrapper.appendChild(item);
    wrapper.classList.add(Enum.CssClass.SplideSlide);
}

The <img> is a platform-owned node. On O11 and ODC, the runtime is React; moving it invalidates React's fiber record of the node's parent, so the next reconcile (triggered by the aggregate refresh) calls parent.removeChild(img) against a parent that no longer contains it → throw. Every provider redraw() re-ran this and re-wrapped the fresh nodes, making it self-sustaining.

What was done

Stop reparenting and keep the ARIA roles valid without moving any DOM — 3 changes in Splide.ts:

  1. _prepareCarouselItems() — restored the pre-regression class-add loop: every item (incl. <img>) just gets the splide__slide class; no node is created or moved. Kept the Array.from snapshot.
  2. _applyListRoles() — skip list semantics when the list has bare <img> slides: a role="list" whose children aren't list items is invalid ARIA, and an image can't take role="listitem" without losing its image semantics. Also drops a role="list" left from a previous pass when content changes across platform refreshes. The images stay exposed to screen readers via their alt text.
  3. _setListRoles() — strip the role Splide assigns to a bare <img> slide: role="presentation" is valid on an image but removes it from the accessibility tree, so stripping it keeps the image announced via its alt text. Also strip role="tabpanel" from slides that are native list elements (ul/ol/li), via a tagName check.

PR #1138's other improvements (WCAG pagination target size, list/listitem roles for non-img slides, root role="presentation") are preserved untouched.

Test Steps

  1. Open the sample page
  2. Go to the "Test Carousel OnParameterChanged" section.
  3. Toggle the Test_Carousel_OnParameterChanged switch a few times (re-runs OnParametersChanged).
  4. Before fix: NotFoundError … removeChild in the console. After fix: no error; carousel keeps rendering.

Screenshots

N.A.

Checklist

  • tested locally
  • documented the code
  • clean all warnings and errors of eslint
  • requires changes in OutSystems (if so, provide a module with changes)
  • requires new sample page in OutSystems (if so, provide a module with changes)

Stop reparenting bare <img> carousel slides into a wrapper <div> in
_prepareCarouselItems(). The <img> is a platform-owned node; on ODC (React
runtime) moving it invalidates React's fiber parent record, so a
ParameterOnChange aggregate refresh crashes the reconcile with
'NotFoundError: Failed to execute removeChild on Node' (OS-CLRT-60500).
Regressed in v2.29.0 by c307023 (PR #1138).

Restore the pre-regression class-add loop (no node creation/movement) and
preserve PR #1138's a11y intent non-destructively: strip invalid ARIA roles
from <img> slides at the attribute level in _applyListRoles/_setListRoles
instead of moving them into a container.
@gnbm
gnbm requested a review from a team as a code owner July 28, 2026 13:39
@gnbm gnbm added the bug Something isn't working label Jul 28, 2026
@gnbm gnbm changed the title ROU-12937 Fix Carousel removeChild NotFoundError on data refresh ROU-12937: Fix Carousel removeChild NotFoundError on data refresh Jul 28, 2026
@gnbm
gnbm marked this pull request as draft July 28, 2026 16:07
Comment thread src/scripts/Providers/OSUI/Carousel/Splide/Splide.ts Outdated
Comment thread src/scripts/Providers/OSUI/Carousel/Splide/Splide.ts Outdated
Comment thread src/scripts/Providers/OSUI/Carousel/Splide/Splide.ts Outdated
@gnbm
gnbm marked this pull request as ready for review July 28, 2026 17:31
- Skip list semantics when the carousel has bare <img> slides: images can't
  take role="listitem" without replacing their image semantics, and a
  role="list" without list items is invalid. Also drop a list role applied
  on a previous pass when content changes across platform refreshes.
- Correct misleading comments: role="presentation" is valid on an image but
  removes it from the accessibility tree; roles are stripped so images stay
  announced via alt text.
- Strip role="tabpanel" from slides that are native list elements (ul/ol/li)
  via tagName check instead of the descendant query — also fixes bare <li>
  slides that previously kept tabpanel.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes a regression in the Carousel Splide provider where wrapping “bare” <img> slides caused React-driven platforms (O11/ODC) to throw NotFoundError: Failed to execute 'removeChild' on 'Node' during data refresh/reconcile, by ensuring no platform-owned nodes are reparented and by adjusting ARIA role handling for image-only slides.

Changes:

  • Stop reparenting slide DOM nodes in _prepareCarouselItems() (class-only mutation; no wrapper insertion).
  • Skip applying role="list" / role="listitem" when the Splide list contains bare <img> slides, and remove a previously-applied role="list" if content changes to images.
  • Strip provider-assigned roles from bare <img> slides to keep image semantics exposed to assistive tech; remove role="tabpanel" only for native list elements (ul/ol/li) where list semantics should remain implicit.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@gnbm
gnbm requested a review from joselrio July 28, 2026 20:33
Comment thread src/scripts/Providers/OSUI/Carousel/Splide/Splide.ts Outdated
Comment thread src/scripts/Providers/OSUI/Carousel/Splide/Splide.ts Outdated
Comment thread src/scripts/Providers/OSUI/Carousel/Splide/Splide.ts Outdated
Comment thread src/scripts/Providers/OSUI/Carousel/Splide/Splide.ts Outdated
Comment thread src/scripts/Providers/OSUI/Carousel/Splide/Splide.ts Outdated
Comment thread src/scripts/Providers/OSUI/Carousel/Splide/Splide.ts Outdated
Comment thread src/scripts/Providers/OSUI/Carousel/Splide/Splide.ts Outdated
Comment thread src/scripts/Providers/OSUI/Carousel/Splide/Splide.ts
…ousel

Address review feedback: replace raw DOM calls with the framework helpers for
consistency.
- _applyListRoles: Attribute.Get/Set/Remove with A11YAttributes.Role
  constants (AttrName/List/Listitem); detect native-list and image slides by
  reading direct children and checking tagName (kept direct-child-only so
  wrapped images still receive list roles).
- _prepareCarouselItems: Styles.ContainsClass / Styles.AddClass instead of
  classList.
…ector

Revert the Array.from(children).some() refactor: keep _hasImageSlides as a
concise direct-child querySelector (consistent with the adjacent
_hasNativeListChildren, which is now unchanged from 2.29.0) and restore the
querySelectorAll(':scope > *') loop. Keeps the Helper.Dom.Attribute/Styles +
Role-constant conversions from the review.
@gnbm
gnbm requested a review from joselrio July 29, 2026 11:26
gnbm added 4 commits July 29, 2026 12:35
…ector

Use Helper.Dom.TagSelector / TagSelectorAll (the framework querySelector
wrapper, used across the codebase with compound selectors) instead of raw
querySelector in _applyListRoles. The ':scope >' selectors keep the direct-child
semantics (a wrapped image must still receive list roles), so _applyListRoles is
now free of raw DOM queries.
@sonarqubecloud

Copy link
Copy Markdown

@gnbm
gnbm merged commit 227fc77 into dev Jul 29, 2026
14 of 19 checks passed
@gnbm
gnbm deleted the ROU-12937 branch July 29, 2026 14:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants