From bf6c3e7bb0b63e79d674a04b9b50c88209ba0b6f Mon Sep 17 00:00:00 2001 From: damyanpetev Date: Thu, 12 Nov 2026 17:07:40 +0200 Subject: [PATCH 1/2] fix(dom): return null from getRoot while disconnected Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 1 + src/components/highlight/service.ts | 17 ++--------------- src/components/popover/position/native.ts | 4 ++-- src/components/radio/controller.ts | 3 +-- src/components/stepper/stepper.ts | 2 +- src/components/virtualization/recycle.ts | 2 +- src/components/virtualization/spec.md | 4 +++- .../virtualization/virtualization.spec.ts | 19 +++++++++++++++++++ .../virtualization/virtualization.ts | 5 +++++ src/internals/bases/alert.ts | 4 ++-- src/internals/controllers/group.ts | 3 +-- src/internals/controllers/roving-focus.ts | 2 +- src/internals/directives/drag.ts | 2 +- src/internals/utils/dom.ts | 14 ++++++++------ 14 files changed, 48 insertions(+), 34 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ffa347f45..beebd876e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -422,6 +422,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - A list larger than the maximum scroll size of the browser shows its last items, and its items move evenly during a scroll. [#2410](https://github.com/IgniteUI/igniteui-webcomponents/pull/2410) - A new `data` array measures the new items at the rendered indices again. [#2412](https://github.com/IgniteUI/igniteui-webcomponents/pull/2412) - A `data` change in the same task as `scrollToIndex()` renders at the new offset. [#2545](https://github.com/IgniteUI/igniteui-webcomponents/pull/2545) + - A property change on a detached virtual scroll no longer throws an error. ### Security - #### Tile manager diff --git a/src/components/highlight/service.ts b/src/components/highlight/service.ts index d0c8a512a..82827ff80 100644 --- a/src/components/highlight/service.ts +++ b/src/components/highlight/service.ts @@ -10,20 +10,6 @@ import type IgcHighlightComponent from './highlight.js'; type Match = { node: Node; indices: [start: number, end: number] }; -/** - * Resolves the tree scope that must own the `::highlight()` rules. The rules are - * tree-scoped, so slotted content needs the host root node, not the render root. - * Firefox enforces this, Chromium currently does not. - * - * Returns `null` while the host is detached or inside a non-styleable fragment. - */ -function getStyleRoot( - host: IgcHighlightComponent -): (DocumentOrShadowRoot & Node) | null { - const root = getRoot(host); - return 'adoptedStyleSheets' in root ? root : null; -} - /* jsonAPIPlainObject */ /* marshalByValue */ /** @@ -271,7 +257,8 @@ class HighlightService implements ReactiveController { return; } - const root = getStyleRoot(this._host); + // `::highlight()` rules are tree-scoped (Firefox enforces it): slotted content needs the host's root. + const root = getRoot(this._host); if (this._attachedRoot !== root) { this._removeStyleSheet(); diff --git a/src/components/popover/position/native.ts b/src/components/popover/position/native.ts index 88e27edaa..3647df5e2 100644 --- a/src/components/popover/position/native.ts +++ b/src/components/popover/position/native.ts @@ -1,5 +1,5 @@ import { isServer } from 'lit'; -import { getRoot, isPopoverOpen } from '#internals/utils/dom.js'; +import { isPopoverOpen } from '#internals/utils/dom.js'; import { toggleEventListener } from '#internals/utils/events.js'; import { applyArrowStyles, @@ -106,7 +106,7 @@ export class NativePositionStrategy extends PopoverPositionStrategy { super.attach(target, container); container.toggleAttribute('data-anchored', true); - this._anchorObserver.observe(getRoot(target), { + this._anchorObserver.observe(target.getRootNode(), { childList: true, subtree: true, }); diff --git a/src/components/radio/controller.ts b/src/components/radio/controller.ts index b68928b9d..82ddd5ed1 100644 --- a/src/components/radio/controller.ts +++ b/src/components/radio/controller.ts @@ -2,7 +2,6 @@ import { createGroupRegistry, type GroupMemberController, } from '#internals/controllers/group.js'; -import { getRoot } from '#internals/utils/dom.js'; import type IgcRadioComponent from './radio.js'; /** @@ -14,7 +13,7 @@ import type IgcRadioComponent from './radio.js'; */ const radioGroups = createGroupRegistry({ keyOf: (radio) => radio.name || '', - scopeOf: (radio) => radio.form ?? getRoot(radio), + scopeOf: (radio) => radio.form ?? radio.getRootNode(), deriveState: (radios) => radios.some((radio) => radio.checked && !radio.disabled), }); diff --git a/src/components/stepper/stepper.ts b/src/components/stepper/stepper.ts index d8f9cf4c5..241e52536 100644 --- a/src/components/stepper/stepper.ts +++ b/src/components/stepper/stepper.ts @@ -350,7 +350,7 @@ export default class IgcStepperComponent extends EventEmitterMixin