at.*_*at. 5 textarea selection keyboard-events elm
我<textarea>在Elm中实现了这样一个功能,即制表符可以缩进和缩进,而不是将焦点更改为另一个HTML元素。效果很好,除了缩进有时会导致选择消失!如果要选择第5个字符到第12个字符,请按shift- tab,这将删除2个制表符,但同时也会使选择变为位置10的光标。选择范围应保持不变。
我在Ellie拥有SSCCE:https ://ellie-app.com/3x2qQdLqpHga1/2
这里是一些截图来说明问题。按安装程序将显示以下内容:
然后按Unindent 应该显示以下内容(“ def \ ng”的选择仍保持不变):
不幸的是,按Unindent 实际上显示以下内容。文字是没有锯齿的罚款,但选择范围消失而且也只是在之间的光标g和h:
有趣的问题和出色的问题说明!
问题是,由于某种原因,当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
Run Code Online (Sandbox Code Playgroud)