diff --git a/README.md b/README.md index a960cde..496c70d 100644 --- a/README.md +++ b/README.md @@ -14,10 +14,10 @@ const Greeting = ({ name = 'World' }) => (

Hello, {name}!

); -register(Greeting, 'x-greeting', ['name'], { shadow: true, mode: 'open', adoptedStyleSheets: [] }); -// ^ ^ ^ ^ ^ ^ -// | HTML tag name | use shadow-dom | use adoptedStyleSheets -// Component definition Observed attributes Encapsulation mode for the shadow DOM tree +register(Greeting, 'x-greeting', ['name'], { shadow: true, mode: 'open', adoptedStyleSheets: [], serializable: true }); +// ^ ^ ^ ^ ^ ^ ^ +// | HTML tag name | use shadow-dom | use adoptedStyleSheets | +// Component definition Observed attributes Encapsulation mode for the shadow DOM tree Root is serializable ``` > _**\* Note:** as per the [Custom Elements specification](https://html.spec.whatwg.org/multipage/custom-elements.html#valid-custom-element-name), the tag name must contain a hyphen._ @@ -81,7 +81,7 @@ register(FullName, 'full-name'); ### Passing slots as props -The `register()` function also accepts an optional fourth parameter, an options bag. At present, it allows you to opt-in to using shadow DOM for your custom element by setting the `shadow` property to `true`, and if so, you can also specify the encapsulation mode with `mode`, which can be either `'open'` or `'closed'`. +The `register()` function also accepts an optional fourth parameter, an options bag. At present, it allows you to opt-in to using shadow DOM for your custom element by setting the `shadow` property to `true`, and if so, you can also specify the encapsulation mode with `mode`, which can be either `'open'` or `'closed'`. Additionally, you may mark the shadow root as being serializable with the boolean `serializable` property. When using shadow DOM, you can make use of named `` elements in your component to forward the custom element's children into specific places in the shadow tree. diff --git a/src/index.d.ts b/src/index.d.ts index 8fc37c3..3703fe9 100644 --- a/src/index.d.ts +++ b/src/index.d.ts @@ -8,6 +8,7 @@ type Options = shadow: true; mode?: 'open' | 'closed'; adoptedStyleSheets?: CSSStyleSheet[]; + serializable?: boolean; }; /** diff --git a/src/index.js b/src/index.js index 5148a0d..1ae5828 100644 --- a/src/index.js +++ b/src/index.js @@ -15,7 +15,10 @@ export default function register(Component, tagName, propNames, options) { inst._vdomComponent = Component; if (options && options.shadow) { - inst._root = inst.attachShadow({ mode: options.mode || 'open' }); + inst._root = inst.attachShadow({ + mode: options.mode || 'open', + serializable: options.serializable ?? false, + }); if (options.adoptedStyleSheets) { inst._root.adoptedStyleSheets = options.adoptedStyleSheets; diff --git a/test/index.test.jsx b/test/index.test.jsx index 4fce9fe..734ab6c 100644 --- a/test/index.test.jsx +++ b/test/index.test.jsx @@ -421,4 +421,47 @@ describe('web components', () => { '

Light DOM Children

Child 1

Child 2

' ); }); + + it('supports the `serializable` option', async () => { + function SerializableComponent() { + return
Serializable Shadow DOM
; + } + + function NonSerializableComponent() { + return
Non-serializable Shadow DOM
; + } + + registerElement(SerializableComponent, 'x-serializable', [], { + shadow: true, + serializable: true, + }); + + registerElement(NonSerializableComponent, 'x-non-serializable', [], { + shadow: true, + }); + + root.innerHTML = ` + + + `; + + const serializableEl = document.querySelector('x-serializable'); + const nonSerializableEl = document.querySelector('x-non-serializable'); + + assert.isTrue(serializableEl.shadowRoot.serializable); + assert.isFalse(nonSerializableEl.shadowRoot.serializable); + + const serializableHtml = serializableEl.getHTML({ + serializableShadowRoots: true, + }); + const nonSerializableHtml = nonSerializableEl.getHTML({ + serializableShadowRoots: true, + }); + + assert.equal( + serializableHtml, + '' + ); + assert.isEmpty(nonSerializableHtml); + }); });