wrec 0.4.1 → 0.4.3
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 +52 -18
- package/dist/wrec.min.js +1 -1
- package/package.json +1 -1
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
|
|
41
|
-
"
|
|
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 =
|
|
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 =
|
|
67
|
+
static html = html`
|
|
68
68
|
<div>
|
|
69
|
-
<button onClick="decrement" type="button"
|
|
70
|
-
|
|
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>
|
|
@@ -103,15 +104,21 @@ Here are the steps:
|
|
|
103
104
|
|
|
104
105
|
## More Detail
|
|
105
106
|
|
|
107
|
+
When the value of an attribute is a Boolean,
|
|
108
|
+
wrec adds the attribute to the element with no value
|
|
109
|
+
or removes the attribute from the element.
|
|
110
|
+
This is commonly used for attributes like `disabled`.
|
|
111
|
+
|
|
106
112
|
To wire event listeners,
|
|
107
113
|
Wrec looks for attributes whose name begins with "on".
|
|
108
114
|
It assumes the remainder of the attribute name is an event name.
|
|
109
115
|
It also assumes that the value of the attribute is either
|
|
110
116
|
a method name that should be called or code that should be executed
|
|
111
117
|
when that event is dispatched.
|
|
112
|
-
For example, the attribute `onclick="increment"
|
|
118
|
+
For example, with the attribute `onclick="increment"`,
|
|
119
|
+
if `increment` is a method in the component, wrec will
|
|
113
120
|
add an event listener to the element containing the attribute
|
|
114
|
-
for "click" events and
|
|
121
|
+
for "click" events and call `this.increment(event)`.
|
|
115
122
|
Alternatively, the attribute `onclick="this.count++"`
|
|
116
123
|
adds an event listener that increments `this.count`
|
|
117
124
|
when the element is clicked.
|
|
@@ -129,16 +136,31 @@ Only attribute values and text content
|
|
|
129
136
|
that refer to modified web component properties are updated.
|
|
130
137
|
Attribute values and text content that contain references to properties
|
|
131
138
|
must be valid JavaScript expressions that are NOT surrounded by `${...}`.
|
|
132
|
-
For an example of this kind of web component, see `
|
|
139
|
+
For an example of this kind of web component, see `examples/hello-world.js`.
|
|
140
|
+
|
|
141
|
+
Wrec evaluates JavaScript expressions in the context of a web component instance
|
|
142
|
+
which can be referred to with the `this` keyword in the expressions.
|
|
133
143
|
|
|
134
144
|
Wrec supports conditional and iterative generation of HTML.
|
|
135
|
-
See `
|
|
145
|
+
See `examples/temperature-eval.js` for an example of a web component
|
|
136
146
|
that conditionally decides what to render based on an attribute value.
|
|
137
|
-
See `
|
|
147
|
+
See `examples/radio-group.js` for an example of a web component
|
|
138
148
|
that iterates over values in a comma-delimited attribute value
|
|
139
149
|
to determine what to render.
|
|
140
150
|
|
|
141
|
-
|
|
151
|
+
Data binding in Lit is not two-way like in wrec.
|
|
152
|
+
A Lit component cannot simply pass one of its properties to
|
|
153
|
+
a child Lit component and have the child can update the property.
|
|
154
|
+
The child must dispatch custom events that
|
|
155
|
+
the parent listens for so it can update its own state.
|
|
156
|
+
For an example of this, see
|
|
157
|
+
[wrec-compare](https://github.com/mvolkmann/lit-examples/blob/main/wrec-compare/binding-demo.ts).
|
|
158
|
+
|
|
159
|
+
Wrec supports two-way data binding.
|
|
160
|
+
See the example component binding-demo
|
|
161
|
+
and the components it renders.
|
|
162
|
+
|
|
163
|
+
Wrec two-way data binding can be used with HTML form elements.
|
|
142
164
|
|
|
143
165
|
- `input` and `select` elements can have a `value` attribute
|
|
144
166
|
whose value is "this.somePropertyName".
|
|
@@ -149,7 +171,7 @@ In all these cases, if the user changes the value of the form element,
|
|
|
149
171
|
the specified property is updated.
|
|
150
172
|
When the property is updated,
|
|
151
173
|
the displayed value of the form element is updated.
|
|
152
|
-
For examples, see `
|
|
174
|
+
For examples, see `examples/data-bind.js`.
|
|
153
175
|
|
|
154
176
|
Web components that extend `Wrec` can contribute values to
|
|
155
177
|
form submissions by adding the following line to their class definition.
|
|
@@ -184,7 +206,7 @@ to any domain except that of your web app:
|
|
|
184
206
|
|
|
185
207
|
## More Examples
|
|
186
208
|
|
|
187
|
-
Check out the web app in the `
|
|
209
|
+
Check out the web app in the `examples` directory.
|
|
188
210
|
To run it, cd to that directory, enter `npm install`,
|
|
189
211
|
enter `npm run dev`, and browse localhost:5173.
|
|
190
212
|
|
|
@@ -194,5 +216,17 @@ The next two uses the Wrec library.
|
|
|
194
216
|
Compare the files `counter-vanilla.js` and `counter-wrec.js`
|
|
195
217
|
to see how much using Wrec simplifies the code.
|
|
196
218
|
|
|
197
|
-
The `
|
|
219
|
+
The `examples` app renders several other
|
|
220
|
+
web components that are built with wrec.
|
|
198
221
|
Examine their code for more examples of wrec usage.
|
|
222
|
+
|
|
223
|
+
## Tests
|
|
224
|
+
|
|
225
|
+
wrec has an extensive set of Playwright tests.
|
|
226
|
+
To run them:
|
|
227
|
+
|
|
228
|
+
1. Clone the wrec repository.
|
|
229
|
+
1. cd to the `examples` directory.
|
|
230
|
+
1. Enter `npm install`.
|
|
231
|
+
1. Enter `npm run testui`.
|
|
232
|
+
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.#
|
|
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;
|