diff --git a/site/content/docs/03-run-time.md b/site/content/docs/03-run-time.md
index d7bf3757dfe9..13a3966c8209 100644
--- a/site/content/docs/03-run-time.md
+++ b/site/content/docs/03-run-time.md
@@ -949,7 +949,7 @@ The following initialisation options can be provided:
| option | default | description |
| --- | --- | --- |
-| `target` | **none** | An `HTMLElement` to render to. This option is required
+| `target` | **none** | An `HTMLElement` or `ShadowRoot` to render to. This option is required
| `anchor` | `null` | A child of `target` to render the component immediately before
| `props` | `{}` | An object of properties to supply to the component
| `context` | `new Map()` | A `Map` of root-level context key-value pairs to supply to the component
diff --git a/src/compiler/compile/render_dom/index.ts b/src/compiler/compile/render_dom/index.ts
index 7894c4211579..b3ee1e91b177 100644
--- a/src/compiler/compile/render_dom/index.ts
+++ b/src/compiler/compile/render_dom/index.ts
@@ -50,11 +50,8 @@ export default function dom(
if (should_add_css) {
body.push(b`
- function ${add_css}() {
- var style = @element("style");
- style.id = "${component.stylesheet.id}-style";
- style.textContent = "${styles}";
- @append(@_document.head, style);
+ function ${add_css}(target) {
+ @append_styles(target, "${component.stylesheet.id}", "${styles}");
}
`);
}
@@ -486,7 +483,7 @@ export default function dom(
${css.code && b`this.shadowRoot.innerHTML = \`\`;`}
- @init(this, { target: this.shadowRoot, props: ${init_props}, customElement: true }, ${definition}, ${has_create_fragment ? 'create_fragment' : 'null'}, ${not_equal}, ${prop_indexes}, ${dirty});
+ @init(this, { target: this.shadowRoot, props: ${init_props}, customElement: true }, ${definition}, ${has_create_fragment ? 'create_fragment' : 'null'}, ${not_equal}, ${prop_indexes}, null, ${dirty});
${dev_props_check}
@@ -533,12 +530,21 @@ export default function dom(
name: options.dev ? '@SvelteComponentDev' : '@SvelteComponent'
};
+ const optional_parameters = [];
+ if (should_add_css) {
+ optional_parameters.push(add_css);
+ } else if (dirty) {
+ optional_parameters.push(x`null`);
+ }
+ if (dirty) {
+ optional_parameters.push(dirty);
+ }
+
const declaration = b`
class ${name} extends ${superclass} {
constructor(options) {
super(${options.dev && 'options'});
- ${should_add_css && b`if (!@_document.getElementById("${component.stylesheet.id}-style")) ${add_css}();`}
- @init(this, options, ${definition}, ${has_create_fragment ? 'create_fragment' : 'null'}, ${not_equal}, ${prop_indexes}, ${dirty});
+ @init(this, options, ${definition}, ${has_create_fragment ? 'create_fragment' : 'null'}, ${not_equal}, ${prop_indexes}, ${optional_parameters});
${options.dev && b`@dispatch_dev("SvelteRegisterComponent", { component: this, tagName: "${name.name}", options, id: create_fragment.name });`}
${dev_props_check}
diff --git a/src/runtime/internal/Component.ts b/src/runtime/internal/Component.ts
index 0d1d65cbdc79..e706928af305 100644
--- a/src/runtime/internal/Component.ts
+++ b/src/runtime/internal/Component.ts
@@ -38,6 +38,7 @@ interface T$$ {
on_destroy: any[];
skip_bound: boolean;
on_disconnect: any[];
+ root:Element|ShadowRoot
}
export function bind(component, name, callback) {
@@ -103,7 +104,7 @@ function make_dirty(component, i) {
component.$$.dirty[(i / 31) | 0] |= (1 << (i % 31));
}
-export function init(component, options, instance, create_fragment, not_equal, props, dirty = [-1]) {
+export function init(component, options, instance, create_fragment, not_equal, props, append_styles, dirty = [-1]) {
const parent_component = current_component;
set_current_component(component);
@@ -128,9 +129,12 @@ export function init(component, options, instance, create_fragment, not_equal, p
// everything else
callbacks: blank_object(),
dirty,
- skip_bound: false
+ skip_bound: false,
+ root: options.target || parent_component.$$.root
};
+ append_styles && append_styles($$.root);
+
let ready = false;
$$.ctx = instance
diff --git a/src/runtime/internal/dev.ts b/src/runtime/internal/dev.ts
index 73fd32bf1989..c20559f603bb 100644
--- a/src/runtime/internal/dev.ts
+++ b/src/runtime/internal/dev.ts
@@ -105,7 +105,7 @@ export interface SvelteComponentDev {
[accessor: string]: any;
}
interface IComponentOptions = Record> {
- target: Element;
+ target: Element|ShadowRoot;
anchor?: Element;
props?: Props;
context?: Map;
diff --git a/src/runtime/internal/dom.ts b/src/runtime/internal/dom.ts
index c1909dafe213..7af26917856d 100644
--- a/src/runtime/internal/dom.ts
+++ b/src/runtime/internal/dom.ts
@@ -125,6 +125,42 @@ function init_hydrate(target: NodeEx) {
}
}
+export function append_styles(
+ target: Node,
+ style_sheet_id: string,
+ styles: string
+) {
+ const append_styles_to = get_root_for_styles(target);
+
+ if (!append_styles_to?.getElementById(style_sheet_id)) {
+ const style = element('style');
+ style.id = style_sheet_id;
+ style.textContent = styles;
+ append_stylesheet(append_styles_to, style);
+ }
+}
+
+export function get_root_for_node(node: Node) {
+ if (!node) return document;
+
+ return (node.getRootNode ? node.getRootNode() : node.ownerDocument); // check for getRootNode because IE is still supported
+}
+
+function get_root_for_styles(node: Node) {
+ const root = get_root_for_node(node);
+ return (root as ShadowRoot).host ? root as ShadowRoot : root as Document;
+}
+
+export function append_empty_stylesheet(node: Node) {
+ const style_element = element('style') as HTMLStyleElement;
+ append_stylesheet(get_root_for_styles(node), style_element);
+ return style_element;
+}
+
+function append_stylesheet(node: ShadowRoot | Document, style: HTMLStyleElement) {
+ append((node as Document).head || node, style);
+}
+
export function append(target: NodeEx, node: NodeEx) {
if (is_hydrating) {
init_hydrate(target);
diff --git a/src/runtime/internal/style_manager.ts b/src/runtime/internal/style_manager.ts
index 8060e65a5d3f..a646c9b916f7 100644
--- a/src/runtime/internal/style_manager.ts
+++ b/src/runtime/internal/style_manager.ts
@@ -1,4 +1,4 @@
-import { element } from './dom';
+import { append_empty_stylesheet, get_root_for_node } from './dom';
import { raf } from './environment';
interface ExtendedDoc extends Document {
@@ -29,9 +29,9 @@ export function create_rule(node: Element & ElementCSSInlineStyle, a: number, b:
const rule = keyframes + `100% {${fn(b, 1 - b)}}\n}`;
const name = `__svelte_${hash(rule)}_${uid}`;
- const doc = node.ownerDocument as ExtendedDoc;
+ const doc = get_root_for_node(node) as unknown as ExtendedDoc;
active_docs.add(doc);
- const stylesheet = doc.__svelte_stylesheet || (doc.__svelte_stylesheet = doc.head.appendChild(element('style') as HTMLStyleElement).sheet as CSSStyleSheet);
+ const stylesheet = doc.__svelte_stylesheet || (doc.__svelte_stylesheet = append_empty_stylesheet(node).sheet as CSSStyleSheet);
const current_rules = doc.__svelte_rules || (doc.__svelte_rules = {});
if (!current_rules[name]) {
diff --git a/src/runtime/internal/utils.ts b/src/runtime/internal/utils.ts
index 9a372617239c..f487732b778a 100644
--- a/src/runtime/internal/utils.ts
+++ b/src/runtime/internal/utils.ts
@@ -89,7 +89,7 @@ export function create_slot(definition, ctx, $$scope, fn) {
}
}
-export function get_slot_context(definition, ctx, $$scope, fn) {
+function get_slot_context(definition, ctx, $$scope, fn) {
return definition[1] && fn
? assign($$scope.ctx.slice(), definition[1](fn(ctx)))
: $$scope.ctx;
diff --git a/test/js/samples/collapses-text-around-comments/expected.js b/test/js/samples/collapses-text-around-comments/expected.js
index bd1f3ce3d218..63a32bbf3863 100644
--- a/test/js/samples/collapses-text-around-comments/expected.js
+++ b/test/js/samples/collapses-text-around-comments/expected.js
@@ -2,6 +2,7 @@
import {
SvelteComponent,
append,
+ append_styles,
attr,
detach,
element,
@@ -13,11 +14,8 @@ import {
text
} from "svelte/internal";
-function add_css() {
- var style = element("style");
- style.id = "svelte-1a7i8ec-style";
- style.textContent = "p.svelte-1a7i8ec{color:red}";
- append(document.head, style);
+function add_css(target) {
+ append_styles(target, "svelte-1a7i8ec", "p.svelte-1a7i8ec{color:red}");
}
function create_fragment(ctx) {
@@ -58,9 +56,8 @@ function instance($$self, $$props, $$invalidate) {
class Component extends SvelteComponent {
constructor(options) {
super();
- if (!document.getElementById("svelte-1a7i8ec-style")) add_css();
- init(this, options, instance, create_fragment, safe_not_equal, { foo: 0 });
+ init(this, options, instance, create_fragment, safe_not_equal, { foo: 0 }, add_css);
}
}
-export default Component;
\ No newline at end of file
+export default Component;
diff --git a/test/js/samples/css-media-query/expected.js b/test/js/samples/css-media-query/expected.js
index f4776700599d..d74343220699 100644
--- a/test/js/samples/css-media-query/expected.js
+++ b/test/js/samples/css-media-query/expected.js
@@ -1,7 +1,7 @@
/* generated by Svelte vX.Y.Z */
import {
SvelteComponent,
- append,
+ append_styles,
attr,
detach,
element,
@@ -11,11 +11,8 @@ import {
safe_not_equal
} from "svelte/internal";
-function add_css() {
- var style = element("style");
- style.id = "svelte-1slhpfn-style";
- style.textContent = "@media(min-width: 1px){div.svelte-1slhpfn{color:red}}";
- append(document.head, style);
+function add_css(target) {
+ append_styles(target, "svelte-1slhpfn", "@media(min-width: 1px){div.svelte-1slhpfn{color:red}}");
}
function create_fragment(ctx) {
@@ -41,9 +38,8 @@ function create_fragment(ctx) {
class Component extends SvelteComponent {
constructor(options) {
super();
- if (!document.getElementById("svelte-1slhpfn-style")) add_css();
- init(this, options, null, create_fragment, safe_not_equal, {});
+ init(this, options, null, create_fragment, safe_not_equal, {}, add_css);
}
}
-export default Component;
\ No newline at end of file
+export default Component;
diff --git a/test/js/samples/css-shadow-dom-keyframes/expected.js b/test/js/samples/css-shadow-dom-keyframes/expected.js
index 4d188201eb27..5d65949488d6 100644
--- a/test/js/samples/css-shadow-dom-keyframes/expected.js
+++ b/test/js/samples/css-shadow-dom-keyframes/expected.js
@@ -46,7 +46,8 @@ class Component extends SvelteElement {
null,
create_fragment,
safe_not_equal,
- {}
+ {},
+ null
);
if (options) {
@@ -58,4 +59,4 @@ class Component extends SvelteElement {
}
customElements.define("custom-element", Component);
-export default Component;
\ No newline at end of file
+export default Component;
diff --git a/test/sourcemaps/samples/compile-option-dev/test.js b/test/sourcemaps/samples/compile-option-dev/test.js
index bf240a5a8927..f169ab8832b1 100644
--- a/test/sourcemaps/samples/compile-option-dev/test.js
+++ b/test/sourcemaps/samples/compile-option-dev/test.js
@@ -4,8 +4,8 @@ const b64dec = s => Buffer.from(s, 'base64').toString();
export async function test({ assert, css, js }) {
- // We check that the css source map embedded in the js is accurate
- const match = js.code.match(/\tstyle\.textContent = "(.*?)(?:\\n\/\*# sourceMappingURL=data:(.*?);charset=(.*?);base64,(.*?) \*\/)?";\n/);
+ // We check that the css source map embedded in the js is accurate
+ const match = js.code.match(/\tappend_styles\(target, "svelte-.{6}", "(.*?)(?:\\n\/\*# sourceMappingURL=data:(.*?);charset=(.*?);base64,(.*?) \*\/)?"\);\n/);
assert.notEqual(match, null);
const [mimeType, encoding, cssMapBase64] = match.slice(2);