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();
|
||||
render(newVNode, {
|
||||
host: this,
|
||||
oldVNode: this.#renderedVNode
|
||||
oldVNode: this.#renderedVNode,
|
||||
});
|
||||
this.#renderedVNode = newVNode;
|
||||
}
|
||||
|
||||
@ -21,7 +21,7 @@ import {
|
||||
*/
|
||||
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..
|
||||
// ---> 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 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
|
||||
@ -43,22 +43,36 @@ export function render(vnode, opts = {}) {
|
||||
|
||||
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);
|
||||
}else if(vnode?.type === ShadowDOM) {
|
||||
if(!parent.node) {
|
||||
throw new Error("Can't identify to what element ShadowDOM is to be attached");
|
||||
}
|
||||
}else{
|
||||
host.data = vnode;
|
||||
}
|
||||
}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' });
|
||||
}else if(vnode?.type === Host){
|
||||
if(!parent.node) {
|
||||
throw new Error("Can't identify to what element ShadowDOM is to be attached");
|
||||
}
|
||||
host = parent.node;
|
||||
}else if(typeof(vnode?.type) === 'string'){
|
||||
}else{
|
||||
host = parent.node.shadowRoot;
|
||||
}
|
||||
}else if(vnode?.type === Host){
|
||||
// Host-keyword -> PARENT
|
||||
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);
|
||||
}else if(vnode?.type?.tagName){
|
||||
// Object-type -> CUSTOM-ELEMENT
|
||||
host = document.createElement(vnode.type.tagName);
|
||||
}else{
|
||||
throw new Error("Unrecognized vnode type", vnode);
|
||||
@ -68,70 +82,102 @@ export function render(vnode, opts = {}) {
|
||||
// Props
|
||||
if (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 styleKey in props.style) {
|
||||
host.style[ styleKey ] = props.style[ styleKey ];
|
||||
}
|
||||
}
|
||||
for (let key in props) {
|
||||
let val = props[key];
|
||||
for(let {deleted, key, value, prevValue} of propOps){
|
||||
if(VNODEPROP_IGNORE[key]){
|
||||
// NO-OP
|
||||
}else if(VNODEPROP_DIRECT[key]){
|
||||
host[key] = val;
|
||||
host[key] = value;
|
||||
}else{
|
||||
if(!VNODEPROP_EXCLUDE_DIRECT[key] && !key.indexOf('-')){
|
||||
host[key] = val;
|
||||
}
|
||||
if(key.slice(0,2)==='on' && key[2]>='A' && key[2]<='Z'){
|
||||
if(val instanceof Function){
|
||||
host.addEventListener(
|
||||
// Convert camelCase to dash-case
|
||||
key[2].toLowerCase()+key.slice(3).replace(/[A-Z]/g, function(c){return('-'+c.toLowerCase())}),
|
||||
val
|
||||
);
|
||||
if(value===prevValue) {
|
||||
// NO-OP, hasn't changed
|
||||
}else if(!VNODEPROP_EXCLUDE_DIRECT[key] && !key.indexOf('-')){
|
||||
host[key] = value;
|
||||
}else if(key.slice(0,2)==='on' && key[2]>='A' && key[2]<='Z'){
|
||||
// Convert camelCase to dash-case
|
||||
let eventName = key[2].toLowerCase()+key.slice(3).replace(/[A-Z]/g, function(c){return('-'+c.toLowerCase())});
|
||||
if(deleted) {
|
||||
host.removeEventListener(eventName, value);
|
||||
}else if(value instanceof Function){
|
||||
host.addEventListener(eventName, value);
|
||||
}else{
|
||||
new Error("Unsupported event-handler");
|
||||
}
|
||||
|
||||
}else {
|
||||
if (val === false || val===null || val==='') {
|
||||
if(deleted){
|
||||
host.removeAttribute(key);
|
||||
} else if (val === true) {
|
||||
host.setAttribute(key, "");
|
||||
} else {
|
||||
host.setAttribute(key, val);
|
||||
}else{
|
||||
if (value === false || value===null || value==='') {
|
||||
host.removeAttribute(key);
|
||||
} 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
|
||||
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 oldChildVQueue = oldVNode?.children instanceof Array? oldVNode.children.slice()
|
||||
: oldVNode?.children!==null&&oldVNode?.children!==undefined? [oldVNode.children] : [];
|
||||
let childQueue = [];
|
||||
let oldChildQueue = [];
|
||||
let hostIndex = 0;
|
||||
while(childVQueue.length){
|
||||
let child = childVQueue.splice(0,1)[0];
|
||||
// TODO support chidl instances of Element
|
||||
let oldChild = oldChildVQueue.splice(0,1)[0];
|
||||
if(child instanceof Array){
|
||||
childVQueue.splice(0,0,...child);
|
||||
if(oldChild instanceof Array){
|
||||
// here we typically know we should be checking Refs
|
||||
oldChildVQueue.splice(0,0,...oldChild);
|
||||
}
|
||||
}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({
|
||||
vnode: child,
|
||||
opts: {
|
||||
...opts,
|
||||
host: undefined,
|
||||
},
|
||||
parent: { // TODO specify a previous child or index here if need be...
|
||||
node:host,
|
||||
console.log("Updating:", child, oldChild);
|
||||
|
||||
// Really dirty way here to pass in what was probably the old element for this, and it must absolutely go
|
||||
oldChild: oldVNode?host.childNodes[childQueue.length]:null
|
||||
let updateNode = (oldChild?.type === child?.type);
|
||||
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);
|
||||
@ -139,7 +185,10 @@ export function render(vnode, opts = {}) {
|
||||
|
||||
if(parent){
|
||||
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{
|
||||
parent.node.appendChild(host);
|
||||
}
|
||||
|
||||
@ -28,7 +28,9 @@ export class MyTodo extends CustomElement{
|
||||
<todo-item
|
||||
model={ item.id }
|
||||
checked={( item.checked )}
|
||||
>{ item.text }</todo-item>
|
||||
>
|
||||
{ item.text }
|
||||
</todo-item>
|
||||
)}
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
@ -28,10 +28,12 @@ export class TodoItem extends CustomElement{
|
||||
handleChange = ()=>{
|
||||
this.dispatchEvent(new CustomEvent('check', {
|
||||
detail: (this.checked=!this.checked),
|
||||
bubbles: true
|
||||
}));
|
||||
};
|
||||
handleClick = ()=>{
|
||||
this.dispatchEvent(new CustomEvent('remove', {
|
||||
bubbles: true
|
||||
}));
|
||||
};
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user