Saving state before severe refactoring (to support a proper update-loop and future extensibility)
This commit is contained in:
parent
863adb9449
commit
fc527cb156
@ -59,7 +59,7 @@ export class CustomElement extends HTMLElement {
|
|||||||
let newVNode = this.render();
|
let newVNode = this.render();
|
||||||
render(newVNode, {
|
render(newVNode, {
|
||||||
host: this,
|
host: this,
|
||||||
oldVNode: this.#renderedVNode
|
oldVNode: this.#renderedVNode,
|
||||||
});
|
});
|
||||||
this.#renderedVNode = newVNode;
|
this.#renderedVNode = newVNode;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -21,7 +21,7 @@ import {
|
|||||||
*/
|
*/
|
||||||
export function render(vnode, opts = {}) {
|
export function render(vnode, opts = {}) {
|
||||||
// TODO figure out how to handle events (its not that easy to create (click)={this.onClick} or something, that is not supported by the @babel/parser and we'd have to fork it..
|
// TODO figure out how to handle events (its not that easy to create (click)={this.onClick} or something, that is not supported by the @babel/parser and we'd have to fork it..
|
||||||
// ---> We've got a basic onClick (react-style) system set up now
|
// --> We've got a basic onClick (react-style) system set up now
|
||||||
// TODO innerHTML, innerText and other tags/props that are trickyer then just mapping value to attribute
|
// TODO innerHTML, innerText and other tags/props that are trickyer then just mapping value to attribute
|
||||||
// TODO ref-prop (should it only return once all child els are created and appended to the child?!)
|
// TODO ref-prop (should it only return once all child els are created and appended to the child?!)
|
||||||
// TODO Proper updating of a previous rendered vnode
|
// TODO Proper updating of a previous rendered vnode
|
||||||
@ -43,22 +43,36 @@ export function render(vnode, opts = {}) {
|
|||||||
|
|
||||||
if(VNODE_EXCLUDE[vnode]) continue;// Skip
|
if(VNODE_EXCLUDE[vnode]) continue;// Skip
|
||||||
|
|
||||||
if(!host){
|
if(!['object', 'function', 'symbol'].includes(typeof(vnode))) {
|
||||||
if(!['object', 'function', 'symbol'].includes(typeof(vnode))) {
|
// Presumed primitive type -> TEXT
|
||||||
|
if(!host) {
|
||||||
host = document.createTextNode(vnode);
|
host = document.createTextNode(vnode);
|
||||||
}else if(vnode?.type === ShadowDOM) {
|
}else{
|
||||||
if(!parent.node) {
|
host.data = vnode;
|
||||||
throw new Error("Can't identify to what element ShadowDOM is to be attached");
|
}
|
||||||
}
|
}else if(vnode?.type === ShadowDOM) {
|
||||||
|
// ShadowDOM-keyword -> SHADOW
|
||||||
|
if(!parent.node) {
|
||||||
|
throw new Error("Can't identify to what element ShadowDOM is to be attached");
|
||||||
|
}
|
||||||
|
if(!host) {
|
||||||
host = parent.node.attachShadow({ mode: 'open' });
|
host = parent.node.attachShadow({ mode: 'open' });
|
||||||
}else if(vnode?.type === Host){
|
}else{
|
||||||
if(!parent.node) {
|
host = parent.node.shadowRoot;
|
||||||
throw new Error("Can't identify to what element ShadowDOM is to be attached");
|
}
|
||||||
}
|
}else if(vnode?.type === Host){
|
||||||
host = parent.node;
|
// Host-keyword -> PARENT
|
||||||
}else if(typeof(vnode?.type) === 'string'){
|
let shadowParent = parent?.node || host;
|
||||||
|
if(!shadowParent) {
|
||||||
|
throw new Error("Can't identify to what element ShadowDOM is to be attached");
|
||||||
|
}
|
||||||
|
host = shadowParent;
|
||||||
|
}else if(!host){
|
||||||
|
if(typeof(vnode?.type) === 'string'){
|
||||||
|
// String-type -> DOM
|
||||||
host = document.createElement(vnode.type);
|
host = document.createElement(vnode.type);
|
||||||
}else if(vnode?.type?.tagName){
|
}else if(vnode?.type?.tagName){
|
||||||
|
// Object-type -> CUSTOM-ELEMENT
|
||||||
host = document.createElement(vnode.type.tagName);
|
host = document.createElement(vnode.type.tagName);
|
||||||
}else{
|
}else{
|
||||||
throw new Error("Unrecognized vnode type", vnode);
|
throw new Error("Unrecognized vnode type", vnode);
|
||||||
@ -68,70 +82,102 @@ export function render(vnode, opts = {}) {
|
|||||||
// Props
|
// Props
|
||||||
if (vnode?.props) {
|
if (vnode?.props) {
|
||||||
let props = vnode.props;
|
let props = vnode.props;
|
||||||
|
let oldProps = oldVNode?.props || {};
|
||||||
|
let propOps = [
|
||||||
|
...Object.entries(oldProps).filter(([key,val])=>!props.hasOwnProperty(key)).map(([key,value])=>({delete:1, key, value})),
|
||||||
|
...Object.entries(props).map(([key,value])=>({delete:1, key, value, prevValue: oldProps[key]}))
|
||||||
|
];
|
||||||
|
|
||||||
if (props.style && typeof (props.style) === 'object') {
|
for(let {deleted, key, value, prevValue} of propOps){
|
||||||
for (let styleKey in props.style) {
|
|
||||||
host.style[ styleKey ] = props.style[ styleKey ];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (let key in props) {
|
|
||||||
let val = props[key];
|
|
||||||
if(VNODEPROP_IGNORE[key]){
|
if(VNODEPROP_IGNORE[key]){
|
||||||
// NO-OP
|
// NO-OP
|
||||||
}else if(VNODEPROP_DIRECT[key]){
|
}else if(VNODEPROP_DIRECT[key]){
|
||||||
host[key] = val;
|
host[key] = value;
|
||||||
}else{
|
}else{
|
||||||
if(!VNODEPROP_EXCLUDE_DIRECT[key] && !key.indexOf('-')){
|
if(value===prevValue) {
|
||||||
host[key] = val;
|
// NO-OP, hasn't changed
|
||||||
}
|
}else if(!VNODEPROP_EXCLUDE_DIRECT[key] && !key.indexOf('-')){
|
||||||
if(key.slice(0,2)==='on' && key[2]>='A' && key[2]<='Z'){
|
host[key] = value;
|
||||||
if(val instanceof Function){
|
}else if(key.slice(0,2)==='on' && key[2]>='A' && key[2]<='Z'){
|
||||||
host.addEventListener(
|
// Convert camelCase to dash-case
|
||||||
// Convert camelCase to dash-case
|
let eventName = key[2].toLowerCase()+key.slice(3).replace(/[A-Z]/g, function(c){return('-'+c.toLowerCase())});
|
||||||
key[2].toLowerCase()+key.slice(3).replace(/[A-Z]/g, function(c){return('-'+c.toLowerCase())}),
|
if(deleted) {
|
||||||
val
|
host.removeEventListener(eventName, value);
|
||||||
);
|
}else if(value instanceof Function){
|
||||||
|
host.addEventListener(eventName, value);
|
||||||
}else{
|
}else{
|
||||||
new Error("Unsupported event-handler");
|
new Error("Unsupported event-handler");
|
||||||
}
|
}
|
||||||
|
|
||||||
}else {
|
}else {
|
||||||
if (val === false || val===null || val==='') {
|
if(deleted){
|
||||||
host.removeAttribute(key);
|
host.removeAttribute(key);
|
||||||
} else if (val === true) {
|
}else{
|
||||||
host.setAttribute(key, "");
|
if (value === false || value===null || value==='') {
|
||||||
} else {
|
host.removeAttribute(key);
|
||||||
host.setAttribute(key, val);
|
} else if (value === true) {
|
||||||
|
host.setAttribute(key, "");
|
||||||
|
} else {
|
||||||
|
host.setAttribute(key, value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// if (props.style && typeof (props.style) === 'object') {
|
||||||
|
// for (let styleKey in props.style) {
|
||||||
|
// host.style[ styleKey ] = props.style[ styleKey ];
|
||||||
|
// }
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
// Children
|
// Children
|
||||||
if (vnode?.children) {
|
if (vnode?.children) {
|
||||||
|
// TODO: Properly iterate the children update VNode only where necessary (we'll probably just reach another point where we throw the whole thing upside down again...)
|
||||||
let childVQueue = vnode.children instanceof Array? vnode.children.slice() : [vnode.children];
|
let childVQueue = vnode.children instanceof Array? vnode.children.slice() : [vnode.children];
|
||||||
|
let oldChildVQueue = oldVNode?.children instanceof Array? oldVNode.children.slice()
|
||||||
|
: oldVNode?.children!==null&&oldVNode?.children!==undefined? [oldVNode.children] : [];
|
||||||
let childQueue = [];
|
let childQueue = [];
|
||||||
|
let oldChildQueue = [];
|
||||||
|
let hostIndex = 0;
|
||||||
while(childVQueue.length){
|
while(childVQueue.length){
|
||||||
let child = childVQueue.splice(0,1)[0];
|
let child = childVQueue.splice(0,1)[0];
|
||||||
// TODO support chidl instances of Element
|
let oldChild = oldChildVQueue.splice(0,1)[0];
|
||||||
if(child instanceof Array){
|
if(child instanceof Array){
|
||||||
childVQueue.splice(0,0,...child);
|
childVQueue.splice(0,0,...child);
|
||||||
|
if(oldChild instanceof Array){
|
||||||
|
// here we typically know we should be checking Refs
|
||||||
|
oldChildVQueue.splice(0,0,...oldChild);
|
||||||
|
}
|
||||||
}else{
|
}else{
|
||||||
|
// Really dirty way here to pass in what was probably the old element for this, and it must absolutely go
|
||||||
|
if(oldChild === child){
|
||||||
|
console.log("Not changed, skipping");
|
||||||
|
hostIndex++;
|
||||||
|
}else{
|
||||||
|
let oldChildEl = oldVNode?host.childNodes[hostIndex]:null;
|
||||||
|
|
||||||
childQueue.push({
|
console.log("Updating:", child, oldChild);
|
||||||
vnode: child,
|
|
||||||
opts: {
|
|
||||||
...opts,
|
|
||||||
host: undefined,
|
|
||||||
},
|
|
||||||
parent: { // TODO specify a previous child or index here if need be...
|
|
||||||
node:host,
|
|
||||||
|
|
||||||
// Really dirty way here to pass in what was probably the old element for this, and it must absolutely go
|
let updateNode = (oldChild?.type === child?.type);
|
||||||
oldChild: oldVNode?host.childNodes[childQueue.length]:null
|
if(updateNode){
|
||||||
|
hostIndex++;
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
childQueue.push({
|
||||||
|
vnode: child,
|
||||||
|
opts: {
|
||||||
|
...opts,
|
||||||
|
host: updateNode? oldChildEl : undefined,
|
||||||
|
oldVNode: updateNode? oldChild : undefined
|
||||||
|
},
|
||||||
|
parent: { // TODO specify a previous child or index here if need be...
|
||||||
|
node:host,
|
||||||
|
|
||||||
|
// Really dirty way here to pass in what was probably the old element for this, and it must absolutely go
|
||||||
|
oldChild: oldChildEl
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
queue.splice(0,0,...childQueue);
|
queue.splice(0,0,...childQueue);
|
||||||
@ -139,7 +185,10 @@ export function render(vnode, opts = {}) {
|
|||||||
|
|
||||||
if(parent){
|
if(parent){
|
||||||
if(parent.oldChild){
|
if(parent.oldChild){
|
||||||
parent.node.replaceChild(host, parent.oldChild);
|
if(parent.oldChild !== host) {
|
||||||
|
console.log(parent.node, host);
|
||||||
|
parent.node.replaceChild(host, parent.oldChild);
|
||||||
|
}
|
||||||
}else{
|
}else{
|
||||||
parent.node.appendChild(host);
|
parent.node.appendChild(host);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -28,7 +28,9 @@ export class MyTodo extends CustomElement{
|
|||||||
<todo-item
|
<todo-item
|
||||||
model={ item.id }
|
model={ item.id }
|
||||||
checked={( item.checked )}
|
checked={( item.checked )}
|
||||||
>{ item.text }</todo-item>
|
>
|
||||||
|
{ item.text }
|
||||||
|
</todo-item>
|
||||||
)}
|
)}
|
||||||
</ul>
|
</ul>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@ -28,10 +28,12 @@ export class TodoItem extends CustomElement{
|
|||||||
handleChange = ()=>{
|
handleChange = ()=>{
|
||||||
this.dispatchEvent(new CustomEvent('check', {
|
this.dispatchEvent(new CustomEvent('check', {
|
||||||
detail: (this.checked=!this.checked),
|
detail: (this.checked=!this.checked),
|
||||||
|
bubbles: true
|
||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
handleClick = ()=>{
|
handleClick = ()=>{
|
||||||
this.dispatchEvent(new CustomEvent('remove', {
|
this.dispatchEvent(new CustomEvent('remove', {
|
||||||
|
bubbles: true
|
||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user