wrec 0.5.0 → 0.5.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 +34 -8
- package/dist/wrec.min.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -104,13 +104,15 @@ Here are the steps:
|
|
|
104
104
|
|
|
105
105
|
1. Click the "-" and "+" buttons to verify that the component is working.
|
|
106
106
|
|
|
107
|
-
##
|
|
107
|
+
## Boolean Attributes
|
|
108
108
|
|
|
109
109
|
When the value of an attribute is a Boolean,
|
|
110
110
|
wrec adds the attribute to the element with no value
|
|
111
111
|
or removes the attribute from the element.
|
|
112
112
|
This is commonly used for attributes like `disabled`.
|
|
113
113
|
|
|
114
|
+
## Event Listeners
|
|
115
|
+
|
|
114
116
|
To wire event listeners,
|
|
115
117
|
Wrec looks for attributes whose name begins with "on".
|
|
116
118
|
It assumes the remainder of the attribute name is an event name.
|
|
@@ -130,6 +132,8 @@ does not matter because Wrec lowercases the name.
|
|
|
130
132
|
So the attributes in the previous examples
|
|
131
133
|
can be replaced by `onClick="increment"`.
|
|
132
134
|
|
|
135
|
+
## Reactivity
|
|
136
|
+
|
|
133
137
|
Wrec supports reactivity.
|
|
134
138
|
Attribute values and the text content of elements
|
|
135
139
|
can refer to web component properties with the syntax `this.somePropertyName`.
|
|
@@ -155,6 +159,8 @@ whose value is an array of month names:
|
|
|
155
159
|
<p>The month is ${DAYS[new Date().getDay()]}.</p>
|
|
156
160
|
```
|
|
157
161
|
|
|
162
|
+
## Conditional and Iterative HTML Generation
|
|
163
|
+
|
|
158
164
|
Wrec supports conditional and iterative generation of HTML.
|
|
159
165
|
See `examples/temperature-eval.js` for an example of a web component
|
|
160
166
|
that conditionally decides what to render based on an attribute value.
|
|
@@ -162,13 +168,7 @@ See `examples/radio-group.js` for an example of a web component
|
|
|
162
168
|
that iterates over values in a comma-delimited attribute value
|
|
163
169
|
to determine what to render.
|
|
164
170
|
|
|
165
|
-
Data
|
|
166
|
-
A Lit component cannot simply pass one of its properties to
|
|
167
|
-
a child Lit component and have the child can update the property.
|
|
168
|
-
The child must dispatch custom events that
|
|
169
|
-
the parent listens for so it can update its own state.
|
|
170
|
-
For an example of this, see
|
|
171
|
-
[wrec-compare](https://github.com/mvolkmann/lit-examples/blob/main/wrec-compare/binding-demo.ts).
|
|
171
|
+
## Data Binding
|
|
172
172
|
|
|
173
173
|
Wrec supports two-way data binding.
|
|
174
174
|
See the example component binding-demo
|
|
@@ -187,6 +187,16 @@ When the property is updated,
|
|
|
187
187
|
the displayed value of the form element is updated.
|
|
188
188
|
For examples, see `examples/data-bind.js`.
|
|
189
189
|
|
|
190
|
+
Data binding in Lit is not two-way like in wrec.
|
|
191
|
+
A Lit component cannot simply pass one of its properties to
|
|
192
|
+
a child Lit component and have the child can update the property.
|
|
193
|
+
The child must dispatch custom events that
|
|
194
|
+
the parent listens for so it can update its own state.
|
|
195
|
+
For an example of this, see
|
|
196
|
+
[wrec-compare](https://github.com/mvolkmann/lit-examples/blob/main/wrec-compare/binding-demo.ts).
|
|
197
|
+
|
|
198
|
+
## Form Submissions
|
|
199
|
+
|
|
190
200
|
Web components that extend `Wrec` can contribute values to
|
|
191
201
|
form submissions by adding the following line to their class definition.
|
|
192
202
|
Wrec looks for this automatically does the rest of the work.
|
|
@@ -195,6 +205,22 @@ Wrec looks for this automatically does the rest of the work.
|
|
|
195
205
|
static formAssociated = true;
|
|
196
206
|
```
|
|
197
207
|
|
|
208
|
+
## Error Checking
|
|
209
|
+
|
|
210
|
+
Wrec checks for many kinds of errors and throws an `Error` when they are found.
|
|
211
|
+
Look for messages in the DevTools console.
|
|
212
|
+
The kinds of errors that are detected include:
|
|
213
|
+
|
|
214
|
+
- attribute names in web component instances
|
|
215
|
+
with no matching property declaration
|
|
216
|
+
- attribute values with a type that differs from the declared property type
|
|
217
|
+
- event handling function names that
|
|
218
|
+
don't match any method name in the web component
|
|
219
|
+
- expressions in attribute values or element text content
|
|
220
|
+
that reference undeclared web component properties
|
|
221
|
+
- expressions in element text content
|
|
222
|
+
that do not evaluate to a string or number
|
|
223
|
+
|
|
198
224
|
## Security
|
|
199
225
|
|
|
200
226
|
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;#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.#
|
|
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;
|