Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit e8bb66c

Browse files
committed
fix(core): chrome scoped registry and popover behavior
- fixes issues due to chromium behavior changes for scoped registries - fixes issues due to chromium behavior changes for html popover elements Signed-off-by: Cory Rylan <[email protected]>
1 parent ac77441 commit e8bb66c

3 files changed

Lines changed: 79 additions & 1 deletion

File tree

projects/core/src/internal/decorators/scoped-registry.test.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@
22
// SPDX-License-Identifier: Apache-2.0
33

44
/* eslint-disable @typescript-eslint/no-unsafe-function-type */
5+
import { LitElement } from 'lit';
6+
import { html as staticHtml, unsafeStatic } from 'lit/static-html.js';
57
import { describe, expect, it, beforeEach, afterEach } from 'vitest';
8+
import { createFixture, removeFixture } from '@internals/testing';
69
import { GlobalStateService } from '../services/global.service.js';
710
import type { ElementDefinition } from '../types/index.js';
811
import { supportsScopedRegistry } from '../utils/dom.js';
@@ -87,4 +90,35 @@ describe('scopedRegistry', () => {
8790

8891
expect((element.shadowRootOptions as ShadowRootInit).mode).toBe('closed');
8992
});
93+
94+
it.skipIf(!supportsScopedRegistry)('should use the scoped shadow root as the Lit creation scope', async () => {
95+
const child = createMockElement();
96+
const childTag = unsafeStatic(child.metadata.tag);
97+
const hostTag = `nve-test-scoped-host-${uid}-${counter++}`;
98+
const host = unsafeStatic(hostTag);
99+
100+
class HostElement extends LitElement {
101+
static metadata = { version: '0.0.0', tag: hostTag };
102+
static elementDefinitions = { [child.metadata.tag]: child };
103+
104+
render() {
105+
return staticHtml`<${childTag}></${childTag}>`;
106+
}
107+
}
108+
109+
scopedRegistry()(HostElement as unknown as Function);
110+
customElements.define(hostTag, HostElement);
111+
112+
const fixture = await createFixture(staticHtml`<${host}></${host}>`);
113+
const element = fixture.querySelector<LitElement>(hostTag)!;
114+
115+
try {
116+
await element.updateComplete;
117+
118+
expect(typeof element.renderOptions.creationScope?.importNode).toBe('function');
119+
expect(element.shadowRoot!.querySelector(child.metadata.tag)).toBeInstanceOf(child);
120+
} finally {
121+
removeFixture(fixture);
122+
}
123+
});
90124
});

projects/core/src/internal/decorators/scoped-registry.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,50 @@
11
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
22
// SPDX-License-Identifier: Apache-2.0
33

4+
import type { RenderOptions } from 'lit';
45
import { GlobalStateService } from '../services/global.service.js';
56
import type { ElementDefinition, LegacyDecoratorTarget } from '../types/index.js';
67
import { defineElement, supportsScopedRegistry } from '../utils/dom.js';
78

9+
interface ScopedRegistryHost extends HTMLElement {
10+
createRenderRoot?: () => HTMLElement | DocumentFragment;
11+
renderOptions?: RenderOptions;
12+
}
13+
14+
const litCreationScopeElements = new WeakSet<ElementDefinition>();
15+
16+
/** Lit passes a legacy `deep` boolean, but scoped registries require `ImportNodeOptions`. https://html.spec.whatwg.org/multipage/custom-elements.html#scoped-custom-element-registries */
17+
function createScopedCreationScope(ownerDocument: Document, customElementRegistry: CustomElementRegistry) {
18+
return {
19+
importNode: (node: Node, deep = false) =>
20+
ownerDocument.importNode(node, {
21+
customElementRegistry,
22+
selfOnly: !deep
23+
})
24+
} satisfies NonNullable<RenderOptions['creationScope']>;
25+
}
26+
27+
function attachLitCreationScope(element: ElementDefinition, customElementRegistry: CustomElementRegistry) {
28+
if (litCreationScopeElements.has(element)) return;
29+
30+
const host = element.prototype as ScopedRegistryHost;
31+
const createRenderRoot = host.createRenderRoot;
32+
if (!createRenderRoot) return;
33+
34+
litCreationScopeElements.add(element);
35+
Object.defineProperty(host, 'createRenderRoot', {
36+
configurable: true,
37+
value(this: ScopedRegistryHost) {
38+
const renderRoot = createRenderRoot.call(this);
39+
if (renderRoot instanceof ShadowRoot) {
40+
this.renderOptions ??= {};
41+
this.renderOptions.creationScope = createScopedCreationScope(renderRoot.ownerDocument, customElementRegistry);
42+
}
43+
return renderRoot;
44+
}
45+
});
46+
}
47+
848
/** decorator which registers element dependencies with the scoped custom element registry when available */
949
export function scopedRegistry(): ClassDecorator {
1050
return (target: LegacyDecoratorTarget) => {
@@ -16,6 +56,7 @@ export function scopedRegistry(): ClassDecorator {
1656
configurable: true,
1757
value: { ...(element.shadowRootOptions ?? { mode: 'open' }), customElementRegistry }
1858
});
59+
attachLitCreationScope(element, customElementRegistry);
1960
}
2061
defineElement(element, customElementRegistry);
2162
};

projects/core/src/internal/utils/focus.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,10 @@ export function onListboxActivate(
8484
e.preventDefault();
8585
});
8686

87-
element.addEventListener('pointerup', (e: PointerEvent) => {
87+
// Chrome's LightDismissFromClick runs auto-popover light dismiss from click;
88+
// opening on pointerup can close immediately in the same gesture.
89+
// https://issues.chromium.org/issues/408010435
90+
element.addEventListener('click', (e: PointerEvent) => {
8891
e.preventDefault();
8992

9093
if (!element.disabled) {

0 commit comments

Comments
 (0)