diff --git a/src/compiler/compile/Component.ts b/src/compiler/compile/Component.ts
index 9822529ece61..3dfb4fd275cf 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 12b161aeeb0e..5fe11fd2f5eb 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 26fa4a70f81f..fddf9373439a 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,22 @@ export default function dom(
}
if (options.customElement) {
+ const lightDom = options.shadowDom === 'none';
const declaration = b`
class ${name} extends @SvelteElement {
constructor(options) {
super();
-
- ${css.code && b`this.shadowRoot.innerHTML = \`\`;`}
-
- @init(this, { target: this.shadowRoot }, ${definition}, ${has_create_fragment ? 'create_fragment': 'null'}, ${not_equal}, ${prop_indexes}, ${dirty});
+ ${!lightDom && b`
+ this._root =this.attachShadow({ mode: '${options.shadowDom}' });
+ ` || b`
+ this._copycontent();
+ this.slotObserver = new MutationObserver(() => this._slotcontent());
+ this.slotObserver.observe(this, {childList: true, subtree: true});
+ `}
+ ${css.code && !lightDom && b`this._root.innerHTML = \`\`;`}
+ ${should_add_css && lightDom && b`if (!@_document.getElementById("${component.stylesheet.id}-style")) ${add_css}();`}
+
+ @init(this, { target: ${lightDom ? 'this' : 'this._root'} }, ${definition}, ${has_create_fragment ? 'create_fragment': 'null'}, ${not_equal}, ${prop_indexes}, ${dirty});
${dev_props_check}
@@ -476,6 +484,82 @@ export default function dom(
});
}
+ if (lightDom) {
+ declaration.body.body.push({
+ type: 'MethodDefinition',
+ kind: 'method',
+ static: false,
+ computed: false,
+ key: { type: 'Identifier', name: '_copycontent' },
+ value: x`function() {
+ if(this.children){
+ this._content = Array.from(this.childNodes)
+ while (this.firstChild) {
+ this.removeChild(this.firstChild)
+ }
+ }
+ }` as FunctionExpression
+ });
+
+ declaration.body.body.push({
+ type: 'MethodDefinition',
+ kind: 'method',
+ static: false,
+ computed: false,
+ key: { type: 'Identifier', name: '_slotcontent' },
+ value: x`function() {
+ if(this.slotting) return; // prevent running in parallel
+ this.slotting = true;
+ if(this._content){
+ let namedslots = Array.from(this.querySelectorAll("slot[name]"))
+ let defaultslot = this.querySelector("slot:not([name])")
+ let named = {}
+ if(!namedslots.length && !defaultslot) return(this.slotting=false);
+ let slotted = []
+ this._content.filter((node)=> node.slot ).forEach((node)=> named[node.slot] = node )
+ namedslots.forEach(slot =>{
+ this._content.forEach(node =>{ //append all named slots
+ if(named[node.slot] && slot.getAttribute("name") == node.slot){
+ if(!slot.hasAttribute("hasupdated")){
+ while (slot.firstChild) {
+ slot.removeChild(slot.firstChild)
+ }
+ slot.appendChild(named[node.slot]);
+ }
+ slot.setAttribute("hasupdated","")
+ slotted.push(node)
+ }
+ })
+ })
+ if(!defaultslot) return(this.slotting=false);
+ // get all nodes without a slot attribute
+ let toAppend = this._content.filter(node => node.hasAttribute && !node.hasAttribute("slot"))
+ // remove default
+ if(!defaultslot.hasAttribute("hasupdated") && toAppend.length){
+ while (defaultslot.firstChild) {
+ defaultslot.removeChild(defaultslot.firstChild)
+ }
+ }
+ toAppend.forEach(node => !defaultslot.hasAttribute("hasupdated") && defaultslot.appendChild(node) )
+ defaultslot.setAttribute("hasupdated","")
+ }
+ this.slotting = false
+ }
+ }` as FunctionExpression
+ });
+
+ declaration.body.body.push({
+ type: 'MethodDefinition',
+ kind: 'method',
+ static: false,
+ computed: false,
+ key: { type: 'Identifier', name: 'disconnectedCallback' },
+ value: x`function() {
+ this.slotObserver.disconnect();
+ }` as FunctionExpression
+ });
+ }
+
declaration.body.body.push(...accessors);
body.push(declaration);
diff --git a/src/compiler/interfaces.ts b/src/compiler/interfaces.ts
index a5e286462ff3..a7932615fa33 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 10588a08046b..9426b6a5ecad 100644
--- a/src/runtime/internal/Component.ts
+++ b/src/runtime/internal/Component.ts
@@ -165,9 +165,10 @@ export let SvelteElement;
if (typeof HTMLElement === 'function') {
SvelteElement = class extends HTMLElement {
$$: T$$;
+ protected _content;
+ slotting;
constructor() {
super();
- this.attachShadow({ mode: 'open' });
}
connectedCallback() {
diff --git a/test/custom-elements/samples/shadowdom-closed/main.svelte b/test/custom-elements/samples/shadowdom-closed/main.svelte
new file mode 100644
index 000000000000..43c2300bc04f
--- /dev/null
+++ b/test/custom-elements/samples/shadowdom-closed/main.svelte
@@ -0,0 +1,8 @@
+
default fallback content
+foo fallback content
+