-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 1e05665
Showing
4 changed files
with
206 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
node_modules |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
# Svelte-tag | ||
|
||
A webcomponet wrapper for svelte. | ||
|
||
Be aware, I don't have a lot of time to support this package. I've mainly open sourced it | ||
because I've noticed lots of people with similar use cases. | ||
|
||
## Why? | ||
|
||
Svelte already allows you to create webcomponents. However it has a couple of flaws: | ||
* All of your nested components have to be webcomponents as the render flag applies to everything. | ||
* You have to use shadow dom. | ||
* You have to deal with lots of bugs. | ||
* You loose many features svelte has for inter-component communication. | ||
|
||
This solves this by just embedding your app inside a single component. | ||
|
||
## How do I use it? | ||
|
||
```javascript | ||
import component from "svelte-tag" | ||
import App from "your-app.svelte" | ||
new component({component:App,tagname:"hello-world",href="/your/stylesheet.css",attributes:["name"]}) | ||
``` | ||
|
||
Now anywhere you use the `<hello-world>` tag you'll get a svelte app. | ||
|
||
## Todo | ||
|
||
- [ ] Upload Tests | ||
- [ ] Setup CI |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,149 @@ | ||
/* | ||
Usage - convert svelte app to web component | ||
import component from "../libs/component.js" | ||
new component({component:App,tagname:"hello-world",href="/your/stylesheet.css",attributes:["name"]}) | ||
*/ | ||
|
||
// witchcraft from svelte issue - https://github.com/sveltejs/svelte/issues/2588 | ||
import { detach, insert, noop } from 'svelte/internal'; | ||
function createSlots(slots) { | ||
const svelteSlots = {}; | ||
for (const slotName in slots) { | ||
svelteSlots[slotName] = [createSlotFn(slots[slotName])]; | ||
} | ||
function createSlotFn(element) { | ||
return function() { | ||
return { | ||
c: noop, | ||
m: function mount(target, anchor) { | ||
insert(target, element.cloneNode(true), anchor); | ||
}, | ||
d: function destroy(detaching) { | ||
if (detaching && element.innerHTML){ | ||
detach(element); | ||
} | ||
}, | ||
l: noop, | ||
}; | ||
} | ||
} | ||
return svelteSlots; | ||
} | ||
|
||
export default function(opts){ | ||
class Wrapper extends HTMLElement{ | ||
|
||
constructor() { | ||
super(); | ||
this.slotcount = 0 | ||
let root = opts.shadow ? this.attachShadow({ mode: 'open' }) : this | ||
// link generated style | ||
if(opts.href && opts.shadow){ | ||
let link = document.createElement('link'); | ||
link.setAttribute("href",opts.href) | ||
link.setAttribute("rel","stylesheet") | ||
root.appendChild(link); | ||
} | ||
if(opts.shadow){ | ||
this._root = document.createElement('div') | ||
root.appendChild(this._root) | ||
}else{ | ||
this._root = root | ||
} | ||
} | ||
|
||
static get observedAttributes() { | ||
return opts.attributes || [] | ||
} | ||
|
||
connectedCallback(){ | ||
let props = opts.defaults ? opts.defaults : {}; | ||
let slots | ||
props.$$scope = {} | ||
Array.from(this.attributes).forEach( attr => props[attr.name] = attr.value ) | ||
props.$$scope = {} | ||
if(opts.shadow){ | ||
slots = this.getShadowSlots() | ||
let props = opts.defaults ? opts.defaults : {}; | ||
props.$$scope = {} | ||
this.observer = new MutationObserver(this.processMutations.bind(this,{root:this._root,props})) | ||
this.observer.observe(this,{childList: true, subtree: true, attributes: false}) | ||
}else{ | ||
slots = this.getSlots() | ||
} | ||
this.slotcount = Object.keys(slots).length | ||
props.$$slots = createSlots(slots) | ||
this.elem = new opts.component({ target: this._root, props}); | ||
} | ||
|
||
disconnectedCallback(){ | ||
if(this.observe){ | ||
this.observer.disconnect() | ||
} | ||
try{ this.elem.$destroy()}catch(err){} // detroy svelte element when removed from dom | ||
} | ||
|
||
unwrap(from){ | ||
let node = new DocumentFragment() | ||
while (from.firstChild) { | ||
node.appendChild(from.removeChild(from.firstChild)); | ||
} | ||
return node | ||
} | ||
|
||
getSlots(){ | ||
const namedSlots = this.querySelectorAll('[slot]') | ||
let slots = {} | ||
namedSlots.forEach(n=>{ | ||
slots[n.slot] = this.unwrap(n) | ||
this.removeChild(n) | ||
}) | ||
if(this.innerHTML.length){ | ||
slots.default = this.unwrap(this) | ||
this.innerHTML = "" | ||
} | ||
return slots | ||
} | ||
|
||
getShadowSlots(){ | ||
const namedSlots = this.querySelectorAll('[slot]') | ||
let slots = {} | ||
let htmlLength = this.innerHTML.length | ||
namedSlots.forEach(n=>{ | ||
slots[n.slot] = document.createElement("slot") | ||
slots[n.slot].setAttribute("name",n.slot) | ||
htmlLength-=n.outerHTML.length | ||
}) | ||
if(htmlLength>0){ | ||
slots.default = document.createElement("slot") | ||
} | ||
return slots | ||
} | ||
|
||
processMutations({root,props},mutations){ | ||
for(let mutation of mutations){ | ||
if(mutation.type=="childList"){ | ||
let slots = this.getShadowSlots() | ||
if(Object.keys(slots).length){ | ||
props.$$slots = createSlots(slots) | ||
this.elem.$set({"$$slots":createSlots(slots)}) | ||
// do full re-render on slot count change - needed for tabs component | ||
if(this.slotcount != Object.keys(slots).length){ | ||
Array.from(this.attributes).forEach( attr => props[attr.name] = attr.value ) | ||
this.slotcount = Object.keys(slots).length | ||
root.innerHTML = "" | ||
this.elem = new opts.component({ target: root, props}); | ||
} | ||
} | ||
} | ||
} | ||
} | ||
|
||
attributeChangedCallback(name, oldValue, newValue) { | ||
if(this.elem && newValue!=oldValue){ | ||
this.elem.$set({[name]:newValue}) | ||
} | ||
} | ||
} | ||
window.customElements.define(opts.tagname, Wrapper); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
{ | ||
"name": "svelte-tag", | ||
"version": "1.0.0", | ||
"description": "webcomponent wrapper for svelte", | ||
"main": "index.js", | ||
"scripts": { | ||
"test": "npm test" | ||
}, | ||
"repository": { | ||
"type": "git", | ||
"url": "git+ssh://git@github.com/crisward/svelte-tag.git" | ||
}, | ||
"keywords": [ | ||
"svelte", | ||
"webcomponent", | ||
"slots", | ||
"wrapper" | ||
], | ||
"author": "Cris Ward", | ||
"license": "ISC", | ||
"bugs": { | ||
"url": "https://github.com/crisward/svelte-tag/issues" | ||
}, | ||
"homepage": "https://github.com/crisward/svelte-tag#readme" | ||
} |