Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New Writing For Lego #38

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
718 changes: 2 additions & 716 deletions dist/lego.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/lego.min.js
Original file line number Diff line number Diff line change
@@ -1 +1 @@
const e={},t=e=>null===e||Array.isArray(e)&&0===e.length,n=e=>Array.isArray(e)&&e.length>0,i=e=>"string"==typeof e||"number"==typeof e,r=e=>1===e?.vtype,o=e=>2===e?.vtype,s=e=>4===e?.vtype;function c(t,n,...i){return n=n??e,function(e,t,n){if(n!=n)throw new Error("Invalid NaN key");const i="string"==typeof e?1:(r=e,"function"==typeof r?.mount?4:"function"==typeof e?2:void 0);var r;if(void 0===i)throw new Error("Invalid VNode type");return{vtype:i,type:e,key:n,props:t}}(t,n=i.length>1?Object.assign({},n,{children:i}):1===i.length?Object.assign({},n,{children:i[0]}):n,n.key)}function l(e){return{mount(t,n){t[e]=n},patch(t,n,i){n!==i&&(t[e]=n)},unmount(t,n){t[e]=null}}}const d={selected:l("selected"),checked:l("checked"),value:l("value"),innerHTML:l("innerHTML")},u="http://www.w3.org/1999/xlink",h={show:u,actuate:u,href:u};function f(e){if(1===e.type)return e.node;if(4===e.type)return f(e.children[0]);if(8===e.type)return f(e.childRef);throw new Error("Unkown ref type "+JSON.stringify(e))}function p(e){if(1===e.type)return e.node.parentNode;if(4===e.type)return p(e.children[0]);if(8===e.type)return p(e.childRef);throw new Error("Unkown ref type "+e)}function a(e){if(1===e.type)return e.node.nextSibling;if(4===e.type)return a(e.children[e.children.length-1]);if(8===e.type)return a(e.childRef);throw new Error("Unkown ref type "+JSON.stringify(e))}function y(e,t,n){if(1===t.type)e.insertBefore(t.node,n);else if(4===t.type)t.children.forEach((t=>{y(e,t,n)}));else{if(8!==t.type)throw new Error("Unkown ref type "+JSON.stringify(t));y(e,t.childRef,n)}}function v(e,t){if(1===t.type)e.removeChild(t.node);else if(4===t.type)t.children.forEach((t=>{v(e,t)}));else{if(8!==t.type)throw new Error("Unkown ref type "+t);v(e,t.childRef)}}function w(e,t,n,i){if(!0===n)e.setAttribute(t,"");else if(!1===n)e.removeAttribute(t);else{var r=i?h[t]:void 0;void 0!==r?e.setAttributeNS(r,t,n):e.setAttribute(t,n)}}const m={isSvg:!1,directives:d};class _{constructor(e,t){this.props=e,this._STATE_={env:t,vnode:null,parentDomNode:null,ref:g(null)},this.render=this.render.bind(this)}setProps(e){this.oldProps=this.props,this.props=e}render(e){const t=this._STATE_,n=t.vnode;if(t.vnode=e,null==t.parentDomNode){let n=p(t.ref);if(null==n)return void(t.ref=g(e,t.env));t.parentDomNode=n}t.ref=k(t.parentDomNode,e,n,t.ref,t.env)}}function g(e,c=m){if(t(e))return{type:1,node:document.createComment("NULL")};if(i(e))return{type:1,node:document.createTextNode(e)};if(r(e)){let t,{type:n,props:i}=e;"svg"!==n||c.isSvg||(c=Object.assign({},c,{isSVG:!0})),t=c.isSVG?document.createElementNS("http://www.w3.org/2000/svg",n):document.createElement(n),function(e,t,n){for(var i in t)"key"===i||"children"===i||i in n.directives||(i.startsWith("on")?e[i.toLowerCase()]=t[i]:w(e,i,t[i],n.isSVG))}(t,i,c);let r=null==i.children?i.children:g(i.children,c);return null!=r&&y(t,r),function(e,t,n){for(let i in t)i in n.directives&&n.directives[i].mount(e,t[i])}(t,i,c),{type:1,node:t,children:r}}if(n(e))return{type:4,children:e.map((e=>g(e,c)))};if(o(e)){let t=e.type(e.props);return{type:8,childRef:g(t,c),childState:t}}if(s(e)){let t=new _(e.props,c);return e.type.mount(t),{type:8,childRef:t._STATE_.ref,childState:t}}if(e instanceof Node)return{type:1,node:e};if(void 0===e)throw new Error("mount: vnode is undefined!");throw new Error("mount: Invalid Vnode!")}function S(e,c,l,d,u=m){if(l===c)return d;if(t(c)&&t(l))return d;if(i(c)&&i(l))return d.node.nodeValue=c,d;if(r(c)&&r(l)&&c.type===l.type){"svg"!==c.type||u.isSvg||(u=Object.assign({},u,{isSVG:!0})),function(e,t,n,i){for(var r in t)if("key"!==r&&"children"!==r&&!(r in i.directives)){var o=n[r],s=t[r];o!==s&&(r.startsWith("on")?e[r.toLowerCase()]=s:w(e,r,s,i.isSVG))}for(r in n)"key"===r||"children"===r||r in i.directives||r in t||(r.startsWith("on")?e[r.toLowerCase()]=null:e.removeAttribute(r))}(d.node,c.props,l.props,u);let e=l.props.children,t=c.props.children;return null==e?null!=t&&(d.children=g(t,u),y(d.node,d.children)):null==t?(d.node.textContent="",b(e,d.children,u),d.children=null):d.children=k(d.node,t,e,d.children,u),function(e,t,n,i){for(let r in t)r in i.directives&&i.directives[r].patch(e,t[r],n[r]);for(let r in n)r in i.directives&&!(r in t)&&i.directives[r].unmount(e,n[r])}(d.node,c.props,l.props,u),d}if(n(c)&&n(l))return function(e,t,n,i,r){const o=a(i),s=Array(t.length);let c,l,d,u,h,p=i.children,w=0,m=0,_=t.length-1,E=n.length-1;for(;w<=_&&m<=E;){if(null===p[m]){m++;continue}if(null===p[E]){E--;continue}if(c=n[m],l=t[w],l?.key===c?.key){d=p[m],u=s[w]=k(e,l,c,d,r),w++,m++;continue}if(c=n[E],l=t[_],l?.key===c?.key){d=p[E],u=s[_]=k(e,l,c,d,r),_--,E--;continue}if(null==h){h={};for(let e=m;e<=E;e++)c=n[e],null!=c?.key&&(h[c.key]=e)}l=t[w];const i=null!=l?.key?h[l.key]:null;null!=i?(c=n[i],d=p[i],u=s[w]=S(e,l,c,d,r),y(e,u,f(p[m])),u!==d&&(v(e,d),b(c,d,r)),p[i]=null):(u=s[w]=g(l,r),y(e,u,f(p[m]))),w++}const A=_<t.length-1?f(s[_+1]):o;for(;w<=_;){const n=g(t[w],r);s[w]=n,y(e,n,A),w++}for(;m<=E;)d=p[m],null!=d&&(v(e,d),b(n[m],d,r)),m++;i.children=s}(e,c,l,d,u),d;if(o(c)&&o(l)&&c.type===l.type){let t=c.type;if(null!=t.shouldUpdate?t.shouldUpdate(l.props,c.props):function(e,t){if(e===t)return!1;for(let n in t)if(e[n]!==t[n])return!0;return!1}(l.props,c.props)){let n=t(c.props),i=S(e,n,d.childState,d.childRef,u);return i!==d.childRef?{type:8,childRef:i,childState:n}:(d.childState=n,d)}return d}if(s(c)&&s(l)&&c.type===l.type){const t=d.childState,n=t._STATE_;return n.env=u,n.parentNode=e,t.setProps(c.props),c.type.patch(t),d.childRef!==n.ref?{type:8,childRef:n.ref,childState:t}:d}return c instanceof Node&&l instanceof Node?(d.node=c,d):g(c,u)}function b(e,t,i){r(e)?(!function(e,t,n){for(let i in t)i in n.directives&&n.directives[i].unmount(e,t[i])}(t.node,e.props,i),null!=e.props.children&&b(e.props.children,t.children,i)):n(e)?e.forEach(((e,n)=>b(e,t.children[n],i))):o(e)?b(t.childState,t.childRef,i):s(e)&&e.type.unmount(t.childState)}function k(e,t,n,i,r){const o=S(e,t,n,i,r);return o!==i&&(!function(e,t,n){y(e,t,f(n)),v(e,n)}(e,o,i),b(n,i,r)),o}function E(e,t,n={}){let i=t.$$PETIT_DOM_REF,r=Object.assign({},m);if(Object.assign(r.directives,n.directives),null==i){const n=g(e,r);t.$$PETIT_DOM_REF={ref:n,vnode:e},t.textContent="",y(t,n,null)}else i.ref=k(t,e,i.vnode,i.ref,r),i.vnode=e}function A(e){if(e.includes("-")){const t=e.split("-");e=t[0]+t.splice(1).map((e=>e[0].toUpperCase()+e.substr(1))).join("")}return e}class N extends HTMLElement{constructor(){super(),this.useShadowDOM=!0,this.__isConnected=!1,this.__state={},this.init&&this.init(),this.watchProps=Object.keys(this.__state),this.__attributesToState(),this.document=this.useShadowDOM?this.attachShadow({mode:"open"}):this}__attributesToState(){Object.assign(this.state,Array.from(this.attributes).reduce(((e,t)=>Object.assign(e,{[A(t.name)]:t.value})),{}))}get vdom(){return({state:e})=>""}get vstyle(){return({state:e})=>""}setAttribute(e,t){super.setAttribute(e,t);const n=A(e);this.watchProps.includes(n)&&this.render({[n]:t})}removeAttribute(e){super.removeAttribute(e);const t=A(e);this.watchProps.includes(t)&&t in this.state&&(this.render({[t]:null}),delete this.state[t])}connectedCallback(){this.__isConnected=!0,this.connected&&this.connected(),this.render()}disconnectedCallback(){this.__isConnected=!1,this.setState({}),this.disconnected&&this.disconnected()}async setState(e={}){Object.assign(this.__state,e),this.changed&&this.__isConnected&&await this.changed(e)}set state(e){this.setState(e)}get state(){return this.__state}async render(e){if(await this.setState(e),this.__isConnected)return E([this.vdom({state:this.__state}),this.vstyle({state:this.__state})],this.document)}}export{N as Component,c as h,E as render};
export{h,render}from"../../../../../../node_modules/petit-dom/src/index.js";export{default as Component}from"../../../../../../src/lib/Component.js";
Binary file modified dist/lego.min.js.gz
Binary file not shown.
12 changes: 5 additions & 7 deletions docs/10-getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,15 +35,13 @@ index.html
Create a file called __bricks/hello-world.html__:

```html
<template>
<p>Hello ${state.name}</p>
</template>

<script>
export default class extends Lego {
init() { this.state = { name: "World!" } }
}
const state = { name: "World!" } }
</script>

<template>
<p>Hello ${ state.name }</p>
</template>
```

Compile with `npx lego` (or `yarn lego`)
Expand Down
13 changes: 7 additions & 6 deletions docs/20.04-custom.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,15 @@ parent: Usage
A custom directive will interpret in JS whatever you pass as value.

```html
<template>
<a :href="this.getUrl('144')">Visit Profile</a>
</template>
<script>
export default class extends Lego {
getUrl(id) { return `/user/${id}` }
function getUrl(id) {
return `/user/${id}`
}
</script>

<template>
<a :href="getUrl('144')">Visit Profile</a>
</template>
```

outputs
Expand All @@ -31,5 +32,5 @@ outputs
[Boolean attributes](https://www.w3.org/TR/html5/infrastructure.html#sec-boolean-attributes)

Example: `<input type=checkbox :checked="state.agreed" :required="state.mustAgree">`.
With the following state: `this.state = { agreed: false, mustAgree: true }` would render
With the following state: `const state = { agreed: false, mustAgree: true }` would render
`<input type=checkbox required="required">`.
13 changes: 6 additions & 7 deletions docs/20.05-event.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,13 @@ parent: Usage
#### `@` Directive for binding Events

```html
<template>
<button @click="sayHi" name="the button">click</button>

<script>
export default class extends Lego {
sayHi(event) {
alert(`You clicked to says hi! 👋🏼`)
}
function sayHi(event) {
const buttonName = event.target.name
alert(`You clicked ${ buttonName } to says hi! 👋🏼`)
}
</script>
<template>
<button @click="sayHi" name="the-button">click</button>
</template>
```
18 changes: 9 additions & 9 deletions docs/20.06-reactive.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,26 +14,26 @@ The `state` is where the reactiveness takes place.
declare a `state` object in the `init()` function with default values:

```js
init() {
this.state = {
user: { firstname: 'John', lastname: 'Doe' },
status: "Happy 😄"
}
const state = {
user: { firstname: 'John', lastname: 'Doe' },
status: "Happy 😄"
}

const title = "This title is non-reactive"
```

Displaying a _state_ value is as simple as writing `${state.theValue}` in your HTML.

When you need your component to react, call the `this.render()` method
When you need your component to react, call the `render()` method
with your updated state:

```
updateStatus(event) {
this.render({ status: "Laughing 😂" })
function updateStatus(event) {
render({ status: "Laughing 😂" })
}
```

This will refresh your component where needed.
This will update your component only where needed.

When `state` is just mutated, the `changed(changedProps)` is called.
This `changed()` method is called before (re-)rendering.
41 changes: 10 additions & 31 deletions docs/20.07-attributes.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ Will write `…<p>important information: <span>This user is in Paris</span></p>`
[See more advanced examples](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_templates_and_slots#Adding_flexibility_with_slots).


### Reactive CSS `<style>`
### Reactive CSS Within `<style>`

CSS is much more fun when it's scoped.
Here it come with the web-components.
Expand All @@ -63,18 +63,14 @@ Well, you should know that the css is reactive too! 😲
Writing CSS is as easy as

```html
<script>
const state = { fontScale: 1 }
</script>

<template>
<h1>Bonjour!</h1>
</template>

<script>
export default class extends Lego {
init() {
this.state = { fontScale: 1 }
}
}
</script>

<style>
:host {
font-size: ${state.fontScale}rem;
Expand All @@ -98,16 +94,14 @@ You can use variables in your CSS just like in your templates.

Example:
```html
<script>
const state = { color: '#357' }
</script>

<template>
<h1>Bonjour<h1>
</template>
<script>
export default class extends Lego {
init() {
this.state = { color: '#357' }
}
}
</script>

<style>
h1 {
color: ${ state.color };
Expand All @@ -116,18 +110,3 @@ Example:
```

will apply the `#357` color onto `h1`.


## `<script>` tag

The script tag is has a special behavior.
You will create a class extending the component, that's how you build your
full component with advanced script.

To do so extend the `_`, that's a naming convention:

```js
export default class extends Lego {
}
```
48 changes: 42 additions & 6 deletions docs/20.10-script.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,28 +7,64 @@ nav_order: 10
parent: Usage
---

## `<script>` tag

The script tag is has a special behavior.
You will create a class extending the component, that's how you build your

## `<script extend>` tag

The "script extend" tag is has a special behavior.
You will create a class extending the component, that's how you extend your
full component with advanced script.

To do so extend the `Lego` default class:
To do so extend the `Lego` class, that's a naming convention:

```js
export default class extends Lego {
}
```

From there you can catch `connected()` events, `changed()` when the state is updated,
override `setAttributes()` and all other Lego or HTMLElement properties.

It's also a great place to define custom events or custom methods for your component
that are accessible from the outside:

/bricks/my-element.html
```html
<script extend>
export default class extends Lego {
getStatus() {
return `you are requesting status of ${ this.nodeName }`)
}
connected() {
console.info('the component is connected to the dom')
}
}
</script>
```

/bricks/index.html
```html
<my-element id="elem"></my-element>
<script>
console.debug(document.querySelector('#elem').getStatus())
</script>
```

### Accessing the component's DOM

Even if it's not the most recommended way it might occur that you need to access a DOM element from the script tag.

In which case the shortcut `this.document` will gain you access to the DOM,
wether it's the Shadow DOM (default) or you toggled to Light DOM (overriding).

`this.document` has all the methods you may expect from a document such as
`this.document` has all the native methods you may expect from a document such as
`querySelector`, `getElementById`, `querySelectorAll`…

Ex: `this.document.querySelectorAll('a')`
```js
export default class extends Lego {
init() {
console.log(this.document.querySelectorAll('a'))
}
}
```
14 changes: 7 additions & 7 deletions docs/40-compiling.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,16 @@ nav_order: 40
Compiling Lego component is built-in with no extra installation; pretty straighforward.

```sh
LEGO_URL=</url/to/lego.min.js> npx lego <source_path> <target_file_path>
npx lego [-w] [source_path] [target_path]
```

Would compile the _source_path_ file or folder (recursively) into _target_file_path_ js file using lego.min.js from the declared url.
- **-w**: watching for changes in components and recreating
- **source_path**: (default: ./bricks) either a file or a directory (relative or absolute) where you write your HTML components. If it's a directory, it will recursively read all the _.html_ files and compile them into the _target_file_.
- **target_path**: (default: ./dist) the path (relative or absolute) where your JS components will be generated. That folder will be created and contain all the components.

As mentioned before, when developing you probably want to watch for changes with the `-w`
option: `npx lego -w <source_path> <target_file_path>`

Therefore `npx lego` would compile the _source_path_ files or folder (recursively) into _target_file_path_ js file using lego.min.js (from ./bricks to ./dist by default).

**source_path**: either a file or a directory (relative or absolute). If it's a directory, it will recursively read all the _.html_ files and compile them into the _target_file_.
As mentioned before, when developing you probably want to watch for changes with the `-w`
option: `npx lego -w <source_path> <target_file_path>`

**target_file_path**: (default: _components.js_) the path (relative or absolute) to a _.js_ file.
That file will be created and contain all the components.
54 changes: 24 additions & 30 deletions docs/60-advanced.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,39 +15,33 @@ Let's write a web-component that:
__bricks/user-profile.html__

```html
<template>
<div>
<h1>${state.firstName} ${state.lastName}'s profile</h1>
<p>Welcome ${state.firstName}!</p>
<section :if="state.fruits.length">
<h3>You favorite fruits:</h3>
<ul>
<li :for="fruit in state.fruits">${fruit.name} ${fruit.icon}</li>
</ul>
</section>

<p :if="state.registered">You are registered!</p>

<button @click="edit">Edit my profile</button>
</div>
</template>

<script>
export default class extends Lego {
init() {
this.state = {
registered: false,
firstName: 'John',
lastName: 'Doe',
fruits: [{ name: 'Apple', icon: '🍎' }, { name: 'Pineapple', icon: '🍍' }]
}
}
const state = {
registered: false,
firstName: 'John',
lastName: 'Doe',
fruits: [{ name: 'Apple', icon: '🍎' }, { name: 'Pineapple', icon: '🍍' }]
}

register() {
this.render({ registered: confirm('You are about to register…') })
}
function register() {
render({ registered: confirm('You are about to register…') })
}
</script>

<template>
<h1>${ state.firstName } ${ state.lastName }'s profile</h1>
<p>Welcome ${ state.firstName }!</p>
<section :if="state.fruits.length">
<h3>The best ${ state.fruits.length } fruit you like:</h3>
<ul>
<li :for="fruit in state.fruits">${ fruit.name } ${ fruit.icon }</li>
</ul>
</section>

<p :if="state.registered">You are registered!</p>

<button @click="register">Register now</button>
</template>
```

Compile this component: `npx lego bricks`
Expand All @@ -61,7 +55,7 @@ _index.html_
<script src="./dist/index.js" type="module"></script>
```

Run your webserver and see your little app!
Run your web server and see your little app!


> When developing you may want to automatically watch files changes.
Expand Down
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@polight/lego",
"version": "1.8.3",
"description": "Tiny webcomponent lib for future-proof HTML mentors",
"version": "1.8.5",
"description": "Tiny Web-Components lib for future-proof HTML mentors",
"main": "index.js",
"type": "module",
"scripts": {
Expand All @@ -13,6 +13,7 @@
"parse5": "^7.1.2"
},
"devDependencies": {
"petit-dom": "^0.6.0",
"@rollup/plugin-node-resolve": "*",
"rollup": "*",
"rollup-plugin-gzip": "*",
Expand Down
Loading