diff --git a/src/compiler/compile/Component.ts b/src/compiler/compile/Component.ts index c80ebb6ddc..989f819c70 100644 --- a/src/compiler/compile/Component.ts +++ b/src/compiler/compile/Component.ts @@ -46,6 +46,7 @@ interface ComponentOptions { accessors?: boolean; preserveWhitespace?: boolean; ceProps?: Record; + shadowdom?: 'open' | 'none'; } const regex_leading_directory_separator = /^[/\\]/; @@ -1574,6 +1575,17 @@ function process_component_options(component: Component, nodes) { break; } + case 'shadowdom': { + const shadowdom = get_value(attribute, compiler_errors.invalid_shadowdom_attribute); + + if (shadowdom !== 'open' && shadowdom !== 'none') { + return component.error(attribute, compiler_errors.invalid_shadowdom_attribute); + } + + component_options.shadowdom = shadowdom; + break; + } + case 'ceProps': { const error = () => component.error(attribute, compiler_errors.invalid_ceProps_attribute); const { value } = attribute; diff --git a/src/compiler/compile/compiler_errors.ts b/src/compiler/compile/compiler_errors.ts index c7b8554331..e4f1e037b7 100644 --- a/src/compiler/compile/compiler_errors.ts +++ b/src/compiler/compile/compiler_errors.ts @@ -206,6 +206,10 @@ export default { code: 'invalid-tag-attribute', message: "'tag' must be a string literal" }, + invalid_shadowdom_attribute: { + code: 'invalid-shadowdom-attribute', + message: "'shadowdom' must be either 'open' or 'none'" + }, invalid_ceProps_attribute: { code: 'invalid-ceProps-attribute', message: "'ceProps' must be a statically analyzable object literal of the form " + diff --git a/src/compiler/compile/render_dom/index.ts b/src/compiler/compile/render_dom/index.ts index feb2cadf40..cfb110aac6 100644 --- a/src/compiler/compile/render_dom/index.ts +++ b/src/compiler/compile/render_dom/index.ts @@ -555,9 +555,10 @@ export default function dom( .filter(accessor => !writable_props.some(prop => prop.export_name === accessor.key.name)) .map(accessor => `"${accessor.key.name}"`) .join(','); + const use_shadow_dom = component.component_options.shadowdom !== 'none' ? 'true' : 'false'; body.push( - b`@_customElements.define("${component.tag}", @create_custom_element(${name}, ${JSON.stringify(props_str)}, [${slots_str}], [${accessors_str}]));` + b`@_customElements.define("${component.tag}", @create_custom_element(${name}, ${JSON.stringify(props_str)}, [${slots_str}], [${accessors_str}], ${use_shadow_dom}));` ); } diff --git a/src/runtime/internal/Component.ts b/src/runtime/internal/Component.ts index b30ca481c5..b48017522f 100644 --- a/src/runtime/internal/Component.ts +++ b/src/runtime/internal/Component.ts @@ -157,10 +157,13 @@ if (typeof HTMLElement === 'function') { constructor( private $$componentCtor: ComponentType, - private $$slots: string[] + private $$slots: string[], + use_shadow_dom: boolean ) { super(); - this.attachShadow({ mode: 'open' }); + if (use_shadow_dom) { + this.attachShadow({ mode: 'open' }); + } } addEventListener(type: string, listener: any, options?: any): void { @@ -237,7 +240,7 @@ if (typeof HTMLElement === 'function') { } this.$$component = new this.$$componentCtor({ - target: this.shadowRoot!, + target: this.shadowRoot || this, props: { ...this.$$data, $$slots, @@ -330,17 +333,19 @@ interface CustomElementPropDefinition { * @param props_definition The props to observe * @param slots The slots to create * @param accessors Other accessors besides the ones for props the component has + * @param use_shadow_dom Whether to use shadow DOM * @returns A custom element class */ export function create_custom_element( Component: ComponentType, props_definition: Record, slots: string[], - accessors: string[] + accessors: string[], + use_shadow_dom: boolean ) { const Class = class extends SvelteElement { constructor() { - super(Component, slots); + super(Component, slots, use_shadow_dom); this.$$props_definition = props_definition; } diff --git a/test/custom-elements/samples/no-shadow-dom/main.svelte b/test/custom-elements/samples/no-shadow-dom/main.svelte new file mode 100644 index 0000000000..ff7ded0eb9 --- /dev/null +++ b/test/custom-elements/samples/no-shadow-dom/main.svelte @@ -0,0 +1,13 @@ + + + + +

Hello {name}!

+ + diff --git a/test/custom-elements/samples/no-shadow-dom/test.js b/test/custom-elements/samples/no-shadow-dom/test.js new file mode 100644 index 0000000000..29abe702ae --- /dev/null +++ b/test/custom-elements/samples/no-shadow-dom/test.js @@ -0,0 +1,16 @@ +import * as assert from 'assert'; +import { tick } from 'svelte'; +import './main.svelte'; + +export default async function (target) { + target.innerHTML = ''; + await tick(); + + const el = target.querySelector('custom-element'); + const h1 = el.querySelector('h1'); + + assert.equal(el.name, 'world'); + assert.equal(el.shadowRoot, null); + assert.equal(h1.innerHTML, 'Hello world!'); + assert.equal(getComputedStyle(h1).color, 'rgb(255, 0, 0)'); +} diff --git a/test/custom-elements/samples/no-svelte-options/test.js b/test/custom-elements/samples/no-svelte-options/test.js index 0ff4e7c7d2..f619c4232b 100644 --- a/test/custom-elements/samples/no-svelte-options/test.js +++ b/test/custom-elements/samples/no-svelte-options/test.js @@ -4,7 +4,7 @@ import CustomElement from './main.svelte'; import { create_custom_element } from 'svelte/internal'; export default async function (target) { - customElements.define('no-tag', create_custom_element(CustomElement, {name: {}}, [], [])); + customElements.define('no-tag', create_custom_element(CustomElement, {name: {}}, [], [], true)); target.innerHTML = ''; await tick(); diff --git a/test/custom-elements/samples/no-tag-warning/test.js b/test/custom-elements/samples/no-tag-warning/test.js index e6b82e8ec8..4558f195f9 100644 --- a/test/custom-elements/samples/no-tag-warning/test.js +++ b/test/custom-elements/samples/no-tag-warning/test.js @@ -4,7 +4,7 @@ import CustomElement from './main.svelte'; import { create_custom_element } from 'svelte/internal'; export default async function (target) { - customElements.define('no-tag', create_custom_element(CustomElement, { name: {}}, [], [])); + customElements.define('no-tag', create_custom_element(CustomElement, { name: {}}, [], [], true)); target.innerHTML = ''; await tick(); diff --git a/test/custom-elements/samples/no-tag/test.js b/test/custom-elements/samples/no-tag/test.js index 4967b31fba..b364bc6bcc 100644 --- a/test/custom-elements/samples/no-tag/test.js +++ b/test/custom-elements/samples/no-tag/test.js @@ -4,7 +4,7 @@ import CustomElement from './main.svelte'; import { create_custom_element } from 'svelte/internal'; export default async function (target) { - customElements.define('no-tag', create_custom_element(CustomElement, { name: {} }, [], [])); + customElements.define('no-tag', create_custom_element(CustomElement, { name: {} }, [], [], true)); target.innerHTML = ''; await tick(); diff --git a/test/js/samples/css-shadow-dom-keyframes/expected.js b/test/js/samples/css-shadow-dom-keyframes/expected.js index 0fd49de5f7..97c40eca12 100644 --- a/test/js/samples/css-shadow-dom-keyframes/expected.js +++ b/test/js/samples/css-shadow-dom-keyframes/expected.js @@ -44,5 +44,5 @@ class Component extends SvelteComponent { } } -customElements.define("custom-element", create_custom_element(Component, {}, [], [])); +customElements.define("custom-element", create_custom_element(Component, {}, [], [], true)); export default Component; \ No newline at end of file