feat: allow passing ShadowRootInit to custom element shadow options

pull/17088/head
Bladesheng 2 months ago
parent 8ebc3b1337
commit f6f4e67028

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

@ -2061,7 +2061,7 @@ export interface SvelteHTMLElements {
| undefined
| {
tag?: string;
shadow?: 'open' | 'none' | undefined;
shadow?: 'open' | 'none' | ShadowRootInit | undefined;
props?:
| Record<
string,

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

@ -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`);
}
/**

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

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

@ -88,7 +88,7 @@ export namespace AST {
css?: 'injected';
customElement?: {
tag?: string;
shadow?: 'open' | 'none';
shadow?: 'open' | 'none' | (ShadowRootInit & { clonable?: boolean });
props?: Record<
string,
{

@ -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<string, CustomElementPropDefinition>} 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() {

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

Loading…
Cancel
Save