wrec 0.5.11 → 0.7.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
@@ -106,6 +106,65 @@ Here are the steps:
106
106
 
107
107
  1. Click the "-" and "+" buttons to verify that the component is working.
108
108
 
109
+ ## Reactivity
110
+
111
+ Wrec supports reactivity.
112
+ Attribute values and the text content of elements
113
+ can refer to web component properties with the syntax `this.somePropertyName`.
114
+ In this context, `this` always refers to the parent web component.
115
+ The DOM of the web component is surgically updated.
116
+ Only attribute values and text content
117
+ that refer to modified web component properties are updated.
118
+ Attribute values and text content that contain references to properties
119
+ must be valid JavaScript expressions that are NOT surrounded by `${...}`.
120
+ For an example of this kind of web component, see `examples/hello-world.js`.
121
+
122
+ Wrec evaluates JavaScript expressions in the context of a web component instance
123
+ which can be referred to with the `this` keyword in the expressions.
124
+
125
+ To render "this." followed by a valid JavaScript identifier without
126
+ replacing it with the value of the expression, escaping with a double period.
127
+ For example, `this.count` will be reactively replaced with its value,
128
+ but `this..count` will render the text with a single period.
129
+
130
+ In insert the value of an expression
131
+ that does not use properties of the web component,
132
+ into an HTML template string,
133
+ surround the expression with the syntax `${...}`.
134
+ For example, assuming `DAYS` is a variable
135
+ whose value is an array of month names:
136
+
137
+ ```html
138
+ <p>The month is ${DAYS[new Date().getDay()]}.</p>
139
+ ```
140
+
141
+ ## Reactive CSS
142
+
143
+ CSS property values cannot refer to web component properties
144
+ with the syntax `this.somePropertyName`.
145
+ This is because the CSS parser in browsers removes invalid property values.
146
+ A workaround is to place such references in a CSS variable
147
+ and refer to that variable from a CSS property.
148
+ For an example of this, see `examples/data-binding.js`
149
+ which contains the following CSS:
150
+
151
+ ```css
152
+ p {
153
+ --color: this.color;
154
+ --size: this.size;
155
+ color: var(--color);
156
+ font-size: calc(var(--size) * 1px);
157
+ margin: 6px 0;
158
+ }
159
+ ```
160
+
161
+ CSS variable values can be any valid JavaScript expression.
162
+ The example above can be changed to double the size as follows:
163
+
164
+ ```css
165
+ --size: this.size * 2;
166
+ ```
167
+
109
168
  ## Required Attributes
110
169
 
111
170
  The configuration object for required attributes in a Wrec component
@@ -149,33 +208,6 @@ see the `checked` property in `examples/toggle-switch.js`.
149
208
  The component defined in `examples/binding-demo.js`
150
209
  listens for that event, as does the `script` in `examples/index.html`.
151
210
 
152
- ## Reactivity
153
-
154
- Wrec supports reactivity.
155
- Attribute values and the text content of elements
156
- can refer to web component properties with the syntax `this.somePropertyName`.
157
- In this context, `this` always refers to the parent web component.
158
- The DOM of the web component is surgically updated.
159
- Only attribute values and text content
160
- that refer to modified web component properties are updated.
161
- Attribute values and text content that contain references to properties
162
- must be valid JavaScript expressions that are NOT surrounded by `${...}`.
163
- For an example of this kind of web component, see `examples/hello-world.js`.
164
-
165
- Wrec evaluates JavaScript expressions in the context of a web component instance
166
- which can be referred to with the `this` keyword in the expressions.
167
-
168
- In insert the value of an expression
169
- that does not use properties of the web component,
170
- into an HTML template string,
171
- surround the expression with the syntax `${...}`.
172
- For example, assuming `DAYS` is a variable
173
- whose value is an array of month names:
174
-
175
- ```html
176
- <p>The month is ${DAYS[new Date().getDay()]}.</p>
177
- ```
178
-
179
211
  ## Conditional and Iterative HTML Generation
180
212
 
181
213
  Wrec supports conditional and iterative generation of HTML.
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,defaultForType=t=>t===Number?0:t!==Boolean&&"";function updateAttribute(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)}class Wrec extends HTMLElement{#t=new Map;#e;#s;#r=new Map;constructor(){super(),this.attachShadow({mode:"open"});this.constructor["#propertyToExpressionsMap"]||(this.constructor["#propertyToExpressionsMap"]=new Map),this.constructor.formAssociated&&(this.#s=this.attachInternals(),this.#e=new FormData,this.#s.setFormValue(this.#e))}attributeChangedCallback(t,e,s){const r=this.#i(t,s);this[t]=r,this.#o(t,r)}#n(t,e,s){t.addEventListener("input",t=>{this[e]=t.target.value});let r=this.#r.get(e);r||(r=[],this.#r.set(e,r)),r.push(s?{element:t,attrName:s}:t)}#a(){const t=this.constructor;let{_template:e}=t;if(!e){e=t.template=document.createElement("template");let s=t.css?`<style>${t.css}</style>`:"";s+=t.html,e.innerHTML=s}this.shadowRoot.replaceChildren(e.content.cloneNode(!0))}connectedCallback(){this.#u(),this.#h(),this.#a(),requestAnimationFrame(()=>{this.#l(this.shadowRoot),this.#c(this.shadowRoot),this.constructor.processed=!0})}#h(){const{observedAttributes:t,properties:e}=this.constructor;for(const[s,r]of Object.entries(e))this.#p(s,r,t)}#p(t,e,s){e.required&&!this.hasAttribute(t)&&this.#m(this,t,"is a required attribute");const r=s.includes(t)&&this.hasAttribute(t)?this.#d(t):e.value||defaultForType(e.type),i="#"+t;this[i]=r,Object.defineProperty(this,t,{enumerable:!0,get(){return this[i]},set(s){if(s===this[i])return;if(this[i]=s,this.hasAttribute(t)){s!==this.#d(t)&&updateAttribute(this,t,s)}this.#f(t);const r=this.propertyToParentPropertyMap,o=r?r.get(t):null;if(o){this.getRootNode().host.setAttribute(o,s)}this.#o(t,s),e.dispatch&&this.dispatchEvent(new CustomEvent("change",{bubbles:!0,composed:!0,detail:{propertyName:t}}))}})}static elementName(){return this.name.replace(/([a-z0-9])([A-Z])/g,"$1-$2").toLowerCase()}#b(t){const e=t.localName.includes("-");for(const s of t.getAttributeNames()){const r=t.getAttribute(s),i=this.#E(t,r);if(i){const r=this[i];if(void 0===r&&this.#R(t,s,i),t[i]=r,"value"===s&&this.#n(t,i,s),e){let e=t.propertyToParentPropertyMap;e||(e=new Map,t.propertyToParentPropertyMap=e),e.set(s,i)}}this.#v(r,t,s)}}#g(expression){return(()=>eval(expression)).call(this)}#y(t){const{localName:e}=t;if("style"===e)return;const s=t.textContent.trim(),r=this.#E(t,s);"textarea"===e&&r?(this.#n(t,r),t.textContent=this[r]):this.#v(s,t)}#c(t){const e=t.querySelectorAll("*");for(const t of e)this.#b(t),t.firstElementChild||this.#y(t)}static get observedAttributes(){return Object.keys(this.properties||{})}#E(t,e){if(!REFERENCE_RE.test(e))return;const s=e.substring(SKIP);return void 0===this[s]&&this.#R(t,null,s),s}#f(t){const e=this.constructor["#propertyToExpressionsMap"].get(t)||[];for(const t of e){const e=this.#g(t),s=this.#t.get(t)||[];for(const t of s)if(t instanceof Element)this.#N(t,e);else{const{element:s,attrName:r}=t;updateAttribute(s,r,e)}}requestAnimationFrame(()=>{this.#A(t)})}static register(){const t=this.elementName();customElements.get(t)||customElements.define(t,this)}#v(t,e,s){const r=this.#w(e,s,t);if(!r)return;this.constructor.processed||r.forEach(e=>{const s=e.substring(SKIP),r=this.constructor["#propertyToExpressionsMap"];let i=r.get(s);i||(i=[],r.set(s,i)),i.push(t)});let i=this.#t.get(t);i||(i=[],this.#t.set(t,i)),i.push(s?{element:e,attrName:s}:e);const o=this.#g(t);s?updateAttribute(e,s,o):this.#N(e,o)}#o(t,e){this.#e&&(this.#e.set(t,e),this.#s.setFormValue(this.#e))}#m(t,e,s){throw new Error(`component ${this.constructor.elementName()}`+(t?`, element "${t.localName}"`:"")+(e?`, attribute "${e}"`:"")+` ${s}`)}#R(t,e,s){this.#m(t,e,`refers to missing property "${s}"`)}#d(t){return this.#i(t,this.getAttribute(t))}#i(t,e){if(e?.match(REFERENCES_RE))return e;const{type:s}=this.constructor.properties[t];if(s===String)return e;if(s===Number){const s=Number(e);if(!isNaN(s))return s;this.#m(null,t,`must be a number, but was "${e}"`)}if(s===Boolean)return"true"===e||"false"!==e&&(e&&e!==t&&this.#m(null,t,"is a Boolean attribute, so its value must match attribute name or be missing"),e===t);this.#m(null,t,"does not specify its type")}#A(t){const e=this[t],s=this.#r.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;updateAttribute(s,r,e),s[r]=e}}#N(t,e){const{localName:s}=t,r=typeof e;"string"!==r&&"number"!==r&&this.#m(t,null," computed content is not a string or number"),"textarea"===s?t.value=e:"string"===r&&e.trim().startsWith("<")?(t.innerHTML=e,this.#l(t),this.#c(t)):t.textContent=e}#u(){const t=new Set(Object.keys(this.constructor.properties));for(const e of this.getAttributeNames())"id"!==e&&(e.startsWith("on")||t.has(e)||this.#m(null,e,"is not a supported attribute"))}#w(t,e,s){const r=s.match(REFERENCES_RE);if(r)return r.forEach(s=>{const r=s.substring(SKIP);void 0===this[r]&&this.#R(t,e,r)}),r}#l(root){for(const element of root.querySelectorAll("*")){const attributesToRemove=[];for(const attr of element.attributes){const attrName=attr.name;if(attrName.startsWith("on")){const eventName=attrName.slice(2).toLowerCase(),attrValue=attr.value;let fn;this.#w(element,attrName,attrValue),"function"==typeof this[attrValue]?fn=t=>this[attrValue](t):(this.#w(element,attrName,attrValue),fn=event=>eval(attrValue)),element.addEventListener(eventName,fn),attributesToRemove.push(attrName)}}for(const t of attributesToRemove)element.removeAttribute(t)}}}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,defaultForType=t=>t===Number?0:t!==Boolean&&"";function updateAttribute(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)}function updateValue(t,e,s){t instanceof CSSRule?t.style.setProperty(e,s):updateAttribute(t,e,s)}class Wrec extends HTMLElement{#t=new Map;#e;#s;#r=new Map;constructor(){super(),this.attachShadow({mode:"open"});this.constructor["#propertyToExpressionsMap"]||(this.constructor["#propertyToExpressionsMap"]=new Map),this.constructor.formAssociated&&(this.#s=this.attachInternals(),this.#e=new FormData,this.#s.setFormValue(this.#e))}attributeChangedCallback(t,e,s){const r=this.#o(t,s);this[t]=r,this.#i(t,r)}#n(t,e,s){t.addEventListener("input",t=>{this[e]=t.target.value});let r=this.#r.get(e);r||(r=[],this.#r.set(e,r)),r.push(s?{element:t,attrName:s}:t)}#a(){const t=this.constructor;let{_template:e}=t;if(!e){e=t.template=document.createElement("template");let s=t.css?`<style>${t.css}</style>`:"";s+=t.html,e.innerHTML=s}this.shadowRoot.replaceChildren(e.content.cloneNode(!0))}connectedCallback(){this.#l(),this.#u(),this.#a(),requestAnimationFrame(()=>{this.#h(this.shadowRoot),this.#c(this.shadowRoot),this.constructor.processed=!0})}#u(){const{observedAttributes:t,properties:e}=this.constructor;for(const[s,r]of Object.entries(e))this.#p(s,r,t)}#p(t,e,s){e.required&&!this.hasAttribute(t)&&this.#m(this,t,"is a required attribute");const r=s.includes(t)&&this.hasAttribute(t)?this.#d(t):e.value||defaultForType(e.type),o="#"+t;this[o]=r,Object.defineProperty(this,t,{enumerable:!0,get(){return this[o]},set(s){if(s===this[o])return;if(this[o]=s,this.hasAttribute(t)){s!==this.#d(t)&&updateAttribute(this,t,s)}this.#f(t);const r=this.propertyToParentPropertyMap,i=r?r.get(t):null;if(i){this.getRootNode().host.setAttribute(i,s)}this.#i(t,s),e.dispatch&&this.dispatchEvent(new CustomEvent("change",{bubbles:!0,composed:!0,detail:{propertyName:t}}))}})}static elementName(){return this.name.replace(/([a-z0-9])([A-Z])/g,"$1-$2").toLowerCase()}#b(t){const e=t.localName.includes("-");for(const s of t.getAttributeNames()){const r=t.getAttribute(s),o=this.#E(t,r);if(o){const r=this[o];if(void 0===r&&this.#R(t,s,o),t[o]=r,"value"===s&&this.#n(t,o,s),e){let e=t.propertyToParentPropertyMap;e||(e=new Map,t.propertyToParentPropertyMap=e),e.set(s,o)}}this.#v(r,t,s)}}#y(expression){return(()=>eval(expression)).call(this)}#g(t){const{localName:e}=t;if("style"===e){for(const e of t.sheet.cssRules)if(e.type===CSSRule.STYLE_RULE)for(const t of e.style)if(t.startsWith("--")){const s=e.style.getPropertyValue(t);this.#v(s,e,t)}}else{const s=t.textContent.trim(),r=this.#E(t,s);"textarea"===e&&r?(this.#n(t,r),t.textContent=this[r]):this.#v(s,t)}}#c(t){const e=t.querySelectorAll("*");for(const t of e)this.#b(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 void 0===this[s]&&this.#R(t,null,s),s}#f(t){const e=this.constructor["#propertyToExpressionsMap"].get(t)||[];for(const t of e){const e=this.#y(t),s=this.#t.get(t)||[];for(const t of s)t instanceof Element?this.#N(t,e):updateValue(t.element,t.attrName,e)}requestAnimationFrame(()=>{this.#A(t)})}static register(){const t=this.elementName();customElements.get(t)||customElements.define(t,this)}#v(t,e,s){const r=this.#w(e,s,t);if(!r){const r=t.replaceAll("this..","this.");return void(s?updateValue(e,s,r):e.textContent=r)}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.#t.get(t);o||(o=[],this.#t.set(t,o)),o.push(s?{element:e,attrName:s}:e);const i=this.#y(t);s?updateValue(e,s,i):this.#N(e,i)}#i(t,e){this.#e&&(this.#e.set(t,e),this.#s.setFormValue(this.#e))}#m(t,e,s){throw new Error(`component ${this.constructor.elementName()}`+(t?`, element "${t.localName}"`:"")+(e?`, attribute "${e}"`:"")+` ${s}`)}#R(t,e,s){this.#m(t,e,`refers to missing property "${s}"`)}#d(t){return this.#o(t,this.getAttribute(t))}#o(t,e){if(e?.match(REFERENCES_RE))return e;const{type:s}=this.constructor.properties[t];if(s===String)return e;if(s===Number){const s=Number(e);if(!isNaN(s))return s;this.#m(null,t,`must be a number, but was "${e}"`)}if(s===Boolean)return"true"===e||"false"!==e&&(e&&e!==t&&this.#m(null,t,"is a Boolean attribute, so its value must match attribute name or be missing"),e===t);this.#m(null,t,"does not specify its type")}#A(t){const e=this[t],s=this.#r.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;updateAttribute(s,r,e),s[r]=e}}#N(t,e){const{localName:s}=t,r=typeof e;"string"!==r&&"number"!==r&&this.#m(t,null," computed content is not a string or number"),"textarea"===s?t.value=e:"string"===r&&e.trim().startsWith("<")?(t.innerHTML=e,this.#h(t),this.#c(t)):t.textContent=e}#l(){const t=new Set(Object.keys(this.constructor.properties));for(const e of this.getAttributeNames())"id"!==e&&(e.startsWith("on")||t.has(e)||this.#m(null,e,"is not a supported attribute"))}#w(t,e,s){const r=s.match(REFERENCES_RE);if(r)return r.forEach(s=>{const r=s.substring(SKIP);void 0===this[r]&&this.#R(t,e,r)}),r}#h(root){for(const element of root.querySelectorAll("*")){const attributesToRemove=[];for(const attr of element.attributes){const attrName=attr.name;if(attrName.startsWith("on")){const eventName=attrName.slice(2).toLowerCase(),attrValue=attr.value;let fn;this.#w(element,attrName,attrValue),"function"==typeof this[attrValue]?fn=t=>this[attrValue](t):(this.#w(element,attrName,attrValue),fn=event=>eval(attrValue)),element.addEventListener(eventName,fn),attributesToRemove.push(attrName)}}for(const t of attributesToRemove)element.removeAttribute(t)}}}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.5.11",
5
+ "version": "0.7.0",
6
6
  "license": "MIT",
7
7
  "repository": {
8
8
  "type": "git",