chore: format code with prettier (#14)
This commit is contained in:
parent
6eb93b5dd3
commit
488743e011
28 changed files with 951 additions and 316 deletions
5
.vscode/settings.json
vendored
5
.vscode/settings.json
vendored
|
@ -1,3 +1,4 @@
|
|||
{
|
||||
"js/ts.implicitProjectConfig.checkJs": true
|
||||
}
|
||||
"js/ts.implicitProjectConfig.checkJs": true,
|
||||
"editor.formatOnSave": true
|
||||
}
|
||||
|
|
|
@ -5,13 +5,10 @@ export class Counter extends WebComponent {
|
|||
onInit() {
|
||||
this.props.count = 0;
|
||||
this.onclick = () => ++this.props.count;
|
||||
attachEffect(
|
||||
this.props.count,
|
||||
(count) => console.log(count)
|
||||
);
|
||||
attachEffect(this.props.count, (count) => console.log(count));
|
||||
}
|
||||
|
||||
afterViewInit(){
|
||||
afterViewInit() {
|
||||
attachEffect(this.props.count, (count) => console.log(count + 100));
|
||||
}
|
||||
|
||||
|
|
|
@ -6,13 +6,10 @@ export class Decrease extends WebComponent {
|
|||
onInit() {
|
||||
this.props.count = 999;
|
||||
this.onclick = () => --this.props.count;
|
||||
attachEffect(
|
||||
this.props.count,
|
||||
(count) => console.log(count)
|
||||
);
|
||||
attachEffect(this.props.count, (count) => console.log(count));
|
||||
}
|
||||
|
||||
afterViewInit(){
|
||||
afterViewInit() {
|
||||
attachEffect(this.props.count, (count) => console.log(count + 100));
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { WebComponent } from "../../src/index.js"
|
||||
import { WebComponent } from "../../src/index.js";
|
||||
|
||||
export class BooleanPropTest extends WebComponent {
|
||||
static properties = ["is-inline", "anotherone"];
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
// @ts-check
|
||||
import { WebComponent, html } from "../../src/index.js"
|
||||
import { WebComponent, html } from "../../src/index.js";
|
||||
|
||||
export class Counter extends WebComponent {
|
||||
static props = {
|
||||
count: 0
|
||||
}
|
||||
count: 0,
|
||||
};
|
||||
get template() {
|
||||
return html`
|
||||
<button onClick=${() => ++this.props.count} id="btn">
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// @ts-check
|
||||
import { WebComponent } from "../../src/index.js"
|
||||
import { WebComponent } from "../../src/index.js";
|
||||
|
||||
export class HelloWorld extends WebComponent {
|
||||
static properties = ["count", "emotion"];
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// @ts-check
|
||||
import { WebComponent } from "../../src/index.js"
|
||||
import { WebComponent } from "../../src/index.js";
|
||||
|
||||
class SimpleText extends WebComponent {
|
||||
clickCallback() {
|
||||
|
|
|
@ -2,7 +2,7 @@ import { WebComponent } from "../../src/index.js";
|
|||
|
||||
export class HelloWorld extends WebComponent {
|
||||
static props = {
|
||||
myName: 'World',
|
||||
myName: "World",
|
||||
};
|
||||
get template() {
|
||||
return `<p>Hello ${this.props.myName}</p>`;
|
||||
|
|
|
@ -6,18 +6,17 @@ export class Counter extends WebComponent {
|
|||
count: 123,
|
||||
};
|
||||
get template() {
|
||||
|
||||
const list = ['a', 'b', 'c', 'what']
|
||||
const list = ["a", "b", "c", "what"];
|
||||
const links = [
|
||||
{
|
||||
url: 'https://ayco.io',
|
||||
text: 'Ayo Ayco'
|
||||
url: "https://ayco.io",
|
||||
text: "Ayo Ayco",
|
||||
},
|
||||
{
|
||||
url: 'https://ayco.io/gh/McFly',
|
||||
text: 'McFly'
|
||||
}
|
||||
]
|
||||
url: "https://ayco.io/gh/McFly",
|
||||
text: "McFly",
|
||||
},
|
||||
];
|
||||
|
||||
return html`
|
||||
<button
|
||||
|
@ -35,14 +34,13 @@ export class Counter extends WebComponent {
|
|||
<label data-my-name="Ayo" for="the-input">Name</label>
|
||||
<input id="the-input" type="foo" value="Name:" />
|
||||
</form>
|
||||
${
|
||||
list.map(item => html`<p>${item}</p>`)
|
||||
}
|
||||
${list.map((item) => html`<p>${item}</p>`)}
|
||||
<h3 about="Elephant">Links</h3>
|
||||
<ul>
|
||||
${
|
||||
links.map(link => html`<li><a href=${link.url} target="_blank">${link.text}</a></li>`)
|
||||
}
|
||||
${links.map(
|
||||
(link) =>
|
||||
html`<li><a href=${link.url} target="_blank">${link.text}</a></li>`,
|
||||
)}
|
||||
</ul>
|
||||
`;
|
||||
}
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
// @ts-check
|
||||
import { WebComponent } from "../../src/WebComponent.js";
|
||||
import { WebComponent, html } from "../../src/index.js";
|
||||
|
||||
export class Counter extends WebComponent {
|
||||
static properties = ["count"];
|
||||
onInit() {
|
||||
this.props.count = 1;
|
||||
let i = 1
|
||||
this.onclick = ()=> ++this.props.count
|
||||
let i = 1;
|
||||
this.onclick = () => ++this.props.count;
|
||||
let double = () => i * 2;
|
||||
console.log(double());
|
||||
i = 3;
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import { WebComponent } from "../../src/WebComponent.js";
|
||||
import { WebComponent } from "../../src/index.js";
|
||||
|
||||
export class HelloWorld extends WebComponent {
|
||||
static properties = ["name"];
|
||||
onInit() {
|
||||
this.props.name = 'a';
|
||||
this.onclick = ()=> this.props.name += 'a'
|
||||
this.props.name = "a";
|
||||
this.onclick = () => (this.props.name += "a");
|
||||
}
|
||||
get template() {
|
||||
return `<button>W${this.props.name}h!</button>`;
|
||||
|
|
|
@ -1,43 +1,43 @@
|
|||
import { WebComponent } from "../../src/WebComponent.js";
|
||||
import { WebComponent } from "../../src/index.js";
|
||||
|
||||
export class ObjectText extends WebComponent {
|
||||
// static properties = ["object"];
|
||||
static props = {
|
||||
object: {
|
||||
hello: 'worldzz',
|
||||
age: 2
|
||||
}
|
||||
}
|
||||
hello: "worldzz",
|
||||
age: 2,
|
||||
},
|
||||
};
|
||||
onChanges() {
|
||||
console.log('>>> object', this.props.object)
|
||||
console.log(">>> object", this.props.object);
|
||||
}
|
||||
get template() {
|
||||
const greeting = document.createElement('textarea')
|
||||
const greeting = document.createElement("textarea");
|
||||
greeting.innerHTML = this.props.object.hello;
|
||||
greeting.setAttribute('id', 'greeting-field');
|
||||
const greetingLabel = document.createElement('label');
|
||||
greetingLabel.setAttribute('for', 'greeting-field');
|
||||
greetingLabel.textContent = 'Hello';
|
||||
greeting.setAttribute("id", "greeting-field");
|
||||
const greetingLabel = document.createElement("label");
|
||||
greetingLabel.setAttribute("for", "greeting-field");
|
||||
greetingLabel.textContent = "Hello";
|
||||
greeting.onkeyup = () => {
|
||||
this.props.object = {
|
||||
...this.props.object,
|
||||
hello: greeting.value
|
||||
hello: greeting.value,
|
||||
};
|
||||
}
|
||||
const ageField = document.createElement('input');
|
||||
};
|
||||
const ageField = document.createElement("input");
|
||||
ageField.value = this.props.object.age;
|
||||
ageField.setAttribute('id', 'age-field');
|
||||
const ageLabel = document.createElement('label')
|
||||
ageLabel.setAttribute('for', 'age-field');
|
||||
ageLabel.textContent = 'Age'
|
||||
ageField.setAttribute("id", "age-field");
|
||||
const ageLabel = document.createElement("label");
|
||||
ageLabel.setAttribute("for", "age-field");
|
||||
ageLabel.textContent = "Age";
|
||||
ageField.onkeyup = () => {
|
||||
this.props.object = {
|
||||
...this.props.object,
|
||||
age: ageField.value
|
||||
}
|
||||
}
|
||||
const form = document.createElement('form');
|
||||
form.append(greetingLabel, greeting, ageLabel, ageField)
|
||||
age: ageField.value,
|
||||
};
|
||||
};
|
||||
const form = document.createElement("form");
|
||||
form.append(greetingLabel, greeting, ageLabel, ageField);
|
||||
return form;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
// @ts-check
|
||||
import { WebComponent } from "../../src/WebComponent.js";
|
||||
import { WebComponent } from "../../src/index.js";
|
||||
|
||||
export class Toggle extends WebComponent {
|
||||
static properties = ["toggle"];
|
||||
onInit() {
|
||||
this.props.toggle = false;
|
||||
this.onclick = ()=>this.handleToggle()
|
||||
this.onclick = () => this.handleToggle();
|
||||
}
|
||||
handleToggle() {
|
||||
this.props.toggle = !this.props.toggle;
|
||||
this.props.toggle = !this.props.toggle;
|
||||
}
|
||||
get template() {
|
||||
return `<button id="toggle">${this.props.toggle ? 'On':'Off'}</button>`;
|
||||
return `<button id="toggle">${this.props.toggle ? "On" : "Off"}</button>`;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
20
package-lock.json
generated
20
package-lock.json
generated
|
@ -1,18 +1,19 @@
|
|||
{
|
||||
"name": "web-component-base",
|
||||
"version": "2.0.0",
|
||||
"version": "2.0.0-beta.21",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "web-component-base",
|
||||
"version": "2.0.0",
|
||||
"version": "2.0.0-beta.21",
|
||||
"license": "MIT",
|
||||
"workspaces": [
|
||||
"site"
|
||||
],
|
||||
"devDependencies": {
|
||||
"@size-limit/preset-small-lib": "^11.0.0",
|
||||
"prettier": "^3.1.1",
|
||||
"typescript": "^5.2.2",
|
||||
"uglify-js": "^3.17.4"
|
||||
}
|
||||
|
@ -3284,6 +3285,21 @@
|
|||
"pathe": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/prettier": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.1.1.tgz",
|
||||
"integrity": "sha512-22UbSzg8luF4UuZtzgiUOfcGM8s4tjBv6dJRT7j275NXsy2jb4aJa4NNveul5x4eqlF1wuhuR2RElK71RvmVaw==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"prettier": "bin/prettier.cjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/prettier/prettier?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/pretty-bytes": {
|
||||
"version": "6.1.1",
|
||||
"resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-6.1.1.tgz",
|
||||
|
|
|
@ -41,7 +41,8 @@
|
|||
"publish:patch:beta": "npm version patch && npm run pub:beta",
|
||||
"publish:minor": "npm version minor && npm run pub",
|
||||
"publish:minor:beta": "npm version minor && npm run pub:beta",
|
||||
"check:size": "npm run build && size-limit ./dist/WebComponent.js"
|
||||
"check:size": "npm run build && size-limit ./dist/WebComponent.js",
|
||||
"pretty": "prettier --write \"./**/*.{js,jsx,mjs,cjs,ts,tsx,json}\""
|
||||
},
|
||||
"repository": "https://github.com/ayoayco/web-component-base",
|
||||
"homepage": "https://WebComponent.io",
|
||||
|
@ -58,6 +59,7 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"@size-limit/preset-small-lib": "^11.0.0",
|
||||
"prettier": "^3.1.1",
|
||||
"typescript": "^5.2.2",
|
||||
"uglify-js": "^3.17.4"
|
||||
},
|
||||
|
|
|
@ -1 +1 @@
|
|||
export default defineNitroConfig({ extends: '@mcflyjs/config' });
|
||||
export default defineNitroConfig({ extends: "@mcflyjs/config" });
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -26,12 +26,12 @@ class CodeBlockComponent extends HTMLElement {
|
|||
margin: "1em 0",
|
||||
fontSize: "large",
|
||||
overflow: "auto",
|
||||
borderRadius: '5px'
|
||||
borderRadius: "5px",
|
||||
};
|
||||
|
||||
if (inline) {
|
||||
style.display = 'inline';
|
||||
style.padding = '0.25em 0.3em';
|
||||
style.display = "inline";
|
||||
style.padding = "0.25em 0.3em";
|
||||
}
|
||||
|
||||
Object.keys(style).forEach((rule) => {
|
||||
|
|
|
@ -9,11 +9,13 @@ class MyHelloWorld extends WebComponent {
|
|||
// Triggered when the component is connected to the DOM
|
||||
onInit() {
|
||||
let count = 0;
|
||||
this.onclick = () => this.props.myName = `Clicked ${++count}x`
|
||||
this.onclick = () => (this.props.myName = `Clicked ${++count}x`);
|
||||
}
|
||||
|
||||
// give readonly template
|
||||
get template() {
|
||||
return `<button style="cursor:pointer">Hello ${this.props.myName ?? 'World'}!</button>`;
|
||||
return `<button style="cursor:pointer">Hello ${
|
||||
this.props.myName ?? "World"
|
||||
}!</button>`;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
class MyQuote extends WebComponent {
|
||||
static properties = ['type'];
|
||||
static properties = ["type"];
|
||||
|
||||
/**
|
||||
* @type {HTMLElement}
|
||||
*/
|
||||
get wrapper() {
|
||||
return this.querySelector('#wrapper');
|
||||
return this.querySelector("#wrapper");
|
||||
}
|
||||
|
||||
afterViewInit() {
|
||||
|
@ -18,13 +18,12 @@ class MyQuote extends WebComponent {
|
|||
margin: "1em 0",
|
||||
fontSize: "large",
|
||||
overflow: "auto",
|
||||
borderRadius: '5px'
|
||||
borderRadius: "5px",
|
||||
};
|
||||
|
||||
Object.keys(style).forEach((rule) => {
|
||||
this.wrapper.style[rule] = style[rule];
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
get template() {
|
||||
|
|
|
@ -5,10 +5,10 @@ class HelloWorld extends HTMLElement {
|
|||
|
||||
connectedCallback() {
|
||||
let count = 0;
|
||||
const currentName = this.getAttribute('my-name');
|
||||
const currentName = this.getAttribute("my-name");
|
||||
|
||||
if (!currentName) {
|
||||
this.setAttribute('my-name', 'World')
|
||||
this.setAttribute("my-name", "World");
|
||||
}
|
||||
|
||||
this.onclick = () => this.setAttribute("my-name", `Clicked ${++count}x`);
|
||||
|
|
|
@ -1,4 +1,10 @@
|
|||
import { createElement, getKebabCase, getCamelCase, serialize, deserialize } from "./utils/index.js";
|
||||
import {
|
||||
createElement,
|
||||
getKebabCase,
|
||||
getCamelCase,
|
||||
serialize,
|
||||
deserialize,
|
||||
} from "./utils/index.js";
|
||||
|
||||
/**
|
||||
* A minimal base class to reduce the complexity of creating reactive custom elements
|
||||
|
@ -69,20 +75,17 @@ export class WebComponent extends HTMLElement {
|
|||
*/
|
||||
onChanges(changes) {}
|
||||
|
||||
constructor() {
|
||||
constructor() {
|
||||
super();
|
||||
this.#initializeProps();
|
||||
}
|
||||
|
||||
static get observedAttributes() {
|
||||
const propKeys = this.props ? Object.keys(this.props).map(camelCase => getKebabCase(camelCase)) : [];
|
||||
const propKeys = this.props
|
||||
? Object.keys(this.props).map((camelCase) => getKebabCase(camelCase))
|
||||
: [];
|
||||
|
||||
return [...(
|
||||
new Set([
|
||||
...this.properties,
|
||||
...propKeys
|
||||
])
|
||||
)]
|
||||
return [...new Set([...this.properties, ...propKeys])];
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
|
@ -135,7 +138,11 @@ export class WebComponent extends HTMLElement {
|
|||
}
|
||||
effectsMap[prop].push(value.callback);
|
||||
} else if (typeMap[prop] !== typeof value) {
|
||||
throw TypeError(`Cannot assign ${typeof value} to ${typeMap[prop]} property (setting '${prop}' of ${meta.constructor.name})`)
|
||||
throw TypeError(
|
||||
`Cannot assign ${typeof value} to ${
|
||||
typeMap[prop]
|
||||
} property (setting '${prop}' of ${meta.constructor.name})`,
|
||||
);
|
||||
} else if (oldValue !== value) {
|
||||
obj[prop] = value;
|
||||
effectsMap[prop]?.forEach((f) => f(value));
|
||||
|
@ -157,19 +164,19 @@ export class WebComponent extends HTMLElement {
|
|||
}
|
||||
|
||||
#initializeProps() {
|
||||
let initialProps = {}
|
||||
if(this.constructor.props) {
|
||||
let initialProps = {};
|
||||
if (this.constructor.props) {
|
||||
initialProps = this.constructor.props;
|
||||
Object.keys(initialProps).forEach(camelCase => {
|
||||
const value = initialProps[camelCase]
|
||||
this.#typeMap[camelCase] = typeof value
|
||||
this.setAttribute(getKebabCase(camelCase), serialize(value))
|
||||
})
|
||||
Object.keys(initialProps).forEach((camelCase) => {
|
||||
const value = initialProps[camelCase];
|
||||
this.#typeMap[camelCase] = typeof value;
|
||||
this.setAttribute(getKebabCase(camelCase), serialize(value));
|
||||
});
|
||||
}
|
||||
if (!this.#props) {
|
||||
this.#props = new Proxy(
|
||||
initialProps,
|
||||
this.#handler((key, value) => this.setAttribute(key, value), this)
|
||||
this.#handler((key, value) => this.setAttribute(key, value), this),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -179,7 +186,7 @@ export class WebComponent extends HTMLElement {
|
|||
if (typeof this.template === "string") {
|
||||
this.innerHTML = this.template;
|
||||
return;
|
||||
} else if (typeof this.template === 'object') {
|
||||
} else if (typeof this.template === "object") {
|
||||
const tree = this.template;
|
||||
|
||||
// TODO: smart diffing
|
||||
|
|
73
src/html.js
73
src/html.js
|
@ -2,10 +2,79 @@
|
|||
* htm -- https://github.com/developit/htm
|
||||
* For license information please see ./vendors/htm/LICENSE.txt
|
||||
*/
|
||||
const htm=(new Map,function(n){for(var e,l,s=arguments,t=1,u="",r="",o=[0],f=function(n){1===t&&(n||(u=u.replace(/^\s*\n\s*|\s*\n\s*$/g,"")))?o.push(n?s[n]:u):3===t&&(n||u)?(o[1]=n?s[n]:u,t=2):2===t&&"..."===u&&n?o[2]=Object.assign(o[2]||{},s[n]):2===t&&u&&!n?(o[2]=o[2]||{})[u]=!0:t>=5&&(5===t?((o[2]=o[2]||{})[l]=n?u?u+s[n]:s[n]:u,t=6):(n||u)&&(o[2][l]+=n?u+s[n]:u)),u=""},i=0;i<n.length;i++){i&&(1===t&&f(),f(i));for(var p=0;p<n[i].length;p++)e=n[i][p],1===t?"<"===e?(f(),o=[o,"",null],t=3):u+=e:4===t?"--"===u&&">"===e?(t=1,u=""):u=e+u[0]:r?e===r?r="":u+=e:'"'===e||"'"===e?r=e:">"===e?(f(),t=1):t&&("="===e?(t=5,l=u,u=""):"/"===e&&(t<5||">"===n[i][p+1])?(f(),3===t&&(o=o[0]),t=o,(o=o[0]).push(this.apply(null,t.slice(1))),t=0):" "===e||"\t"===e||"\n"===e||"\r"===e?(f(),t=2):u+=e),3===t&&"!--"===u&&(t=4,o=o[0])}return f(),o.length>2?o.slice(1):o[1]});
|
||||
const htm =
|
||||
(new Map(),
|
||||
function (n) {
|
||||
for (
|
||||
var e,
|
||||
l,
|
||||
s = arguments,
|
||||
t = 1,
|
||||
u = "",
|
||||
r = "",
|
||||
o = [0],
|
||||
f = function (n) {
|
||||
1 === t && (n || (u = u.replace(/^\s*\n\s*|\s*\n\s*$/g, "")))
|
||||
? o.push(n ? s[n] : u)
|
||||
: 3 === t && (n || u)
|
||||
? ((o[1] = n ? s[n] : u), (t = 2))
|
||||
: 2 === t && "..." === u && n
|
||||
? (o[2] = Object.assign(o[2] || {}, s[n]))
|
||||
: 2 === t && u && !n
|
||||
? ((o[2] = o[2] || {})[u] = !0)
|
||||
: t >= 5 &&
|
||||
(5 === t
|
||||
? (((o[2] = o[2] || {})[l] = n
|
||||
? u
|
||||
? u + s[n]
|
||||
: s[n]
|
||||
: u),
|
||||
(t = 6))
|
||||
: (n || u) && (o[2][l] += n ? u + s[n] : u)),
|
||||
(u = "");
|
||||
},
|
||||
i = 0;
|
||||
i < n.length;
|
||||
i++
|
||||
) {
|
||||
i && (1 === t && f(), f(i));
|
||||
for (var p = 0; p < n[i].length; p++)
|
||||
(e = n[i][p]),
|
||||
1 === t
|
||||
? "<" === e
|
||||
? (f(), (o = [o, "", null]), (t = 3))
|
||||
: (u += e)
|
||||
: 4 === t
|
||||
? "--" === u && ">" === e
|
||||
? ((t = 1), (u = ""))
|
||||
: (u = e + u[0])
|
||||
: r
|
||||
? e === r
|
||||
? (r = "")
|
||||
: (u += e)
|
||||
: '"' === e || "'" === e
|
||||
? (r = e)
|
||||
: ">" === e
|
||||
? (f(), (t = 1))
|
||||
: t &&
|
||||
("=" === e
|
||||
? ((t = 5), (l = u), (u = ""))
|
||||
: "/" === e && (t < 5 || ">" === n[i][p + 1])
|
||||
? (f(),
|
||||
3 === t && (o = o[0]),
|
||||
(t = o),
|
||||
(o = o[0]).push(this.apply(null, t.slice(1))),
|
||||
(t = 0))
|
||||
: " " === e || "\t" === e || "\n" === e || "\r" === e
|
||||
? (f(), (t = 2))
|
||||
: (u += e)),
|
||||
3 === t && "!--" === u && ((t = 4), (o = o[0]));
|
||||
}
|
||||
return f(), o.length > 2 ? o.slice(1) : o[1];
|
||||
});
|
||||
|
||||
function h(type, props, ...children) {
|
||||
return {type, props, children};
|
||||
return { type, props, children };
|
||||
}
|
||||
|
||||
export const html = htm.bind(h);
|
||||
|
|
|
@ -2,7 +2,7 @@ export function createElement(tree) {
|
|||
if (tree.type === undefined) {
|
||||
if (Array.isArray(tree)) {
|
||||
const frag = document.createDocumentFragment();
|
||||
frag.replaceChildren(...tree.map(leaf => createElement(leaf)))
|
||||
frag.replaceChildren(...tree.map((leaf) => createElement(leaf)));
|
||||
return frag;
|
||||
}
|
||||
return document.createTextNode(tree);
|
||||
|
@ -14,7 +14,7 @@ export function createElement(tree) {
|
|||
if (domProp in el) {
|
||||
el[domProp] = tree.props[prop];
|
||||
} else {
|
||||
el.setAttribute(prop, tree.props[prop])
|
||||
el.setAttribute(prop, tree.props[prop]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
export function getKebabCase(str) {
|
||||
return str.replace(
|
||||
/[A-Z]+(?![a-z])|[A-Z]/g,
|
||||
($, ofs) => (ofs ? "-" : "") + $.toLowerCase()
|
||||
($, ofs) => (ofs ? "-" : "") + $.toLowerCase(),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -2,4 +2,4 @@ export { serialize } from "./serialize.mjs";
|
|||
export { deserialize } from "./deserialize.mjs";
|
||||
export { getCamelCase } from "./get-camel-case.mjs";
|
||||
export { getKebabCase } from "./get-kebab-case.mjs";
|
||||
export {createElement} from "./create-element.mjs";
|
||||
export { createElement } from "./create-element.mjs";
|
||||
|
|
|
@ -1,200 +0,0 @@
|
|||
import { createElement } from "./utils/create-element.mjs";
|
||||
import { getCamelCase } from "./utils/get-camel-case.mjs";
|
||||
import { getKebabCase } from "./utils/get-kebab-case.mjs";
|
||||
import { serialize } from "./utils/serialize.mjs";
|
||||
|
||||
/**
|
||||
* A minimal base class to reduce the complexity of creating reactive custom elements
|
||||
* @license MIT <https://opensource.org/licenses/MIT>
|
||||
* @author Ayo Ayco <https://ayo.ayco.io>
|
||||
* @see https://www.npmjs.com/package/web-component-base#readme
|
||||
*/
|
||||
export class WebComponent extends HTMLElement {
|
||||
/**
|
||||
* Array of strings that tells the browsers which attributes will cause a render
|
||||
* @type {Array<string>}
|
||||
*/
|
||||
static properties = [];
|
||||
|
||||
/**
|
||||
* Blueprint for the Proxy props
|
||||
*/
|
||||
static props;
|
||||
|
||||
/**
|
||||
* Read-only string property that represents how the component will be rendered
|
||||
* @returns {string | Node | (string | Node)[]}
|
||||
* @see https://www.npmjs.com/package/web-component-base#template-vs-render
|
||||
*/
|
||||
get template() {
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Read-only property containing camelCase counterparts of observed attributes.
|
||||
* @typedef {{[name: string]: any}} PropStringMap
|
||||
* @see https://www.npmjs.com/package/web-component-base#prop-access
|
||||
* @see https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/dataset
|
||||
* @type {PropStringMap}
|
||||
*/
|
||||
get props() {
|
||||
return this.#props;
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {PropStringMap}
|
||||
*/
|
||||
#props;
|
||||
|
||||
/**
|
||||
* Triggered after view is initialized
|
||||
*/
|
||||
afterViewInit() {}
|
||||
|
||||
/**
|
||||
* Triggered when the component is connected to the DOM
|
||||
*/
|
||||
onInit() {}
|
||||
|
||||
/**
|
||||
* Triggered when the component is disconnected from the DOM
|
||||
*/
|
||||
onDestroy() {}
|
||||
|
||||
/**
|
||||
* Triggered when an attribute value changes
|
||||
* @typedef {{
|
||||
* property: string,
|
||||
* previousValue: any,
|
||||
* currentValue: any
|
||||
* }} Changes
|
||||
* @param {Changes} changes
|
||||
*/
|
||||
onChanges(changes) {}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.#initializeProps();
|
||||
}
|
||||
|
||||
static get observedAttributes() {
|
||||
const propKeys = this.props ? Object.keys(this.props).map(camelCase => getKebabCase(camelCase)) : [];
|
||||
|
||||
return [...(
|
||||
new Set([
|
||||
...this.properties,
|
||||
...propKeys
|
||||
])
|
||||
)]
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
this.onInit();
|
||||
this.render();
|
||||
this.afterViewInit();
|
||||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
this.onDestroy();
|
||||
}
|
||||
|
||||
attributeChangedCallback(property, previousValue, currentValue) {
|
||||
const camelCaps = getCamelCase(property);
|
||||
|
||||
if (previousValue !== currentValue) {
|
||||
this[property] = currentValue === "" || currentValue;
|
||||
this[camelCaps] = this[property];
|
||||
|
||||
this.#handleUpdateProp(camelCaps, this[property]);
|
||||
|
||||
this.render();
|
||||
this.onChanges({ property, previousValue, currentValue });
|
||||
}
|
||||
}
|
||||
|
||||
#handleUpdateProp(key, stringifiedValue) {
|
||||
const restored = deserialize(stringifiedValue, this.#typeMap[key]);
|
||||
if (restored !== this.props[key]) this.props[key] = restored;
|
||||
}
|
||||
|
||||
#typeMap = {};
|
||||
#effectsMap = {};
|
||||
|
||||
#handler(setter, meta) {
|
||||
const effectsMap = meta.#effectsMap;
|
||||
const typeMap = meta.#typeMap;
|
||||
|
||||
return {
|
||||
set(obj, prop, value) {
|
||||
const oldValue = obj[prop];
|
||||
|
||||
if (!(prop in typeMap)) {
|
||||
typeMap[prop] = typeof value;
|
||||
}
|
||||
|
||||
if (value.attach === "effect") {
|
||||
if (!effectsMap[prop]) {
|
||||
effectsMap[prop] = [];
|
||||
}
|
||||
effectsMap[prop].push(value.callback);
|
||||
} else if (typeMap[prop] !== typeof value) {
|
||||
throw TypeError(`Cannot assign ${typeof value} to ${typeMap[prop]} property (setting '${prop}' of ${meta.constructor.name})`)
|
||||
} else if (oldValue !== value) {
|
||||
obj[prop] = value;
|
||||
effectsMap[prop]?.forEach((f) => f(value));
|
||||
const kebab = getKebabCase(prop);
|
||||
setter(kebab, serialize(value));
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
get(obj, prop) {
|
||||
// TODO: handle non-objects
|
||||
if (obj[prop] !== null && obj[prop] !== undefined) {
|
||||
Object.getPrototypeOf(obj[prop]).proxy = meta.#props;
|
||||
Object.getPrototypeOf(obj[prop]).prop = prop;
|
||||
}
|
||||
return obj[prop];
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
#initializeProps() {
|
||||
let initialProps = {}
|
||||
if(this.constructor.props) {
|
||||
initialProps = this.constructor.props;
|
||||
Object.keys(initialProps).forEach(camelCase => {
|
||||
const value = initialProps[camelCase]
|
||||
this.#typeMap[camelCase] = typeof value
|
||||
this.setAttribute(getKebabCase(camelCase), serialize(value))
|
||||
})
|
||||
}
|
||||
if (!this.#props) {
|
||||
this.#props = new Proxy(
|
||||
initialProps,
|
||||
this.#handler((key, value) => this.setAttribute(key, value), this)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#prevDOM;
|
||||
render() {
|
||||
if (typeof this.template === "string") {
|
||||
this.innerHTML = this.template;
|
||||
return;
|
||||
} else if (typeof this.template === 'object') {
|
||||
const tree = this.template;
|
||||
|
||||
// TODO: smart diffing
|
||||
if (JSON.stringify(this.#prevDOM) !== JSON.stringify(tree)) {
|
||||
// render
|
||||
const el = createElement(tree);
|
||||
if (el) {
|
||||
if (Array.isArray(el)) this.replaceChildren(...el);
|
||||
else this.replaceChildren(el);
|
||||
}
|
||||
this.#prevDOM = tree;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
{
|
||||
"compilerOptions": {
|
||||
"allowJs": true,
|
||||
|
@ -6,8 +5,5 @@
|
|||
"declaration": true,
|
||||
"emitDeclarationOnly": true
|
||||
},
|
||||
"include": [
|
||||
"src/*",
|
||||
"src/utils/*"
|
||||
],
|
||||
"include": ["src/*", "src/utils/*"]
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue