Skip to content

Commit 2ffb503

Browse files
authored
fix(compiler): add deletegatesFocus to custom elements targets (#3117)
this commit allows `delegatesFocus` to be properly applied to components generated using the following output targets: - dist-custom-elements - dist-custom-elements-bundle the generation of the `attachShadow` call is moved from a standalone function to being attached to the prototype of the custom element when we proxy it. the reason for this is that we need the component metadata to to determine whether or not each individual component should have delegateFocus enabled or not. this led to the removal of the original standalone attachShadow function. I do not consider this to be a breaking change, as we don't publicly state our runtime APIs are available for general consumption. this change also led to the transition from using ts.create*() calls to ts.factory.create*() calls for nativeAttachShadowStatement, which is the general direction I'd like to take such calls, since the former is now deprecated STENCIL-90: "dist-custom-elements-bundle" does not set delegatesFocus when attaching shadow
1 parent 63dbb47 commit 2ffb503

14 files changed

Lines changed: 243 additions & 16 deletions

File tree

src/compiler/transformers/component-native/native-constructor.ts

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import type * as d from '../../../declarations';
22
import { addCreateEvents } from '../create-event';
33
import { addLegacyProps } from '../legacy-props';
4-
import { ATTACH_SHADOW, RUNTIME_APIS, addCoreRuntimeApi } from '../core-runtime-apis';
4+
import { RUNTIME_APIS, addCoreRuntimeApi } from '../core-runtime-apis';
55
import ts from 'typescript';
66

77
export const updateNativeConstructor = (
@@ -57,7 +57,13 @@ export const updateNativeConstructor = (
5757
}
5858
};
5959

60-
const nativeInit = (moduleFile: d.Module, cmp: d.ComponentCompilerMeta) => {
60+
/**
61+
* Generates a series of expression statements used to help initialize a Stencil component
62+
* @param moduleFile the Stencil module that will be instantiated
63+
* @param cmp the component's metadata
64+
* @returns the generated expression statements
65+
*/
66+
const nativeInit = (moduleFile: d.Module, cmp: d.ComponentCompilerMeta): ReadonlyArray<ts.ExpressionStatement> => {
6167
const initStatements = [nativeRegisterHostStatement()];
6268
if (cmp.encapsulation === 'shadow') {
6369
initStatements.push(nativeAttachShadowStatement(moduleFile));
@@ -71,10 +77,21 @@ const nativeRegisterHostStatement = () => {
7177
);
7278
};
7379

74-
const nativeAttachShadowStatement = (moduleFile: d.Module) => {
80+
/**
81+
* Generates an expression statement for attaching a shadow DOM tree to an element.
82+
* @param moduleFile the Stencil module that will use the generated expression statement
83+
* @returns the generated expression statement
84+
*/
85+
const nativeAttachShadowStatement = (moduleFile: d.Module): ts.ExpressionStatement => {
7586
addCoreRuntimeApi(moduleFile, RUNTIME_APIS.attachShadow);
76-
77-
return ts.createStatement(ts.createCall(ts.createIdentifier(ATTACH_SHADOW), undefined, [ts.createThis()]));
87+
// Create an expression statement, `this.__attachShadow();`
88+
return ts.factory.createExpressionStatement(
89+
ts.factory.createCallExpression(
90+
ts.factory.createPropertyAccessExpression(ts.factory.createThis(), ts.factory.createIdentifier('__attachShadow')),
91+
undefined,
92+
undefined
93+
)
94+
);
7895
};
7996

8097
const createNativeConstructorSuper = () => {
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import { mockCompilerCtx } from '@stencil/core/testing';
2+
import * as d from '@stencil/core/declarations';
3+
import { transpileModule } from './transpile';
4+
import { nativeComponentTransform } from '../component-native/tranform-to-native-component';
5+
6+
describe('nativeComponentTransform', () => {
7+
let compilerCtx: d.CompilerCtx;
8+
let transformOpts: d.TransformOptions;
9+
10+
beforeEach(() => {
11+
compilerCtx = mockCompilerCtx();
12+
transformOpts = {
13+
coreImportPath: '@stencil/core',
14+
componentExport: 'customelement',
15+
componentMetadata: null,
16+
currentDirectory: '/',
17+
proxy: null,
18+
style: 'static',
19+
styleImportData: undefined,
20+
};
21+
});
22+
23+
describe('updateNativeComponentClass', () => {
24+
it("adds __attachShadow() calls when a component doesn't have a constructor", () => {
25+
const code = `
26+
@Component({
27+
tag: 'cmp-a',
28+
shadow: true,
29+
})
30+
export class CmpA {
31+
@Prop() foo: number;
32+
}
33+
`;
34+
35+
const transformer = nativeComponentTransform(compilerCtx, transformOpts);
36+
37+
const transpiledModule = transpileModule(code, null, compilerCtx, null, [], [transformer]);
38+
39+
expect(transpiledModule.outputText).toContain(
40+
`import { attachShadow as __stencil_attachShadow, defineCustomElement as __stencil_defineCustomElement } from "@stencil/core";`
41+
);
42+
expect(transpiledModule.outputText).toContain(`this.__attachShadow()`);
43+
});
44+
45+
it('adds __attachShadow() calls when a component has a constructor', () => {
46+
const code = `
47+
@Component({
48+
tag: 'cmp-a',
49+
shadow: true,
50+
})
51+
export class CmpA {
52+
@Prop() foo: number;
53+
54+
constructor() {
55+
super();
56+
}
57+
}
58+
`;
59+
60+
const transformer = nativeComponentTransform(compilerCtx, transformOpts);
61+
62+
const transpiledModule = transpileModule(code, null, compilerCtx, null, [], [transformer]);
63+
64+
expect(transpiledModule.outputText).toContain(
65+
`import { attachShadow as __stencil_attachShadow, defineCustomElement as __stencil_defineCustomElement } from "@stencil/core";`
66+
);
67+
expect(transpiledModule.outputText).toContain(`this.__attachShadow()`);
68+
});
69+
});
70+
});

src/hydrate/platform/index.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,6 @@ export { hydrateApp } from './hydrate-app';
161161

162162
export {
163163
addHostEventListeners,
164-
attachShadow,
165164
defineCustomElement,
166165
forceModeUpdate,
167166
proxyCustomElement,

src/runtime/bootstrap-custom-element.ts

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,23 @@ export const proxyCustomElement = (Cstr: any, compactMeta: d.ComponentRuntimeMet
5252
originalDisconnectedCallback.call(this);
5353
}
5454
},
55+
__attachShadow() {
56+
if (supportsShadow) {
57+
if (BUILD.shadowDelegatesFocus) {
58+
this.attachShadow({
59+
mode: 'open',
60+
delegatesFocus: !!(cmpMeta.$flags$ & CMP_FLAGS.shadowDelegatesFocus),
61+
});
62+
} else {
63+
this.attachShadow({ mode: 'open' });
64+
}
65+
} else {
66+
(this as any).shadowRoot = this;
67+
}
68+
},
5569
});
5670
Cstr.is = cmpMeta.$tagName$;
71+
5772
return proxyComponent(Cstr, cmpMeta, PROXY_FLAGS.isElementConstructor | PROXY_FLAGS.proxyState);
5873
};
5974

@@ -80,11 +95,3 @@ export const forceModeUpdate = (elm: d.RenderNode) => {
8095
}
8196
}
8297
};
83-
84-
export const attachShadow = (el: HTMLElement) => {
85-
if (supportsShadow) {
86-
el.attachShadow({ mode: 'open' });
87-
} else {
88-
(el as any).shadowRoot = el;
89-
}
90-
};

src/runtime/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
export { addHostEventListeners } from './host-listener';
2-
export { attachShadow, defineCustomElement, forceModeUpdate, proxyCustomElement } from './bootstrap-custom-element';
2+
export { defineCustomElement, forceModeUpdate, proxyCustomElement } from './bootstrap-custom-element';
33
export { bootstrapLazy } from './bootstrap-lazy';
44
export { connectedCallback } from './connected-callback';
55
export { createEvent } from './event-emitter';

test/karma/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
"karma.prod": "npm run build.sibling && npm run build.invisible-prehydration && npm run build.app && npm run karma.webpack && npm run build.prerender && npm run karma",
2020
"karma.ie": "karma start karma.config.js --browsers=IE --single-run=false",
2121
"karma.edge": "karma start karma.config.js --browsers=Edge --single-run=false",
22-
"karma.webpack": "webpack-cli --config test-app/esm-webpack/webpack.config.js && webpack-cli --config test-app/custom-elements-output-webpack/webpack.config.js && webpack-cli --config test-app/custom-elements-output-tag-class-different/webpack.config.js",
22+
"karma.webpack": "webpack-cli --config test-app/esm-webpack/webpack.config.js && webpack-cli --config test-app/custom-elements-output-webpack/webpack.config.js && webpack-cli --config test-app/custom-elements-output-tag-class-different/webpack.config.js && webpack-cli --config test-app/custom-elements-delegates-focus/webpack.config.js",
2323
"start": "node ../../bin/stencil build --dev --watch --serve --es5"
2424
},
2525
"devDependencies": {

test/karma/test-app/components.d.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,10 @@ export namespace Components {
7676
}
7777
interface CustomElementRootDifferentNameThanClass {
7878
}
79+
interface CustomElementsDelegatesFocus {
80+
}
81+
interface CustomElementsNoDelegatesFocus {
82+
}
7983
interface CustomEventRoot {
8084
}
8185
interface DelegatesFocus {
@@ -458,6 +462,18 @@ declare global {
458462
prototype: HTMLCustomElementRootDifferentNameThanClassElement;
459463
new (): HTMLCustomElementRootDifferentNameThanClassElement;
460464
};
465+
interface HTMLCustomElementsDelegatesFocusElement extends Components.CustomElementsDelegatesFocus, HTMLStencilElement {
466+
}
467+
var HTMLCustomElementsDelegatesFocusElement: {
468+
prototype: HTMLCustomElementsDelegatesFocusElement;
469+
new (): HTMLCustomElementsDelegatesFocusElement;
470+
};
471+
interface HTMLCustomElementsNoDelegatesFocusElement extends Components.CustomElementsNoDelegatesFocus, HTMLStencilElement {
472+
}
473+
var HTMLCustomElementsNoDelegatesFocusElement: {
474+
prototype: HTMLCustomElementsNoDelegatesFocusElement;
475+
new (): HTMLCustomElementsNoDelegatesFocusElement;
476+
};
461477
interface HTMLCustomEventRootElement extends Components.CustomEventRoot, HTMLStencilElement {
462478
}
463479
var HTMLCustomEventRootElement: {
@@ -1077,6 +1093,8 @@ declare global {
10771093
"custom-element-nested-child": HTMLCustomElementNestedChildElement;
10781094
"custom-element-root": HTMLCustomElementRootElement;
10791095
"custom-element-root-different-name-than-class": HTMLCustomElementRootDifferentNameThanClassElement;
1096+
"custom-elements-delegates-focus": HTMLCustomElementsDelegatesFocusElement;
1097+
"custom-elements-no-delegates-focus": HTMLCustomElementsNoDelegatesFocusElement;
10801098
"custom-event-root": HTMLCustomEventRootElement;
10811099
"delegates-focus": HTMLDelegatesFocusElement;
10821100
"dom-reattach": HTMLDomReattachElement;
@@ -1246,6 +1264,10 @@ declare namespace LocalJSX {
12461264
}
12471265
interface CustomElementRootDifferentNameThanClass {
12481266
}
1267+
interface CustomElementsDelegatesFocus {
1268+
}
1269+
interface CustomElementsNoDelegatesFocus {
1270+
}
12491271
interface CustomEventRoot {
12501272
}
12511273
interface DelegatesFocus {
@@ -1514,6 +1536,8 @@ declare namespace LocalJSX {
15141536
"custom-element-nested-child": CustomElementNestedChild;
15151537
"custom-element-root": CustomElementRoot;
15161538
"custom-element-root-different-name-than-class": CustomElementRootDifferentNameThanClass;
1539+
"custom-elements-delegates-focus": CustomElementsDelegatesFocus;
1540+
"custom-elements-no-delegates-focus": CustomElementsNoDelegatesFocus;
15171541
"custom-event-root": CustomEventRoot;
15181542
"delegates-focus": DelegatesFocus;
15191543
"dom-reattach": DomReattach;
@@ -1643,6 +1667,8 @@ declare module "@stencil/core" {
16431667
"custom-element-nested-child": LocalJSX.CustomElementNestedChild & JSXBase.HTMLAttributes<HTMLCustomElementNestedChildElement>;
16441668
"custom-element-root": LocalJSX.CustomElementRoot & JSXBase.HTMLAttributes<HTMLCustomElementRootElement>;
16451669
"custom-element-root-different-name-than-class": LocalJSX.CustomElementRootDifferentNameThanClass & JSXBase.HTMLAttributes<HTMLCustomElementRootDifferentNameThanClassElement>;
1670+
"custom-elements-delegates-focus": LocalJSX.CustomElementsDelegatesFocus & JSXBase.HTMLAttributes<HTMLCustomElementsDelegatesFocusElement>;
1671+
"custom-elements-no-delegates-focus": LocalJSX.CustomElementsNoDelegatesFocus & JSXBase.HTMLAttributes<HTMLCustomElementsNoDelegatesFocusElement>;
16461672
"custom-event-root": LocalJSX.CustomEventRoot & JSXBase.HTMLAttributes<HTMLCustomEventRootElement>;
16471673
"delegates-focus": LocalJSX.DelegatesFocus & JSXBase.HTMLAttributes<HTMLDelegatesFocusElement>;
16481674
"dom-reattach": LocalJSX.DomReattach & JSXBase.HTMLAttributes<HTMLDomReattachElement>;
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { Component, Host, h } from '@stencil/core';
2+
3+
@Component({
4+
tag: 'custom-elements-delegates-focus',
5+
styleUrl: 'shared-delegates-focus.css',
6+
shadow: {
7+
delegatesFocus: true,
8+
},
9+
})
10+
export class CustomElementsDelegatesFocus {
11+
render() {
12+
return (
13+
<Host>
14+
<input />
15+
</Host>
16+
);
17+
}
18+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { Component, Host, h } from '@stencil/core';
2+
3+
@Component({
4+
tag: 'custom-elements-no-delegates-focus',
5+
styleUrl: 'shared-delegates-focus.css',
6+
shadow: true,
7+
})
8+
export class CustomElementsNoDelegatesFocus {
9+
render() {
10+
return (
11+
<Host>
12+
<input />
13+
</Host>
14+
);
15+
}
16+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import { defineCustomElement } from '../../test-components/custom-elements-delegates-focus';
2+
3+
defineCustomElement();

0 commit comments

Comments
 (0)