wrec 0.4.3 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -4,17 +4,21 @@
4
4
 
5
5
  Wrec is a small, zero dependency library that
6
6
  greatly simplifies building web components.
7
- It was inspired by [Lit](https://lit.dev).
8
-
9
- Wrec has fewer features than Lit.
10
- In exchange, Wrec:
7
+ Its main features are that it automates
8
+ wiring event listeners and implementing reactivity.
11
9
 
12
- - is much smaller than Lit (about 1/4 of the size)
13
- - doesn't require any tooling
14
- - doesn't require a build process
10
+ Wrec was inspired by [Lit](https://lit.dev).
11
+ It has the following advantages over Lit:
15
12
 
16
- The main features of Wrec are that it automates
17
- wiring event listeners and implementing reactivity.
13
+ - Wrec is simpler ... just a single class to extend (Wrec).
14
+ - Wrec is slightly smaller ... 4K versus 5.8K minified.
15
+ - Wrec has a cleaner syntax ... no need to
16
+ surround JS expressions with `${...}`.
17
+ - Wrec provides automatic 2-way data binding ...
18
+ no need to dispatch custom events and listen for them.
19
+ - Wrec doesn't require a special syntax for Boolean attributes.
20
+ - Wrec enables specifying the content of a `textarea` element
21
+ with a JavaScript expressions in its text content.
18
22
 
19
23
  ## Getting Started
20
24
 
@@ -49,10 +53,8 @@ Here are the steps:
49
53
  };
50
54
 
51
55
  static css = css`
52
- .counter {
53
- display: flex;
54
- align-items: center;
55
- gap: 0.5rem;
56
+ :host {
57
+ display: block;
56
58
  }
57
59
 
58
60
  button {
@@ -66,7 +68,11 @@ Here are the steps:
66
68
 
67
69
  static html = html`
68
70
  <div>
69
- <button onClick="decrement" type="button" disabled="this.count === 0">
71
+ <button
72
+ onClick="this.count--"
73
+ type="button"
74
+ disabled="this.count === 0"
75
+ >
70
76
  -
71
77
  </button>
72
78
  <span>this.count</span>
@@ -74,10 +80,6 @@ Here are the steps:
74
80
  <span>(this.count < 10 ? "single" : "double") + " digit"</span>
75
81
  </div>
76
82
  `;
77
-
78
- decrement() {
79
- if (this.count > 0) this.count--;
80
- }
81
83
  }
82
84
 
83
85
  MyCounter.register();
@@ -131,6 +133,7 @@ can be replaced by `onClick="increment"`.
131
133
  Wrec supports reactivity.
132
134
  Attribute values and the text content of elements
133
135
  can refer to web component properties with the syntax `this.somePropertyName`.
136
+ In this context, `this` always refers to the parent web component.
134
137
  The DOM of the web component is surgically updated.
135
138
  Only attribute values and text content
136
139
  that refer to modified web component properties are updated.
@@ -141,6 +144,17 @@ For an example of this kind of web component, see `examples/hello-world.js`.
141
144
  Wrec evaluates JavaScript expressions in the context of a web component instance
142
145
  which can be referred to with the `this` keyword in the expressions.
143
146
 
147
+ In insert the value of an expression
148
+ that does not use properties of the web component,
149
+ into an HTML template string,
150
+ surround the expression with the syntax `${...}`.
151
+ For example, assuming `DAYS` is a variable
152
+ whose value is an array of month names:
153
+
154
+ ```html
155
+ <p>The month is ${DAYS[new Date().getDay()]}.</p>
156
+ ```
157
+
144
158
  Wrec supports conditional and iterative generation of HTML.
145
159
  See `examples/temperature-eval.js` for an example of a web component
146
160
  that conditionally decides what to render based on an attribute value.
package/dist/wrec.min.js CHANGED
@@ -1 +1 @@
1
- const FIRST_CHAR="a-zA-Z_$",OTHER_CHAR=FIRST_CHAR+"0-9",IDENTIFIER=`[${FIRST_CHAR}][${OTHER_CHAR}]*`,REFERENCE_RE=new RegExp(`^this.${IDENTIFIER}$`),REFERENCES_RE=new RegExp(`this.${IDENTIFIER}`,"g"),SKIP=5;class Wrec extends HTMLElement{static#t=document.createElement("template");#e=new Map;#s;#r;#o=new Map;constructor(){super(),this.attachShadow({mode:"open"});this.constructor["#propertyToExpressionsMap"]||(this.constructor["#propertyToExpressionsMap"]=new Map),this.constructor.formAssociated&&(this.#r=this.attachInternals(),this.#s=new FormData,this.#r.setFormValue(this.#s))}attributeChangedCallback(t,e,s){const r=this.#n(t,s);this[t]=r,this.#i(t,r)}#a(t,e,s){t.addEventListener("input",t=>{this[e]=t.target.value});let r=this.#o.get(e);r||(r=[],this.#o.set(e,r)),r.push(s?{element:t,attrName:s}:t)}buildDOM(){const t=this.constructor;let e=t.css?`<style>${t.css}</style>`:"";e+=t.html,Wrec.#t.innerHTML=e,this.shadowRoot.replaceChildren(Wrec.#t.content.cloneNode(!0))}connectedCallback(){this.#c(),this.buildDOM(),requestAnimationFrame(()=>{this.#l(this.shadowRoot),this.#h(this.shadowRoot),this.constructor.processed=!0})}#c(){const t=this.constructor.properties,{observedAttributes:e}=this.constructor;for(const[s,r]of Object.entries(t))this.#p(s,r,e)}#p(t,e,s){const r=s.includes(t)&&this.hasAttribute(t)?this.#u(t):e.value,o="#"+t;this[o]=r,Object.defineProperty(this,t,{enumerable:!0,get(){return this[o]},set(e){if(e===this[o])return;if(this[o]=e,this.hasAttribute(t)){e!==this.#u(t)&&this.#m(this,t,e)}this.#d(t);const s=this.propertyToParentPropertyMap,r=s?s.get(t):null;if(r){this.getRootNode().host.setAttribute(r,e)}this.#i(t,e)}})}#f(t){const e=t.localName.includes("-");for(const s of t.getAttributeNames()){const r=t.getAttribute(s);if(REFERENCE_RE.test(r)){const o=r.substring(SKIP),n=this[o];if(!n){const t=this.constructor.name;throw new Error(`component ${t} missing property "${o}"`)}if(t[o]=n,"value"===s&&this.#a(t,o,s),e){let e=t.propertyToParentPropertyMap;e||(e=new Map,t.propertyToParentPropertyMap=e),e.set(s,o)}}this.#E(r,t,s)}}#b(expression){return(()=>eval(expression)).call(this)}#g(t){const{localName:e}=t;if("style"===e)return;const s=t.textContent.trim();if("textarea"===e&&REFERENCE_RE.test(s)){const e=s.substring(SKIP);this.#a(t,e),t.textContent=this[e]}else this.#E(s,t)}#u(t){return this.#n(t,this.getAttribute(t))}#n(t,e){const s=this.constructor.properties[t].type;return s===Number?Number(e):s===Boolean?Boolean(e):e}#h(t){const e=t.querySelectorAll("*");for(const t of e)this.#f(t),t.firstElementChild||this.#g(t)}static get observedAttributes(){return Object.keys(this.properties||{})}#d(t){const e=this.constructor["#propertyToExpressionsMap"].get(t)||[];for(const t of e){const e=this.#b(t),s=this.#e.get(t)||[];for(const t of s)if(t instanceof Element)this.#R(t,e);else{const{element:s,attrName:r}=t;this.#m(s,r,e)}}requestAnimationFrame(()=>{this.#A(t)})}static register(){const t=Wrec.#y(this.name);customElements.get(t)||customElements.define(t,this)}#E(t,e,s){const r=t.match(REFERENCES_RE);if(!r)return;this.constructor.processed||r.forEach(e=>{const s=e.substring(SKIP),r=this.constructor["#propertyToExpressionsMap"];let o=r.get(s);o||(o=[],r.set(s,o)),o.push(t)});let o=this.#e.get(t);o||(o=[],this.#e.set(t,o)),o.push(s?{element:e,attrName:s}:e);const n=this.#b(t);s?this.#m(e,s,n):this.#R(e,n)}static#y=t=>t.replace(/([a-z0-9])([A-Z])/g,"$1-$2").toLowerCase();#i(t,e){this.#s&&(this.#s.set(t,e),this.#r.setFormValue(this.#s))}#m(t,e,s){const r=t.getAttribute(e);"boolean"==typeof s?s?r!==e&&t.setAttribute(e,e):t.removeAttribute(e):r!==s&&t.setAttribute(e,s)}#A(t){const e=this[t],s=this.#o.get(t)||[];for(const t of s)if(t instanceof Element)"textarea"===t.localName?t.value=e:t.textContent=e;else{const{element:s,attrName:r}=t;this.#m(s,r,e),s[r]=e}}#R(t,e){const{localName:s}=t;"textarea"===s?t.value=e:"string"==typeof e&&e.trim().startsWith("<")?(t.innerHTML=e,this.#l(t),this.#h(t)):t.textContent=e}#l(root){const elements=root.querySelectorAll("*");for(const element of elements)for(const attr of element.attributes){const{name:name}=attr;if(name.startsWith("on")){const eventName=name.slice(2).toLowerCase(),{value:value}=attr,fn="function"==typeof this[value]?t=>this[value](t):()=>eval(value);element.addEventListener(eventName,fn),element.removeAttribute(name)}}}}export default Wrec;export const css=String.raw;export const html=String.raw;
1
+ const FIRST_CHAR="a-zA-Z_$",OTHER_CHAR=FIRST_CHAR+"0-9",IDENTIFIER=`[${FIRST_CHAR}][${OTHER_CHAR}]*`,REFERENCE_RE=new RegExp(`^this.${IDENTIFIER}$`),REFERENCES_RE=new RegExp(`this.${IDENTIFIER}`,"g"),SKIP=5;class Wrec extends HTMLElement{static#t=document.createElement("template");#e=new Map;#s;#r;#n=new Map;constructor(){super(),this.attachShadow({mode:"open"});this.constructor["#propertyToExpressionsMap"]||(this.constructor["#propertyToExpressionsMap"]=new Map),this.constructor.formAssociated&&(this.#r=this.attachInternals(),this.#s=new FormData,this.#r.setFormValue(this.#s))}attributeChangedCallback(t,e,s){const r=this.#o(t,s);this[t]=r,this.#i(t,r)}#a(t,e,s){t.addEventListener("input",t=>{this[e]=t.target.value});let r=this.#n.get(e);r||(r=[],this.#n.set(e,r)),r.push(s?{element:t,attrName:s}:t)}buildDOM(){const t=this.constructor;let e=t.css?`<style>${t.css}</style>`:"";e+=t.html,Wrec.#t.innerHTML=e,this.shadowRoot.replaceChildren(Wrec.#t.content.cloneNode(!0))}componentName(){return this.constructor.name}connectedCallback(){this.#c(),this.buildDOM(),requestAnimationFrame(()=>{this.#l(this.shadowRoot),this.#h(this.shadowRoot),this.constructor.processed=!0})}#c(){const t=this.constructor.properties,{observedAttributes:e}=this.constructor;for(const[s,r]of Object.entries(t))this.#u(s,r,e)}#u(t,e,s){const r=s.includes(t)&&this.hasAttribute(t)?this.#p(t):e.value,n="#"+t;this[n]=r,Object.defineProperty(this,t,{enumerable:!0,get(){return this[n]},set(e){if(e===this[n])return;if(this[n]=e,this.hasAttribute(t)){e!==this.#p(t)&&this.#m(this,t,e)}this.#f(t);const s=this.propertyToParentPropertyMap,r=s?s.get(t):null;if(r){this.getRootNode().host.setAttribute(r,e)}this.#i(t,e)}})}#d(t){const e=t.localName.includes("-");for(const s of t.getAttributeNames()){const r=t.getAttribute(s),n=this.#E(t,r);if(n){const r=this[n];if(r||this.#b(t,s,n),t[n]=r,"value"===s&&this.#a(t,n,s),e){let e=t.propertyToParentPropertyMap;e||(e=new Map,t.propertyToParentPropertyMap=e),e.set(s,n)}}this.#R(r,t,s)}}#v(expression){return(()=>eval(expression)).call(this)}#g(t){const{localName:e}=t;if("style"===e)return;const s=t.textContent.trim(),r=this.#E(t,s);"textarea"===e&&r?(this.#a(t,r),t.textContent=this[r]):this.#R(s,t)}#h(t){const e=t.querySelectorAll("*");for(const t of e)this.#d(t),t.firstElementChild||this.#g(t)}static get observedAttributes(){return Object.keys(this.properties||{})}#E(t,e){if(!REFERENCE_RE.test(e))return;const s=e.substring(SKIP);return this[s]||this.#b(t,null,s),s}#f(t){const e=this.constructor["#propertyToExpressionsMap"].get(t)||[];for(const t of e){const e=this.#v(t),s=this.#e.get(t)||[];for(const t of s)if(t instanceof Element)this.#y(t,e);else{const{element:s,attrName:r}=t;this.#m(s,r,e)}}requestAnimationFrame(()=>{this.#A(t)})}static register(){const t=Wrec.#x(this.name);customElements.get(t)||customElements.define(t,this)}#R(t,e,s){const r=this.#N(e,s,t);if(!r)return;this.constructor.processed||r.forEach(e=>{const s=e.substring(SKIP),r=this.constructor["#propertyToExpressionsMap"];let n=r.get(s);n||(n=[],r.set(s,n)),n.push(t)});let n=this.#e.get(t);n||(n=[],this.#e.set(t,n)),n.push(s?{element:e,attrName:s}:e);const o=this.#v(t);s?this.#m(e,s,o):this.#y(e,o)}static#x=t=>t.replace(/([a-z0-9])([A-Z])/g,"$1-$2").toLowerCase();#i(t,e){this.#s&&(this.#s.set(t,e),this.#r.setFormValue(this.#s))}#b(t,e,s){throw new Error(`component ${this.componentName()}, element "${t.localName}"`+(e?`, attribute "${e}"`:"")+` refers to missing property "${s}"`)}#p(t){return this.#o(t,this.getAttribute(t))}#o(t,e){if(e?.match(REFERENCES_RE))return e;const s=this.constructor.properties[t].type;if(s===Number){const s=Number(e);if(!isNaN(s))return s;throw new Error(`attribute/property "${t}" must be a number, but was "${e}"`)}if(s===Boolean&&e&&e!==t)throw new Error("Boolean attribute value must match attribute name or be missing");return e}#m(t,e,s){const r=t.getAttribute(e);"boolean"==typeof s?s?r!==e&&t.setAttribute(e,e):t.removeAttribute(e):r!==s&&t.setAttribute(e,s)}#A(t){const e=this[t],s=this.#n.get(t)||[];for(const t of s)if(t instanceof Element)"textarea"===t.localName?t.value=e:t.textContent=e;else{const{element:s,attrName:r}=t;this.#m(s,r,e),s[r]=e}}#y(t,e){const{localName:s}=t;"textarea"===s?t.value=e:"string"==typeof e&&e.trim().startsWith("<")?(t.innerHTML=e,this.#l(t),this.#h(t)):t.textContent=e}#N(t,e,s){const r=s.match(REFERENCES_RE);if(r)return r.forEach(s=>{const r=s.substring(SKIP);this[r]||this.#b(t,e,r)}),r}#l(root){const elements=root.querySelectorAll("*");for(const element of elements)for(const attr of element.attributes){const{name:name}=attr;if(name.startsWith("on")){const eventName=name.slice(2).toLowerCase(),{value:value}=attr;let fn;if(this.#N(element,name,value),"function"==typeof this[value])fn=t=>this[value](t);else{const matches=this.#N(element,name,value);matches?fn=()=>eval(value):this.#b(element,name,value)}element.addEventListener(eventName,fn),element.removeAttribute(name)}}}}export default Wrec;export const css=String.raw;export const html=String.raw;
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "wrec",
3
3
  "description": "a small library that greatly simplifies building web components",
4
4
  "author": "R. Mark Volkmann",
5
- "version": "0.4.3",
5
+ "version": "0.5.0",
6
6
  "license": "MIT",
7
7
  "repository": {
8
8
  "type": "git",