From 8cc67854b6e0493040406b793b2d65cbc6bdc097 Mon Sep 17 00:00:00 2001 From: Cris Ward Date: Mon, 27 Jan 2020 20:20:49 +0000 Subject: [PATCH 01/12] [WIP] #1748 - Custom elements light dom, open / closed --- src/compiler/compile/Component.ts | 15 +++++++++++++-- src/compiler/compile/index.ts | 1 + src/compiler/compile/render_dom/index.ts | 17 +++++++++++------ src/compiler/interfaces.ts | 7 ++++++- src/runtime/internal/Component.ts | 1 - .../css-shadow-dom-keyframes/expected.js | 1 + 6 files changed, 32 insertions(+), 10 deletions(-) diff --git a/src/compiler/compile/Component.ts b/src/compiler/compile/Component.ts index 9822529ece61..9ed74ea3a983 100644 --- a/src/compiler/compile/Component.ts +++ b/src/compiler/compile/Component.ts @@ -14,7 +14,7 @@ import Stylesheet from './css/Stylesheet'; import { test } from '../config'; import Fragment from './nodes/Fragment'; import internal_exports from './internal_exports'; -import { Ast, CompileOptions, Var, Warning, CssResult } from '../interfaces'; +import { Ast, CompileOptions, Var, Warning, CssResult, ShadowDomMode } from '../interfaces'; import error from '../utils/error'; import get_code_frame from '../utils/get_code_frame'; import flatten_reference from './utils/flatten_reference'; @@ -30,6 +30,7 @@ import check_graph_for_cycles from './utils/check_graph_for_cycles'; import { print, x, b } from 'code-red'; interface ComponentOptions { + shadowdom?: ShadowDomMode; namespace?: string; tag?: string; immutable?: boolean; @@ -158,6 +159,7 @@ export default class Component { }); } this.tag = this.component_options.tag || compile_options.tag; + this.compile_options.shadowDom = this.component_options.shadowdom || "open"; } else { this.tag = this.name.name; } @@ -170,7 +172,7 @@ export default class Component { this.walk_instance_js_post_template(); - if (!compile_options.customElement) this.stylesheet.reify(); + if (!compile_options.customElement || compile_options.shadowDom=="none") this.stylesheet.reify(); this.stylesheet.warn_on_unused_selectors(this); } @@ -1414,7 +1416,16 @@ function process_component_options(component: Component, nodes) { component_options[name] = value; break; } + case 'shadowdom':{ + const code = 'invalid-shadowdom-attribute'; + const message = `'shadowdom' must be set to 'open', 'closed or 'none'`; + const value = get_value(attribute, code, message) + if(value != "open" && value != "none" && value != "closed") + component.error(attribute, { code, message }); + component_options[name] = value; + break; + } default: component.error(attribute, { code: `invalid-options-attribute`, diff --git a/src/compiler/compile/index.ts b/src/compiler/compile/index.ts index 12b161aeeb0e..5fe11fd2f5eb 100644 --- a/src/compiler/compile/index.ts +++ b/src/compiler/compile/index.ts @@ -22,6 +22,7 @@ const valid_options = [ 'hydratable', 'legacy', 'customElement', + 'shadowDom', 'tag', 'css', 'loopGuardTimeout', diff --git a/src/compiler/compile/render_dom/index.ts b/src/compiler/compile/render_dom/index.ts index 26fa4a70f81f..0a3857ee1aea 100644 --- a/src/compiler/compile/render_dom/index.ts +++ b/src/compiler/compile/render_dom/index.ts @@ -20,7 +20,7 @@ export default function dom( block.has_outro_method = true; // prevent fragment being created twice (#1063) - if (options.customElement) block.chunks.create.push(b`this.c = @noop;`); + if (options.customElement && options.shadowDom !== "none") block.chunks.create.push(b`this.c = @noop;`); const body = []; @@ -29,7 +29,7 @@ export default function dom( body.push(b`const ${renderer.file_var} = ${file};`); } - const css = component.stylesheet.render(options.filename, !options.customElement); + const css = component.stylesheet.render(options.filename, (!options.customElement || options.shadowDom === "none")); const styles = component.stylesheet.has_styles && options.dev ? `${css.code}\n/*# sourceMappingURL=${css.map.toUrl()} */` : css.code; @@ -37,9 +37,9 @@ export default function dom( const add_css = component.get_unique_name('add_css'); const should_add_css = ( - !options.customElement && + (!options.customElement && !!styles && - options.css !== false + options.css !== false ) || options.shadowDom === "none" ); if (should_add_css) { @@ -437,14 +437,19 @@ export default function dom( } if (options.customElement) { + const lightDom = options.shadowDom === 'none'; const declaration = b` class ${name} extends @SvelteElement { constructor(options) { super(); + ${!lightDom && b` + this._root =this.attachShadow({ mode: '${options.shadowDom}' }); + `} - ${css.code && b`this.shadowRoot.innerHTML = \`\`;`} + ${css.code && !lightDom && b`this._root.innerHTML = \`\`;`} + ${should_add_css && lightDom && b`if (!@_document.getElementById("${component.stylesheet.id}-style")) ${add_css}();`} - @init(this, { target: this.shadowRoot }, ${definition}, ${has_create_fragment ? 'create_fragment': 'null'}, ${not_equal}, ${prop_indexes}, ${dirty}); + @init(this, { target: ${lightDom ? 'this' : 'this._root'} }, ${definition}, ${has_create_fragment ? 'create_fragment': 'null'}, ${not_equal}, ${prop_indexes}, ${dirty}); ${dev_props_check} diff --git a/src/compiler/interfaces.ts b/src/compiler/interfaces.ts index a5e286462ff3..a7932615fa33 100644 --- a/src/compiler/interfaces.ts +++ b/src/compiler/interfaces.ts @@ -120,6 +120,7 @@ export interface CompileOptions { hydratable?: boolean; legacy?: boolean; customElement?: boolean; + shadowDom?: ShadowDomMode; tag?: string; css?: boolean; loopGuardTimeout?: number; @@ -166,4 +167,8 @@ export interface Var { export interface CssResult { code: string; map: SourceMap; -} \ No newline at end of file +} + +export type ShadowDomMode = 'none' + | 'open' + | 'closed' \ No newline at end of file diff --git a/src/runtime/internal/Component.ts b/src/runtime/internal/Component.ts index 10588a08046b..f8fbcf8ced2d 100644 --- a/src/runtime/internal/Component.ts +++ b/src/runtime/internal/Component.ts @@ -167,7 +167,6 @@ if (typeof HTMLElement === 'function') { $$: T$$; constructor() { super(); - this.attachShadow({ mode: 'open' }); } connectedCallback() { diff --git a/test/js/samples/css-shadow-dom-keyframes/expected.js b/test/js/samples/css-shadow-dom-keyframes/expected.js index a0a0ebe0211b..aa069dd46550 100644 --- a/test/js/samples/css-shadow-dom-keyframes/expected.js +++ b/test/js/samples/css-shadow-dom-keyframes/expected.js @@ -33,6 +33,7 @@ function create_fragment(ctx) { class Component extends SvelteElement { constructor(options) { super(); + this.attachShadow({ mode: "open" }); this.shadowRoot.innerHTML = ``; init(this, { target: this.shadowRoot }, null, create_fragment, safe_not_equal, {}); From 6b62b69c3400c3f8379770979f19a946926c6246 Mon Sep 17 00:00:00 2001 From: Cris Ward Date: Tue, 28 Jan 2020 07:40:38 +0000 Subject: [PATCH 02/12] fixed linting issues --- src/compiler/compile/Component.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/compiler/compile/Component.ts b/src/compiler/compile/Component.ts index 9ed74ea3a983..3dfb4fd275cf 100644 --- a/src/compiler/compile/Component.ts +++ b/src/compiler/compile/Component.ts @@ -1416,11 +1416,11 @@ function process_component_options(component: Component, nodes) { component_options[name] = value; break; } - case 'shadowdom':{ + case 'shadowdom': { const code = 'invalid-shadowdom-attribute'; const message = `'shadowdom' must be set to 'open', 'closed or 'none'`; - const value = get_value(attribute, code, message) - if(value != "open" && value != "none" && value != "closed") + const value = get_value(attribute, code, message); + if (value != "open" && value != "none" && value != "closed") component.error(attribute, { code, message }); component_options[name] = value; From 2ce702293e9a612d53b8d978406e51eb44ccb768 Mon Sep 17 00:00:00 2001 From: Cris Ward Date: Tue, 28 Jan 2020 07:47:11 +0000 Subject: [PATCH 03/12] fixed tests --- test/js/samples/css-shadow-dom-keyframes/expected.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/js/samples/css-shadow-dom-keyframes/expected.js b/test/js/samples/css-shadow-dom-keyframes/expected.js index aa069dd46550..e05a7c02412a 100644 --- a/test/js/samples/css-shadow-dom-keyframes/expected.js +++ b/test/js/samples/css-shadow-dom-keyframes/expected.js @@ -33,9 +33,9 @@ function create_fragment(ctx) { class Component extends SvelteElement { constructor(options) { super(); - this.attachShadow({ mode: "open" }); - this.shadowRoot.innerHTML = ``; - init(this, { target: this.shadowRoot }, null, create_fragment, safe_not_equal, {}); + this._root = this.attachShadow({ mode: "open" }); + this._root.innerHTML = ``; + init(this, { target: this._root }, null, create_fragment, safe_not_equal, {}); if (options) { if (options.target) { From d21d020648b5ee025fc79b339939319529f28bf3 Mon Sep 17 00:00:00 2001 From: Cris Ward Date: Tue, 28 Jan 2020 13:06:00 +0000 Subject: [PATCH 04/12] Added tests for shadoom and css with none --- .../samples/shadowdom-closed/main.svelte | 8 ++++++++ .../samples/shadowdom-closed/test.js | 13 +++++++++++++ .../samples/shadowdom-none-css/main.svelte | 10 ++++++++++ .../samples/shadowdom-none-css/test.js | 10 ++++++++++ .../samples/shadowdom-none/main.svelte | 8 ++++++++ test/custom-elements/samples/shadowdom-none/test.js | 12 ++++++++++++ 6 files changed, 61 insertions(+) create mode 100644 test/custom-elements/samples/shadowdom-closed/main.svelte create mode 100644 test/custom-elements/samples/shadowdom-closed/test.js create mode 100644 test/custom-elements/samples/shadowdom-none-css/main.svelte create mode 100644 test/custom-elements/samples/shadowdom-none-css/test.js create mode 100644 test/custom-elements/samples/shadowdom-none/main.svelte create mode 100644 test/custom-elements/samples/shadowdom-none/test.js diff --git a/test/custom-elements/samples/shadowdom-closed/main.svelte b/test/custom-elements/samples/shadowdom-closed/main.svelte new file mode 100644 index 000000000000..43c2300bc04f --- /dev/null +++ b/test/custom-elements/samples/shadowdom-closed/main.svelte @@ -0,0 +1,8 @@ + + + + +

Hello {name}!

+ diff --git a/test/custom-elements/samples/shadowdom-closed/test.js b/test/custom-elements/samples/shadowdom-closed/test.js new file mode 100644 index 000000000000..be2b9743e0c9 --- /dev/null +++ b/test/custom-elements/samples/shadowdom-closed/test.js @@ -0,0 +1,13 @@ +import * as assert from 'assert'; +import './main.svelte'; + +export default function (target) { + target.innerHTML = ''; + const el = target.querySelector('custom-element'); + + assert.equal(el.name, 'world'); + + const h1 = el._root.querySelector('h1'); + assert.equal(h1.textContent, 'Hello world'); + assert.equal(el.shadowRoot, null); +} \ No newline at end of file diff --git a/test/custom-elements/samples/shadowdom-none-css/main.svelte b/test/custom-elements/samples/shadowdom-none-css/main.svelte new file mode 100644 index 000000000000..20061da3cbb1 --- /dev/null +++ b/test/custom-elements/samples/shadowdom-none-css/main.svelte @@ -0,0 +1,10 @@ + + + + +

Hello World

+ diff --git a/test/custom-elements/samples/shadowdom-none-css/test.js b/test/custom-elements/samples/shadowdom-none-css/test.js new file mode 100644 index 000000000000..9f6ec3c20ef6 --- /dev/null +++ b/test/custom-elements/samples/shadowdom-none-css/test.js @@ -0,0 +1,10 @@ +import * as assert from 'assert'; +import './main.svelte'; + +export default function (target) { + target.innerHTML = ''; + const el = target.querySelector('custom-element'); + const h1 = el.querySelector('h1'); + const colour = getComputedStyle(h1).color; + assert.equal(colour,"rgb(255, 0, 0)"); +} \ No newline at end of file diff --git a/test/custom-elements/samples/shadowdom-none/main.svelte b/test/custom-elements/samples/shadowdom-none/main.svelte new file mode 100644 index 000000000000..79a40384ec03 --- /dev/null +++ b/test/custom-elements/samples/shadowdom-none/main.svelte @@ -0,0 +1,8 @@ + + + + +

Hello {name}!

+ diff --git a/test/custom-elements/samples/shadowdom-none/test.js b/test/custom-elements/samples/shadowdom-none/test.js new file mode 100644 index 000000000000..7cd82a30eeec --- /dev/null +++ b/test/custom-elements/samples/shadowdom-none/test.js @@ -0,0 +1,12 @@ +import * as assert from 'assert'; +import './main.svelte'; + +export default function (target) { + target.innerHTML = ''; + const el = target.querySelector('custom-element'); + + assert.equal(el.name, 'world'); + + const h1 = el.querySelector('h1'); + assert.equal(h1.textContent, 'Hello world!'); +} \ No newline at end of file From cf732809d6a1e0d2ae0b334cb463b01b22ece3d6 Mon Sep 17 00:00:00 2001 From: Cris Ward Date: Tue, 28 Jan 2020 13:11:08 +0000 Subject: [PATCH 05/12] fixed test --- test/custom-elements/samples/shadowdom-closed/test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/custom-elements/samples/shadowdom-closed/test.js b/test/custom-elements/samples/shadowdom-closed/test.js index be2b9743e0c9..cc0ed2ff8ed6 100644 --- a/test/custom-elements/samples/shadowdom-closed/test.js +++ b/test/custom-elements/samples/shadowdom-closed/test.js @@ -8,6 +8,6 @@ export default function (target) { assert.equal(el.name, 'world'); const h1 = el._root.querySelector('h1'); - assert.equal(h1.textContent, 'Hello world'); + assert.equal(h1.textContent, 'Hello world!'); assert.equal(el.shadowRoot, null); } \ No newline at end of file From 24084433725d59fcef442e068b8686bfdd03fb63 Mon Sep 17 00:00:00 2001 From: Cris Ward Date: Thu, 30 Jan 2020 21:45:58 +0000 Subject: [PATCH 06/12] roughed out default slot --- src/compiler/compile/Component.ts | 3 +++ src/runtime/internal/Component.ts | 21 +++++++++++++++++++ .../samples/shadowdom-none-slots/main.svelte | 11 ++++++++++ .../samples/shadowdom-none-slots/test.js | 17 +++++++++++++++ 4 files changed, 52 insertions(+) create mode 100644 test/custom-elements/samples/shadowdom-none-slots/main.svelte create mode 100644 test/custom-elements/samples/shadowdom-none-slots/test.js diff --git a/src/compiler/compile/Component.ts b/src/compiler/compile/Component.ts index 3dfb4fd275cf..f621a137e226 100644 --- a/src/compiler/compile/Component.ts +++ b/src/compiler/compile/Component.ts @@ -160,6 +160,9 @@ export default class Component { } this.tag = this.component_options.tag || compile_options.tag; this.compile_options.shadowDom = this.component_options.shadowdom || "open"; + if(this.compile_options.shadowDom === "none"){ + // handle slots here? + } } else { this.tag = this.name.name; } diff --git a/src/runtime/internal/Component.ts b/src/runtime/internal/Component.ts index f8fbcf8ced2d..f64df23f559d 100644 --- a/src/runtime/internal/Component.ts +++ b/src/runtime/internal/Component.ts @@ -165,11 +165,14 @@ export let SvelteElement; if (typeof HTMLElement === 'function') { SvelteElement = class extends HTMLElement { $$: T$$; + private _content; constructor() { super(); + this._copycontent() } connectedCallback() { + this._slotcontent() // @ts-ignore todo: improve typings for (const key in this.$$.slotted) { // @ts-ignore todo: improve typings @@ -177,6 +180,24 @@ if (typeof HTMLElement === 'function') { } } + _copycontent(){ + if(this.children){ + this._content = document.createElement("div") + while(this.childNodes.length > 0){ + this._content.appendChild(this.childNodes[0]) + } + } + } + + _slotcontent(){ + if(this._content){ + const slot = this.querySelector("slot") + while(this._content.childNodes.length > 0){ + slot.appendChild(this._content.childNodes[0]) + } + } + } + attributeChangedCallback(attr, _oldValue, newValue) { this[attr] = newValue; } diff --git a/test/custom-elements/samples/shadowdom-none-slots/main.svelte b/test/custom-elements/samples/shadowdom-none-slots/main.svelte new file mode 100644 index 000000000000..ebe2db4e2d0a --- /dev/null +++ b/test/custom-elements/samples/shadowdom-none-slots/main.svelte @@ -0,0 +1,11 @@ + + +
+ +

default fallback content

+
+ + +

foo fallback content

+
+
diff --git a/test/custom-elements/samples/shadowdom-none-slots/test.js b/test/custom-elements/samples/shadowdom-none-slots/test.js new file mode 100644 index 000000000000..5c79a7b2652e --- /dev/null +++ b/test/custom-elements/samples/shadowdom-none-slots/test.js @@ -0,0 +1,17 @@ +import * as assert from 'assert'; +import './main.svelte'; + +export default function (target) { + target.innerHTML = ` + + slotted + `; + + const el = target.querySelector('custom-element'); + + const div = el.children[0]; + const [slot0] = div.children; + + assert.equal(slot0.children[1], target.querySelector('strong')); + //assert.equal(slot1.assignedNodes().length, 0); +} \ No newline at end of file From 2a481054638cabcac351d4608c862cc760b4fc11 Mon Sep 17 00:00:00 2001 From: Cris Ward Date: Sun, 2 Feb 2020 13:55:28 +0000 Subject: [PATCH 07/12] basic working example, need refining and adding to compile step --- src/compiler/compile/render_dom/index.ts | 5 ++- src/runtime/internal/Component.ts | 46 ++++++++++++++++++------ 2 files changed, 40 insertions(+), 11 deletions(-) diff --git a/src/compiler/compile/render_dom/index.ts b/src/compiler/compile/render_dom/index.ts index 0a3857ee1aea..cf629e851a2e 100644 --- a/src/compiler/compile/render_dom/index.ts +++ b/src/compiler/compile/render_dom/index.ts @@ -444,8 +444,11 @@ export default function dom( super(); ${!lightDom && b` this._root =this.attachShadow({ mode: '${options.shadowDom}' }); + ` || b` + this._copycontent(); + const observer = new MutationObserver(() => this._slotcontent()); + observer.observe(this, {childList: true, subtree: true}); `} - ${css.code && !lightDom && b`this._root.innerHTML = \`\`;`} ${should_add_css && lightDom && b`if (!@_document.getElementById("${component.stylesheet.id}-style")) ${add_css}();`} diff --git a/src/runtime/internal/Component.ts b/src/runtime/internal/Component.ts index f64df23f559d..2bb574bae613 100644 --- a/src/runtime/internal/Component.ts +++ b/src/runtime/internal/Component.ts @@ -165,14 +165,13 @@ export let SvelteElement; if (typeof HTMLElement === 'function') { SvelteElement = class extends HTMLElement { $$: T$$; - private _content; + protected _content; + slotting; constructor() { super(); - this._copycontent() } connectedCallback() { - this._slotcontent() // @ts-ignore todo: improve typings for (const key in this.$$.slotted) { // @ts-ignore todo: improve typings @@ -182,20 +181,47 @@ if (typeof HTMLElement === 'function') { _copycontent(){ if(this.children){ - this._content = document.createElement("div") - while(this.childNodes.length > 0){ - this._content.appendChild(this.childNodes[0]) + this._content = Array.from(this.childNodes) + while (this.firstChild) { + this.removeChild(this.firstChild) } } } _slotcontent(){ + if(this.slotting) return; + this.slotting = true; if(this._content){ - const slot = this.querySelector("slot") - while(this._content.childNodes.length > 0){ - slot.appendChild(this._content.childNodes[0]) - } + let namedslots = Array.from(this.querySelectorAll("slot[name]")) + let defaultslot = this.querySelector("slot:not([name])") + let named = {} + if(!namedslots.length && !defaultslot) return(this.slotting=false); + let slotted = [] + this._content.filter((node : HTMLElement)=> node.slot ).forEach((node : HTMLElement)=> named[node.slot] = node ) + namedslots.forEach(slot =>{ + this._content.forEach(node =>{ //append all named slots + if(named[node.slot] && slot.getAttribute("name")==node.slot){ + if(!slot.hasAttribute("hasupdated")){ + slot.appendChild(named[node.slot]); + slot.setAttribute("hasupdated","") + } + slotted.push(node) + } + }) + }) + if(!defaultslot) return(this.slotting=false); + // put what evers left info default slot + this._content + .filter(node => slotted.indexOf(node)==-1) + .forEach(node => { + if(!defaultslot.hasAttribute("hasupdated")){ + + defaultslot.appendChild(node) + } + }) + defaultslot.setAttribute("hasupdated","") } + this.slotting = false } attributeChangedCallback(attr, _oldValue, newValue) { From d032451b87e58ae1492fc4afba202a0151e19741 Mon Sep 17 00:00:00 2001 From: Cris Ward Date: Sun, 2 Feb 2020 20:20:24 +0000 Subject: [PATCH 08/12] moved slotting to compile step --- src/compiler/compile/render_dom/index.ts | 62 ++++++++++++++++++++++++ src/runtime/internal/Component.ts | 45 ----------------- 2 files changed, 62 insertions(+), 45 deletions(-) diff --git a/src/compiler/compile/render_dom/index.ts b/src/compiler/compile/render_dom/index.ts index cf629e851a2e..8aaf68dc45b6 100644 --- a/src/compiler/compile/render_dom/index.ts +++ b/src/compiler/compile/render_dom/index.ts @@ -484,6 +484,68 @@ export default function dom( }); } + if(lightDom){ + declaration.body.body.push({ + type: 'MethodDefinition', + kind: 'method', + static: false, + computed: false, + key: { type: 'Identifier', name: '_copycontent' }, + value: x`function() { + if(this.children){ + this._content = Array.from(this.childNodes) + while (this.firstChild) { + this.removeChild(this.firstChild) + } + } + }` as FunctionExpression + }); + + declaration.body.body.push({ + type: 'MethodDefinition', + kind: 'method', + static: false, + computed: false, + key: { type: 'Identifier', name: '_slotcontent' }, + value: x`function() { + if(this.slotting) return; // prevent running in parallel + this.slotting = true; + if(this._content){ + let namedslots = Array.from(this.querySelectorAll("slot[name]")) + let defaultslot = this.querySelector("slot:not([name])") + let named = {} + if(!namedslots.length && !defaultslot) return(this.slotting=false); + let slotted = [] + this._content.filter((node)=> node.slot ).forEach((node)=> named[node.slot] = node ) + namedslots.forEach(slot =>{ + this._content.forEach(node =>{ //append all named slots + if(named[node.slot] && slot.getAttribute("name") == node.slot){ + if(!slot.hasAttribute("hasupdated")){ + slot.appendChild(named[node.slot]); + slot.setAttribute("hasupdated","") + } + slotted.push(node) + } + }) + }) + if(!defaultslot) return(this.slotting=false); + // put what evers left info default slot + this._content + .filter(node => slotted.indexOf(node)==-1) + .forEach(node => { + if(!defaultslot.hasAttribute("hasupdated")){ + + defaultslot.appendChild(node) + } + }) + defaultslot.setAttribute("hasupdated","") + } + this.slotting = false + } + }` as FunctionExpression + }); + } + declaration.body.body.push(...accessors); body.push(declaration); diff --git a/src/runtime/internal/Component.ts b/src/runtime/internal/Component.ts index 2bb574bae613..9426b6a5ecad 100644 --- a/src/runtime/internal/Component.ts +++ b/src/runtime/internal/Component.ts @@ -179,51 +179,6 @@ if (typeof HTMLElement === 'function') { } } - _copycontent(){ - if(this.children){ - this._content = Array.from(this.childNodes) - while (this.firstChild) { - this.removeChild(this.firstChild) - } - } - } - - _slotcontent(){ - if(this.slotting) return; - this.slotting = true; - if(this._content){ - let namedslots = Array.from(this.querySelectorAll("slot[name]")) - let defaultslot = this.querySelector("slot:not([name])") - let named = {} - if(!namedslots.length && !defaultslot) return(this.slotting=false); - let slotted = [] - this._content.filter((node : HTMLElement)=> node.slot ).forEach((node : HTMLElement)=> named[node.slot] = node ) - namedslots.forEach(slot =>{ - this._content.forEach(node =>{ //append all named slots - if(named[node.slot] && slot.getAttribute("name")==node.slot){ - if(!slot.hasAttribute("hasupdated")){ - slot.appendChild(named[node.slot]); - slot.setAttribute("hasupdated","") - } - slotted.push(node) - } - }) - }) - if(!defaultslot) return(this.slotting=false); - // put what evers left info default slot - this._content - .filter(node => slotted.indexOf(node)==-1) - .forEach(node => { - if(!defaultslot.hasAttribute("hasupdated")){ - - defaultslot.appendChild(node) - } - }) - defaultslot.setAttribute("hasupdated","") - } - this.slotting = false - } - attributeChangedCallback(attr, _oldValue, newValue) { this[attr] = newValue; } From f5910857143ca9d474e39a32198acbcd6d00a631 Mon Sep 17 00:00:00 2001 From: Cris Ward Date: Sun, 2 Feb 2020 20:33:21 +0000 Subject: [PATCH 09/12] lint fix --- src/compiler/compile/Component.ts | 3 --- src/compiler/compile/render_dom/index.ts | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/src/compiler/compile/Component.ts b/src/compiler/compile/Component.ts index f621a137e226..3dfb4fd275cf 100644 --- a/src/compiler/compile/Component.ts +++ b/src/compiler/compile/Component.ts @@ -160,9 +160,6 @@ export default class Component { } this.tag = this.component_options.tag || compile_options.tag; this.compile_options.shadowDom = this.component_options.shadowdom || "open"; - if(this.compile_options.shadowDom === "none"){ - // handle slots here? - } } else { this.tag = this.name.name; } diff --git a/src/compiler/compile/render_dom/index.ts b/src/compiler/compile/render_dom/index.ts index 8aaf68dc45b6..1dcffff0564a 100644 --- a/src/compiler/compile/render_dom/index.ts +++ b/src/compiler/compile/render_dom/index.ts @@ -484,7 +484,7 @@ export default function dom( }); } - if(lightDom){ + if (lightDom) { declaration.body.body.push({ type: 'MethodDefinition', kind: 'method', From a2ac1d91fa6cfb93b19475976742a1daacf8f4eb Mon Sep 17 00:00:00 2001 From: Cris Ward Date: Sun, 2 Feb 2020 21:31:18 +0000 Subject: [PATCH 10/12] disconnecting mutation observer on disconnect --- src/compiler/compile/render_dom/index.ts | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/compiler/compile/render_dom/index.ts b/src/compiler/compile/render_dom/index.ts index 1dcffff0564a..55691aecf65b 100644 --- a/src/compiler/compile/render_dom/index.ts +++ b/src/compiler/compile/render_dom/index.ts @@ -446,8 +446,8 @@ export default function dom( this._root =this.attachShadow({ mode: '${options.shadowDom}' }); ` || b` this._copycontent(); - const observer = new MutationObserver(() => this._slotcontent()); - observer.observe(this, {childList: true, subtree: true}); + this.slotObserver = new MutationObserver(() => this._slotcontent()); + this.slotObserver.observe(this, {childList: true, subtree: true}); `} ${css.code && !lightDom && b`this._root.innerHTML = \`\`;`} ${should_add_css && lightDom && b`if (!@_document.getElementById("${component.stylesheet.id}-style")) ${add_css}();`} @@ -544,6 +544,17 @@ export default function dom( } }` as FunctionExpression }); + + declaration.body.body.push({ + type: 'MethodDefinition', + kind: 'method', + static: false, + computed: false, + key: { type: 'Identifier', name: 'disconnectedCallback' }, + value: x`function() { + this.slotObserver.disconnect(); + }` as FunctionExpression + }); } declaration.body.body.push(...accessors); From 4c7e96cca3d52012881152912ebeba15b72f6197 Mon Sep 17 00:00:00 2001 From: Cris Ward Date: Mon, 3 Feb 2020 13:17:04 +0000 Subject: [PATCH 11/12] removing default slot content before adding passed in content --- src/compiler/compile/render_dom/index.ts | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/src/compiler/compile/render_dom/index.ts b/src/compiler/compile/render_dom/index.ts index 55691aecf65b..433f43c774d9 100644 --- a/src/compiler/compile/render_dom/index.ts +++ b/src/compiler/compile/render_dom/index.ts @@ -521,24 +521,27 @@ export default function dom( this._content.forEach(node =>{ //append all named slots if(named[node.slot] && slot.getAttribute("name") == node.slot){ if(!slot.hasAttribute("hasupdated")){ + while (slot.firstChild) { + slot.removeChild(slot.firstChild) + } slot.appendChild(named[node.slot]); - slot.setAttribute("hasupdated","") } + slot.setAttribute("hasupdated","") slotted.push(node) } }) }) if(!defaultslot) return(this.slotting=false); // put what evers left info default slot - this._content - .filter(node => slotted.indexOf(node)==-1) - .forEach(node => { - if(!defaultslot.hasAttribute("hasupdated")){ - - defaultslot.appendChild(node) - } - }) - defaultslot.setAttribute("hasupdated","") + let toAppend = this._content.filter(node => slotted.indexOf(node)==-1) + // remove default + if(!defaultslot.hasAttribute("hasupdated") && toAppend.length){ + while (defaultslot.firstChild) { + defaultslot.removeChild(defaultslot.firstChild) + } + } + toAppend.forEach(node => !defaultslot.hasAttribute("hasupdated") && defaultslot.appendChild(node) ) + defaultslot.setAttribute("hasupdated","") } this.slotting = false } From 68c4cc8b364ff7617dc50bfd916a64eae2d33ff2 Mon Sep 17 00:00:00 2001 From: Cris Ward Date: Mon, 3 Feb 2020 13:25:17 +0000 Subject: [PATCH 12/12] Fixing inconsistencies with named slots between open and none --- src/compiler/compile/render_dom/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/compiler/compile/render_dom/index.ts b/src/compiler/compile/render_dom/index.ts index 433f43c774d9..fddf9373439a 100644 --- a/src/compiler/compile/render_dom/index.ts +++ b/src/compiler/compile/render_dom/index.ts @@ -532,8 +532,8 @@ export default function dom( }) }) if(!defaultslot) return(this.slotting=false); - // put what evers left info default slot - let toAppend = this._content.filter(node => slotted.indexOf(node)==-1) + // get all nodes without a slot attribute + let toAppend = this._content.filter(node => node.hasAttribute && !node.hasAttribute("slot")) // remove default if(!defaultslot.hasAttribute("hasupdated") && toAppend.length){ while (defaultslot.firstChild) {