wrec 0.0.2 → 0.0.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 +1 -1
- package/dist/wrec.min.js +1 -0
- package/package.json +11 -2
- package/.vscode/settings.json +0 -3
- package/demo/counter-vanilla.js +0 -91
- package/demo/counter-wreck.js +0 -47
- package/demo/data-bind.js +0 -65
- package/demo/hello-world.js +0 -22
- package/demo/index.html +0 -38
- package/demo/multiply-numbers.js +0 -19
- package/demo/number-input.js +0 -53
- package/demo/number-slider.js +0 -29
- package/demo/package-lock.json +0 -341
- package/demo/package.json +0 -11
- package/demo/radio-group.js +0 -83
- package/demo/temperature-eval.js +0 -16
- package/shipwreck.png +0 -0
- package/wrec.js +0 -348
package/README.md
CHANGED
package/dist/wrec.min.js
ADDED
|
@@ -0,0 +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,"g"),SKIP=5;class Wrec extends HTMLElement{static#t=new Map;static#e=document.createElement("template");#s=new Map;#r;#o;#n=new Map;constructor(){super(),this.attachShadow({mode:"open"}),this.constructor.formAssociated&&(this.#o=this.attachInternals(),this.#r=new FormData,this.#o.setFormValue(this.#r))}attributeChangedCallback(t,e,s){const r=this.#i(t,s);this[t]=r,this.#a(t,r)}#c(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(){let t=this.constructor.prototype.css?`<style>${this.css()}</style>`:"";t+=this.html(),Wrec.#e.innerHTML=t,this.shadowRoot.replaceChildren(Wrec.#e.content.cloneNode(!0))}connectedCallback(){this.#l(),this.buildDOM(),requestAnimationFrame(()=>{this.#p(this.shadowRoot),this.#h(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.#u(s,r,e)}#u(t,e,s){const r=s.includes(t)&&this.hasAttribute(t)?this.#m(t):e.value;this["_"+t]=r,Object.defineProperty(this,t,{enumerable:!0,get(){return this["_"+t]},set(e){if(e===this["_"+t])return;this["_"+t]=e;if(this.constructor.properties[t].reflect&&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.#a(t,e)}})}#b(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(t.setAttribute(s,n),t[s]=n,this.#c(t,o,s),e){let e=t.propertyToParentPropertyMap;e||(e=new Map,t.propertyToParentPropertyMap=e),e.set(s,o)}}this.#E(r,t,s)}}static#g(expression,context){return function(){return eval(expression)}.call(context)}#R(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);t.textContent=this[e],this.#c(t,e)}else this.#E(s,t)}#m(t){return this.#i(t,this.getAttribute(t))}#i(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.#b(t),t.firstElementChild||this.#R(t)}static get observedAttributes(){return Object.keys(this.properties||{})}#f(t){const e=Wrec.#t.get(t)||[];for(const t of e){const e=Wrec.#g(t,this),s=this.#s.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)}}this.#y(t)}static register(){const t=Wrec.#v(this.name);customElements.get(t)||customElements.define(t,this)}#E(t,e,s){const r=t.match(REFERENCE_RE);if(!r)return;this.constructor.processed||r.forEach(e=>{const s=e.substring(SKIP);let r=Wrec.#t.get(s);r||(r=[],Wrec.#t.set(s,r)),r.push(t)});let o=this.#s.get(t);o||(o=[],this.#s.set(t,o)),o.push(s?{element:e,attrName:s}:e);const n=Wrec.#g(t,this);s?this.#d(e,s,n):this.#A(e,n)}static#v=t=>t.replace(/([a-z0-9])([A-Z])/g,"$1-$2").toLowerCase();#a(t,e){this.#r&&(this.#r.set(t,e),this.#o.setFormValue(this.#r))}#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)}#y(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;"textarea"===s?t.value=e:"string"==typeof e&&e.trim().startsWith("<")?(t.innerHTML=e,this.#p(t),this.#h(t)):t.textContent=e}#p(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;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wrec",
|
|
3
3
|
"author": "R. Mark Volkmann",
|
|
4
|
-
"version": "0.0.
|
|
4
|
+
"version": "0.0.4",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
@@ -11,9 +11,18 @@
|
|
|
11
11
|
"web",
|
|
12
12
|
"component"
|
|
13
13
|
],
|
|
14
|
+
"main": "dist/wrec.min.js",
|
|
15
|
+
"exports": {
|
|
16
|
+
"import": "./dist/wrec.min.js",
|
|
17
|
+
"require": "./dist/wrec.min.js"
|
|
18
|
+
},
|
|
19
|
+
"files": [
|
|
20
|
+
"dist/wrec.min.js",
|
|
21
|
+
"README.md"
|
|
22
|
+
],
|
|
14
23
|
"scripts": {
|
|
15
24
|
"lint": "oxlint",
|
|
16
|
-
"minify": "terser wrec.js -c -m -o wrec.min.js"
|
|
25
|
+
"minify": "terser wrec.js -c -m -o dist/wrec.min.js"
|
|
17
26
|
},
|
|
18
27
|
"devDependencies": {
|
|
19
28
|
"oxlint": "^1.5.0",
|
package/.vscode/settings.json
DELETED
package/demo/counter-vanilla.js
DELETED
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
const template = document.createElement("template");
|
|
2
|
-
template.innerHTML = /*html*/ `
|
|
3
|
-
<style>
|
|
4
|
-
:not(:defined) {
|
|
5
|
-
visibility: hidden;
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
.counter {
|
|
9
|
-
display: flex;
|
|
10
|
-
align-items: center;
|
|
11
|
-
gap: 0.5rem;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
button {
|
|
15
|
-
background-color: lightgreen;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
button:disabled {
|
|
19
|
-
background-color: gray;
|
|
20
|
-
}
|
|
21
|
-
</style>
|
|
22
|
-
<div>
|
|
23
|
-
<button id="decrement-btn" type="button">-</button>
|
|
24
|
-
<span></span>
|
|
25
|
-
<button id="increment-btn" type="button">+</button>
|
|
26
|
-
</div>
|
|
27
|
-
`;
|
|
28
|
-
|
|
29
|
-
class CounterVanilla extends HTMLElement {
|
|
30
|
-
static get observedAttributes() {
|
|
31
|
-
return ["count"];
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
constructor() {
|
|
35
|
-
super();
|
|
36
|
-
this.attachShadow({ mode: "open" });
|
|
37
|
-
}
|
|
38
|
-
attributeChangedCallback() {
|
|
39
|
-
if (this.isConnected) this.#update();
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
connectedCallback() {
|
|
43
|
-
const root = this.shadowRoot;
|
|
44
|
-
root.appendChild(template.content.cloneNode(true));
|
|
45
|
-
|
|
46
|
-
this.decrementBtn = root.querySelector("#decrement-btn");
|
|
47
|
-
this.decrementBtn.addEventListener("click", () => {
|
|
48
|
-
this.decrement();
|
|
49
|
-
});
|
|
50
|
-
root.querySelector("#increment-btn").addEventListener("click", () => {
|
|
51
|
-
this.increment();
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
this.span = root.querySelector("span");
|
|
55
|
-
this.#update();
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
// Treat the count attribute as the source of truth
|
|
59
|
-
// rather than adding a property.
|
|
60
|
-
get count() {
|
|
61
|
-
return this.getAttribute("count") || 0;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
set count(newCount) {
|
|
65
|
-
this.setAttribute("count", newCount);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
decrement() {
|
|
69
|
-
if (this.count == 0) return;
|
|
70
|
-
|
|
71
|
-
this.count--;
|
|
72
|
-
// this.count gets converted to a string,
|
|
73
|
-
// so we have to use == instead of === on the next line.
|
|
74
|
-
if (this.count == 0) {
|
|
75
|
-
this.decrementBtn.setAttribute("disabled", "disabled");
|
|
76
|
-
}
|
|
77
|
-
this.#update();
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
increment() {
|
|
81
|
-
this.count++;
|
|
82
|
-
this.decrementBtn.removeAttribute("disabled");
|
|
83
|
-
this.#update();
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
#update() {
|
|
87
|
-
if (this.span) this.span.textContent = this.count;
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
customElements.define("counter-vanilla", CounterVanilla);
|
package/demo/counter-wreck.js
DELETED
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
import Wrec from "../Wrec.js";
|
|
2
|
-
|
|
3
|
-
class CounterWrec extends Wrec {
|
|
4
|
-
static properties = {
|
|
5
|
-
count: { type: Number, reflect: true },
|
|
6
|
-
};
|
|
7
|
-
|
|
8
|
-
css() {
|
|
9
|
-
return /*css*/ `
|
|
10
|
-
:not(:defined) {
|
|
11
|
-
visibility: hidden;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
.counter {
|
|
15
|
-
display: flex;
|
|
16
|
-
align-items: center;
|
|
17
|
-
gap: 0.5rem;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
button {
|
|
21
|
-
background-color: lightgreen;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
button:disabled {
|
|
25
|
-
background-color: gray;
|
|
26
|
-
}
|
|
27
|
-
`;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
html() {
|
|
31
|
-
return /*html*/ `
|
|
32
|
-
<div>
|
|
33
|
-
<button onClick="decrement" type="button"
|
|
34
|
-
disabled="this.count === 0">-</button>
|
|
35
|
-
<span>this.count</span>
|
|
36
|
-
<button onClick="this.count++" type="button">+</button>
|
|
37
|
-
<span>(this.count < 10 ? "single" : "double") + " digit"</span>
|
|
38
|
-
</div>
|
|
39
|
-
`;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
decrement() {
|
|
43
|
-
if (this.count > 0) this.count--;
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
CounterWrec.register();
|
package/demo/data-bind.js
DELETED
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
import Wrec from "../wrec.js";
|
|
2
|
-
|
|
3
|
-
class DataBind extends Wrec {
|
|
4
|
-
static formAssociated = true;
|
|
5
|
-
static properties = {
|
|
6
|
-
color: { type: String, reflect: true },
|
|
7
|
-
name: { type: String, reflect: true },
|
|
8
|
-
score: { type: Number, reflect: true },
|
|
9
|
-
story: { type: String, reflect: true },
|
|
10
|
-
};
|
|
11
|
-
|
|
12
|
-
css() {
|
|
13
|
-
return /*css*/ `
|
|
14
|
-
:host {
|
|
15
|
-
font-family: sans-serif;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
label {
|
|
19
|
-
font-weight: bold;
|
|
20
|
-
}
|
|
21
|
-
`;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
html() {
|
|
25
|
-
return /*html*/ `
|
|
26
|
-
<div>
|
|
27
|
-
<div>
|
|
28
|
-
<label>Name:</label>
|
|
29
|
-
<input value="this.name">
|
|
30
|
-
<p>Hello, <span>this.name</span>!</p>
|
|
31
|
-
</div>
|
|
32
|
-
<div style="display: flex">
|
|
33
|
-
<label for="color">Color:</label>
|
|
34
|
-
<radio-group name="color" options="red,green,blue" value="this.color"></radio-group>
|
|
35
|
-
</div>
|
|
36
|
-
<div>
|
|
37
|
-
<label>Color:</label>
|
|
38
|
-
<select value="this.color">
|
|
39
|
-
<option value="red">Red</option>
|
|
40
|
-
<option value="green">Green</option>
|
|
41
|
-
<option value="blue">Blue</option>
|
|
42
|
-
</select>
|
|
43
|
-
</div>
|
|
44
|
-
<p>You selected the color <span>this.color</span>.</p>
|
|
45
|
-
<div>
|
|
46
|
-
<label>Story:</label>
|
|
47
|
-
<textarea>this.story</textarea>
|
|
48
|
-
<p>Your story is <span>this.story</span>.</p>
|
|
49
|
-
</div>
|
|
50
|
-
<number-input label="Favorite Number:" value="this.score"></number-input>
|
|
51
|
-
<number-slider label="Slider:" value="this.score"></number-slider>
|
|
52
|
-
<p>Your score is <span>this.score</span>.</p>
|
|
53
|
-
</div>
|
|
54
|
-
`;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
formResetCallback() {
|
|
58
|
-
this.color = "red";
|
|
59
|
-
this.name = "";
|
|
60
|
-
this.score = 0;
|
|
61
|
-
this.story = "";
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
DataBind.register();
|
package/demo/hello-world.js
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import Wrec from "../wrec.js";
|
|
2
|
-
|
|
3
|
-
class HelloWorld extends Wrec {
|
|
4
|
-
static properties = {
|
|
5
|
-
name: { type: String, value: "World", reflect: true },
|
|
6
|
-
};
|
|
7
|
-
|
|
8
|
-
css() {
|
|
9
|
-
return /*css*/ `p { color: purple; }`;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
html() {
|
|
13
|
-
return /*html*/ `
|
|
14
|
-
<p>
|
|
15
|
-
Hello, <span>this.name</span>.
|
|
16
|
-
Shouting <span>this.name.toUpperCase()</span>!
|
|
17
|
-
</p>
|
|
18
|
-
`;
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
HelloWorld.register();
|
package/demo/index.html
DELETED
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
<html>
|
|
2
|
-
<head>
|
|
3
|
-
<style></style>
|
|
4
|
-
|
|
5
|
-
<script src="counter-vanilla.js" type="module"></script>
|
|
6
|
-
<script src="counter-wrec.js" type="module"></script>
|
|
7
|
-
<script src="data-bind.js" type="module"></script>
|
|
8
|
-
<script src="hello-world.js" type="module"></script>
|
|
9
|
-
<script src="multiply-numbers.js" type="module"></script>
|
|
10
|
-
<script src="number-input.js" type="module"></script>
|
|
11
|
-
<script src="number-slider.js" type="module"></script>
|
|
12
|
-
<script src="radio-group.js" type="module"></script>
|
|
13
|
-
<script src="temperature-eval.js" type="module"></script>
|
|
14
|
-
</head>
|
|
15
|
-
<body>
|
|
16
|
-
<h2>Vanilla</h2>
|
|
17
|
-
<counter-vanilla count="3"></counter-vanilla>
|
|
18
|
-
|
|
19
|
-
<h2>Wrec</h2>
|
|
20
|
-
<temperature-eval temperature="100"></temperature-eval>
|
|
21
|
-
<counter-wrec count="3"></counter-wrec>
|
|
22
|
-
<counter-wrec count="1"></counter-wrec>
|
|
23
|
-
<hello-world></hello-world>
|
|
24
|
-
<hello-world name="Mark"></hello-world>
|
|
25
|
-
<multiply-numbers n1="3" n2="4"></multiply-numbers>
|
|
26
|
-
<hr />
|
|
27
|
-
<form method="GET" action="/missing">
|
|
28
|
-
<data-bind
|
|
29
|
-
name="Mark"
|
|
30
|
-
color="blue"
|
|
31
|
-
score="5"
|
|
32
|
-
story="Once upon a time..."
|
|
33
|
-
></data-bind>
|
|
34
|
-
<button>Submit</button>
|
|
35
|
-
<button type="reset">Reset</button>
|
|
36
|
-
</form>
|
|
37
|
-
</body>
|
|
38
|
-
</html>
|
package/demo/multiply-numbers.js
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import Wrec from "../wrec.js";
|
|
2
|
-
|
|
3
|
-
class MultiplyNumbers extends Wrec {
|
|
4
|
-
static properties = {
|
|
5
|
-
n1: { type: Number },
|
|
6
|
-
n2: { type: Number },
|
|
7
|
-
};
|
|
8
|
-
|
|
9
|
-
html() {
|
|
10
|
-
return /*html*/ `
|
|
11
|
-
<p>
|
|
12
|
-
<span>this.n1</span> * <span>this.n2</span> =
|
|
13
|
-
<span>this.n1 * this.n2</span>
|
|
14
|
-
</p>
|
|
15
|
-
`;
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
MultiplyNumbers.register();
|
package/demo/number-input.js
DELETED
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
import Wrec from "../wrec.js";
|
|
2
|
-
|
|
3
|
-
class NumberInput extends Wrec {
|
|
4
|
-
static formAssociated = true;
|
|
5
|
-
static properties = {
|
|
6
|
-
label: { type: String, reflect: true },
|
|
7
|
-
value: { type: Number, reflect: true },
|
|
8
|
-
};
|
|
9
|
-
|
|
10
|
-
css() {
|
|
11
|
-
return /*css*/ `
|
|
12
|
-
button {
|
|
13
|
-
background-color: cornflowerblue;
|
|
14
|
-
border: none;
|
|
15
|
-
border-radius: 50%;
|
|
16
|
-
color: white;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
input[type="number"] {
|
|
20
|
-
text-align: right;
|
|
21
|
-
width: 2rem;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
input[type="number"]::-webkit-inner-spin-button,
|
|
25
|
-
input[type="number"]::-webkit-outer-spin-button {
|
|
26
|
-
appearance: none;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
label { font-weight: bold; }
|
|
30
|
-
`;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
html() {
|
|
34
|
-
return /*html*/ `
|
|
35
|
-
<div>
|
|
36
|
-
<label>this.label</label>
|
|
37
|
-
<button onclick="decrement" type="button">-</button>
|
|
38
|
-
<input type="number" value="this.value" />
|
|
39
|
-
<button onclick="increment" type="button">+</button>
|
|
40
|
-
</div>
|
|
41
|
-
`;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
decrement() {
|
|
45
|
-
if (this.value > 0) this.value--;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
increment() {
|
|
49
|
-
this.value++;
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
NumberInput.register();
|
package/demo/number-slider.js
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import Wrec from "../wrec.js";
|
|
2
|
-
|
|
3
|
-
class NumberSlider extends Wrec {
|
|
4
|
-
static properties = {
|
|
5
|
-
label: { type: String, reflect: true },
|
|
6
|
-
value: { type: Number, reflect: true },
|
|
7
|
-
};
|
|
8
|
-
|
|
9
|
-
css() {
|
|
10
|
-
return /*css*/ `
|
|
11
|
-
input[type="number"] {
|
|
12
|
-
width: 6rem;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
label { font-weight: bold; }
|
|
16
|
-
`;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
html() {
|
|
20
|
-
return /*html*/ `
|
|
21
|
-
<div>
|
|
22
|
-
<label>this.label</label>
|
|
23
|
-
<input type="range" min="0" value="this.value" />
|
|
24
|
-
</div>
|
|
25
|
-
`;
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
NumberSlider.register();
|
package/demo/package-lock.json
DELETED
|
@@ -1,341 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "wrec-demo",
|
|
3
|
-
"lockfileVersion": 3,
|
|
4
|
-
"requires": true,
|
|
5
|
-
"packages": {
|
|
6
|
-
"": {
|
|
7
|
-
"name": "wrec-demo",
|
|
8
|
-
"devDependencies": {
|
|
9
|
-
"oxlint": "^1.5.0",
|
|
10
|
-
"vite": "^7.0.1"
|
|
11
|
-
}
|
|
12
|
-
},
|
|
13
|
-
"node_modules/@esbuild/darwin-arm64": {
|
|
14
|
-
"version": "0.25.5",
|
|
15
|
-
"cpu": [
|
|
16
|
-
"arm64"
|
|
17
|
-
],
|
|
18
|
-
"dev": true,
|
|
19
|
-
"license": "MIT",
|
|
20
|
-
"optional": true,
|
|
21
|
-
"os": [
|
|
22
|
-
"darwin"
|
|
23
|
-
],
|
|
24
|
-
"engines": {
|
|
25
|
-
"node": ">=18"
|
|
26
|
-
}
|
|
27
|
-
},
|
|
28
|
-
"node_modules/@oxlint/darwin-arm64": {
|
|
29
|
-
"version": "1.5.0",
|
|
30
|
-
"cpu": [
|
|
31
|
-
"arm64"
|
|
32
|
-
],
|
|
33
|
-
"dev": true,
|
|
34
|
-
"license": "MIT",
|
|
35
|
-
"optional": true,
|
|
36
|
-
"os": [
|
|
37
|
-
"darwin"
|
|
38
|
-
]
|
|
39
|
-
},
|
|
40
|
-
"node_modules/@rollup/rollup-darwin-arm64": {
|
|
41
|
-
"version": "4.44.1",
|
|
42
|
-
"cpu": [
|
|
43
|
-
"arm64"
|
|
44
|
-
],
|
|
45
|
-
"dev": true,
|
|
46
|
-
"license": "MIT",
|
|
47
|
-
"optional": true,
|
|
48
|
-
"os": [
|
|
49
|
-
"darwin"
|
|
50
|
-
]
|
|
51
|
-
},
|
|
52
|
-
"node_modules/@types/estree": {
|
|
53
|
-
"version": "1.0.8",
|
|
54
|
-
"dev": true,
|
|
55
|
-
"license": "MIT"
|
|
56
|
-
},
|
|
57
|
-
"node_modules/esbuild": {
|
|
58
|
-
"version": "0.25.5",
|
|
59
|
-
"dev": true,
|
|
60
|
-
"hasInstallScript": true,
|
|
61
|
-
"license": "MIT",
|
|
62
|
-
"bin": {
|
|
63
|
-
"esbuild": "bin/esbuild"
|
|
64
|
-
},
|
|
65
|
-
"engines": {
|
|
66
|
-
"node": ">=18"
|
|
67
|
-
},
|
|
68
|
-
"optionalDependencies": {
|
|
69
|
-
"@esbuild/aix-ppc64": "0.25.5",
|
|
70
|
-
"@esbuild/android-arm": "0.25.5",
|
|
71
|
-
"@esbuild/android-arm64": "0.25.5",
|
|
72
|
-
"@esbuild/android-x64": "0.25.5",
|
|
73
|
-
"@esbuild/darwin-arm64": "0.25.5",
|
|
74
|
-
"@esbuild/darwin-x64": "0.25.5",
|
|
75
|
-
"@esbuild/freebsd-arm64": "0.25.5",
|
|
76
|
-
"@esbuild/freebsd-x64": "0.25.5",
|
|
77
|
-
"@esbuild/linux-arm": "0.25.5",
|
|
78
|
-
"@esbuild/linux-arm64": "0.25.5",
|
|
79
|
-
"@esbuild/linux-ia32": "0.25.5",
|
|
80
|
-
"@esbuild/linux-loong64": "0.25.5",
|
|
81
|
-
"@esbuild/linux-mips64el": "0.25.5",
|
|
82
|
-
"@esbuild/linux-ppc64": "0.25.5",
|
|
83
|
-
"@esbuild/linux-riscv64": "0.25.5",
|
|
84
|
-
"@esbuild/linux-s390x": "0.25.5",
|
|
85
|
-
"@esbuild/linux-x64": "0.25.5",
|
|
86
|
-
"@esbuild/netbsd-arm64": "0.25.5",
|
|
87
|
-
"@esbuild/netbsd-x64": "0.25.5",
|
|
88
|
-
"@esbuild/openbsd-arm64": "0.25.5",
|
|
89
|
-
"@esbuild/openbsd-x64": "0.25.5",
|
|
90
|
-
"@esbuild/sunos-x64": "0.25.5",
|
|
91
|
-
"@esbuild/win32-arm64": "0.25.5",
|
|
92
|
-
"@esbuild/win32-ia32": "0.25.5",
|
|
93
|
-
"@esbuild/win32-x64": "0.25.5"
|
|
94
|
-
}
|
|
95
|
-
},
|
|
96
|
-
"node_modules/fdir": {
|
|
97
|
-
"version": "6.4.6",
|
|
98
|
-
"dev": true,
|
|
99
|
-
"license": "MIT",
|
|
100
|
-
"peerDependencies": {
|
|
101
|
-
"picomatch": "^3 || ^4"
|
|
102
|
-
},
|
|
103
|
-
"peerDependenciesMeta": {
|
|
104
|
-
"picomatch": {
|
|
105
|
-
"optional": true
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
},
|
|
109
|
-
"node_modules/fsevents": {
|
|
110
|
-
"version": "2.3.3",
|
|
111
|
-
"dev": true,
|
|
112
|
-
"license": "MIT",
|
|
113
|
-
"optional": true,
|
|
114
|
-
"os": [
|
|
115
|
-
"darwin"
|
|
116
|
-
],
|
|
117
|
-
"engines": {
|
|
118
|
-
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
|
119
|
-
}
|
|
120
|
-
},
|
|
121
|
-
"node_modules/nanoid": {
|
|
122
|
-
"version": "3.3.11",
|
|
123
|
-
"dev": true,
|
|
124
|
-
"funding": [
|
|
125
|
-
{
|
|
126
|
-
"type": "github",
|
|
127
|
-
"url": "https://github.com/sponsors/ai"
|
|
128
|
-
}
|
|
129
|
-
],
|
|
130
|
-
"license": "MIT",
|
|
131
|
-
"bin": {
|
|
132
|
-
"nanoid": "bin/nanoid.cjs"
|
|
133
|
-
},
|
|
134
|
-
"engines": {
|
|
135
|
-
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
|
|
136
|
-
}
|
|
137
|
-
},
|
|
138
|
-
"node_modules/oxlint": {
|
|
139
|
-
"version": "1.5.0",
|
|
140
|
-
"dev": true,
|
|
141
|
-
"license": "MIT",
|
|
142
|
-
"bin": {
|
|
143
|
-
"oxc_language_server": "bin/oxc_language_server",
|
|
144
|
-
"oxlint": "bin/oxlint"
|
|
145
|
-
},
|
|
146
|
-
"engines": {
|
|
147
|
-
"node": ">=8.*"
|
|
148
|
-
},
|
|
149
|
-
"funding": {
|
|
150
|
-
"url": "https://github.com/sponsors/Boshen"
|
|
151
|
-
},
|
|
152
|
-
"optionalDependencies": {
|
|
153
|
-
"@oxlint/darwin-arm64": "1.5.0",
|
|
154
|
-
"@oxlint/darwin-x64": "1.5.0",
|
|
155
|
-
"@oxlint/linux-arm64-gnu": "1.5.0",
|
|
156
|
-
"@oxlint/linux-arm64-musl": "1.5.0",
|
|
157
|
-
"@oxlint/linux-x64-gnu": "1.5.0",
|
|
158
|
-
"@oxlint/linux-x64-musl": "1.5.0",
|
|
159
|
-
"@oxlint/win32-arm64": "1.5.0",
|
|
160
|
-
"@oxlint/win32-x64": "1.5.0"
|
|
161
|
-
}
|
|
162
|
-
},
|
|
163
|
-
"node_modules/picocolors": {
|
|
164
|
-
"version": "1.1.1",
|
|
165
|
-
"dev": true,
|
|
166
|
-
"license": "ISC"
|
|
167
|
-
},
|
|
168
|
-
"node_modules/picomatch": {
|
|
169
|
-
"version": "4.0.2",
|
|
170
|
-
"dev": true,
|
|
171
|
-
"license": "MIT",
|
|
172
|
-
"engines": {
|
|
173
|
-
"node": ">=12"
|
|
174
|
-
},
|
|
175
|
-
"funding": {
|
|
176
|
-
"url": "https://github.com/sponsors/jonschlinkert"
|
|
177
|
-
}
|
|
178
|
-
},
|
|
179
|
-
"node_modules/postcss": {
|
|
180
|
-
"version": "8.5.6",
|
|
181
|
-
"dev": true,
|
|
182
|
-
"funding": [
|
|
183
|
-
{
|
|
184
|
-
"type": "opencollective",
|
|
185
|
-
"url": "https://opencollective.com/postcss/"
|
|
186
|
-
},
|
|
187
|
-
{
|
|
188
|
-
"type": "tidelift",
|
|
189
|
-
"url": "https://tidelift.com/funding/github/npm/postcss"
|
|
190
|
-
},
|
|
191
|
-
{
|
|
192
|
-
"type": "github",
|
|
193
|
-
"url": "https://github.com/sponsors/ai"
|
|
194
|
-
}
|
|
195
|
-
],
|
|
196
|
-
"license": "MIT",
|
|
197
|
-
"dependencies": {
|
|
198
|
-
"nanoid": "^3.3.11",
|
|
199
|
-
"picocolors": "^1.1.1",
|
|
200
|
-
"source-map-js": "^1.2.1"
|
|
201
|
-
},
|
|
202
|
-
"engines": {
|
|
203
|
-
"node": "^10 || ^12 || >=14"
|
|
204
|
-
}
|
|
205
|
-
},
|
|
206
|
-
"node_modules/rollup": {
|
|
207
|
-
"version": "4.44.1",
|
|
208
|
-
"dev": true,
|
|
209
|
-
"license": "MIT",
|
|
210
|
-
"dependencies": {
|
|
211
|
-
"@types/estree": "1.0.8"
|
|
212
|
-
},
|
|
213
|
-
"bin": {
|
|
214
|
-
"rollup": "dist/bin/rollup"
|
|
215
|
-
},
|
|
216
|
-
"engines": {
|
|
217
|
-
"node": ">=18.0.0",
|
|
218
|
-
"npm": ">=8.0.0"
|
|
219
|
-
},
|
|
220
|
-
"optionalDependencies": {
|
|
221
|
-
"@rollup/rollup-android-arm-eabi": "4.44.1",
|
|
222
|
-
"@rollup/rollup-android-arm64": "4.44.1",
|
|
223
|
-
"@rollup/rollup-darwin-arm64": "4.44.1",
|
|
224
|
-
"@rollup/rollup-darwin-x64": "4.44.1",
|
|
225
|
-
"@rollup/rollup-freebsd-arm64": "4.44.1",
|
|
226
|
-
"@rollup/rollup-freebsd-x64": "4.44.1",
|
|
227
|
-
"@rollup/rollup-linux-arm-gnueabihf": "4.44.1",
|
|
228
|
-
"@rollup/rollup-linux-arm-musleabihf": "4.44.1",
|
|
229
|
-
"@rollup/rollup-linux-arm64-gnu": "4.44.1",
|
|
230
|
-
"@rollup/rollup-linux-arm64-musl": "4.44.1",
|
|
231
|
-
"@rollup/rollup-linux-loongarch64-gnu": "4.44.1",
|
|
232
|
-
"@rollup/rollup-linux-powerpc64le-gnu": "4.44.1",
|
|
233
|
-
"@rollup/rollup-linux-riscv64-gnu": "4.44.1",
|
|
234
|
-
"@rollup/rollup-linux-riscv64-musl": "4.44.1",
|
|
235
|
-
"@rollup/rollup-linux-s390x-gnu": "4.44.1",
|
|
236
|
-
"@rollup/rollup-linux-x64-gnu": "4.44.1",
|
|
237
|
-
"@rollup/rollup-linux-x64-musl": "4.44.1",
|
|
238
|
-
"@rollup/rollup-win32-arm64-msvc": "4.44.1",
|
|
239
|
-
"@rollup/rollup-win32-ia32-msvc": "4.44.1",
|
|
240
|
-
"@rollup/rollup-win32-x64-msvc": "4.44.1",
|
|
241
|
-
"fsevents": "~2.3.2"
|
|
242
|
-
}
|
|
243
|
-
},
|
|
244
|
-
"node_modules/source-map-js": {
|
|
245
|
-
"version": "1.2.1",
|
|
246
|
-
"dev": true,
|
|
247
|
-
"license": "BSD-3-Clause",
|
|
248
|
-
"engines": {
|
|
249
|
-
"node": ">=0.10.0"
|
|
250
|
-
}
|
|
251
|
-
},
|
|
252
|
-
"node_modules/tinyglobby": {
|
|
253
|
-
"version": "0.2.14",
|
|
254
|
-
"dev": true,
|
|
255
|
-
"license": "MIT",
|
|
256
|
-
"dependencies": {
|
|
257
|
-
"fdir": "^6.4.4",
|
|
258
|
-
"picomatch": "^4.0.2"
|
|
259
|
-
},
|
|
260
|
-
"engines": {
|
|
261
|
-
"node": ">=12.0.0"
|
|
262
|
-
},
|
|
263
|
-
"funding": {
|
|
264
|
-
"url": "https://github.com/sponsors/SuperchupuDev"
|
|
265
|
-
}
|
|
266
|
-
},
|
|
267
|
-
"node_modules/vite": {
|
|
268
|
-
"version": "7.0.1",
|
|
269
|
-
"dev": true,
|
|
270
|
-
"license": "MIT",
|
|
271
|
-
"dependencies": {
|
|
272
|
-
"esbuild": "^0.25.0",
|
|
273
|
-
"fdir": "^6.4.6",
|
|
274
|
-
"picomatch": "^4.0.2",
|
|
275
|
-
"postcss": "^8.5.6",
|
|
276
|
-
"rollup": "^4.40.0",
|
|
277
|
-
"tinyglobby": "^0.2.14"
|
|
278
|
-
},
|
|
279
|
-
"bin": {
|
|
280
|
-
"vite": "bin/vite.js"
|
|
281
|
-
},
|
|
282
|
-
"engines": {
|
|
283
|
-
"node": "^20.19.0 || >=22.12.0"
|
|
284
|
-
},
|
|
285
|
-
"funding": {
|
|
286
|
-
"url": "https://github.com/vitejs/vite?sponsor=1"
|
|
287
|
-
},
|
|
288
|
-
"optionalDependencies": {
|
|
289
|
-
"fsevents": "~2.3.3"
|
|
290
|
-
},
|
|
291
|
-
"peerDependencies": {
|
|
292
|
-
"@types/node": "^20.19.0 || >=22.12.0",
|
|
293
|
-
"jiti": ">=1.21.0",
|
|
294
|
-
"less": "^4.0.0",
|
|
295
|
-
"lightningcss": "^1.21.0",
|
|
296
|
-
"sass": "^1.70.0",
|
|
297
|
-
"sass-embedded": "^1.70.0",
|
|
298
|
-
"stylus": ">=0.54.8",
|
|
299
|
-
"sugarss": "^5.0.0",
|
|
300
|
-
"terser": "^5.16.0",
|
|
301
|
-
"tsx": "^4.8.1",
|
|
302
|
-
"yaml": "^2.4.2"
|
|
303
|
-
},
|
|
304
|
-
"peerDependenciesMeta": {
|
|
305
|
-
"@types/node": {
|
|
306
|
-
"optional": true
|
|
307
|
-
},
|
|
308
|
-
"jiti": {
|
|
309
|
-
"optional": true
|
|
310
|
-
},
|
|
311
|
-
"less": {
|
|
312
|
-
"optional": true
|
|
313
|
-
},
|
|
314
|
-
"lightningcss": {
|
|
315
|
-
"optional": true
|
|
316
|
-
},
|
|
317
|
-
"sass": {
|
|
318
|
-
"optional": true
|
|
319
|
-
},
|
|
320
|
-
"sass-embedded": {
|
|
321
|
-
"optional": true
|
|
322
|
-
},
|
|
323
|
-
"stylus": {
|
|
324
|
-
"optional": true
|
|
325
|
-
},
|
|
326
|
-
"sugarss": {
|
|
327
|
-
"optional": true
|
|
328
|
-
},
|
|
329
|
-
"terser": {
|
|
330
|
-
"optional": true
|
|
331
|
-
},
|
|
332
|
-
"tsx": {
|
|
333
|
-
"optional": true
|
|
334
|
-
},
|
|
335
|
-
"yaml": {
|
|
336
|
-
"optional": true
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
}
|
package/demo/package.json
DELETED
package/demo/radio-group.js
DELETED
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
import Wrec from "../wrec.js";
|
|
2
|
-
|
|
3
|
-
class RadioGroup extends Wrec {
|
|
4
|
-
// This is the only thing a Wrec subclass
|
|
5
|
-
// must contain to contribute to form submission.
|
|
6
|
-
static formAssociated = true;
|
|
7
|
-
|
|
8
|
-
static properties = {
|
|
9
|
-
default: { type: String },
|
|
10
|
-
name: { type: String },
|
|
11
|
-
options: { type: String },
|
|
12
|
-
value: { type: String },
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
attributeChangedCallback(attr, oldValue, newValue) {
|
|
16
|
-
super.attributeChangedCallback(attr, oldValue, newValue);
|
|
17
|
-
if (attr === "value") {
|
|
18
|
-
const inputs = this.shadowRoot.querySelectorAll("input");
|
|
19
|
-
for (const input of inputs) {
|
|
20
|
-
input.checked = input.value === newValue;
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
connectedCallback() {
|
|
26
|
-
super.connectedCallback();
|
|
27
|
-
if (!this.default) this.default = this.options.split(",")[0];
|
|
28
|
-
if (!this.value) this.value = this.default;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
css() {
|
|
32
|
-
return /*css*/ `
|
|
33
|
-
.radio-group {
|
|
34
|
-
display: flex;
|
|
35
|
-
gap: 0.25rem;
|
|
36
|
-
|
|
37
|
-
> div {
|
|
38
|
-
display: flex;
|
|
39
|
-
align-items: center;
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
`;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
html() {
|
|
46
|
-
// This web component uses iteration to determine what to render.
|
|
47
|
-
return /*html*/ `
|
|
48
|
-
<div class="radio-group">
|
|
49
|
-
this.options.split(",").map((option) => this.makeRadio(option)).join("")
|
|
50
|
-
</div>
|
|
51
|
-
`;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// This method cannot be private because it is called when
|
|
55
|
-
// a change event is dispatched from a radio button.
|
|
56
|
-
handleChange(event) {
|
|
57
|
-
const { value } = event.target;
|
|
58
|
-
this.value = value;
|
|
59
|
-
|
|
60
|
-
// This allows users of the this web component to listen for changes.
|
|
61
|
-
this.dispatchEvent(new Event("change"));
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// This method cannot be private because it is
|
|
65
|
-
// called from the expression in the html method.
|
|
66
|
-
makeRadio(option) {
|
|
67
|
-
return /*html*/ `
|
|
68
|
-
<div>
|
|
69
|
-
<input
|
|
70
|
-
type="radio"
|
|
71
|
-
id="${option}"
|
|
72
|
-
name="${this.name}"
|
|
73
|
-
value="${option}"
|
|
74
|
-
${option === this.value ? "checked" : ""}
|
|
75
|
-
onchange="handleChange"
|
|
76
|
-
/>
|
|
77
|
-
<label for="${option}">${option}</label>
|
|
78
|
-
</div>
|
|
79
|
-
`;
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
RadioGroup.register();
|
package/demo/temperature-eval.js
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import Wrec from "../wrec.js";
|
|
2
|
-
|
|
3
|
-
class TemperatureEval extends Wrec {
|
|
4
|
-
static properties = {
|
|
5
|
-
temperature: { type: Number },
|
|
6
|
-
};
|
|
7
|
-
|
|
8
|
-
html() {
|
|
9
|
-
// This web component uses conditional logic to determine what to render.
|
|
10
|
-
return /*html*/ `
|
|
11
|
-
<p>this.temperature < 32 ? "freezing" : "not freezing"</p>
|
|
12
|
-
`;
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
TemperatureEval.register();
|
package/shipwreck.png
DELETED
|
Binary file
|
package/wrec.js
DELETED
|
@@ -1,348 +0,0 @@
|
|
|
1
|
-
const FIRST_CHAR = "a-zA-Z_$";
|
|
2
|
-
const OTHER_CHAR = FIRST_CHAR + "0-9";
|
|
3
|
-
const IDENTIFIER = `[${FIRST_CHAR}][${OTHER_CHAR}]*`;
|
|
4
|
-
const REFERENCE_RE = new RegExp("this." + IDENTIFIER, "g");
|
|
5
|
-
const SKIP = "this.".length;
|
|
6
|
-
|
|
7
|
-
class Wrec extends HTMLElement {
|
|
8
|
-
static #propertyToExpressionsMap = new Map();
|
|
9
|
-
static #template = document.createElement("template");
|
|
10
|
-
|
|
11
|
-
#expressionReferencesMap = new Map();
|
|
12
|
-
#formData;
|
|
13
|
-
#internals;
|
|
14
|
-
#propertyToBindingsMap = new Map();
|
|
15
|
-
|
|
16
|
-
constructor() {
|
|
17
|
-
super();
|
|
18
|
-
this.attachShadow({ mode: "open" });
|
|
19
|
-
|
|
20
|
-
if (this.constructor.formAssociated) {
|
|
21
|
-
this.#internals = this.attachInternals();
|
|
22
|
-
this.#formData = new FormData();
|
|
23
|
-
this.#internals.setFormValue(this.#formData);
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
attributeChangedCallback(attrName, _, newValue) {
|
|
28
|
-
// Update the corresponding property.
|
|
29
|
-
const value = this.#getTypedValue(attrName, newValue);
|
|
30
|
-
this[attrName] = value;
|
|
31
|
-
this.#setFormValue(attrName, value);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
#bind(element, propertyName, attrName) {
|
|
35
|
-
element.addEventListener("input", (event) => {
|
|
36
|
-
this[propertyName] = event.target.value;
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
let bindings = this.#propertyToBindingsMap.get(propertyName);
|
|
40
|
-
if (!bindings) {
|
|
41
|
-
bindings = [];
|
|
42
|
-
this.#propertyToBindingsMap.set(propertyName, bindings);
|
|
43
|
-
}
|
|
44
|
-
bindings.push(attrName ? { element, attrName } : element);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
// This is not private so it can be called from subclasses.
|
|
48
|
-
buildDOM() {
|
|
49
|
-
let template = this.constructor.prototype.css
|
|
50
|
-
? `<style>${this.css()}</style>`
|
|
51
|
-
: "";
|
|
52
|
-
template += this.html();
|
|
53
|
-
Wrec.#template.innerHTML = template;
|
|
54
|
-
|
|
55
|
-
this.shadowRoot.replaceChildren(Wrec.#template.content.cloneNode(true));
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
connectedCallback() {
|
|
59
|
-
this.#defineProperties();
|
|
60
|
-
this.buildDOM();
|
|
61
|
-
|
|
62
|
-
// Wait for the DOM to update.
|
|
63
|
-
requestAnimationFrame(() => {
|
|
64
|
-
this.#wireEvents(this.shadowRoot);
|
|
65
|
-
this.#makeReactive(this.shadowRoot);
|
|
66
|
-
this.constructor.processed = true;
|
|
67
|
-
});
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
#defineProperties() {
|
|
71
|
-
const properties = this.constructor.properties;
|
|
72
|
-
const { observedAttributes } = this.constructor;
|
|
73
|
-
for (const [name, options] of Object.entries(properties)) {
|
|
74
|
-
this.#defineProperty(name, options, observedAttributes);
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
#defineProperty(propertyName, options, observedAttributes) {
|
|
79
|
-
// Copy the property value to a new property with a leading underscore.
|
|
80
|
-
// The property is replaced below with Object.defineProperty.
|
|
81
|
-
const value =
|
|
82
|
-
observedAttributes.includes(propertyName) &&
|
|
83
|
-
this.hasAttribute(propertyName)
|
|
84
|
-
? this.#getTypedAttribute(propertyName)
|
|
85
|
-
: options.value;
|
|
86
|
-
this["_" + propertyName] = value;
|
|
87
|
-
|
|
88
|
-
Object.defineProperty(this, propertyName, {
|
|
89
|
-
enumerable: true,
|
|
90
|
-
get() {
|
|
91
|
-
return this["_" + propertyName];
|
|
92
|
-
},
|
|
93
|
-
set(value) {
|
|
94
|
-
const oldValue = this["_" + propertyName];
|
|
95
|
-
if (value === oldValue) return;
|
|
96
|
-
|
|
97
|
-
this["_" + propertyName] = value;
|
|
98
|
-
|
|
99
|
-
// If the property propertyName is configured to "reflect" and
|
|
100
|
-
// there is a matching attribute on the custom element,
|
|
101
|
-
// update that attribute.
|
|
102
|
-
const options = this.constructor.properties[propertyName];
|
|
103
|
-
if (options.reflect && this.hasAttribute(propertyName)) {
|
|
104
|
-
const oldValue = this.#getTypedAttribute(propertyName);
|
|
105
|
-
if (value !== oldValue) {
|
|
106
|
-
this.#updateAttribute(this, propertyName, value);
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
this.#react(propertyName);
|
|
111
|
-
|
|
112
|
-
// If this property is bound to a parent web component property,
|
|
113
|
-
// update that as well.
|
|
114
|
-
const map = this.propertyToParentPropertyMap;
|
|
115
|
-
const parentProperty = map ? map.get(propertyName) : null;
|
|
116
|
-
if (parentProperty) {
|
|
117
|
-
const parent = this.getRootNode().host;
|
|
118
|
-
parent.setAttribute(parentProperty, value);
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
this.#setFormValue(propertyName, value);
|
|
122
|
-
},
|
|
123
|
-
});
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
#evaluateAttributes(element) {
|
|
127
|
-
const isWC = element.localName.includes("-");
|
|
128
|
-
|
|
129
|
-
for (const attrName of element.getAttributeNames()) {
|
|
130
|
-
const text = element.getAttribute(attrName);
|
|
131
|
-
if (REFERENCE_RE.test(text)) {
|
|
132
|
-
// Configure data binding.
|
|
133
|
-
const propertyName = text.substring(SKIP);
|
|
134
|
-
const propertyValue = this[propertyName];
|
|
135
|
-
element.setAttribute(attrName, propertyValue);
|
|
136
|
-
element[attrName] = propertyValue;
|
|
137
|
-
this.#bind(element, propertyName, attrName);
|
|
138
|
-
|
|
139
|
-
// If the element is a web component,
|
|
140
|
-
// save a mapping from the attribute name in this web component
|
|
141
|
-
// to the property name in the parent web component.
|
|
142
|
-
if (isWC) {
|
|
143
|
-
let map = element.propertyToParentPropertyMap;
|
|
144
|
-
if (!map) {
|
|
145
|
-
map = new Map();
|
|
146
|
-
element.propertyToParentPropertyMap = map;
|
|
147
|
-
}
|
|
148
|
-
map.set(attrName, propertyName);
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
this.#registerPlaceholders(text, element, attrName);
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
static #evaluateInContext(expression, context) {
|
|
157
|
-
return function () {
|
|
158
|
-
// oxlint-disable-next-line no-eval
|
|
159
|
-
return eval(expression);
|
|
160
|
-
}.call(context);
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
#evaluateText(element) {
|
|
164
|
-
const { localName } = element;
|
|
165
|
-
|
|
166
|
-
// Don't allow style elements to be affected by property values.
|
|
167
|
-
if (localName === "style") return;
|
|
168
|
-
|
|
169
|
-
const text = element.textContent.trim();
|
|
170
|
-
if (localName === "textarea" && REFERENCE_RE.test(text)) {
|
|
171
|
-
// Configure data binding.
|
|
172
|
-
const propertyName = text.substring(SKIP);
|
|
173
|
-
element.textContent = this[propertyName];
|
|
174
|
-
this.#bind(element, propertyName);
|
|
175
|
-
} else {
|
|
176
|
-
this.#registerPlaceholders(text, element);
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
#getTypedAttribute(attrName) {
|
|
181
|
-
return this.#getTypedValue(attrName, this.getAttribute(attrName));
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
#getTypedValue(attrName, stringValue) {
|
|
185
|
-
const type = this.constructor.properties[attrName].type;
|
|
186
|
-
if (type === Number) return Number(stringValue);
|
|
187
|
-
if (type === Boolean) return Boolean(stringValue);
|
|
188
|
-
return stringValue;
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
#makeReactive(root) {
|
|
192
|
-
const elements = root.querySelectorAll("*");
|
|
193
|
-
for (const element of elements) {
|
|
194
|
-
this.#evaluateAttributes(element);
|
|
195
|
-
|
|
196
|
-
// If the element has no child elements, evaluate its text content.
|
|
197
|
-
if (!element.firstElementChild) this.#evaluateText(element);
|
|
198
|
-
}
|
|
199
|
-
//console.log("#propertyToExpressionsMap =", Wrec.#propertyToExpressionsMap);
|
|
200
|
-
//console.log("#expressionReferencesMap =", this.#expressionReferencesMap);
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
static get observedAttributes() {
|
|
204
|
-
return Object.keys(this.properties || {});
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
#react(propertyName) {
|
|
208
|
-
// Update all expression references.
|
|
209
|
-
const expressions = Wrec.#propertyToExpressionsMap.get(propertyName) || [];
|
|
210
|
-
for (const expression of expressions) {
|
|
211
|
-
const value = Wrec.#evaluateInContext(expression, this);
|
|
212
|
-
const references = this.#expressionReferencesMap.get(expression) || [];
|
|
213
|
-
for (const reference of references) {
|
|
214
|
-
if (reference instanceof Element) {
|
|
215
|
-
this.#updateElementContent(reference, value);
|
|
216
|
-
} else {
|
|
217
|
-
const { element, attrName } = reference;
|
|
218
|
-
this.#updateAttribute(element, attrName, value);
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
this.#updateBindings(propertyName);
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
static register() {
|
|
227
|
-
const elementName = Wrec.#toKebabCase(this.name);
|
|
228
|
-
if (!customElements.get(elementName)) {
|
|
229
|
-
customElements.define(elementName, this);
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
// Do not place untrusted expressions in
|
|
234
|
-
// attribute values or the text content of elements!
|
|
235
|
-
#registerPlaceholders(text, element, attrName) {
|
|
236
|
-
const matches = text.match(REFERENCE_RE);
|
|
237
|
-
if (!matches) return;
|
|
238
|
-
|
|
239
|
-
// Only map properties to expressions once for each web component because
|
|
240
|
-
// the mapping will be the same for every instance of the web component.
|
|
241
|
-
if (!this.constructor.processed) {
|
|
242
|
-
matches.forEach((capture) => {
|
|
243
|
-
const propertyName = capture.substring(SKIP);
|
|
244
|
-
let expressions = Wrec.#propertyToExpressionsMap.get(propertyName);
|
|
245
|
-
if (!expressions) {
|
|
246
|
-
expressions = [];
|
|
247
|
-
Wrec.#propertyToExpressionsMap.set(propertyName, expressions);
|
|
248
|
-
}
|
|
249
|
-
expressions.push(text);
|
|
250
|
-
});
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
let references = this.#expressionReferencesMap.get(text);
|
|
254
|
-
if (!references) {
|
|
255
|
-
references = [];
|
|
256
|
-
this.#expressionReferencesMap.set(text, references);
|
|
257
|
-
}
|
|
258
|
-
references.push(attrName ? { element, attrName } : element);
|
|
259
|
-
|
|
260
|
-
const value = Wrec.#evaluateInContext(text, this);
|
|
261
|
-
if (attrName) {
|
|
262
|
-
this.#updateAttribute(element, attrName, value);
|
|
263
|
-
} else {
|
|
264
|
-
this.#updateElementContent(element, value);
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
static #toKebabCase = (str) =>
|
|
269
|
-
str
|
|
270
|
-
// Insert a dash before each uppercase letter
|
|
271
|
-
// that is preceded by a lowercase letter or digit.
|
|
272
|
-
.replace(/([a-z0-9])([A-Z])/g, "$1-$2")
|
|
273
|
-
.toLowerCase();
|
|
274
|
-
|
|
275
|
-
#setFormValue(propertyName, value) {
|
|
276
|
-
if (!this.#formData) return;
|
|
277
|
-
this.#formData.set(propertyName, value);
|
|
278
|
-
this.#internals.setFormValue(this.#formData);
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
#updateAttribute(element, attrName, value) {
|
|
282
|
-
const currentValue = element.getAttribute(attrName);
|
|
283
|
-
if (typeof value === "boolean") {
|
|
284
|
-
if (value) {
|
|
285
|
-
if (currentValue !== attrName) {
|
|
286
|
-
element.setAttribute(attrName, attrName);
|
|
287
|
-
}
|
|
288
|
-
} else {
|
|
289
|
-
element.removeAttribute(attrName);
|
|
290
|
-
}
|
|
291
|
-
} else if (currentValue !== value) {
|
|
292
|
-
element.setAttribute(attrName, value);
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
#updateBindings(propertyName) {
|
|
297
|
-
const value = this[propertyName];
|
|
298
|
-
const bindings = this.#propertyToBindingsMap.get(propertyName) || [];
|
|
299
|
-
for (const binding of bindings) {
|
|
300
|
-
if (binding instanceof Element) {
|
|
301
|
-
if (binding.localName === "textarea") {
|
|
302
|
-
binding.value = value;
|
|
303
|
-
} else {
|
|
304
|
-
binding.textContent = value;
|
|
305
|
-
}
|
|
306
|
-
} else {
|
|
307
|
-
const { element, attrName } = binding;
|
|
308
|
-
this.#updateAttribute(element, attrName, value);
|
|
309
|
-
element[attrName] = value;
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
#updateElementContent(element, text) {
|
|
315
|
-
const { localName } = element;
|
|
316
|
-
if (localName === "textarea") {
|
|
317
|
-
element.value = text;
|
|
318
|
-
} else if (typeof text === "string" && text.trim().startsWith("<")) {
|
|
319
|
-
element.innerHTML = text;
|
|
320
|
-
this.#wireEvents(element);
|
|
321
|
-
this.#makeReactive(element);
|
|
322
|
-
} else {
|
|
323
|
-
element.textContent = text;
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
#wireEvents(root) {
|
|
328
|
-
const elements = root.querySelectorAll("*");
|
|
329
|
-
for (const element of elements) {
|
|
330
|
-
for (const attr of element.attributes) {
|
|
331
|
-
const { name } = attr;
|
|
332
|
-
if (name.startsWith("on")) {
|
|
333
|
-
const eventName = name.slice(2).toLowerCase();
|
|
334
|
-
const { value } = attr;
|
|
335
|
-
const fn =
|
|
336
|
-
typeof this[value] === "function"
|
|
337
|
-
? (event) => this[value](event)
|
|
338
|
-
: // oxlint-disable-next-line no-eval
|
|
339
|
-
() => eval(value);
|
|
340
|
-
element.addEventListener(eventName, fn);
|
|
341
|
-
element.removeAttribute(name);
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
export default Wrec;
|