wrec 0.4.2 → 0.4.4
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 +41 -21
- package/dist/wrec.min.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,17 +4,21 @@
|
|
|
4
4
|
|
|
5
5
|
Wrec is a small, zero dependency library that
|
|
6
6
|
greatly simplifies building web components.
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
Wrec has fewer features than Lit.
|
|
10
|
-
In exchange, Wrec:
|
|
7
|
+
Its main features are that it automates
|
|
8
|
+
wiring event listeners and implementing reactivity.
|
|
11
9
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
- doesn't require a build process
|
|
10
|
+
Wrec was inspired by [Lit](https://lit.dev).
|
|
11
|
+
It has the following advantages over Lit:
|
|
15
12
|
|
|
16
|
-
|
|
17
|
-
|
|
13
|
+
- Wrec is simpler ... just a single class to extend (Wrec).
|
|
14
|
+
- Wrec is slightly smaller ... 4K versus 5.8K minified.
|
|
15
|
+
- Wrec has a cleaner syntax ... no need to
|
|
16
|
+
surround JS expressions with `${...}`.
|
|
17
|
+
- Wrec provides automatic 2-way data binding ...
|
|
18
|
+
no need to dispatch custom events and listen for them.
|
|
19
|
+
- Wrec doesn't require a special syntax for Boolean attributes.
|
|
20
|
+
- Wrec enables specifying the content of a `textarea` element
|
|
21
|
+
with a JavaScript expressions in its text content.
|
|
18
22
|
|
|
19
23
|
## Getting Started
|
|
20
24
|
|
|
@@ -49,10 +53,8 @@ Here are the steps:
|
|
|
49
53
|
};
|
|
50
54
|
|
|
51
55
|
static css = css`
|
|
52
|
-
|
|
53
|
-
display:
|
|
54
|
-
align-items: center;
|
|
55
|
-
gap: 0.5rem;
|
|
56
|
+
:host {
|
|
57
|
+
display: block;
|
|
56
58
|
}
|
|
57
59
|
|
|
58
60
|
button {
|
|
@@ -66,7 +68,11 @@ Here are the steps:
|
|
|
66
68
|
|
|
67
69
|
static html = html`
|
|
68
70
|
<div>
|
|
69
|
-
<button
|
|
71
|
+
<button
|
|
72
|
+
onClick="this.count--"
|
|
73
|
+
type="button"
|
|
74
|
+
disabled="this.count === 0"
|
|
75
|
+
>
|
|
70
76
|
-
|
|
71
77
|
</button>
|
|
72
78
|
<span>this.count</span>
|
|
@@ -74,10 +80,6 @@ Here are the steps:
|
|
|
74
80
|
<span>(this.count < 10 ? "single" : "double") + " digit"</span>
|
|
75
81
|
</div>
|
|
76
82
|
`;
|
|
77
|
-
|
|
78
|
-
decrement() {
|
|
79
|
-
if (this.count > 0) this.count--;
|
|
80
|
-
}
|
|
81
83
|
}
|
|
82
84
|
|
|
83
85
|
MyCounter.register();
|
|
@@ -104,15 +106,21 @@ Here are the steps:
|
|
|
104
106
|
|
|
105
107
|
## More Detail
|
|
106
108
|
|
|
109
|
+
When the value of an attribute is a Boolean,
|
|
110
|
+
wrec adds the attribute to the element with no value
|
|
111
|
+
or removes the attribute from the element.
|
|
112
|
+
This is commonly used for attributes like `disabled`.
|
|
113
|
+
|
|
107
114
|
To wire event listeners,
|
|
108
115
|
Wrec looks for attributes whose name begins with "on".
|
|
109
116
|
It assumes the remainder of the attribute name is an event name.
|
|
110
117
|
It also assumes that the value of the attribute is either
|
|
111
118
|
a method name that should be called or code that should be executed
|
|
112
119
|
when that event is dispatched.
|
|
113
|
-
For example, the attribute `onclick="increment"
|
|
120
|
+
For example, with the attribute `onclick="increment"`,
|
|
121
|
+
if `increment` is a method in the component, wrec will
|
|
114
122
|
add an event listener to the element containing the attribute
|
|
115
|
-
for "click" events and
|
|
123
|
+
for "click" events and call `this.increment(event)`.
|
|
116
124
|
Alternatively, the attribute `onclick="this.count++"`
|
|
117
125
|
adds an event listener that increments `this.count`
|
|
118
126
|
when the element is clicked.
|
|
@@ -142,7 +150,19 @@ See `examples/radio-group.js` for an example of a web component
|
|
|
142
150
|
that iterates over values in a comma-delimited attribute value
|
|
143
151
|
to determine what to render.
|
|
144
152
|
|
|
145
|
-
|
|
153
|
+
Data binding in Lit is not two-way like in wrec.
|
|
154
|
+
A Lit component cannot simply pass one of its properties to
|
|
155
|
+
a child Lit component and have the child can update the property.
|
|
156
|
+
The child must dispatch custom events that
|
|
157
|
+
the parent listens for so it can update its own state.
|
|
158
|
+
For an example of this, see
|
|
159
|
+
[wrec-compare](https://github.com/mvolkmann/lit-examples/blob/main/wrec-compare/binding-demo.ts).
|
|
160
|
+
|
|
161
|
+
Wrec supports two-way data binding.
|
|
162
|
+
See the example component binding-demo
|
|
163
|
+
and the components it renders.
|
|
164
|
+
|
|
165
|
+
Wrec two-way data binding can be used with HTML form elements.
|
|
146
166
|
|
|
147
167
|
- `input` and `select` elements can have a `value` attribute
|
|
148
168
|
whose value is "this.somePropertyName".
|
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;
|