wrec 0.4.1 → 0.4.2

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
@@ -37,37 +37,38 @@ Here are the steps:
37
37
  ```
38
38
 
39
39
  1. Create the file `my-counter.js` containing the following.
40
- The comments `/*css*/` and `/*html*/` trigger the VS Code extension
41
- "es6-string-html" to add syntax highlighting to the CSS and HTML strings.
40
+ The tagged template literals with the tags `css` and `html` trigger the VS Code extension
41
+ "Prettier" to add syntax highlighting and format the CSS and HTML strings.
42
42
 
43
43
  ```js
44
- import Wrec from "wrec";
44
+ import Wrec, { css, html } from "wrec";
45
45
 
46
46
  class MyCounter extends Wrec {
47
47
  static properties = {
48
48
  count: { type: Number },
49
49
  };
50
50
 
51
- static css = /*css*/ `
51
+ static css = css`
52
52
  .counter {
53
53
  display: flex;
54
54
  align-items: center;
55
55
  gap: 0.5rem;
56
56
  }
57
-
57
+
58
58
  button {
59
59
  background-color: lightgreen;
60
60
  }
61
-
61
+
62
62
  button:disabled {
63
63
  background-color: gray;
64
64
  }
65
65
  `;
66
66
 
67
- static html = /*html*/ `
67
+ static html = html`
68
68
  <div>
69
- <button onClick="decrement" type="button"
70
- disabled="this.count === 0">-</button>
69
+ <button onClick="decrement" type="button" disabled="this.count === 0">
70
+ -
71
+ </button>
71
72
  <span>this.count</span>
72
73
  <button onClick="this.count++" type="button">+</button>
73
74
  <span>(this.count < 10 ? "single" : "double") + " digit"</span>
@@ -129,12 +130,15 @@ Only attribute values and text content
129
130
  that refer to modified web component properties are updated.
130
131
  Attribute values and text content that contain references to properties
131
132
  must be valid JavaScript expressions that are NOT surrounded by `${...}`.
132
- For an example of this kind of web component, see `demo/hello-world.js`.
133
+ For an example of this kind of web component, see `examples/hello-world.js`.
134
+
135
+ Wrec evaluates JavaScript expressions in the context of a web component instance
136
+ which can be referred to with the `this` keyword in the expressions.
133
137
 
134
138
  Wrec supports conditional and iterative generation of HTML.
135
- See `demo/temperature-eval.js` for an example of a web component
139
+ See `examples/temperature-eval.js` for an example of a web component
136
140
  that conditionally decides what to render based on an attribute value.
137
- See `demo/radio-group.js` for an example of a web component
141
+ See `examples/radio-group.js` for an example of a web component
138
142
  that iterates over values in a comma-delimited attribute value
139
143
  to determine what to render.
140
144
 
@@ -149,7 +153,7 @@ In all these cases, if the user changes the value of the form element,
149
153
  the specified property is updated.
150
154
  When the property is updated,
151
155
  the displayed value of the form element is updated.
152
- For examples, see `demo/data-bind.js`.
156
+ For examples, see `examples/data-bind.js`.
153
157
 
154
158
  Web components that extend `Wrec` can contribute values to
155
159
  form submissions by adding the following line to their class definition.
@@ -184,7 +188,7 @@ to any domain except that of your web app:
184
188
 
185
189
  ## More Examples
186
190
 
187
- Check out the web app in the `demo` directory.
191
+ Check out the web app in the `examples` directory.
188
192
  To run it, cd to that directory, enter `npm install`,
189
193
  enter `npm run dev`, and browse localhost:5173.
190
194
 
@@ -194,5 +198,17 @@ The next two uses the Wrec library.
194
198
  Compare the files `counter-vanilla.js` and `counter-wrec.js`
195
199
  to see how much using Wrec simplifies the code.
196
200
 
197
- The `demo` app renders several other web components that are built with wrec.
201
+ The `examples` app renders several other
202
+ web components that are built with wrec.
198
203
  Examine their code for more examples of wrec usage.
204
+
205
+ ## Tests
206
+
207
+ wrec has an extensive set of Playwright tests.
208
+ To run them:
209
+
210
+ 1. Clone the wrec repository.
211
+ 1. cd to the `examples` directory.
212
+ 1. Enter `npm install`.
213
+ 1. Enter `npm run testui`.
214
+ 1. Click the right pointing triangle.
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.#u(s,r,e)}#u(t,e,s){const r=s.includes(t)&&this.hasAttribute(t)?this.#p(t):e.value;this["_"+t]=r,Object.defineProperty(this,t,{enumerable:!0,get(){return this["_"+t]},set(e){if(e===this["_"+t])return;if(this["_"+t]=e,this.hasAttribute(t)){e!==this.#p(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);if(t[o]=this[o],"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)}#p(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.#v(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#v=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;
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.#u(s,r,e)}#u(t,e,s){const r=s.includes(t)&&this.hasAttribute(t)?this.#p(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.#p(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);if(t[o]=this[o],"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)}#p(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.#v(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#v=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;
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.1",
5
+ "version": "0.4.2",
6
6
  "license": "MIT",
7
7
  "repository": {
8
8
  "type": "git",