lit-element でリスナはコンポーネント内メソッドの方がいい?
子コンポーネントから親コンポーネントのメソッドを呼び出すために関数をプロパティで渡す時
子コンポーネント側で変換やチェック処理がいらないなら 親から受け取ったのをそのままリスナに設定してる
こういう感じ

import { LitElement, html, css } from "https://unpkg.com/lit-element@2.4.0/lit-element.js?module"

customElements.define("elem-1", class extends LitElement {
onClickButton = () => {
console.log("CLICKED")
}

render() {
return html`
<elem-2 .onClickButton=${this.onClickButton}></elem-2>
`
}
})

customElements.define("elem-2", class extends LitElement {
static properties = {
onClickButton: { type: Function },
}

render() {
return html`
<button @click=${this.onClickButton}>button</button>
`
}
})

document.body.append(document.createElement("elem-1"))

最初に受け取ったときや変更されたときに render 処理が必要だから properties で定義が必要

リスナ用の受け取る関数の変更で rerender をさせたくないので リスナにはコンポーネント内のメソッドを設定するといいのかも
受け取る関数は properties 定義してないただのプロパティとして受け取る

import { LitElement, html, css } from "https://unpkg.com/lit-element@2.4.0/lit-element.js?module"

customElements.define("elem-1", class extends LitElement {
onClickButton = () => {
console.log("CLICKED")
}

render() {
return html`
<elem-2 .onClickButton=${this.onClickButton}></elem-2>
`
}
})

customElements.define("elem-2", class extends LitElement {
_onClickButton = (eve) => {
this.onClickButton(eve)
}

render() {
return html`
<button @click=${this._onClickButton}>button</button>
`
}
})

document.body.append(document.createElement("elem-1"))

properties に定義いらないし rerender を減らせる
だけど lit-element だと React の関数コンポーネントと違って毎回リスナ関数を作り直さない
親側で動的に関数を作ったりしてない限りは変更がないので 初回以外に rerender が発生しないことが多そう

個人的には properties は減らしたいけど どっちもどっちなのかも
1 つの機能だけのツールだと全部のデータを最上位コンポーネントで管理することになる
ページが 1 つで機能も 1 つだけだとコンポーネントに分けても結局データは全部最上位になる
あるデータを管理(ローカルに登録して表示と編集)するツール

機能を分けて 3 つのコンポーネント

● ルートコンポーネント
  (1) リスト表示
  (2) 選択中の項目のデータを編集
  (3) リストの全部を専用ライブラリでグラフィカル表示・選択中はマウス操作で編集可能

編集中データなどは (2) の編集用コンポーネントだけで持ちたいけど (3) のコンポーネントでも編集できる
ルートコンポーネントで編集中も持つと 保存ボタンやリセットボタンが (2) にあるけど 押したことをルートコンポーネントに伝えるだけ
ルートコンポーネントでデータのチェックや保存などが必要
ほとんどがルートコンポーネントの処理になって複雑になってくる
一応ページ全部で 1000 行は超える程度のコード量なので そのほとんどをルートコンポーネントに押し込むのはなんかイヤ

それに (1) ~ (3) はほとんどルートコンポーネントから受け取ったデータの表示と押されたボタンを親に伝えるだけになってる
WebComponents らしい機能は持ってないし WebComponents (lit-element) で書くと長くなるだけ
こういうタイプなら React のほうが楽にかけそう
WSL2 が起動できないときの対処方法
WSL2 を起動しようとすると

Windows の仮想マシン プラットフォーム機能を有効にして、BIOS で仮想化が有効になっていることを確認してください。

というエラーが表示されるとき

参考の URL などのチュートリアルに従って Windows の機能の有効化や dism.exe を使って WSL や仮想マシンプラットフォームを有効に設定済み
◯ タスクマネージャのパフォーマンスタブの CPU のパネルでは 「仮想化: 有効」 になってる
◯ WSL1 は動いてる

なのに WSL2 が起動できない という状況なら bcdedit コマンドが必要かも
管理者権限で

bcdedit

を実行して出てくるリストに

hypervisorlaunchtype    Off

があると 実行必要
↓のコマンドを実行して Off から Auto にしないといけない

bcdedit /set hypervisorlaunchtype auto
HTML を使って CustomElement2
前回の続き
前は JavaScript だけ扱いを特別にしたけど HTML ファイル中にあったほうがいいかなと思ったので HTML ファイルに全部書く

使う側は一緒で foo-bar.js を読み込んで foo-bar タグを書いておく

[page.html]
<script type="module" src="foo-bar.js"></script>

<foo-bar></foo-bar>

[foo-bar.js]
import { importComponent } from "./base.js"

importComponent("foo-bar.html").then(component => {
customElements.define("foo-bar", component)
})

コンポーネントを import して customElements.define で定義

[base.js]
export const importComponent = async (htmlpath) => {
const res = await fetch(htmlpath)
const html = await res.text()
const doc = new DOMParser().parseFromString(html, "text/html")
const fragment = document.createDocumentFragment()
const scripts = [...doc.querySelectorAll("script")]
scripts.forEach(x => x.remove())
fragment.append(...doc.head.childNodes, ...doc.body.childNodes)

const module = {}
class Base extends HTMLElement {
constructor() {
super()
this.attachShadow({ mode: "open" })
this.shadowRoot.append(this.constructor.fragment.cloneNode(true))
this.elements = Object.fromEntries(
Array.from(
this.shadowRoot.querySelectorAll("[id]"),
elem => [elem.id, elem]
)
)
}
}
Base.fragment = fragment

for(const script of scripts) {
const source = script.textContent
const fn = Function("module", "Base", source)
fn(module, Base)
}

return module.exports
}

importComponent は HTML を fetch してパースしていろいろ
script を含んだコンポーネントの HTML

[foo-bar.html]
<style>
.foo { font-size: 20px; }
.bar { color: red; }
.active { font-weight: bold; }
</style>

<div>
<div class="foo">FOO</div>
<div class="bar">BAR</div>
<button id="btn">Button</button>
</div>

<script>

module.exports = class extends Base {
connectedCallback() {
this.elements.btn.addEventListener("click", (eve) => {
eve.target.classList.toggle("active")
})
}
}

</script>

script タグ中のエクスポートは Node.js 形式で module.exports に代入
Base を継承したクラスをエクスポートする
HTML を使って CustomElement
ふと HTMLImports を思い出したのでこんなことしてみた

[base.js]
const initCustomElement = async (template) => {
const res = await fetch(template)
const html = await res.text()
const doc = new DOMParser().parseFromString(html, "text/html")
const fragment = document.createDocumentFragment()
fragment.append(...doc.head.childNodes, ...doc.body.childNodes)
return fragment
}

export class Base extends HTMLElement {
constructor() {
super()
const C = this.constructor
if (!C.ready) {
C.ready = initCustomElement(C.template)
}

this.attachShadow({ mode: "open" })
C.ready.then((fragment) => {
this.shadowRoot.append(fragment.cloneNode(true))
})
}
}

[foo-bar.js]
import { Base } from "./base.js"

customElements.define("foo-bar", class extends Base {
static template = "foo-bar.html"
})

[foo-bar.html]
<style>
.foo { font-size: 20px; }
.bar { color: red; }
</style>

<div>
<div class="foo">FOO</div>
<div class="bar">BAR</div>
</div>

innerHTML やスタイルは別の HTML ファイルに書く
script タグは HTML に含めず import される JavaScript ファイル側に書いてる
CustomElement の constructor の処理で HTML を fetch して ShadowDOM に append

表示するためのページ

[page.html]
<script type="module" src="foo-bar.js"></script>

<foo-bar></foo-bar>