Prop-decorators and supporting DOMElements as VNodes in the renderer
This commit is contained in:
parent
698656c8f6
commit
e4eef2cc1a
1
.idea/csx.iml
generated
1
.idea/csx.iml
generated
@ -3,6 +3,7 @@
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<excludeFolder url="file://$MODULE_DIR$/.tmp" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/public" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/temp" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/tmp" />
|
||||
</content>
|
||||
|
||||
@ -17,7 +17,7 @@
|
||||
"jsdoc": "latest",
|
||||
"sass": "latest",
|
||||
"rollup": "latest",
|
||||
"rollup-plugin-babel": "latest",
|
||||
"rollup-plugin-babel": "csx",
|
||||
"rollup-plugin-node-resolve": "latest",
|
||||
"rollup-plugin-commonjs": "latest",
|
||||
"rollup-plugin-terser": "latest",
|
||||
@ -37,8 +37,5 @@
|
||||
"build:csx": "cd packages/csx && npm run build",
|
||||
"watch:babel-transform-csx": "cd packages/babel-plugin-transform-csx && npm run watch",
|
||||
"watch:csx": "cd packages/csx && npm run watch-es6"
|
||||
},
|
||||
"resolutions": {
|
||||
"@babel/helpers": "file:./packages/babel-helpers"
|
||||
}
|
||||
}
|
||||
|
||||
@ -36,7 +36,7 @@
|
||||
"scripts": {
|
||||
"build": "rollup -c",
|
||||
"watch": "rollup -c -w",
|
||||
"npm-publish": "npm run build && npm publish --registry https://npm.cerxes.net"
|
||||
"npm-publish": "npm run build && npm publish --registry https://npm.cerxes.net --tag latest"
|
||||
},
|
||||
"module": "./src/index.js",
|
||||
"main": "./dist/index.js"
|
||||
|
||||
@ -35,7 +35,7 @@
|
||||
"watch-cjs": "rollup -c -w",
|
||||
"build-es6": "npx babel ./src --out-dir=lib",
|
||||
"watch-es6": "npx babel ./src --out-dir=lib -w",
|
||||
"npm-publish": "npm run build && npm publish --registry https://npm.cerxes.net"
|
||||
"npm-publish": "npm run build && npm publish --registry https://npm.cerxes.net --tag latest"
|
||||
},
|
||||
"module": "./lib/index.js",
|
||||
"main": "./dist/index.js"
|
||||
|
||||
@ -1,62 +1,8 @@
|
||||
import {render} from "../vdom";
|
||||
|
||||
|
||||
/** Helper class to mark an initializer-value to be used on first get of a value**/
|
||||
class InitializerValue{ constructor(value){ this.value = value; } }
|
||||
|
||||
/**
|
||||
* The decorators proposal has changed since @babel implemented it. This code will need to change at some point...
|
||||
*/
|
||||
export function State() {
|
||||
return function decorator(target, key, descriptor){
|
||||
let {get: oldGet, set: oldSet} = descriptor;
|
||||
let valueKey='__'+key;
|
||||
|
||||
// Rewrite the property as if using getters and setters (if needed)
|
||||
descriptor.get = oldGet = oldGet || function(){
|
||||
let val = this[valueKey];
|
||||
if(val instanceof InitializerValue){
|
||||
this[valueKey] = val = val.value.call(this);
|
||||
}
|
||||
return val;
|
||||
};
|
||||
oldSet = oldSet || function(newVal){
|
||||
this[valueKey]=newVal;
|
||||
return newVal;
|
||||
};
|
||||
// Overwrite the setter to call markDirty whenever it is used
|
||||
descriptor.set = function(newValue){
|
||||
let result = oldSet.call(this, newValue);
|
||||
this.markDirty && this.markDirty();
|
||||
return result;
|
||||
};
|
||||
|
||||
// Update the descriptor to match with using getters and setters
|
||||
target.kind = 'method'; // update to get and set if need be..
|
||||
delete descriptor.writable;
|
||||
|
||||
// Catch usage of initial value or initalizers
|
||||
if(descriptor.value){
|
||||
Object.defineProperty(target, valueKey, {
|
||||
writable: true,
|
||||
value: descriptor.value
|
||||
});
|
||||
delete descriptor.value;
|
||||
}else if(descriptor.initializer){
|
||||
Object.defineProperty(target, valueKey, {
|
||||
writable: true,
|
||||
value: new InitializerValue(descriptor.initializer)
|
||||
});
|
||||
delete descriptor.initializer;
|
||||
}
|
||||
|
||||
return descriptor;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO a proper Prop-decorator
|
||||
export {State as Prop};
|
||||
|
||||
// TODO the custom-element class could be removed, and its functionality implemented through the @defineElement decorator
|
||||
// Currently there are issues: if a custom-element reimplements the connectedCallback, they need to magically know that
|
||||
// they should run the super.connectCallback(). to make sure it actually gets rendered on mounting to the DOM...
|
||||
|
||||
/**
|
||||
* This CustomElement class is to avoid having to do an ugly workaround in every custom-element:
|
||||
|
||||
@ -1,2 +1,4 @@
|
||||
export * from './define-element';
|
||||
export * from './custom-element';
|
||||
export * from "./prop";
|
||||
export * from "./state";
|
||||
62
packages/csx/src/custom-element/prop.js
Normal file
62
packages/csx/src/custom-element/prop.js
Normal file
@ -0,0 +1,62 @@
|
||||
import { trackValue } from "../decorator-utils/track-value";
|
||||
|
||||
/**
|
||||
* Decorate a variable as a property of the custom-element.
|
||||
* @param {Object} opts
|
||||
* @param {boolean} opts.attr - Update the property when an attribute with specified name is set. Defaults to the property-name
|
||||
* @param {boolean} opts.reflect - Reflect the property back to attributes when the property is set
|
||||
*/
|
||||
export function Prop(opts) {
|
||||
opts = opts || {};
|
||||
return function decorator(target, key, descriptor) {
|
||||
// Dev-note: Tis is run for every instance made of the decorated class ...
|
||||
// console.log("Prop " + key, target);
|
||||
// TODO could check the prototype if a markDirty function exists, throw an error if it doesn't
|
||||
|
||||
// Register this prop on the class, so the VDOM-renderer can find this and set the prop instead of the attribute (if @meta is every introduced as a built-in decorator, this would be the place to use it)
|
||||
if (!target.constructor.props) {
|
||||
target.constructor.props = new Set();
|
||||
}
|
||||
target.constructor.props.add(key);
|
||||
|
||||
let attrName = opts.attr !== false && typeof (opts.attr) === "string" ? opts.attr : key;
|
||||
|
||||
// Prop behavior
|
||||
let trackedDecorator = trackValue({ target, key, descriptor }, function (value) {
|
||||
//console.log(`Prop ${key} was set: ` + JSON.stringify(value));
|
||||
if (opts.reflect) {
|
||||
if ((value ?? false) === false) {
|
||||
this.removeAttribute(attrName);
|
||||
} else if (value === true) {
|
||||
this.setAttribute(attrName, "");
|
||||
} else {
|
||||
this.setAttribute(attrName, value);
|
||||
}
|
||||
}
|
||||
this.markDirty && this.markDirty();
|
||||
});
|
||||
|
||||
|
||||
// Registering as attr and subscribing to relevant callbacks
|
||||
if (opts.attr !== false || opts.attr === undefined) {
|
||||
let oldCallback = target.attributeChangedCallback;
|
||||
target.attributeChangedCallback = function (...args) {
|
||||
let [name, oldValue, newValue] = args;
|
||||
if (name === attrName) {
|
||||
// This is our tracked prop, pipe the attribute-value the the property-setter
|
||||
//console.log(`Attribute ${attrName} was set: ` + JSON.stringify(newValue));
|
||||
trackedDecorator.set.call(this, newValue);
|
||||
}
|
||||
if (oldCallback) oldCallback.call(this, ...args);
|
||||
};
|
||||
|
||||
let observedAttrs = Array.from(new Set([attrName, ...(target.constructor.observedAttributes || [])]));
|
||||
Object.defineProperty(target.constructor, 'observedAttributes', {
|
||||
get: () => observedAttrs,
|
||||
configurable: true
|
||||
});
|
||||
}
|
||||
|
||||
return trackedDecorator
|
||||
}
|
||||
}
|
||||
13
packages/csx/src/custom-element/state.js
Normal file
13
packages/csx/src/custom-element/state.js
Normal file
@ -0,0 +1,13 @@
|
||||
import {trackValue} from "../decorator-utils/track-value";
|
||||
|
||||
|
||||
/**
|
||||
* Decorate a variable as part of the component-state, and will thus trigger a re-render whenever it is changed
|
||||
*/
|
||||
export function State() {
|
||||
return function decorator(target, key, descriptor){
|
||||
// Dev-note: Tis is run for every instance made of the decorated class ...
|
||||
// console.log("State " + key, target);
|
||||
return trackValue({target, key, descriptor}, function(value){ this.markDirty && this.markDirty() });
|
||||
}
|
||||
}
|
||||
72
packages/csx/src/decorator-utils/track-value.js
Normal file
72
packages/csx/src/decorator-utils/track-value.js
Normal file
@ -0,0 +1,72 @@
|
||||
|
||||
/** Helper class to mark an initializer-value to be used on first get of a value**/
|
||||
class InitializerValue{ constructor(value){ this.value = value; } }
|
||||
|
||||
|
||||
/**
|
||||
* This callback type is called `requestCallback` and is displayed as a global symbol.
|
||||
*
|
||||
* @callback trackValueCallback
|
||||
* @param {any} value
|
||||
* @param {PropertyKey} prop
|
||||
*/
|
||||
|
||||
/**
|
||||
* This is an implementation of https://github.com/tc39/proposal-decorators/blob/master/NEXTBUILTINS.md#tracked
|
||||
* to use until support for the new decorators proposal lands in @babel
|
||||
*
|
||||
* @param {Object} decoratorArgs
|
||||
* @param {any} decoratorArgs.target
|
||||
* @param {PropertyKey} decoratorArgs.key
|
||||
* @param {PropertyDescriptor} decoratorArgs.descriptor
|
||||
* @param {trackValueCallback} cb
|
||||
* @return {PropertyDescriptor}
|
||||
*/
|
||||
export function trackValue({target, key, descriptor}, cb){
|
||||
let {get: oldGet, set: oldSet} = descriptor;
|
||||
let valueKey='__'+key;
|
||||
|
||||
if(!cb) throw Error("No callback given to track property. No point in decorating the prop");
|
||||
// TODO the story here of handling an initializer value is a bit dirty... Ideally, we'd want to run the value initializer before the class-constructor is called..
|
||||
|
||||
// Rewrite the property as if using getters and setters (if needed)
|
||||
descriptor.get = oldGet = oldGet || function(){
|
||||
let val = this[valueKey];
|
||||
if(val instanceof InitializerValue){
|
||||
this[valueKey] = val = val.value.call(this);
|
||||
}
|
||||
return val;
|
||||
};
|
||||
oldSet = oldSet || function(newVal){
|
||||
this[valueKey]=newVal;
|
||||
return newVal;
|
||||
};
|
||||
// Overwrite the setter to call markDirty whenever it is used
|
||||
descriptor.set = function(newValue){
|
||||
let oldvalue = oldGet.call(this);
|
||||
let result = oldSet.call(this, newValue);
|
||||
if(oldvalue!==newValue) cb.call(this,newValue, key);
|
||||
return result;
|
||||
};
|
||||
|
||||
// Update the descriptor to match with using getters and setters
|
||||
target.kind = 'method'; // update to get and set if need be..
|
||||
delete descriptor.writable;
|
||||
|
||||
// Catch usage of initial-value or value-initalizers and handle'em
|
||||
if(descriptor.value){
|
||||
Object.defineProperty(target, valueKey, {
|
||||
writable: true,
|
||||
value: descriptor.value
|
||||
});
|
||||
delete descriptor.value;
|
||||
}else if(descriptor.initializer){
|
||||
Object.defineProperty(target, valueKey, {
|
||||
writable: true,
|
||||
value: new InitializerValue(descriptor.initializer)
|
||||
});
|
||||
delete descriptor.initializer;
|
||||
}
|
||||
|
||||
return descriptor;
|
||||
}
|
||||
@ -3,17 +3,18 @@ import {
|
||||
HostNodeRenderer, Host,
|
||||
ShadowNodeRenderer, ShadowDOM,
|
||||
PrimitiveRenderer, Primitive,
|
||||
NodeTreeRenderer
|
||||
NodeTreeRenderer, NativeRenderer
|
||||
} from "./renderers";
|
||||
|
||||
// TODO Element renderer (for things that are already DOM-elements)
|
||||
export function getNodeMeta(vnode){
|
||||
if(vnode===undefined||vnode===null) return undefined; // Indicate it shouldn't render
|
||||
if(vnode instanceof Element) return {renderer: NativeRenderer, normedType: Element};
|
||||
let type = vnode?.type;
|
||||
if(!type) return {renderer: PrimitiveRenderer, normedType: Primitive};
|
||||
else if(type===Host) return {renderer: HostNodeRenderer, normedType: Host};
|
||||
else if(type===ShadowDOM) return {renderer: ShadowNodeRenderer, normedType: ShadowDOM};
|
||||
else return {renderer: NodeTreeRenderer, normedType: type};
|
||||
else return {renderer: NodeTreeRenderer, normedType: window.customElements?.get(type)??type};
|
||||
}
|
||||
|
||||
|
||||
@ -70,7 +71,7 @@ export function render(vnode, opts = {}) {
|
||||
// Create the element if no matching existing element was set
|
||||
let newlyCreated = false;
|
||||
if (!item.host) {
|
||||
item.host = renderer.create(item);
|
||||
item.host = renderer.create(item, meta);
|
||||
newlyCreated = true;
|
||||
|
||||
if(item.vnode?.props?.ref){// If props specify a ref-function, queue it to be called at the end of the render
|
||||
@ -79,10 +80,10 @@ export function render(vnode, opts = {}) {
|
||||
}
|
||||
|
||||
// Update the element
|
||||
renderer.update(item);
|
||||
renderer.update(item, meta);
|
||||
|
||||
// Update children
|
||||
if(item.vnode?.children || item.old?.children) {
|
||||
if(meta.normedType!==Element && (item.vnode?.children || item.old?.children)) {
|
||||
let childTypes = new Set();
|
||||
|
||||
// Flatten and organize new vNode-children (this could be a separate function, or implemented using a helper function (because mucht of the code is similar between old/new vnodes)
|
||||
|
||||
@ -2,3 +2,4 @@ export * from "./hostnode";
|
||||
export * from "./nodetree";
|
||||
export * from "./nodeprimitive";
|
||||
export * from "./shadownode";
|
||||
export * from "./nativeelement"
|
||||
23
packages/csx/src/vdom/renderers/nativeelement.js
Normal file
23
packages/csx/src/vdom/renderers/nativeelement.js
Normal file
@ -0,0 +1,23 @@
|
||||
import '../types';
|
||||
|
||||
/**
|
||||
* Takes care of rendering a Native DOM-Element
|
||||
*
|
||||
* @class
|
||||
* @implements {VNodeRenderer}
|
||||
*/
|
||||
export const NativeRenderer = {
|
||||
/**
|
||||
* @param {VRenderItem} item
|
||||
*/
|
||||
create(item){
|
||||
return item.vnode;
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {VRenderItem} item
|
||||
*/
|
||||
update(item){
|
||||
return;// NO-OP
|
||||
}
|
||||
};
|
||||
@ -12,6 +12,11 @@ const VNODEPROP_IGNORE = {
|
||||
['key']: true,
|
||||
['ref']: true
|
||||
};
|
||||
const VNODE_SPECIAL_PROPS = {
|
||||
['key']: false,
|
||||
['ref']: false,
|
||||
// TODO: className, (style), event/events, (see react-docs)
|
||||
}
|
||||
|
||||
let namespace = {
|
||||
svg: "http://www.w3.org/2000/svg"
|
||||
@ -48,8 +53,10 @@ export const NodeTreeRenderer = {
|
||||
/**
|
||||
* @param {VRenderItem} item
|
||||
*/
|
||||
update(item){
|
||||
update(item, meta){
|
||||
let vnode = item.vnode;
|
||||
let vtype = meta?.normedType||item.vnode?.type;
|
||||
|
||||
/**
|
||||
* @type {VNodeProps}
|
||||
*/
|
||||
@ -82,10 +89,11 @@ export const NodeTreeRenderer = {
|
||||
|
||||
// Now apply each
|
||||
for(let [key, newVal, oldVal] of propDiffs){
|
||||
if(VNODEPROP_IGNORE[key]){
|
||||
// Prop to be ignored (like 'key')
|
||||
}else if(VNODEPROP_DIRECT[key]){
|
||||
// Direct-value prop only (e.g. checked attribute of checkbox will reflect in attributes automatically, no need to set the attribute..)
|
||||
let special = VNODE_SPECIAL_PROPS[key];
|
||||
if(special === false){
|
||||
// Ignore the prop
|
||||
}else if(vtype.props?.has && vtype.props.has(key)){
|
||||
// Registered prop of the Custom-Element
|
||||
host[key] = newVal;
|
||||
}else if(key.slice(0,2)==='on' && key[2]>='A' && key[2]<='Z'){
|
||||
// Event-prop
|
||||
@ -96,13 +104,8 @@ export const NodeTreeRenderer = {
|
||||
}else{
|
||||
host.addEventListener(eventName, newVal);
|
||||
}
|
||||
// TODO might want to support objects for defining events, so we can specifiy passive or not, and other event options
|
||||
}else{
|
||||
if(!VNODEPROP_EXCLUDE_DIRECT[key] && !item.inSvg){
|
||||
// TODO there are many properties we do not want to be setting directly.. (transform attr on svg's is a good example...)
|
||||
// Unless otherwise excluded, set the prop directly on the Element as well (because this is what we'd typically want to do passing complex objects into custom-elements)
|
||||
host[key] = newVal;
|
||||
}
|
||||
// Assumed to be just an attribute
|
||||
if(newVal===undefined || newVal === false || newVal===null || newVal===''){
|
||||
host.removeAttribute(key);
|
||||
}else if(newVal === true){
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,11 +1,47 @@
|
||||
import {render} from "../../packages/csx";
|
||||
import style from "./index.scss";
|
||||
import {SvgLoader} from "./svg-loader";
|
||||
import {SvgTester} from "./svg-tester";
|
||||
import {SvgTesterTwo} from "./svg-tester-two";
|
||||
|
||||
let loader = render(<SvgLoader inverted="yes"/>);
|
||||
|
||||
document.body.appendChild(render(<style>{style}</style>));
|
||||
document.body.appendChild(render(
|
||||
<div class="center-me">
|
||||
<h3>SVG Loader</h3>
|
||||
<SvgLoader />
|
||||
{loader}
|
||||
<h3>SVG Tester</h3>
|
||||
<SvgTester/>
|
||||
<h3>SVG Tester Two</h3>
|
||||
<SvgTesterTwo/>
|
||||
</div>
|
||||
));
|
||||
|
||||
|
||||
setTimeout(()=>{
|
||||
console.log("Uninverting");
|
||||
loader.removeAttribute("inverted");
|
||||
|
||||
setTimeout(()=>{
|
||||
console.log("Inverting");
|
||||
loader.setAttribute("inverted", "ja");
|
||||
|
||||
setTimeout(()=>{
|
||||
console.log("Stays inverted");
|
||||
loader.setAttribute("inverted", "");
|
||||
|
||||
setTimeout(()=>{
|
||||
console.log("Inverted color");
|
||||
loader.setAttribute("inverted-color", "#0F0");
|
||||
}, 1000);
|
||||
}, 1000);
|
||||
}, 1000);
|
||||
|
||||
|
||||
}, 1000);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import {CustomElement, defineElement, Host, ShadowDOM, State} from "../../packages/csx";
|
||||
import {CustomElement, defineElement, Host, ShadowDOM, State, Prop} from "../../packages/csx";
|
||||
import loaderComponentShadowStyle from './svg-loader.shadow.scss';
|
||||
|
||||
// TODO configurability, like inverted and not with props...
|
||||
@ -13,6 +13,8 @@ export class SvgLoader extends CustomElement{
|
||||
// Private properties
|
||||
|
||||
// Properties
|
||||
@Prop({reflect: true}) inverted;
|
||||
@Prop({reflect: true, attr: "inverted-color"}) invertedColor = "#000";
|
||||
|
||||
// Handlers
|
||||
|
||||
@ -27,7 +29,7 @@ export class SvgLoader extends CustomElement{
|
||||
|
||||
<div class="loader-content">
|
||||
<div class="spinner">
|
||||
<svg width="38" height="38" viewBox="0 0 38 38" xmlns="http://www.w3.org/2000/svg" stroke="#000">
|
||||
<svg width="38" height="38" viewBox="0 0 38 38" xmlns="http://www.w3.org/2000/svg" stroke={(this.inverted??false)!==false? this.invertedColor : "#F00"}>
|
||||
<g fill="none" fill-rule="evenodd">
|
||||
<g transform="translate(1 1)" stroke-width="2">
|
||||
<circle stroke-opacity=".5" cx="18" cy="18" r="18"/>
|
||||
|
||||
43
test/svg/svg-tester-two.jsx
Normal file
43
test/svg/svg-tester-two.jsx
Normal file
@ -0,0 +1,43 @@
|
||||
import {CustomElement, defineElement, Host, ShadowDOM, State, Prop} from "../../packages/csx";
|
||||
import {SvgLoader} from "./svg-loader";
|
||||
|
||||
@defineElement('svg-tester-two')
|
||||
export class SvgTesterTwo extends CustomElement{
|
||||
// Constructor
|
||||
constructor(){
|
||||
super();
|
||||
}
|
||||
|
||||
// Private properties
|
||||
states = [
|
||||
{ inverted: true, invertedColor: "#F00"},
|
||||
{ inverted: true, invertedColor: "#FF0"},
|
||||
{ inverted: true, invertedColor: "#0FF"},
|
||||
{ inverted: false},
|
||||
{ inverted: true, invertedColor: "#0F0"},
|
||||
];
|
||||
|
||||
// Properties
|
||||
@State() state = this.states[0];
|
||||
|
||||
// Handlers
|
||||
|
||||
// CustomElement
|
||||
connectedCallback() {
|
||||
setInterval(()=>{
|
||||
// Moving state
|
||||
let curIndex = this.states.indexOf(this.state);
|
||||
this.state = this.states[(curIndex+1)>=this.states.length?0:curIndex+1];
|
||||
}, 1000);
|
||||
super.connectedCallback();
|
||||
}
|
||||
|
||||
render(){
|
||||
// invertedColor instead of inverted-color is the only difference!
|
||||
return (
|
||||
<Host>
|
||||
<SvgLoader inverted={this.state.inverted} invertedColor={this.state.invertedColor} />
|
||||
</Host>
|
||||
);
|
||||
}
|
||||
}
|
||||
42
test/svg/svg-tester.jsx
Normal file
42
test/svg/svg-tester.jsx
Normal file
@ -0,0 +1,42 @@
|
||||
import {CustomElement, defineElement, Host, ShadowDOM, State, Prop} from "../../packages/csx";
|
||||
import {SvgLoader} from "./svg-loader";
|
||||
|
||||
@defineElement('svg-tester')
|
||||
export class SvgTester extends CustomElement{
|
||||
// Constructor
|
||||
constructor(){
|
||||
super();
|
||||
}
|
||||
|
||||
// Private properties
|
||||
states = [
|
||||
{ inverted: true, invertedColor: "#F00"},
|
||||
{ inverted: true, invertedColor: "#FF0"},
|
||||
{ inverted: true, invertedColor: "#0FF"},
|
||||
{ inverted: false},
|
||||
{ inverted: true, invertedColor: "#0F0"},
|
||||
];
|
||||
|
||||
// Properties
|
||||
@State() state = this.states[0];
|
||||
|
||||
// Handlers
|
||||
|
||||
// CustomElement
|
||||
connectedCallback() {
|
||||
setInterval(()=>{
|
||||
// Moving state
|
||||
let curIndex = this.states.indexOf(this.state);
|
||||
this.state = this.states[(curIndex+1)>=this.states.length?0:curIndex+1];
|
||||
}, 1000);
|
||||
super.connectedCallback();
|
||||
}
|
||||
|
||||
render(){
|
||||
return (
|
||||
<Host>
|
||||
<SvgLoader inverted={this.state.inverted} inverted-color={this.state.invertedColor} />
|
||||
</Host>
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -18,19 +18,19 @@ export class MyTodo extends CustomElement{
|
||||
<style>{ style }</style>
|
||||
<h1>CSX Todo</h1>
|
||||
<section>
|
||||
<todo-input onSubmit={this.handleSubmit}/>
|
||||
<TodoInput onSubmit={this.handleSubmit}/>
|
||||
<ul id="list-container"
|
||||
onCheck={this.handleCheck}
|
||||
onRemove={this.handleRemove}
|
||||
>
|
||||
{this.todos.map(item =>
|
||||
<todo-item
|
||||
<TodoItem
|
||||
key={item.id}
|
||||
model={ item.id }
|
||||
checked={ item.checked }
|
||||
>
|
||||
{ item.text }
|
||||
</todo-item>
|
||||
</TodoItem>
|
||||
)}
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user