From 6027f664b38af2443bd6e8928bcb678c00cbd370 Mon Sep 17 00:00:00 2001
From: Ayo
Date: Sun, 26 Nov 2023 03:43:41 +0100
Subject: [PATCH 1/9] feat: preserve props type
---
src/WebComponent.js | 96 ++++++++++++++++++++-----------------
type-restore/Counter.mjs | 15 ++++++
type-restore/HelloWorld.mjs | 14 ++++++
type-restore/Object.mjs | 15 ++++++
type-restore/Toggle.mjs | 18 +++++++
type-restore/index.html | 31 ++++++++++++
6 files changed, 146 insertions(+), 43 deletions(-)
create mode 100644 type-restore/Counter.mjs
create mode 100644 type-restore/HelloWorld.mjs
create mode 100644 type-restore/Object.mjs
create mode 100644 type-restore/Toggle.mjs
create mode 100644 type-restore/index.html
diff --git a/src/WebComponent.js b/src/WebComponent.js
index 65e1fcb..7486fcc 100644
--- a/src/WebComponent.js
+++ b/src/WebComponent.js
@@ -87,75 +87,85 @@ export class WebComponent extends HTMLElement {
this.onDestroy();
}
- /**
- * @param {string} property
- * @param {any} previousValue
- * @param {any} currentValue
- */
attributeChangedCallback(property, previousValue, currentValue) {
const camelCaps = this.#getCamelCaps(property);
if (previousValue !== currentValue) {
this[property] = currentValue === "" || currentValue;
this[camelCaps] = this[property]; // remove on v2
- this.props[camelCaps] = this[property];
+
+ this.#handleUpdateProp(camelCaps, currentValue)
this.render();
this.onChanges({ property, previousValue, currentValue });
}
}
- /**
- * Converts a kebab-cased string into camelCaps
- * @param {string} kebab string in kebab-case
- * @returns {string}
- */
+ #handleUpdateProp(key, value) {
+
+ const restored = this.#restoreType(value, this.#typeMap[key])
+
+ if (restored !== this.props[key])
+ this.props[key] = value;
+ }
+
#getCamelCaps(kebab) {
return kebab.replace(/-./g, (x) => x[1].toUpperCase());
}
- /**
- * Proxy handler for observed attribute - property counterpart
- * @param {(qualifiedName: string, value: string) => void} setter
- * @returns
- */
- #handler = (setter) => ({
- set(obj, prop, newValue) {
- const oldValue = obj[prop];
+ #typeMap = {};
- obj[prop] = newValue;
-
- /**
- * Converts camelCaps string into kebab-case
- * @param {string} str
- * @returns {string}
- */
- const getKebab = (str) =>
- str.replace(
- /[A-Z]+(?![a-z])|[A-Z]/g,
- ($, ofs) => (ofs ? "-" : "") + $.toLowerCase()
- );
-
- if (oldValue != newValue) {
- const kebab = getKebab(prop);
- setter(kebab, newValue);
+ #restoreType = (value, type) => {
+ switch (type) {
+ case "string":
+ return value;
+ case "number":
+ case "boolean":
+ return JSON.parse(value);
+ default:
+ return value;
}
+ };
- return true;
- },
- });
+ #handler(setter, typeMap) {
+ const getKebab = (str) => {
+ return str.replace(
+ /[A-Z]+(?![a-z])|[A-Z]/g,
+ ($, ofs) => (ofs ? "-" : "") + $.toLowerCase()
+ );
+ };
+
+
+ return {
+ set(obj, prop, value) {
+ const oldValue = obj[prop];
+
+ if (!(prop in typeMap)) {
+ typeMap[prop] = typeof value;
+ }
+
+ if (oldValue !== value) {
+ obj[prop] = value;
+ const kebab = getKebab(prop);
+ setter(kebab, value);
+ }
+
+ return true;
+ }
+ };
+ }
- /**
- * Initialize the `props` proxy object
- */
#initializeProps() {
if (!this.#props) {
this.#props = new Proxy(
{},
- this.#handler((key, value) => this.setAttribute(key, value))
+ this.#handler(
+ (key, value) => this.setAttribute(key, value),
+ this.#typeMap
+ )
);
}
}
}
-export default WebComponent;
\ No newline at end of file
+export default WebComponent;
diff --git a/type-restore/Counter.mjs b/type-restore/Counter.mjs
new file mode 100644
index 0000000..bf17f0b
--- /dev/null
+++ b/type-restore/Counter.mjs
@@ -0,0 +1,15 @@
+// @ts-check
+import WebComponent from "../src/WebComponent.js";
+
+export class Counter extends WebComponent {
+ static properties = ["count"];
+ onInit() {
+ this.props.count = 0;
+ this.onclick = ()=> ++this.props.count
+ }
+ get template() {
+ return ``;
+ }
+}
+
+customElements.define("my-counter", Counter);
diff --git a/type-restore/HelloWorld.mjs b/type-restore/HelloWorld.mjs
new file mode 100644
index 0000000..d2fe04e
--- /dev/null
+++ b/type-restore/HelloWorld.mjs
@@ -0,0 +1,14 @@
+import WebComponent from "../src/WebComponent.js";
+
+export class HelloWorld extends WebComponent {
+ static properties = ["name"];
+ onInit() {
+ this.props.name = 'a';
+ this.onclick = ()=> this.props.name += 'a'
+ }
+ get template() {
+ return ``;
+ }
+}
+
+customElements.define("my-hello-world", HelloWorld);
diff --git a/type-restore/Object.mjs b/type-restore/Object.mjs
new file mode 100644
index 0000000..0e2800a
--- /dev/null
+++ b/type-restore/Object.mjs
@@ -0,0 +1,15 @@
+import WebComponent from "../src/WebComponent.js";
+
+export class ObjectText extends WebComponent {
+ static properties = ["object"];
+ onInit() {
+ this.props.object = {
+ hello: 'world'
+ };
+ }
+ get template() {
+ return ``;
+ }
+}
+
+customElements.define("my-object", ObjectText);
diff --git a/type-restore/Toggle.mjs b/type-restore/Toggle.mjs
new file mode 100644
index 0000000..3468643
--- /dev/null
+++ b/type-restore/Toggle.mjs
@@ -0,0 +1,18 @@
+// @ts-check
+import WebComponent from "../src/WebComponent.js";
+
+export class Toggle extends WebComponent {
+ static properties = ["toggle"];
+ onInit() {
+ this.props.toggle = false;
+ this.onclick = ()=>this.handleToggle()
+ }
+ handleToggle() {
+ this.props.toggle = !this.props.toggle;
+ }
+ get template() {
+ return ``;
+ }
+}
+
+customElements.define("my-toggle", Toggle);
diff --git a/type-restore/index.html b/type-restore/index.html
new file mode 100644
index 0000000..599deac
--- /dev/null
+++ b/type-restore/index.html
@@ -0,0 +1,31 @@
+
+
+
+
+
+ WC demo
+
+
+
+
+
+
+
+
+ Counter:
+
+
+ Toggle:
+
+
+ String:
+
+
+ Object:
+
+
+
From b3189e2c6e19e327b61f6509f53c3f81fb01c66f Mon Sep 17 00:00:00 2001
From: Ayo
Date: Sun, 26 Nov 2023 03:46:54 +0100
Subject: [PATCH 2/9] chore: update readme
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 112538a..b15f967 100644
--- a/README.md
+++ b/README.md
@@ -135,7 +135,7 @@ this.setAttribute('my-name','hello');
Therefore, this will tell the browser that the UI needs a render if the attribute is one of the component's observed attributes we explicitly provided with `static properties = ['my-name']`;
> The `props` property of `WebComponent` works like `HTMLElement.dataset`, except `dataset` is only for attributes prefixed with `data-`. A camelCase counterpart using `props` will give read/write access to any attribute, with or without the `data-` prefix.
-> However, note that like `HTMLElement.dataset`, values assigned to properties using `WebComponent.props` is always converted into string. This will be improved in later versions.
+> Another advantage over `HTMLElement.dataset` is that `WebComponent.props` can hold primitive types `number`, `boolean`, and `string`.
### Alternatives
From da85a71aece95d37769dc29caa3728ae4d0cc21b Mon Sep 17 00:00:00 2001
From: Ayo
Date: Sun, 26 Nov 2023 03:47:07 +0100
Subject: [PATCH 3/9] 1.13.3
---
package-lock.json | 4 ++--
package.json | 2 +-
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/package-lock.json b/package-lock.json
index a2b0684..8745d87 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "web-component-base",
- "version": "1.13.2",
+ "version": "1.13.3",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "web-component-base",
- "version": "1.13.2",
+ "version": "1.13.3",
"license": "MIT",
"workspaces": [
"site"
diff --git a/package.json b/package.json
index 083ec99..ccb6a9b 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "web-component-base",
- "version": "1.13.2",
+ "version": "1.13.3",
"description": "A zero-dependency, ~600 Bytes (minified & gzipped), JS base class for creating reactive custom elements easily",
"main": "WebComponent.js",
"type": "module",
From 9480f378442ee6b2dbc6942bdfb6d59755a1deaf Mon Sep 17 00:00:00 2001
From: Ayo
Date: Sun, 26 Nov 2023 04:19:19 +0100
Subject: [PATCH 4/9] chore: format
---
src/WebComponent.js | 33 +++++++++++++++------------------
1 file changed, 15 insertions(+), 18 deletions(-)
diff --git a/src/WebComponent.js b/src/WebComponent.js
index 7486fcc..32dd0ef 100644
--- a/src/WebComponent.js
+++ b/src/WebComponent.js
@@ -94,7 +94,7 @@ export class WebComponent extends HTMLElement {
this[property] = currentValue === "" || currentValue;
this[camelCaps] = this[property]; // remove on v2
- this.#handleUpdateProp(camelCaps, currentValue)
+ this.#handleUpdateProp(camelCaps, currentValue);
this.render();
this.onChanges({ property, previousValue, currentValue });
@@ -102,11 +102,9 @@ export class WebComponent extends HTMLElement {
}
#handleUpdateProp(key, value) {
+ const restored = this.#restoreType(value, this.#typeMap[key]);
- const restored = this.#restoreType(value, this.#typeMap[key])
-
- if (restored !== this.props[key])
- this.props[key] = value;
+ if (restored !== this.props[key]) this.props[key] = value;
}
#getCamelCaps(kebab) {
@@ -115,17 +113,17 @@ export class WebComponent extends HTMLElement {
#typeMap = {};
- #restoreType = (value, type) => {
- switch (type) {
- case "string":
- return value;
- case "number":
- case "boolean":
- return JSON.parse(value);
- default:
- return value;
- }
- };
+ #restoreType = (value, type) => {
+ switch (type) {
+ case "string":
+ return value;
+ case "number":
+ case "boolean":
+ return JSON.parse(value);
+ default:
+ return value;
+ }
+ };
#handler(setter, typeMap) {
const getKebab = (str) => {
@@ -135,7 +133,6 @@ export class WebComponent extends HTMLElement {
);
};
-
return {
set(obj, prop, value) {
const oldValue = obj[prop];
@@ -151,7 +148,7 @@ export class WebComponent extends HTMLElement {
}
return true;
- }
+ },
};
}
From 766cf65b4a4b7a163614597796a04a91f02321fd Mon Sep 17 00:00:00 2001
From: Ayo
Date: Sun, 26 Nov 2023 19:36:36 +0100
Subject: [PATCH 5/9] feat(site): update content
---
site/src/pages/index.html | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/site/src/pages/index.html b/site/src/pages/index.html
index bae4099..e5f85ef 100644
--- a/site/src/pages/index.html
+++ b/site/src/pages/index.html
@@ -27,9 +27,9 @@
- By extending our WebComponent base class, you get an easy authoring experience as you would expect in writing your components
+ By extending our base class, you get an easy authoring experience as you would expect in writing your components: