diff --git a/src/compiler/compile/Component.ts b/src/compiler/compile/Component.ts index 9822529ece..9ed74ea3a9 100644 --- a/src/compiler/compile/Component.ts +++ b/src/compiler/compile/Component.ts @@ -14,7 +14,7 @@ import Stylesheet from './css/Stylesheet'; import { test } from '../config'; import Fragment from './nodes/Fragment'; import internal_exports from './internal_exports'; -import { Ast, CompileOptions, Var, Warning, CssResult } from '../interfaces'; +import { Ast, CompileOptions, Var, Warning, CssResult, ShadowDomMode } from '../interfaces'; import error from '../utils/error'; import get_code_frame from '../utils/get_code_frame'; import flatten_reference from './utils/flatten_reference'; @@ -30,6 +30,7 @@ import check_graph_for_cycles from './utils/check_graph_for_cycles'; import { print, x, b } from 'code-red'; interface ComponentOptions { + shadowdom?: ShadowDomMode; namespace?: string; tag?: string; immutable?: boolean; @@ -158,6 +159,7 @@ export default class Component { }); } this.tag = this.component_options.tag || compile_options.tag; + this.compile_options.shadowDom = this.component_options.shadowdom || "open"; } else { this.tag = this.name.name; } @@ -170,7 +172,7 @@ export default class Component { this.walk_instance_js_post_template(); - if (!compile_options.customElement) this.stylesheet.reify(); + if (!compile_options.customElement || compile_options.shadowDom=="none") this.stylesheet.reify(); this.stylesheet.warn_on_unused_selectors(this); } @@ -1414,7 +1416,16 @@ function process_component_options(component: Component, nodes) { component_options[name] = value; break; } + case 'shadowdom':{ + const code = 'invalid-shadowdom-attribute'; + const message = `'shadowdom' must be set to 'open', 'closed or 'none'`; + const value = get_value(attribute, code, message) + if(value != "open" && value != "none" && value != "closed") + component.error(attribute, { code, message }); + component_options[name] = value; + break; + } default: component.error(attribute, { code: `invalid-options-attribute`, diff --git a/src/compiler/compile/index.ts b/src/compiler/compile/index.ts index 12b161aeeb..5fe11fd2f5 100644 --- a/src/compiler/compile/index.ts +++ b/src/compiler/compile/index.ts @@ -22,6 +22,7 @@ const valid_options = [ 'hydratable', 'legacy', 'customElement', + 'shadowDom', 'tag', 'css', 'loopGuardTimeout', diff --git a/src/compiler/compile/render_dom/index.ts b/src/compiler/compile/render_dom/index.ts index 26fa4a70f8..0a3857ee1a 100644 --- a/src/compiler/compile/render_dom/index.ts +++ b/src/compiler/compile/render_dom/index.ts @@ -20,7 +20,7 @@ export default function dom( block.has_outro_method = true; // prevent fragment being created twice (#1063) - if (options.customElement) block.chunks.create.push(b`this.c = @noop;`); + if (options.customElement && options.shadowDom !== "none") block.chunks.create.push(b`this.c = @noop;`); const body = []; @@ -29,7 +29,7 @@ export default function dom( body.push(b`const ${renderer.file_var} = ${file};`); } - const css = component.stylesheet.render(options.filename, !options.customElement); + const css = component.stylesheet.render(options.filename, (!options.customElement || options.shadowDom === "none")); const styles = component.stylesheet.has_styles && options.dev ? `${css.code}\n/*# sourceMappingURL=${css.map.toUrl()} */` : css.code; @@ -37,9 +37,9 @@ export default function dom( const add_css = component.get_unique_name('add_css'); const should_add_css = ( - !options.customElement && + (!options.customElement && !!styles && - options.css !== false + options.css !== false ) || options.shadowDom === "none" ); if (should_add_css) { @@ -437,14 +437,19 @@ export default function dom( } if (options.customElement) { + const lightDom = options.shadowDom === 'none'; const declaration = b` class ${name} extends @SvelteElement { constructor(options) { super(); + ${!lightDom && b` + this._root =this.attachShadow({ mode: '${options.shadowDom}' }); + `} - ${css.code && b`this.shadowRoot.innerHTML = \`\`;`} + ${css.code && !lightDom && b`this._root.innerHTML = \`\`;`} + ${should_add_css && lightDom && b`if (!@_document.getElementById("${component.stylesheet.id}-style")) ${add_css}();`} - @init(this, { target: this.shadowRoot }, ${definition}, ${has_create_fragment ? 'create_fragment': 'null'}, ${not_equal}, ${prop_indexes}, ${dirty}); + @init(this, { target: ${lightDom ? 'this' : 'this._root'} }, ${definition}, ${has_create_fragment ? 'create_fragment': 'null'}, ${not_equal}, ${prop_indexes}, ${dirty}); ${dev_props_check} diff --git a/src/compiler/interfaces.ts b/src/compiler/interfaces.ts index a5e286462f..a7932615fa 100644 --- a/src/compiler/interfaces.ts +++ b/src/compiler/interfaces.ts @@ -120,6 +120,7 @@ export interface CompileOptions { hydratable?: boolean; legacy?: boolean; customElement?: boolean; + shadowDom?: ShadowDomMode; tag?: string; css?: boolean; loopGuardTimeout?: number; @@ -166,4 +167,8 @@ export interface Var { export interface CssResult { code: string; map: SourceMap; -} \ No newline at end of file +} + +export type ShadowDomMode = 'none' + | 'open' + | 'closed' \ No newline at end of file diff --git a/src/runtime/internal/Component.ts b/src/runtime/internal/Component.ts index 10588a0804..f8fbcf8ced 100644 --- a/src/runtime/internal/Component.ts +++ b/src/runtime/internal/Component.ts @@ -167,7 +167,6 @@ if (typeof HTMLElement === 'function') { $$: T$$; constructor() { super(); - this.attachShadow({ mode: 'open' }); } connectedCallback() { diff --git a/test/js/samples/css-shadow-dom-keyframes/expected.js b/test/js/samples/css-shadow-dom-keyframes/expected.js index a0a0ebe021..aa069dd465 100644 --- a/test/js/samples/css-shadow-dom-keyframes/expected.js +++ b/test/js/samples/css-shadow-dom-keyframes/expected.js @@ -33,6 +33,7 @@ function create_fragment(ctx) { class Component extends SvelteElement { constructor(options) { super(); + this.attachShadow({ mode: "open" }); this.shadowRoot.innerHTML = ``; init(this, { target: this.shadowRoot }, null, create_fragment, safe_not_equal, {});