Fix for filtering running unnecessarily if items set from view

This commit is contained in:
Tom Nunn 2023-03-12 20:50:59 +00:00
parent 39a3959c86
commit 4254c3eb96
4 changed files with 79 additions and 41 deletions

View File

@ -3,6 +3,7 @@ module Internal.Model exposing
, blur
, clear
, closeMenu
, currentFilteredOptions
, highlightIndex
, init
, isFocused
@ -127,9 +128,9 @@ wasHighlightedByMouse (Model { highlightedByMouse }) =
highlightedByMouse
toFilteredOptions : Maybe Int -> (a -> String) -> Maybe (Filter a) -> Model a -> List (Option a)
toFilteredOptions minInputLength itemToString filter (Model model) =
if not model.focused then
toFilteredOptions : Bool -> Maybe Int -> (a -> String) -> Maybe (Filter a) -> Model a -> List (Option a)
toFilteredOptions onlyIfFocused minInputLength itemToString filter (Model model) =
if onlyIfFocused && not model.focused then
[]
else
@ -160,6 +161,11 @@ toFilteredOptions_ itemToString filter (Model model) =
|> Filter.filterOptions model.inputValue filter
currentFilteredOptions : Model a -> List (Option a)
currentFilteredOptions (Model { filteredOptions }) =
Maybe.withDefault [] filteredOptions
toInputElementId : Model a -> String
toInputElementId (Model { id }) =
id ++ "-input"
@ -297,6 +303,7 @@ selectOption opt (Model model) =
{ model
| menuOpen = False
, selected = Just (Option.toItem opt)
, highlighted = Nothing
, inputValue = Option.toString opt
, applyFilter = False
}

View File

@ -7,9 +7,9 @@ import Internal.Option exposing (Option)
type Msg a
= InputChanged String (List (Option a))
| OptionClicked (Option a)
| InputFocused (Maybe Int)
| GotNewFilteredOptions (List (Option a))
| InputClicked (Maybe Int)
| InputFocused (Maybe ( List a, List (Option a) ))
| GotNewFilteredOptions ( List a, List (Option a) )
| InputClicked
| InputLostFocus
{ clearInputValue : Bool
, selectExactMatch : Bool

View File

@ -3,7 +3,7 @@ module Internal.Update exposing (update)
import Internal.Effect as Effect exposing (Effect)
import Internal.Model as Model exposing (Model)
import Internal.Msg exposing (Msg(..))
import Internal.Option exposing (Option)
import Internal.Option as Option exposing (Option)
import Internal.RequestState exposing (RequestState(..))
import Internal.UpdateOptions exposing (UpdateOptions)
@ -73,20 +73,29 @@ update_ { request, requestMinInputLength, debounceRequest, onFocus, onLoseFocus,
, Effect.none
)
InputFocused maybeIdx ->
onFocusMenu tagger maybeIdx (request /= Nothing) model
InputFocused maybeOptions ->
(case maybeOptions of
Just ( items, options ) ->
Model.setItems items model
|> Model.setFilteredOptions options
Nothing ->
model
)
|> onFocusMenu tagger (request /= Nothing)
|> withEffect (\_ -> Effect.emitJust onFocus)
InputClicked maybeIdx ->
onFocusMenu tagger maybeIdx (request /= Nothing) model
InputClicked ->
onFocusMenu tagger (request /= Nothing) model
InputLostFocus config filteredOptions ->
( Model.blur config (request /= Nothing) filteredOptions model
, Effect.emitJust onLoseFocus
)
GotNewFilteredOptions options ->
( Model.setFilteredOptions options model
GotNewFilteredOptions ( items, options ) ->
( Model.setItems items model
|> Model.setFilteredOptions options
, Effect.None
)
@ -97,7 +106,7 @@ update_ { request, requestMinInputLength, debounceRequest, onFocus, onLoseFocus,
KeyDown selectOnTab filteredOptions key ->
Model.setFilteredOptions filteredOptions model
|> handleKey selectOnTab tagger key filteredOptions
|> handleKey selectOnTab tagger (request /= Nothing) key filteredOptions
GotContainerAndMenuElements maybeIdx result ->
( model
@ -167,14 +176,19 @@ update_ { request, requestMinInputLength, debounceRequest, onFocus, onLoseFocus,
( model, Effect.none )
onFocusMenu : (Msg a -> msg) -> Maybe Int -> Bool -> Model a -> ( Model a, Effect effect msg )
onFocusMenu tagger maybeOptionIdx hasRequest model =
onFocusMenu : (Msg a -> msg) -> Bool -> Model a -> ( Model a, Effect effect msg )
onFocusMenu tagger hasRequest model =
let
selectedIdx =
Model.toValue model
|> Maybe.andThen (Option.findIndex (Model.currentFilteredOptions model))
in
( Model.setFocused True model
|> Model.highlightIndex maybeOptionIdx False
|> Model.highlightIndex selectedIdx False
, if not hasRequest || Model.toRequestState model == Just Success then
Effect.batch
[ Effect.ScrollMenuToTop (tagger NoOp) (Model.toMenuElementId model)
, getContainerAndMenuElementsEffect maybeOptionIdx tagger model
, getContainerAndMenuElementsEffect selectedIdx tagger model
]
else
@ -182,8 +196,8 @@ onFocusMenu tagger maybeOptionIdx hasRequest model =
)
handleKey : Bool -> (Msg a -> msg) -> String -> List (Option a) -> Model a -> ( Model a, Effect effect msg )
handleKey selectOnTab tagger key filteredOptions model =
handleKey : Bool -> (Msg a -> msg) -> Bool -> String -> List (Option a) -> Model a -> ( Model a, Effect effect msg )
handleKey selectOnTab tagger hasRequest key filteredOptions model =
let
selectHighlighted =
case Model.toHighlighted model |> Maybe.andThen (\idx -> getAt idx filteredOptions) of
@ -195,16 +209,16 @@ handleKey selectOnTab tagger key filteredOptions model =
in
case key of
"ArrowDown" ->
moveHighlight tagger (Basics.min (List.length filteredOptions - 1) (Maybe.withDefault -1 (Model.toHighlighted model) + 1)) model
moveHighlight tagger hasRequest (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
moveHighlight tagger hasRequest (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
moveHighlight tagger hasRequest (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
moveHighlight tagger hasRequest (Basics.max 0 (Maybe.withDefault 0 (Model.toHighlighted model) - 10)) model
"Enter" ->
selectHighlighted
@ -223,17 +237,15 @@ handleKey selectOnTab tagger key filteredOptions model =
( model, Effect.none )
moveHighlight : (Msg a -> msg) -> Int -> Model a -> ( Model a, Effect effect msg )
moveHighlight tagger newHighlighted model =
moveHighlight : (Msg a -> msg) -> Bool -> Int -> Model a -> ( Model a, Effect effect msg )
moveHighlight tagger hasRequest newHighlighted model =
if Model.isOpen model then
( Model.highlightIndex (Just newHighlighted) False model
, getContainerAndMenuElementsEffect (Just newHighlighted) tagger model
)
else
( model
, getContainerAndMenuElementsEffect Nothing tagger model
)
onFocusMenu tagger hasRequest model
getContainerAndMenuElementsEffect : Maybe Int -> (Msg a -> msg) -> Model a -> Effect effect msg

View File

@ -81,7 +81,7 @@ view attribs v =
toElement : Model a -> ViewConfigInternal a msg -> Element msg
toElement model config =
toElement_ (Model.toMenuPlacement config.menuMaxHeight config.menuPlacement model)
(Model.toFilteredOptions config.minInputLength config.itemToString config.filter model)
(Model.toFilteredOptions True config.minInputLength config.itemToString config.filter model)
model
config
@ -141,9 +141,14 @@ toElement_ placement filteredOptions model config =
else
[]
, if Model.isFocused model && Model.requiresNewFilteredOptions model then
[ Element.htmlAttribute (Html.Events.on "focusin" (Decode.succeed (GotNewFilteredOptions filteredOptions |> config.onChange)))
, Element.htmlAttribute (Html.Events.on "mousemove" (Decode.succeed (GotNewFilteredOptions filteredOptions |> config.onChange)))
, if Model.requiresNewFilteredOptions model then
let
options _ =
optionsUpdate model config filteredOptions
in
[ Element.htmlAttribute <| Html.Events.on "keydown" (Decode.lazy (\_ -> Decode.succeed (GotNewFilteredOptions (options ()) |> config.onChange)))
, Element.htmlAttribute <| Html.Events.on "touchstart" (Decode.lazy (\_ -> Decode.succeed (GotNewFilteredOptions (options ()) |> config.onChange)))
, Element.htmlAttribute <| Html.Events.on "mousemove" (Decode.lazy (\_ -> Decode.succeed (GotNewFilteredOptions (options ()) |> config.onChange)))
]
else
@ -155,16 +160,15 @@ toElement_ placement filteredOptions model config =
inputView : List (Option a) -> Model a -> ViewConfigInternal a msg -> Element msg
inputView filteredOptions model config =
let
selectedIdx =
Model.toValue model
|> Maybe.andThen (Option.findIndex filteredOptions)
in
Input.text
(List.concat
[ config.inputAttribs
, [ Events.onFocus (InputFocused selectedIdx |> config.onChange)
, Events.onClick (InputClicked selectedIdx |> config.onChange)
, if Model.requiresNewFilteredOptions model then
[ Element.htmlAttribute (Html.Events.on "focus" (Decode.lazy (\_ -> Decode.succeed (InputFocused (Just <| optionsUpdate model config filteredOptions) |> config.onChange)))) ]
else
[ Events.onFocus (InputFocused Nothing |> config.onChange) ]
, [ Events.onClick (InputClicked |> config.onChange)
, Events.onLoseFocus
(config.onChange
(InputLostFocus
@ -190,7 +194,7 @@ inputView filteredOptions model config =
\v ->
InputChanged v
(Model.setInputValue v model
|> Model.toFilteredOptions config.minInputLength config.itemToString config.filter
|> Model.toFilteredOptions False config.minInputLength config.itemToString config.filter
)
|> config.onChange
, text =
@ -205,6 +209,21 @@ inputView filteredOptions model config =
}
optionsUpdate : Model a -> ViewConfigInternal a msg -> List (Option a) -> ( List a, List (Option a) )
optionsUpdate model config filteredOptions =
( Model.toItems model
, if List.isEmpty filteredOptions then
Model.toFilteredOptions False config.minInputLength config.itemToString config.filter model
else
filteredOptions
)
--)
inputAccessibilityAttributes : List (Option a) -> Model a -> List (Attribute msg)
inputAccessibilityAttributes filteredOptions model =
[ htmlAttribute "role" "combobox"