Elmでcontenteditableな要素を使いたかったのですが、いろいろ問題があって難しいようです。
途中までがんばった結果
今のところ以下のようになっているのですが、それに至るまでの流れを書いておこうと思います。
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が呼ばれません。
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は更新されません。
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
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が壊れる問題を回避しました。
ここまでが冒頭に出した結果になります。
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
といったようにElmでcontenteditableを使うのは大変なようです。気が向いたら続きをやるかもしれません。