Skip to content

Commit 56fe6e3

Browse files
mpesicMickey Pesic
andauthored
feat(moc-doc): serialize delegatesFocus shadow DOM property (#6333)
Co-authored-by: Mickey Pesic <[email protected]>
1 parent 2f363dd commit 56fe6e3

File tree

7 files changed

+66
-0
lines changed

7 files changed

+66
-0
lines changed

src/mock-doc/node.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,7 @@ export class MockElement extends MockNode {
272272

273273
attachShadow(_opts: ShadowRootInit) {
274274
const shadowRoot = this.ownerDocument.createDocumentFragment();
275+
shadowRoot.delegatesFocus = _opts.delegatesFocus ?? false;
275276
this.shadowRoot = shadowRoot;
276277
return shadowRoot;
277278
}

src/mock-doc/serialize-node.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,12 @@ function* streamToHtml(
142142
const mode = ` shadowrootmode="open"`;
143143
yield mode;
144144
output.currentLineWidth += mode.length;
145+
146+
if ((node as any).delegatesFocus) {
147+
const delegatesFocusAttr = ' shadowrootdelegatesfocus';
148+
yield delegatesFocusAttr;
149+
output.currentLineWidth += delegatesFocusAttr.length;
150+
}
145151
}
146152

147153
const attrsLength = (node as HTMLElement).attributes.length;

src/mock-doc/test/serialize-node.spec.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,18 @@ describe('serializeNodeToHtml', () => {
153153
`);
154154
});
155155

156+
it('shadow root to template with focus delegation', () => {
157+
const elm = doc.createElement('cmp-a');
158+
expect(elm.shadowRoot).toEqual(null);
159+
160+
const shadowRoot = elm.attachShadow({ mode: 'open', delegatesFocus: true });
161+
expect(shadowRoot.nodeType).toEqual(11);
162+
expect(elm.shadowRoot.nodeType).toEqual(11);
163+
164+
expect(shadowRoot.host).toEqual(elm);
165+
expect(elm.outerHTML).toContain('<template shadowrootmode="open" shadowrootdelegatesfocus');
166+
});
167+
156168
it('style', () => {
157169
const input = `<style> \n text \n\n</style>`;
158170
doc.body.innerHTML = input;

test/end-to-end/src/components.d.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ export namespace Components {
4444
*/
4545
"initialCounter": number;
4646
}
47+
interface CmpDsdFocus {
48+
}
4749
interface CmpServerVsClient {
4850
}
4951
interface CmpWithSlot {
@@ -319,6 +321,12 @@ declare global {
319321
prototype: HTMLCmpDsdElement;
320322
new (): HTMLCmpDsdElement;
321323
};
324+
interface HTMLCmpDsdFocusElement extends Components.CmpDsdFocus, HTMLStencilElement {
325+
}
326+
var HTMLCmpDsdFocusElement: {
327+
prototype: HTMLCmpDsdFocusElement;
328+
new (): HTMLCmpDsdFocusElement;
329+
};
322330
interface HTMLCmpServerVsClientElement extends Components.CmpServerVsClient, HTMLStencilElement {
323331
}
324332
var HTMLCmpServerVsClientElement: {
@@ -573,6 +581,7 @@ declare global {
573581
"cmp-b": HTMLCmpBElement;
574582
"cmp-c": HTMLCmpCElement;
575583
"cmp-dsd": HTMLCmpDsdElement;
584+
"cmp-dsd-focus": HTMLCmpDsdFocusElement;
576585
"cmp-server-vs-client": HTMLCmpServerVsClientElement;
577586
"cmp-with-slot": HTMLCmpWithSlotElement;
578587
"dom-api": HTMLDomApiElement;
@@ -650,6 +659,8 @@ declare namespace LocalJSX {
650659
*/
651660
"initialCounter"?: number;
652661
}
662+
interface CmpDsdFocus {
663+
}
653664
interface CmpServerVsClient {
654665
}
655666
interface CmpWithSlot {
@@ -805,6 +816,7 @@ declare namespace LocalJSX {
805816
"cmp-b": CmpB;
806817
"cmp-c": CmpC;
807818
"cmp-dsd": CmpDsd;
819+
"cmp-dsd-focus": CmpDsdFocus;
808820
"cmp-server-vs-client": CmpServerVsClient;
809821
"cmp-with-slot": CmpWithSlot;
810822
"dom-api": DomApi;
@@ -863,6 +875,7 @@ declare module "@stencil/core" {
863875
"cmp-b": LocalJSX.CmpB & JSXBase.HTMLAttributes<HTMLCmpBElement>;
864876
"cmp-c": LocalJSX.CmpC & JSXBase.HTMLAttributes<HTMLCmpCElement>;
865877
"cmp-dsd": LocalJSX.CmpDsd & JSXBase.HTMLAttributes<HTMLCmpDsdElement>;
878+
"cmp-dsd-focus": LocalJSX.CmpDsdFocus & JSXBase.HTMLAttributes<HTMLCmpDsdFocusElement>;
866879
"cmp-server-vs-client": LocalJSX.CmpServerVsClient & JSXBase.HTMLAttributes<HTMLCmpServerVsClientElement>;
867880
"cmp-with-slot": LocalJSX.CmpWithSlot & JSXBase.HTMLAttributes<HTMLCmpWithSlotElement>;
868881
"dom-api": LocalJSX.DomApi & JSXBase.HTMLAttributes<HTMLDomApiElement>;

test/end-to-end/src/declarative-shadow-dom/__snapshots__/test.e2e.ts.snap

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@ exports[`renderToString can render nested components 1`] = `
5454
</another-car-list>"
5555
`;
5656
57+
exports[`renderToString renders server-side components with delegated focus 1`] = `"<cmp-dsd-focus class=\\"sc-cmp-dsd-focus-h\\" custom-hydrate-flag=\\"\\" s-id=\\"32\\"><template shadowrootmode=\\"open\\" shadowrootdelegatesfocus><div class=\\"sc-cmp-dsd-focus\\" c-id=\\"32.0.0.0\\"><!--t.32.1.1.0-->Clickable shadow DOM text</div><button class=\\"sc-cmp-dsd-focus\\" c-id=\\"32.2.0.1\\"><!--t.32.3.1.0-->Click me!</button></template><!--r.32--></cmp-dsd-focus>"`;
58+
5759
exports[`renderToString supports passing props to components 1`] = `
5860
"<another-car-detail car=\\"{&quot;year&quot;:2024, &quot;make&quot;: &quot;VW&quot;, &quot;model&quot;: &quot;Vento&quot;}\\" class=\\"sc-another-car-detail-h\\" custom-hydrate-flag=\\"\\" s-id=\\"2\\">
5961
<template shadowrootmode=\\"open\\">
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { Component, h, Host } from '@stencil/core';
2+
3+
@Component({
4+
tag: 'cmp-dsd-focus',
5+
shadow: { delegatesFocus: true },
6+
})
7+
export class ComponentDSDWithFocusDelegation {
8+
render() {
9+
return (
10+
<Host>
11+
<div>Clickable shadow DOM text</div>
12+
<button>Click me!</button>
13+
</Host>
14+
);
15+
}
16+
}

test/end-to-end/src/declarative-shadow-dom/test.e2e.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -382,6 +382,22 @@ describe('renderToString', () => {
382382
</nested-cmp-parent>`);
383383
});
384384

385+
it('renders server-side components with delegated focus', async () => {
386+
const { html } = await renderToString('<cmp-dsd-focus></cmp-dsd-focus>', {
387+
serializeShadowRoot: true,
388+
fullDocument: false,
389+
});
390+
391+
expect(html).toContain('<template shadowrootmode="open" shadowrootdelegatesfocus>');
392+
expect(html).toMatchSnapshot();
393+
394+
const page = await newE2EPage({ html, url: 'https://stencil.com' });
395+
const div = await page.find('cmp-dsd-focus >>> div');
396+
await div.click();
397+
398+
expect(await page.evaluate(() => document.activeElement.outerHTML)).toContain('cmp-dsd-focus');
399+
});
400+
385401
describe('hydrateDocument', () => {
386402
it('can hydrate components with open shadow dom by default', async () => {
387403
const template = `<another-car-detail></another-car-detail>`;

0 commit comments

Comments
 (0)