FEATURE: Add withSelectOnTab

This commit is contained in:
Tom Nunn 2022-11-23 16:00:30 +00:00
parent 144527ed0c
commit 34f576fb3e
6 changed files with 64 additions and 38 deletions

View File

@ -21,7 +21,6 @@ import NoDebug.TodoOrToString
import NoExposingEverything
import NoImportingEverything
import NoMissingTypeAnnotation
import NoMissingTypeAnnotationInLetIn
import NoPrematureLetComputation
import NoSimpleLetBody
import NoUnused.CustomTypeConstructorArgs
@ -51,7 +50,6 @@ config =
, NoExposingEverything.rule
, NoImportingEverything.rule []
, NoMissingTypeAnnotation.rule
, NoMissingTypeAnnotationInLetIn.rule
, NoSimpleLetBody.rule
, NoPrematureLetComputation.rule
, NoUnused.CustomTypeConstructors.rule []

View File

@ -56,7 +56,7 @@ type alias InternalState a =
, items : List a
, selected : Maybe a
, inputValue : String
, highlighted : Int
, highlighted : Maybe Int
, menuOpen : Bool
, containerElement : Maybe Dom.Element
, menuViewPort : Maybe Dom.Viewport
@ -77,7 +77,7 @@ init id =
, items = []
, selected = Nothing
, inputValue = ""
, highlighted = 0
, highlighted = Nothing
, menuOpen = False
, containerElement = Nothing
, menuViewPort = Nothing
@ -111,7 +111,7 @@ toRequestState (Model { requestState }) =
requestState
toHighlighted : Model a -> Int
toHighlighted : Model a -> Maybe Int
toHighlighted (Model { highlighted }) =
highlighted
@ -149,10 +149,10 @@ toOptionElementId (Model { id }) idx =
toOptionState : Model a -> ( Int, a ) -> OptionState
toOptionState (Model { highlighted, selected }) ( idx, a ) =
if highlighted == idx && selected == Just a then
if highlighted == Just idx && selected == Just a then
SelectedAndHighlighted
else if highlighted == idx then
else if highlighted == Just idx then
Highlighted
else if selected == Just a then
@ -240,7 +240,7 @@ selectOption opt (Model model) =
}
highlightIndex : Int -> Model a -> Model a
highlightIndex : Maybe Int -> Model a -> Model a
highlightIndex idx (Model model) =
Model { model | highlighted = idx }
@ -260,7 +260,7 @@ closeMenu (Model model) =
Model
{ model
| menuOpen = False
, highlighted = 0
, highlighted = Nothing
}
@ -270,7 +270,7 @@ clear (Model model) =
{ model
| inputValue = ""
, selected = Nothing
, highlighted = 0
, highlighted = Nothing
, applyFilter = False
, menuOpen = False
}

View File

@ -15,7 +15,7 @@ type Msg a
}
(List (Option a))
| MouseEnteredOption Int
| KeyDown (List (Option a)) String
| KeyDown Bool (List (Option a)) String
| GotContainerAndMenuElements (Result Dom.Error { menu : Dom.Viewport, container : Dom.Element })
| ClearButtonPressed
| InputDebounceReturned String

View File

@ -33,7 +33,13 @@ update_ { request, requestMinInputLength, debounceRequest } tagger msg model =
InputChanged val ->
( model
|> Model.setInputValue val
|> Model.highlightIndex 0
|> Model.highlightIndex
(if String.isEmpty val then
Nothing
else
Just 0
)
|> Model.applyFilter True
|> Model.setSelected Nothing
|> Model.setItems
@ -79,12 +85,12 @@ update_ { request, requestMinInputLength, debounceRequest } tagger msg model =
)
MouseEnteredOption i ->
( Model.highlightIndex i model
( Model.highlightIndex (Just i) model
, Effect.none
)
KeyDown filteredOptions key ->
handleKey tagger model key filteredOptions
KeyDown selectOnTab filteredOptions key ->
handleKey selectOnTab tagger model key filteredOptions
GotContainerAndMenuElements result ->
( model
@ -148,7 +154,7 @@ update_ { request, requestMinInputLength, debounceRequest } tagger msg model =
onFocusMenu : (Msg a -> msg) -> Bool -> Model a -> ( Model a, Effect effect msg )
onFocusMenu tagger hasRequest model =
( Model.setFocused True model
|> Model.highlightIndex 0
|> Model.highlightIndex Nothing
, if not hasRequest || Model.toRequestState model == Just Success then
Effect.batch
[ Effect.ScrollMenuToTop (tagger NoOp) (Model.toMenuElementId model)
@ -160,32 +166,43 @@ onFocusMenu tagger hasRequest model =
)
handleKey : (Msg a -> msg) -> Model a -> String -> List (Option a) -> ( Model a, Effect effect msg )
handleKey tagger model key filteredOptions =
case key of
"ArrowDown" ->
moveHighlight tagger (Basics.min (List.length filteredOptions - 1) (Model.toHighlighted model + 1)) model
"ArrowUp" ->
moveHighlight tagger (Basics.max 0 (Model.toHighlighted model - 1)) model
"PageDown" ->
moveHighlight tagger (Basics.min (List.length filteredOptions - 1) (Model.toHighlighted model + 10)) model
"PageUp" ->
moveHighlight tagger (Basics.max 0 (Model.toHighlighted model - 10)) model
"Enter" ->
case getAt (Model.toHighlighted model) filteredOptions of
handleKey : Bool -> (Msg a -> msg) -> Model a -> String -> List (Option a) -> ( Model a, Effect effect msg )
handleKey selectOnTab tagger model key filteredOptions =
let
selectHighlighted =
case Model.toHighlighted model |> Maybe.andThen (\idx -> getAt idx filteredOptions) of
Just opt ->
( Model.selectOption opt model, Effect.none )
Nothing ->
( Model.closeMenu model, Effect.none )
in
case key of
"ArrowDown" ->
moveHighlight tagger (Basics.min (List.length filteredOptions - 1) (Maybe.withDefault -1 (Model.toHighlighted model) + 1)) model
"ArrowUp" ->
moveHighlight tagger (Basics.max 0 (Maybe.withDefault 0 (Model.toHighlighted model) - 1)) model
"PageDown" ->
moveHighlight tagger (Basics.min (List.length filteredOptions - 1) (Maybe.withDefault -1 (Model.toHighlighted model) + 10)) model
"PageUp" ->
moveHighlight tagger (Basics.max 0 (Maybe.withDefault 0 (Model.toHighlighted model) - 10)) model
"Enter" ->
selectHighlighted
"Escape" ->
( Model.closeMenu model, Effect.none )
"Tab" ->
if selectOnTab then
selectHighlighted
else
( model, Effect.none )
_ ->
( model, Effect.none )
@ -193,7 +210,7 @@ handleKey tagger model key filteredOptions =
moveHighlight : (Msg a -> msg) -> Int -> Model a -> ( Model a, Effect effect msg )
moveHighlight tagger newHighlighted model =
if Model.isOpen model then
( Model.highlightIndex newHighlighted model
( Model.highlightIndex (Just newHighlighted) model
, Effect.batch
[ getContainerAndMenuElementsEffect tagger model
, Effect.GetElementsAndScrollMenu

View File

@ -41,6 +41,7 @@ type alias ViewConfigInternal a msg =
, positionFixed : Bool
, clearInputValueOnBlur : Bool
, selectExactMatchOnBlur : Bool
, selectOnTab : Bool
}
@ -70,6 +71,7 @@ view attribs v =
, positionFixed = False
, clearInputValueOnBlur = False
, selectExactMatchOnBlur = False
, selectOnTab = True
}
@ -146,7 +148,7 @@ inputView filteredOptions model config =
filteredOptions
)
)
, onKeyDown (Model.isOpen model) (KeyDown filteredOptions >> config.onChange)
, onKeyDown (Model.isOpen model) (KeyDown config.selectOnTab filteredOptions >> config.onChange)
, Element.htmlAttribute (Html.Attributes.id <| Model.toInputElementId model)
, Element.inFront <|
if Model.toValue model /= Nothing || Model.toInputValue model /= "" then
@ -177,7 +179,9 @@ inputAccessibilityAttributes filteredOptions model =
, htmlAttribute "aria-autocomplete" "list"
, htmlAttribute "aria-activedescendant" <|
if Model.isOpen model then
Model.toOptionElementId model (Model.toHighlighted model)
Model.toHighlighted model
|> Maybe.map (Model.toOptionElementId model)
|> Maybe.withDefault ""
else
""

View File

@ -5,7 +5,7 @@ module Select exposing
, isMenuOpen, isLoading, isRequestFailed, isFocused
, Msg, update, updateWith
, UpdateOption, request, requestMinInputLength, requestDebounceDelay, onSelectedChange, gotRequestResponse
, ViewConfig, view, withMenuAttributes, MenuPlacement(..), withMenuMaxHeight, withMenuMaxWidth, withNoMatchElement, withOptionElement, OptionState, withClearButton, ClearButton, clearButton, withFilter, withMenuAlwaysAbove, withMenuAlwaysBelow, withMenuPlacementAuto, withMenuPositionFixed, withClearInputValueOnBlur, withSelectExactMatchOnBlur
, ViewConfig, view, withMenuAttributes, MenuPlacement(..), withMenuMaxHeight, withMenuMaxWidth, withNoMatchElement, withOptionElement, OptionState, withClearButton, ClearButton, clearButton, withFilter, withMenuAlwaysAbove, withMenuAlwaysBelow, withMenuPlacementAuto, withMenuPositionFixed, withClearInputValueOnBlur, withSelectExactMatchOnBlur, withSelectOnTab
, toElement
, Effect
)
@ -45,7 +45,7 @@ module Select exposing
# Configure View
@docs ViewConfig, view, withMenuAttributes, MenuPlacement, withMenuMaxHeight, withMenuMaxWidth, withNoMatchElement, withOptionElement, OptionState, withClearButton, ClearButton, clearButton, withFilter, withMenuAlwaysAbove, withMenuAlwaysBelow, withMenuPlacementAuto, withMenuPositionFixed, withClearInputValueOnBlur, withSelectExactMatchOnBlur
@docs ViewConfig, view, withMenuAttributes, MenuPlacement, withMenuMaxHeight, withMenuMaxWidth, withNoMatchElement, withOptionElement, OptionState, withClearButton, ClearButton, clearButton, withFilter, withMenuAlwaysAbove, withMenuAlwaysBelow, withMenuPlacementAuto, withMenuPositionFixed, withClearInputValueOnBlur, withSelectExactMatchOnBlur, withSelectOnTab
# Element
@ -545,6 +545,13 @@ withSelectExactMatchOnBlur v (ViewConfig config) =
ViewConfig { config | selectExactMatchOnBlur = v }
{-| Should we select the highlighted option when the TAB key is pressed?
-}
withSelectOnTab : Bool -> ViewConfig a msg -> ViewConfig a msg
withSelectOnTab v (ViewConfig config) =
ViewConfig { config | selectOnTab = v }
{-| Turn the ViewConfig into an Element.
-}
toElement : Select a -> ViewConfig a msg -> Element msg