Elmでcontenteditableを使いたかった

Elmでcontenteditableな要素を使いたかったのですが、いろいろ問題があって難しいようです。
[途中までがんばった結果](https://github.com/maron8676/elm-contenteditable-sample.git)
今のところ以下のようになっているのですが、それに至るまでの流れを書いておこうと思います。

```Elm
module Main exposing (main)

import Browser
import Html exposing (Html, div, label, text)
import Html.Attributes exposing (contenteditable, value)
import Html.Events exposing (keyCode, on, preventDefaultOn)
import Json.Decode as JD


main : Platform.Program () Model Msg
main =
    Browser.sandbox
        { init = init
        , view = view
        , update = update
        }


type alias Model =
    { text : String
    }


type Msg
    = UpdateText String
    | NoOp


init =
    Model "init"


view : Model -> Html Msg
view model =
    div []
        [ div
            [ contenteditable True
            , preventDefaultOn "keydown"
                (JD.map (\x -> ( NoOp, isEnterCode x )) <| keyCode)
            , on "blur" <|
                JD.map UpdateText
                    (JD.at [ "target", "textContent" ] JD.string)
            ]
            [ text model.text ]
        , label [] [ text <| "edited: " ++ model.text ]
        ]


update : Msg -> Model -> Model
update msg model =
    case msg of
        UpdateText newText ->
            { model | text = newText }

        NoOp ->
            model


isEnterCode : Int -> Bool
isEnterCode code =
    let
        enterCode =
            13
    in
    code == enterCode

```

# 1. onInputではevent.target.valueを参照しているため（？）失敗した
一番最初に書いたのが以下のようなコードでした。event.target.valueが定義されていないためか、そもそもUpdateTextが呼ばれません。

```Elm
view model =
    div []
        [ div [ contenteditable True, onInput UpdateText ] [ text model.text ]
        , label [] [ text <| "edited: " ++ model.text ]
        ]
```

# 2. onInputではだめだった
event.target.valueがあればいいのかと思って以下のようにしました。UpdateTextは呼ばれましたが、画面で編集しているのはevent.target.valueではなくtextContentなのでmodelは更新されません。

```Elm
view : Model -> Html Msg
view model =
    div []
        [ div [ contenteditable True, onInput UpdateText, value model.text ] [ text model.text ]
        , label [] [ text <| "edited: " ++ model.text ]
        ]
```

# 3. カーソルが勝手に移動する&TEAが壊れる
textContentを使ってUpdateTextが呼ばれればいいはずなので自前でMsgを作りました。
modelは期待通り更新されましたが、更新時にカーソルが要素の先頭に移動してしまいます。
また、文面では伝えづらいですが改行を入力するとTEAが壊れることに気づきました。[^1]

[^1]: 改行入力時にブラウザのデフォルト動作でタグが追加されてしまうため、VDOMと整合性が取れなくなったものと思われます。

```Elm
view model =
    div []
        [ div
            [ contenteditable True
            , on "input" <|
                JD.map UpdateText
                    (JD.at [ "target", "textContent" ] JD.string)
            ]
            [ text model.text ]
        , label [] [ text <| "edited: " ++ model.text ]
        ]
```

# 4. リアルタイム更新を妥協した
inputでの更新を諦めてblurイベントを取るようにし、カーソルが移動する問題を回避しました。
Enterキーを検知してpreventDefaultすることで、TEAが壊れる問題を回避しました。
ここまでが冒頭に出した結果になります。

```Elm
view model =
    div []
        [ div
            [ contenteditable True
            , preventDefaultOn "keydown"
                (JD.map (\x -> ( NoOp, isEnterCode x )) <| keyCode)
            , on "blur" <|
                JD.map UpdateText
                    (JD.at [ "target", "textContent" ] JD.string)
            ]
            [ text model.text ]
        , label [] [ text <| "edited: " ++ model.text ]
        ]
```

# 5. まだ問題がある
大体解決したように見えますが、改行を含む文字列をcontenteditableな要素に貼り付けると、やはりTEAが壊れます。
対策するとしたらpasteイベントを検知して事前に除外するくらいでしょうか…[^2]

[^2]: ドラッグ＆ドロップとかでも問題起きそうですね

といったようにElmでcontenteditableを使うのは大変なようです。気が向いたら続きをやるかもしれません。