implement shadowdom option

pull/8457/head
Simon Holthausen 3 years ago
parent 96e97682e0
commit 840a7bed4b

@ -46,6 +46,7 @@ interface ComponentOptions {
accessors?: boolean; accessors?: boolean;
preserveWhitespace?: boolean; preserveWhitespace?: boolean;
ceProps?: Record<string, { reflect?: boolean; type?: 'String' | 'Boolean' | 'Number' | 'Array' | 'Object', attribute?: string }>; ceProps?: Record<string, { reflect?: boolean; type?: 'String' | 'Boolean' | 'Number' | 'Array' | 'Object', attribute?: string }>;
shadowdom?: 'open' | 'none';
} }
const regex_leading_directory_separator = /^[/\\]/; const regex_leading_directory_separator = /^[/\\]/;
@ -1574,6 +1575,17 @@ function process_component_options(component: Component, nodes) {
break; 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': { case 'ceProps': {
const error = () => component.error(attribute, compiler_errors.invalid_ceProps_attribute); const error = () => component.error(attribute, compiler_errors.invalid_ceProps_attribute);
const { value } = attribute; const { value } = attribute;

@ -206,6 +206,10 @@ export default {
code: 'invalid-tag-attribute', code: 'invalid-tag-attribute',
message: "'tag' must be a string literal" 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: { invalid_ceProps_attribute: {
code: 'invalid-ceProps-attribute', code: 'invalid-ceProps-attribute',
message: "'ceProps' must be a statically analyzable object literal of the form " + message: "'ceProps' must be a statically analyzable object literal of the form " +

@ -555,9 +555,10 @@ export default function dom(
.filter(accessor => !writable_props.some(prop => prop.export_name === accessor.key.name)) .filter(accessor => !writable_props.some(prop => prop.export_name === accessor.key.name))
.map(accessor => `"${accessor.key.name}"`) .map(accessor => `"${accessor.key.name}"`)
.join(','); .join(',');
const use_shadow_dom = component.component_options.shadowdom !== 'none' ? 'true' : 'false';
body.push( 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}));`
); );
} }

@ -157,11 +157,14 @@ if (typeof HTMLElement === 'function') {
constructor( constructor(
private $$componentCtor: ComponentType, private $$componentCtor: ComponentType,
private $$slots: string[] private $$slots: string[],
use_shadow_dom: boolean
) { ) {
super(); super();
if (use_shadow_dom) {
this.attachShadow({ mode: 'open' }); this.attachShadow({ mode: 'open' });
} }
}
addEventListener(type: string, listener: any, options?: any): void { addEventListener(type: string, listener: any, options?: any): void {
// We can't determine upfront if the event is a custom event or not, so we have to // We can't determine upfront if the event is a custom event or not, so we have to
@ -237,7 +240,7 @@ if (typeof HTMLElement === 'function') {
} }
this.$$component = new this.$$componentCtor({ this.$$component = new this.$$componentCtor({
target: this.shadowRoot!, target: this.shadowRoot || this,
props: { props: {
...this.$$data, ...this.$$data,
$$slots, $$slots,
@ -330,17 +333,19 @@ interface CustomElementPropDefinition {
* @param props_definition The props to observe * @param props_definition The props to observe
* @param slots The slots to create * @param slots The slots to create
* @param accessors Other accessors besides the ones for props the component has * @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 * @returns A custom element class
*/ */
export function create_custom_element( export function create_custom_element(
Component: ComponentType, Component: ComponentType,
props_definition: Record<string, CustomElementPropDefinition>, props_definition: Record<string, CustomElementPropDefinition>,
slots: string[], slots: string[],
accessors: string[] accessors: string[],
use_shadow_dom: boolean
) { ) {
const Class = class extends SvelteElement { const Class = class extends SvelteElement {
constructor() { constructor() {
super(Component, slots); super(Component, slots, use_shadow_dom);
this.$$props_definition = props_definition; this.$$props_definition = props_definition;
} }

@ -0,0 +1,13 @@
<svelte:options tag="custom-element" shadowdom="none" />
<script>
export let name;
</script>
<h1>Hello {name}!</h1>
<style>
h1 {
color: red;
}
</style>

@ -0,0 +1,16 @@
import * as assert from 'assert';
import { tick } from 'svelte';
import './main.svelte';
export default async function (target) {
target.innerHTML = '<custom-element name="world"></custom-element>';
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)');
}

@ -4,7 +4,7 @@ import CustomElement from './main.svelte';
import { create_custom_element } from 'svelte/internal'; import { create_custom_element } from 'svelte/internal';
export default async function (target) { 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 = '<no-tag name="world"></no-tag>'; target.innerHTML = '<no-tag name="world"></no-tag>';
await tick(); await tick();

@ -4,7 +4,7 @@ import CustomElement from './main.svelte';
import { create_custom_element } from 'svelte/internal'; import { create_custom_element } from 'svelte/internal';
export default async function (target) { 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 = '<no-tag name="world"></no-tag>'; target.innerHTML = '<no-tag name="world"></no-tag>';
await tick(); await tick();

@ -4,7 +4,7 @@ import CustomElement from './main.svelte';
import { create_custom_element } from 'svelte/internal'; import { create_custom_element } from 'svelte/internal';
export default async function (target) { 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 = '<no-tag name="world"></no-tag>'; target.innerHTML = '<no-tag name="world"></no-tag>';
await tick(); await tick();

@ -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; export default Component;
Loading…
Cancel
Save