wrec 0.4.4 → 0.5.1
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 +28 -0
- package/dist/wrec.min.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -133,6 +133,7 @@ can be replaced by `onClick="increment"`.
|
|
|
133
133
|
Wrec supports reactivity.
|
|
134
134
|
Attribute values and the text content of elements
|
|
135
135
|
can refer to web component properties with the syntax `this.somePropertyName`.
|
|
136
|
+
In this context, `this` always refers to the parent web component.
|
|
136
137
|
The DOM of the web component is surgically updated.
|
|
137
138
|
Only attribute values and text content
|
|
138
139
|
that refer to modified web component properties are updated.
|
|
@@ -143,6 +144,17 @@ For an example of this kind of web component, see `examples/hello-world.js`.
|
|
|
143
144
|
Wrec evaluates JavaScript expressions in the context of a web component instance
|
|
144
145
|
which can be referred to with the `this` keyword in the expressions.
|
|
145
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
|
+
|
|
146
158
|
Wrec supports conditional and iterative generation of HTML.
|
|
147
159
|
See `examples/temperature-eval.js` for an example of a web component
|
|
148
160
|
that conditionally decides what to render based on an attribute value.
|
|
@@ -183,6 +195,22 @@ Wrec looks for this automatically does the rest of the work.
|
|
|
183
195
|
static formAssociated = true;
|
|
184
196
|
```
|
|
185
197
|
|
|
198
|
+
## Error Checking
|
|
199
|
+
|
|
200
|
+
Wrec checks for many kinds of errors and throws an `Error` when they are found.
|
|
201
|
+
Look for messages in the DevTools console.
|
|
202
|
+
The kinds of errors that are detected include:
|
|
203
|
+
|
|
204
|
+
- attribute names in web component instances
|
|
205
|
+
with no matching property declaration
|
|
206
|
+
- attribute values with a type that differs from the declared property type
|
|
207
|
+
- event handling function names that
|
|
208
|
+
don't match any method name in the web component
|
|
209
|
+
- expressions in attribute values or element text content
|
|
210
|
+
that reference undeclared web component properties
|
|
211
|
+
- expressions in element text content
|
|
212
|
+
that do not evaluate to a string or number
|
|
213
|
+
|
|
186
214
|
## Security
|
|
187
215
|
|
|
188
216
|
Wrec uses the JavaScript `eval` function to evaluate JavaScript expressions
|
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;#
|
|
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.#i(t,s);this[t]=r,this.#o(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))}connectedCallback(){this.#c(),this.#l(),this.buildDOM(),requestAnimationFrame(()=>{this.#h(this.shadowRoot),this.#u(this.shadowRoot),this.constructor.processed=!0})}#l(){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.#m(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.#m(t)&&this.#d(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.#o(t,e)}})}static elementName(){return this.name.replace(/([a-z0-9])([A-Z])/g,"$1-$2").toLowerCase()}#E(t){const e=t.localName.includes("-");for(const s of t.getAttributeNames()){const r=t.getAttribute(s),n=this.#b(t,r);if(n){const r=this[n];if(r||this.#v(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)}}#g(expression){return(()=>eval(expression)).call(this)}#y(t){const{localName:e}=t;if("style"===e)return;const s=t.textContent.trim(),r=this.#b(t,s);"textarea"===e&&r?(this.#a(t,r),t.textContent=this[r]):this.#R(s,t)}#u(t){const e=t.querySelectorAll("*");for(const t of e)this.#E(t),t.firstElementChild||this.#y(t)}static get observedAttributes(){return Object.keys(this.properties||{})}#b(t,e){if(!REFERENCE_RE.test(e))return;const s=e.substring(SKIP);return this[s]||this.#v(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.#e.get(t)||[];for(const t of s)if(t instanceof Element)this.#A(t,e);else{const{element:s,attrName:r}=t;this.#d(s,r,e)}}requestAnimationFrame(()=>{this.#w(t)})}static register(){const t=this.elementName();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 i=this.#g(t);s?this.#d(e,s,i):this.#A(e,i)}#o(t,e){this.#s&&(this.#s.set(t,e),this.#r.setFormValue(this.#s))}#x(t,e,s){throw new Error(`component ${this.constructor.elementName()}`+(t?`, element "${t.localName}"`:"")+(e?`, attribute "${e}"`:"")+` ${s}`)}#v(t,e,s){this.#x(t,e,`refers to missing property "${s}"`)}#m(t){return this.#i(t,this.getAttribute(t))}#i(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;this.#x(null,t,`must be a number, but was "${e}"`)}return s===Boolean&&e&&e!==t&&this.#x(null,t,"is a Boolean attribute, so its value must match attribute name or be missing"),e}#d(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)}#w(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.#d(s,r,e),s[r]=e}}#A(t,e){const{localName:s}=t,r=typeof e;"string"!==r&&"number"!==r&&this.#x(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.#u(t)):t.textContent=e}#c(){const t=new Set(Object.keys(this.constructor.properties));for(const e of this.getAttributeNames())t.has(e)||this.#x(null,e,"is not a supported attribute")}#N(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.#v(t,e,r)}),r}#h(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.#v(element,name,value)}element.addEventListener(eventName,fn),element.removeAttribute(name)}}}}export default Wrec;export const css=String.raw;export const html=String.raw;
|