support rendering components in a shadow dom (#5870)

pull/6560/head
Hofer Ivan 3 years ago committed by GitHub
parent a6448185d6
commit 5cfefeb6e7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -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

@ -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 = \`<style>${css.code.replace(/\\/g, '\\\\')}${options.dev ? `\n/*# sourceMappingURL=${css.map.toUrl()} */` : ''}</style>\`;`}
@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}

@ -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

@ -105,7 +105,7 @@ export interface SvelteComponentDev {
[accessor: string]: any;
}
interface IComponentOptions<Props extends Record<string, any> = Record<string, any>> {
target: Element;
target: Element|ShadowRoot;
anchor?: Element;
props?: Props;
context?: Map<any, any>;

@ -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);

@ -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]) {

@ -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;

@ -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;
export default Component;

@ -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;
export default Component;

@ -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;
export default Component;

@ -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);

Loading…
Cancel
Save