diff --git a/documentation/docs/98-reference/.generated/compile-errors.md b/documentation/docs/98-reference/.generated/compile-errors.md index c5703c636b..083b081001 100644 --- a/documentation/docs/98-reference/.generated/compile-errors.md +++ b/documentation/docs/98-reference/.generated/compile-errors.md @@ -1102,9 +1102,11 @@ Value must be %list%, if specified ### svelte_options_invalid_customelement_shadow ``` -"shadow" must be either "open" or "none" +"shadow" must be either "open", "none" or `ShadowRootInit` ``` +See https://developer.mozilla.org/en-US/docs/Web/API/Element/attachShadow#options for more information on valid shadow root constructor options + ### svelte_options_invalid_tagname ``` diff --git a/packages/svelte/elements.d.ts b/packages/svelte/elements.d.ts index 17ff100729..34ed251400 100644 --- a/packages/svelte/elements.d.ts +++ b/packages/svelte/elements.d.ts @@ -2061,7 +2061,7 @@ export interface SvelteHTMLElements { | undefined | { tag?: string; - shadow?: 'open' | 'none' | undefined; + shadow?: 'open' | 'none' | ShadowRootInit | undefined; props?: | Record< string, diff --git a/packages/svelte/messages/compile-errors/template.md b/packages/svelte/messages/compile-errors/template.md index ac95bfe4a7..14c9be52e2 100644 --- a/packages/svelte/messages/compile-errors/template.md +++ b/packages/svelte/messages/compile-errors/template.md @@ -411,7 +411,9 @@ HTML restricts where certain elements can appear. In case of a violation the bro ## svelte_options_invalid_customelement_shadow -> "shadow" must be either "open" or "none" +> "shadow" must be either "open", "none" or `ShadowRootInit` + +See https://developer.mozilla.org/en-US/docs/Web/API/Element/attachShadow#options for more information on valid shadow root constructor options ## svelte_options_invalid_tagname diff --git a/packages/svelte/src/compiler/errors.js b/packages/svelte/src/compiler/errors.js index 5e3968215f..0cc51e39a3 100644 --- a/packages/svelte/src/compiler/errors.js +++ b/packages/svelte/src/compiler/errors.js @@ -1550,12 +1550,12 @@ export function svelte_options_invalid_customelement_props(node) { } /** - * "shadow" must be either "open" or "none" + * "shadow" must be either "open", "none" or `ShadowRootInit` * @param {null | number | NodeLike} node * @returns {never} */ export function svelte_options_invalid_customelement_shadow(node) { - e(node, 'svelte_options_invalid_customelement_shadow', `"shadow" must be either "open" or "none"\nhttps://svelte.dev/e/svelte_options_invalid_customelement_shadow`); + e(node, 'svelte_options_invalid_customelement_shadow', `"shadow" must be either "open", "none" or \`ShadowRootInit\`\nhttps://svelte.dev/e/svelte_options_invalid_customelement_shadow`); } /** diff --git a/packages/svelte/src/compiler/phases/1-parse/read/options.js b/packages/svelte/src/compiler/phases/1-parse/read/options.js index a36e101468..24b5f6967b 100644 --- a/packages/svelte/src/compiler/phases/1-parse/read/options.js +++ b/packages/svelte/src/compiler/phases/1-parse/read/options.js @@ -133,11 +133,49 @@ export default function read_options(node) { const shadow = properties.find(([name]) => name === 'shadow')?.[1]; if (shadow) { - const shadowdom = shadow?.value; - if (shadowdom !== 'open' && shadowdom !== 'none') { - e.svelte_options_invalid_customelement_shadow(shadow); + if (shadow.type === 'Literal') { + if (shadow.value !== 'open' && shadow.value !== 'none') { + e.svelte_options_invalid_customelement_shadow(attribute); + } + ce.shadow = shadow.value; + } else if (shadow.type === 'ObjectExpression') { + ce.shadow = { mode: 'open' }; + for (const property of /** @type {ObjectExpression} */ (shadow).properties) { + if ( + property.type !== 'Property' || + property.computed || + property.key.type !== 'Identifier' || + property.value.type !== 'Literal' + ) { + e.svelte_options_invalid_customelement_shadow(attribute); + } + + if (property.key.name === 'mode') { + if (!['open', 'closed'].includes(/** @type {string} */ (property.value.value))) { + e.svelte_options_invalid_customelement_shadow(attribute); + } + ce.shadow.mode = /** @type {any} */ (property.value.value); + } else if (property.key.name === 'slotAssignment') { + if (!['named', 'manual'].includes(/** @type {string} */ (property.value.value))) { + e.svelte_options_invalid_customelement_shadow(attribute); + } + ce.shadow.slotAssignment = /** @type {any} */ (property.value.value); + } else if ( + property.key.name === 'clonable' || + property.key.name === 'delegatesFocus' || + property.key.name === 'serializable' + ) { + if (typeof property.value.value !== 'boolean') { + e.svelte_options_invalid_customelement_shadow(attribute); + } + ce.shadow[property.key.name] = property.value.value; + } else { + e.svelte_options_invalid_customelement_shadow(attribute); + } + } + } else { + e.svelte_options_invalid_customelement_shadow(attribute); } - ce.shadow = shadowdom; } const extend = properties.find(([name]) => name === 'extend')?.[1]; diff --git a/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js b/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js index f51042eb7c..664d980149 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js @@ -643,7 +643,24 @@ export function client_component(analysis, options) { const accessors_str = b.array( analysis.exports.map(({ name, alias }) => b.literal(alias ?? name)) ); - const use_shadow_dom = typeof ce === 'boolean' || ce.shadow !== 'none' ? true : false; + + /** @type {ShadowRootInit | {}} */ + let ce_shadow_root_init = {}; + if (typeof ce === 'boolean' || ce.shadow === 'open' || ce.shadow === undefined) { + ce_shadow_root_init = { mode: 'open' }; + } else if (ce.shadow === 'none') { + ce_shadow_root_init = {}; + } else if (typeof ce.shadow === 'object') { + ce_shadow_root_init = ce.shadow; + } + + /** @type {ESTree.Property[]} */ + const shadow_root_init_str = Object.entries(ce_shadow_root_init).map(([key, value]) => + b.init(key, b.literal(value)) + ); + const shadow_root_init = shadow_root_init_str.length + ? b.object(shadow_root_init_str) + : undefined; const create_ce = b.call( '$.create_custom_element', @@ -651,7 +668,7 @@ export function client_component(analysis, options) { b.object(props_str), slots_str, accessors_str, - b.literal(use_shadow_dom), + shadow_root_init, /** @type {any} */ (typeof ce !== 'boolean' ? ce.extend : undefined) ); diff --git a/packages/svelte/src/compiler/types/template.d.ts b/packages/svelte/src/compiler/types/template.d.ts index 9cf498d187..039c508727 100644 --- a/packages/svelte/src/compiler/types/template.d.ts +++ b/packages/svelte/src/compiler/types/template.d.ts @@ -88,7 +88,7 @@ export namespace AST { css?: 'injected'; customElement?: { tag?: string; - shadow?: 'open' | 'none'; + shadow?: 'open' | 'none' | (ShadowRootInit & { clonable?: boolean }); props?: Record< string, { diff --git a/packages/svelte/src/internal/client/dom/elements/custom-element.js b/packages/svelte/src/internal/client/dom/elements/custom-element.js index 2d118bfab3..d4e550291b 100644 --- a/packages/svelte/src/internal/client/dom/elements/custom-element.js +++ b/packages/svelte/src/internal/client/dom/elements/custom-element.js @@ -35,18 +35,22 @@ if (typeof HTMLElement === 'function') { $$l_u = new Map(); /** @type {any} The managed render effect for reflecting attributes */ $$me; + /** @type {ShadowRoot | null} The ShadowRoot of the custom element */ + $$shadowRoot = null; /** * @param {*} $$componentCtor * @param {*} $$slots - * @param {*} use_shadow_dom + * @param {ShadowRootInit | undefined} shadow_root_init */ - constructor($$componentCtor, $$slots, use_shadow_dom) { + constructor($$componentCtor, $$slots, shadow_root_init) { super(); this.$$ctor = $$componentCtor; this.$$s = $$slots; - if (use_shadow_dom) { - this.attachShadow({ mode: 'open' }); + if (shadow_root_init) { + // We need to store the reference to shadow root, because `closed` shadow root cannot be + // accessed with `this.shadowRoot`. + this.$$shadowRoot = this.attachShadow(shadow_root_init); } } @@ -136,7 +140,7 @@ if (typeof HTMLElement === 'function') { } this.$$c = createClassComponent({ component: this.$$ctor, - target: this.shadowRoot || this, + target: this.$$shadowRoot || this, props: { ...this.$$d, $$slots, @@ -277,7 +281,7 @@ function get_custom_elements_slots(element) { * @param {Record} props_definition The props to observe * @param {string[]} slots The slots to create * @param {string[]} exports Explicitly exported values, other than props - * @param {boolean} use_shadow_dom Whether to use shadow DOM + * @param {ShadowRootInit | undefined} shadow_root_init Options passed to shadow DOM constructor * @param {(ce: new () => HTMLElement) => new () => HTMLElement} [extend] */ export function create_custom_element( @@ -285,12 +289,12 @@ export function create_custom_element( props_definition, slots, exports, - use_shadow_dom, + shadow_root_init, extend ) { let Class = class extends SvelteElement { constructor() { - super(Component, slots, use_shadow_dom); + super(Component, slots, shadow_root_init); this.$$p_d = props_definition; } static get observedAttributes() { diff --git a/packages/svelte/types/index.d.ts b/packages/svelte/types/index.d.ts index 5e3ca77eb5..350ebcd1cb 100644 --- a/packages/svelte/types/index.d.ts +++ b/packages/svelte/types/index.d.ts @@ -1224,7 +1224,7 @@ declare module 'svelte/compiler' { css?: 'injected'; customElement?: { tag?: string; - shadow?: 'open' | 'none'; + shadow?: 'open' | 'none' | (ShadowRootInit & { clonable?: boolean }); props?: Record< string, {