Melt UIの設計をみてみる

## Melt UI
みなさん、Melt UIというSvelteのヘッドレスUIライブラリをご存知でしょうか？

Melt Introductionページで使い方を見てみましょう〜！

## 使い方
https://www.melt-ui.com/docs/introduction

```svelte
<script>
  import { createCollapsible, melt } from '@melt-ui/svelte'
  const {
    elements: { root, content, trigger },
    states: { open }
  } = createCollapsible()
</script>
 
<div use:melt={$root}>
  <button use:melt={$trigger}>{$open ? 'Close' : 'Open'}</button>
  <div use:melt={$content}>Obi-Wan says: Hello there!</div>
</div>
```

こんな感じで使うみたいです。シンプルですね。
`createCollapsible`と`use:melt={$xxx}`を見たところ、どうやら`elements`と`states`からSvelteStoreを返しているようですね。

## 何を提供してくれるのか
`Collapsible`という名前の関数なのでおそらくアコーディオン（ディスクロージャー）のような機能を提供してくれるのでしょう。

アコーディオンは[W3Cのアクセシビリティに配慮した実装方法がまとまっているページ](https://www.w3.org/WAI/ARIA/apg/patterns/accordion/)をみるとキーボード操作や`role`の付与など、いくらか実装が必要なようです。

Melt UIはelementsの`trigger`や`content`の部分がHTML属性の付与やキーボード操作機能を提供してくれます。

:::note warn
完全ではありません。注意。
:::

特に気にしなくてもアクセシビリティがある程度担保されるのは嬉しいポイントです。

コンポーネント化するとコンポーネントから`export let`していない属性や`on:event`していないイベントが受け取れないので自由度が下がってしまいます。
Melt UIはヘッドレスなUIライブラリなのでそこが解決されていてとてもいいですね。マークアップが自由になるのもメリットです。

## Svelte Actions
さて、[SvelteのActions](https://svelte.dev/docs/svelte-action)をご存知でしょうか？

SvelteのActionsは第一引数にnodeを持つ関数で、以下のように使用します。
```svelte
<div use:actionName>
```

以下がMelt UIのコードです。
似ていますね。
```svelte
<div use:melt={$root}>
```

SvelteのActionsは以下のようにしてactionName関数に値を渡すことができ、Melt UIの記述もそれに似ています。
```svelte
<div use:actionName={props}>
```

さて、SvelteのActionsには`use:`した要素にActionsから`return`したオブジェクトを展開してくれる機能はありません。

でも例えばアコーディオンを見てみると、
```svelte
<button use:melt={$trigger(id)}>
```
が
```svelte
<button aria-expanded="true">
```
としてレンダリングされます。
onMount時で属性が付与されているのか？と思いソースを見ると属性がすでに付いています。

SvelteのActionsにはそんな機能はありません。
これをしようと思うと、
```svelte
<button use:action {...$trigger(id)}>
```
こんな感じでuseでのアクションの指定とHTMLに付与する属性を展開する部分の二つが必要なはずです。
これはどういうことでしょうか？

## Preprocessor
これはMelt UIが提供している[Preprocessor](https://www.melt-ui.com/docs/preprocessor)に秘密があります。

How it worksを読んでみるとどうやら、
`use:melt={$root}`
を
`{...$root} use:$root.action`
に変換しているみたいです。

これは賢いですね…

## メリット
それではMelt UIのこの実装のメリットについて考えてみましょう。

まず、先ほど説明したように
```svelte
<button use:action {...$trigger(id)}>
```

このように書く手間が省けており、手を動かす量が減っています。
コードは少なければ少ないほどバグも少なくなりますし、`{...$trigger(id)}`し忘れて必要な属性が付与されないミスも考えられます。

`use:melt={$root}`にまとまっていることは大きなメリットでしょう。

### Actionsの第一引数のnodeの型
また、SvelteのActionsの第一引数はHTMLInputElementなど型を絞ることができます。

Melt UIは特に絞っていないようですが、例えば
```ts
const action: Action<HTMLElement & { disabled: boolean | null }> = (node) => ...
```
のようにすれば`use:action`する要素を[`disabled`属性が許可されている要素](https://developer.mozilla.org/ja/docs/Web/HTML/Attributes#:~:text=%2C%20%3Ctextarea%3E-,disabled,-%3Cbutton%3E%2C)だけに絞ることができます。

```svelte
<button use:action {...$trigger(id)}>
```
Preprocessorを使用しない場合`{...$trigger(id)}`の部分はどの要素にも使用できてしまいます。

ヘッドレスUIライブラリは必要な属性をスプレッド構文で展開する都合上、本来渡せない属性まで渡せてしまってエラーも出ず、そのことに気づけない欠点があります。

SvelteのActionsはnodeの型を絞ることができるのでactionとpropsの展開が一つでできるならそれはかなり嬉しいです。

## デメリット（というか懸念点）
`use:melt={}`でPreprocessorを通すことをどう思うかです。

Melt UIもActionsも知らない人が`use:melt`を見た時にまずSvelteのドキュメントを見に行くでしょう。Svelteは人口が少ないので後から入ってきたメンバーがこれを知らない可能性は十分あります。

なるほどActionsはSvelteの機能なのね、あれ、でも実はPreprocessorで処理されている独自の書き方？というように混乱してしまわないでしょうか？

Svelteの構文に似ている独自の書き方を持ち込むのは僕は好みません。
Svelteが公式にActionsとpropsの展開を同時に行う実装を用意して欲しいなと思っています。

Svelte5では`on:click`などの書き方が`onclick`を単に渡す実装に変更されますが、`use:`はどうなるのか気になっています。
`use:`では大体イベントリスナーを張ることがほとんどなので、Melt UIは`{...$trigger(id)}`から`onclick`などを返すようになって`use:`部分がなくなるのかな？と予想していますが、スプレッド構文での展開は不必要なpropsを渡してしまうことを防げない問題があるのでどうなるのか気になっています。

## 感想
デメリットの部分は気になっていますが、`createHoge`関数からリアクティブな値を持つオブジェクトとActionsを返す実装はコンポーネントを作成するよりもいいなと感じています。

この設計は真似したいですね。