diff --git a/examples/src/EffectRequest.elm b/examples/src/EffectRequest.elm index 5632c90..f45fb4b 100644 --- a/examples/src/EffectRequest.elm +++ b/examples/src/EffectRequest.elm @@ -100,7 +100,7 @@ performEffect effect = Select.Effect.performWithRequest performEffect selectEffect FetchCocktails query -> - fetchCocktails (Select.gotRequestResponse >> SelectMsg) query + fetchCocktails (Select.gotRequestResponse query >> SelectMsg) query diff --git a/examples/src/Request.elm b/examples/src/Request.elm index e2cf827..167b041 100644 --- a/examples/src/Request.elm +++ b/examples/src/Request.elm @@ -71,7 +71,7 @@ fetchCocktails query = Http.get { url = "https://thecocktaildb.com/api/json/v1/1/search.php?s=" ++ String.replace " " "+" query , expect = - Http.expectJson Select.gotRequestResponse + Http.expectJson (Select.gotRequestResponse query) (Decode.field "drinks" (Decode.oneOf [ Decode.list cocktailDecoder diff --git a/src/Internal/Model.elm b/src/Internal/Model.elm index ba33bb8..e34a1e3 100644 --- a/src/Internal/Model.elm +++ b/src/Internal/Model.elm @@ -5,12 +5,14 @@ module Internal.Model exposing , closeMenu , highlightIndex , init + , isFocused , isLoading , isOpen , isRequestFailed , openMenu , selectOption , setElements + , setFocused , setInputValue , setItems , setRequestState @@ -59,6 +61,7 @@ type alias InternalState a = , menuElement : Maybe Dom.Element , requestState : Maybe RequestState , applyFilter : Bool + , focused : Bool } @@ -79,6 +82,7 @@ init id = , menuElement = Nothing , requestState = Nothing , applyFilter = False + , focused = False } @@ -144,7 +148,10 @@ toOptionElementId (Model { id }) idx = toOptionState : Model a -> ( Int, a ) -> OptionState toOptionState (Model { highlighted, selected }) ( idx, a ) = - if highlighted == idx then + if highlighted == idx && selected == Just a then + SelectedAndHighlighted + + else if highlighted == idx then Highlighted else if selected == Just a then @@ -163,6 +170,11 @@ isOpen (Model { menuOpen }) = menuOpen +isFocused : Model a -> Bool +isFocused (Model { focused }) = + focused + + isLoading : Model a -> Bool isLoading (Model { requestState }) = requestState == Just Loading @@ -277,6 +289,11 @@ applyFilter v (Model model) = Model { model | applyFilter = v } +setFocused : Bool -> Model a -> Model a +setFocused v (Model model) = + Model { model | focused = v } + + -- INTERNAL diff --git a/src/Internal/Msg.elm b/src/Internal/Msg.elm index 6c2c7a0..1cb5a88 100644 --- a/src/Internal/Msg.elm +++ b/src/Internal/Msg.elm @@ -15,5 +15,5 @@ type Msg a | GotContainerAndMenuElements (Result Dom.Error { menu : Element, container : Element }) | ClearButtonPressed | InputDebounceReturned String - | GotRequestResponse (Result () (List a)) + | GotRequestResponse String (Result () (List a)) | NoOp diff --git a/src/Internal/OptionState.elm b/src/Internal/OptionState.elm index b01a522..b498423 100644 --- a/src/Internal/OptionState.elm +++ b/src/Internal/OptionState.elm @@ -5,3 +5,4 @@ type OptionState = Idle | Highlighted | Selected + | SelectedAndHighlighted diff --git a/src/Internal/Update.elm b/src/Internal/Update.elm index 3397a0f..80cd18b 100644 --- a/src/Internal/Update.elm +++ b/src/Internal/Update.elm @@ -54,7 +54,8 @@ update tagger maybeRequest msg model = onFocusMenu tagger maybeRequest model InputLostFocus -> - ( Model.closeMenu model + ( Model.setFocused False model + |> Model.closeMenu , Effect.none ) @@ -99,17 +100,23 @@ update tagger maybeRequest msg model = else ( model, Effect.none ) - GotRequestResponse (Ok items) -> - ( model - |> Model.setItems items - |> Model.setRequestState (Just Success) - , getContainerAndMenuElementsEffect tagger model - ) + GotRequestResponse inputVal response -> + if inputVal == Model.toInputValue model then + case response of + Ok items -> + ( model + |> Model.setItems items + |> Model.setRequestState (Just Success) + , getContainerAndMenuElementsEffect tagger model + ) - GotRequestResponse (Err _) -> - ( Model.setRequestState (Just Failed) model - , Effect.none - ) + Err _ -> + ( Model.setRequestState (Just Failed) model + , Effect.none + ) + + else + ( model, Effect.none ) NoOp -> ( model, Effect.none ) @@ -117,7 +124,8 @@ update tagger maybeRequest msg model = onFocusMenu : (Msg a -> msg) -> Maybe (Request effect) -> Model a -> ( Model a, Effect effect msg ) onFocusMenu tagger maybeRequest model = - ( Model.highlightIndex 0 model + ( Model.setFocused True model + |> Model.highlightIndex 0 , if maybeRequest == Nothing || Model.toRequestState model == Just Success then getContainerAndMenuElementsEffect tagger model diff --git a/src/Internal/View.elm b/src/Internal/View.elm index a772429..db679c1 100644 --- a/src/Internal/View.elm +++ b/src/Internal/View.elm @@ -10,7 +10,6 @@ import Element exposing (Attribute, Element) import Element.Background as Background import Element.Border as Border import Element.Events as Events -import Element.Font as Font import Element.Input as Input import Html.Attributes import Html.Events @@ -140,7 +139,7 @@ inputView placement filteredOptions model config = ) { onChange = InputChanged >> config.onChange , text = - if Model.isOpen model then + if Model.isFocused model then Model.toInputValue model else @@ -307,35 +306,25 @@ positionFixedAttributes placement container = defaultOptionElement : (a -> String) -> OptionState -> a -> Element msg defaultOptionElement toString optionState a = - case optionState of - Highlighted -> - Element.el - ([ Background.color (Element.rgb 0.9 0.9 0.9) - , Font.color (Element.rgb 0 0 0) - ] - ++ defaultOptionAttrs - ) - (Element.text (toString a)) + Element.el + [ Element.width Element.fill + , Element.pointer + , Element.paddingXY 14 10 + , Background.color <| + case optionState of + Highlighted -> + Element.rgb 0.95 0.95 0.95 - Selected -> - Element.el - ([ Background.color (Element.rgb 0.65 0.84 0.98) - , Font.color (Element.rgb 0 0 0) - ] - ++ defaultOptionAttrs - ) - (Element.text (toString a)) + Selected -> + Element.rgba 0.64 0.83 0.97 0.8 - Idle -> - Element.el defaultOptionAttrs (Element.text (toString a)) + SelectedAndHighlighted -> + Element.rgba 0.64 0.83 0.97 1 - -defaultOptionAttrs : List (Attribute msg) -defaultOptionAttrs = - [ Element.width Element.fill - , Element.pointer - , Element.paddingXY 14 10 - ] + Idle -> + Element.rgb 1 1 1 + ] + (Element.text (toString a)) defaultNoMatchElement : Element msg diff --git a/src/Select.elm b/src/Select.elm index e6f5896..8da4def 100644 --- a/src/Select.elm +++ b/src/Select.elm @@ -194,7 +194,7 @@ Note that in order to avoid an elm/http dependency in this package, you will nee fetchThings query = Http.get { url = "https://awesome-thing.api/things?search=" ++ query - , expect = Http.expectJson Select.gotRequestResponse (Decode.list thingDecoder) + , expect = Http.expectJson (Select.gotRequestResponse query) (Decode.list thingDecoder) } -} @@ -219,11 +219,12 @@ request = Request.request -{-| Hook the request Cmd result back into update with this Msg +{-| Hook the request Cmd result back into update with this Msg. You need to pass in the string query (input value) +that was used for the request. This is used to match up the correct response to most recent request in case of race conditions. -} -gotRequestResponse : Result err (List a) -> Msg a -gotRequestResponse = - Result.mapError (\_ -> ()) >> Msg.GotRequestResponse +gotRequestResponse : String -> Result err (List a) -> Msg a +gotRequestResponse inputValue = + Result.mapError (\_ -> ()) >> Msg.GotRequestResponse inputValue @@ -307,6 +308,7 @@ type OptionState = Idle | Highlighted | Selected + | SelectedAndHighlighted {-| Provide your own element to show when there are no matches based on the filter and input value. This appears below the input. @@ -379,3 +381,6 @@ mapOptionState state = OptionState.Selected -> Selected + + OptionState.SelectedAndHighlighted -> + SelectedAndHighlighted diff --git a/src/Select/Effect.elm b/src/Select/Effect.elm index 4951ace..bb7b2da 100644 --- a/src/Select/Effect.elm +++ b/src/Select/Effect.elm @@ -86,7 +86,7 @@ update tagger = Select.Effect.performWithRequest performEffect selectEffect FetchThings query -> - fetchThings (Select.gotRequestResponse >> SelectMsg) query + fetchThings (Select.gotRequestResponse query >> SelectMsg) query fetchThings : (Result Http.Error (List thing) -> msg) -> String -> Cmd msg fetchThings tagger query =