榆树 - textarea选择范围消失

时间:2017-06-21 01:20:01

标签: textarea selection keyboard-events elm

我在Elm中实现了<textarea>,使得制表符缩进和取消,而不是将焦点更改为另一个HTML元素。工作很好,除了unindenting有时会导致选择消失!如果我选择第5个字符到第12个字符,我按 shift - tab ,然后删除2个制表符,但它也会使选择更改为光标在第10位。选择范围应保持不变..

我在Ellie有一个SSCCE:https://ellie-app.com/3x2qQdLqpHga1/2

以下是一些说明问题的屏幕截图。按设置显示:

Text setup

然后按 Unindent 显示以下内容(选择“def \ ng”仍然完好无损):

Unindented with selection still intact

不幸的是,按 Unindent 实际会显示以下内容。文本没有缩进,但选择范围消失了,gh之间只有一个光标:

Unindented without selection

1 个答案:

答案 0 :(得分:0)

有趣的问题和出色的问题插图!

问题在于,当selectionStart / selectionEnd属性的一个保持不变时,由于某种原因,不会进行重新渲染。尝试将第42行的5更改为6。

当您在元素结构中引入强制重排时,它可以工作。参见此处:https://ellie-app.com/6Q7h7Lm9XRya1(我将其更新为0.19,以查看是否可以解决问题,但没有解决)。

请注意,这可能会重新呈现整个文本区域,因此如果文本区域是一大段代码,则可能会引起问题。您可以通过在两个相同的文本区域之间切换来解决此问题,在每个文本区域中切换每个渲染区域的可见性。

module Main exposing (Model, Msg(..), main, update, view)

-- Note: this is Elm 0.19

import Browser
import Browser.Dom exposing (focus)
import Html exposing (Html, button, div, text, textarea)
import Html.Attributes exposing (attribute, class, cols, id, property, rows, style, value)
import Html.Events exposing (onClick)
import Html.Lazy exposing (lazy2)
import Json.Encode as Encode
import Task exposing (attempt)


type alias Model =
    { content : String
    , selectionStart : Int
    , selectionEnd : Int
    -- keep counter of renderings for purposes of randomness in rendering loop
    , renderCounter : Int
    }


main =
    Browser.element
        { init = initModel
        , view = view
        , update = update
        , subscriptions = \s -> Sub.none
        }


initModel : () -> ( Model, Cmd Msg )
initModel flags =
    ( Model "" 0 0 0, Cmd.batch [] )


type Msg
    = Setup
    | Unindent
    | NoOp (Result Browser.Dom.Error ())


update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
    let
        newRenderCounter =
            model.renderCounter + 1

    in
    case msg of
        Setup ->
            ( { model
                | content = "\tabc\n\tdef\n\tghi"
                , selectionStart = 5
                , selectionEnd = 12
                , renderCounter = newRenderCounter
              }
            , attempt NoOp <| focus "ta"
            )

        Unindent ->
            ( { model
                | content = "\tabc\ndef\nghi"
                , selectionStart = 5
                , selectionEnd = 10
                , renderCounter = newRenderCounter
              }
            , attempt NoOp <| focus "ta"
            )

        NoOp _ ->
            ( model, Cmd.batch [] )


view : Model -> Html Msg
view model =
    div []
        (viewTextarea model model.renderCounter
            ++ [ button [ onClick Setup ] [ text "Setup" ]
               , button [ onClick Unindent ] [ text "Unindent" ]
               ]
        )


viewTextarea : Model -> Int -> List (Html msg)
viewTextarea model counter =
    let

        rerenderForcer =
            div [attribute "style" "display: none;"] []

        ta =
            textarea
                [ id "ta"
                , cols 40
                , rows 20
                , value model.content
                , property "selectionStart" <| Encode.int model.selectionStart
                , property "selectionEnd" <| Encode.int model.selectionEnd
                ]
                []
    in

    -- this is the clue. by alternating this every render, it seems to force Elm to render the textarea anew, fixing the issue. Probably not very performant though. For a performant version, use an identical textarea instead of the div and make sure the two selectionStart/end properties both differ from the previous iteration. Then alternate visibility between the two every iteration.
    if isEven counter then
        [ ta, rerenderForcer ]

    else
        [ rerenderForcer, ta ]


isEven : Int -> Bool
isEven i =
    modBy 2 i == 0