mirror of
https://github.com/nunntom/elm-ui-select.git
synced 2024-11-26 03:49:25 +03:00
Prevent more unnecessary filtering
This commit is contained in:
parent
a75b080360
commit
559129d85e
@ -25,6 +25,7 @@ exampleProgramTest =
|
||||
[ Test.test "Filter for United Kingdom produces one result" <|
|
||||
\() ->
|
||||
programTest
|
||||
|> focusInput
|
||||
|> ProgramTest.fillIn "" "Choose a country" "United Kingdom"
|
||||
|> ProgramTest.ensureView
|
||||
(Query.find [ Selector.id (Select.toMenuElementId countrySelect) ]
|
||||
@ -38,12 +39,14 @@ exampleProgramTest =
|
||||
, Test.test "Click United Kingdom selects it" <|
|
||||
\() ->
|
||||
programTest
|
||||
|> focusInput
|
||||
|> ProgramTest.fillIn "" "Choose a country" "United"
|
||||
|> Select.Effect.simulateClickOption simulateInputConfig "country-select" "🇬🇧 United Kingdom of Great Britain and Northern Ireland"
|
||||
|> ProgramTest.expectViewHas [ Selector.text "You chose United Kingdom of Great Britain and Northern Ireland" ]
|
||||
, Test.test "Keyboard select United Kingdom" <|
|
||||
\() ->
|
||||
programTest
|
||||
|> focusInput
|
||||
|> ProgramTest.fillIn "" "Choose a country" "United"
|
||||
|> SimulateInput.arrowDown "country-select"
|
||||
|> SimulateInput.enter "country-select"
|
||||
@ -51,12 +54,12 @@ exampleProgramTest =
|
||||
, Test.test "Focusing on the input triggers the onFocus msg" <|
|
||||
\() ->
|
||||
programTest
|
||||
|> ProgramTest.simulateDomEvent (Query.find [ Selector.id (Select.toInputElementId countrySelect) ]) Test.Html.Event.focus
|
||||
|> focusInput
|
||||
|> ProgramTest.expectModel (.inputIsFocused >> Expect.equal (Just True))
|
||||
, Test.test "Input losing focus triggers the onLoseFocus msg" <|
|
||||
\() ->
|
||||
programTest
|
||||
|> ProgramTest.simulateDomEvent (Query.find [ Selector.id (Select.toInputElementId countrySelect) ]) Test.Html.Event.focus
|
||||
|> focusInput
|
||||
|> ProgramTest.simulateDomEvent (Query.find [ Selector.id (Select.toInputElementId countrySelect) ]) Test.Html.Event.blur
|
||||
|> ProgramTest.expectModel (.inputIsFocused >> Expect.equal (Just False))
|
||||
, Test.test "Filling in the input triggers the onInput msg" <|
|
||||
@ -67,26 +70,31 @@ exampleProgramTest =
|
||||
, Test.test "Typing 2 chars with withMinInputLength (Just 3) does not show any items" <|
|
||||
\() ->
|
||||
programTestWith (Select.withMinInputLength (Just 3))
|
||||
|> focusInput
|
||||
|> ProgramTest.fillIn "" "Choose a country" "un"
|
||||
|> ProgramTest.expectViewHasNot [ Selector.text "🇬🇧 United Kingdom of Great Britain and Northern Ireland" ]
|
||||
, Test.test "Typing 3 chars with withMinInputLength (Just 3) does shows items" <|
|
||||
\() ->
|
||||
programTestWith (Select.withMinInputLength (Just 3))
|
||||
|> focusInput
|
||||
|> ProgramTest.fillIn "" "Choose a country" "uni"
|
||||
|> ProgramTest.expectViewHas [ Selector.text "🇬🇧 United Kingdom of Great Britain and Northern Ireland" ]
|
||||
, Test.test "Typing less than minInputLength does not show no matches even if nothing matched" <|
|
||||
\() ->
|
||||
programTestWith (Select.withMinInputLength (Just 5))
|
||||
|> focusInput
|
||||
|> ProgramTest.fillIn "" "Choose a country" "zzzz"
|
||||
|> ProgramTest.expectViewHasNot [ Selector.text "No matches" ]
|
||||
, Test.test "Typing up to the minInputLength shows no matches if nothing matched" <|
|
||||
\() ->
|
||||
programTestWith (Select.withMinInputLength (Just 3))
|
||||
|> focusInput
|
||||
|> ProgramTest.fillIn "" "Choose a country" "zzzz"
|
||||
|> ProgramTest.expectViewHas [ Selector.text "No matches" ]
|
||||
, Test.test "Choosing an option and then focusing back on the input shows all the options again" <|
|
||||
\() ->
|
||||
programTest
|
||||
|> focusInput
|
||||
|> ProgramTest.fillIn "" "Choose a country" "United"
|
||||
|> Select.Effect.simulateClickOption simulateInputConfig "country-select" "🇬🇧 United Kingdom of Great Britain and Northern Ireland"
|
||||
|> ProgramTest.simulateDomEvent (Query.find [ Selector.id (Select.toInputElementId countrySelect) ]) Test.Html.Event.focus
|
||||
@ -155,3 +163,8 @@ simulateEffect effect =
|
||||
, sleep = SimulatedProcess.sleep
|
||||
}
|
||||
selectEffect
|
||||
|
||||
|
||||
focusInput : ProgramTest model msg effect -> ProgramTest model msg effect
|
||||
focusInput =
|
||||
ProgramTest.simulateDomEvent (Query.find [ Selector.id (Select.toInputElementId countrySelect) ]) Test.Html.Event.focus
|
||||
|
@ -1,10 +1,10 @@
|
||||
module RequestTest exposing (exampleProgramTest)
|
||||
|
||||
import EffectRequestExample as App
|
||||
import EffectRequestExample as App exposing (Cocktail)
|
||||
import Http
|
||||
import Json.Decode as Decode
|
||||
import ProgramTest exposing (ProgramTest, SimulatedEffect)
|
||||
import Select
|
||||
import Select exposing (Select)
|
||||
import Select.Effect
|
||||
import SimulateInput
|
||||
import SimulatedEffect.Cmd as SimulatedCmd
|
||||
@ -12,6 +12,7 @@ import SimulatedEffect.Http as SimulateHttp
|
||||
import SimulatedEffect.Process as SimulatedProcess
|
||||
import SimulatedEffect.Task as SimulatedTask
|
||||
import Test exposing (Test)
|
||||
import Test.Html.Event
|
||||
import Test.Html.Query as Query exposing (Single)
|
||||
import Test.Html.Selector as Selector exposing (Selector)
|
||||
|
||||
@ -22,6 +23,7 @@ exampleProgramTest =
|
||||
[ Test.test "Type in Chocolate, and choose second option with keyboard navigation" <|
|
||||
\() ->
|
||||
programTest
|
||||
|> focusInput
|
||||
|> ProgramTest.fillIn "" "Find a cocktail" "Chocolate"
|
||||
|> ProgramTest.advanceTime 500
|
||||
|> ProgramTest.simulateHttpOk "GET"
|
||||
@ -33,6 +35,7 @@ exampleProgramTest =
|
||||
, Test.test "Type in Chocolate, and choose \"Chocolate Drink\" with mouse click" <|
|
||||
\() ->
|
||||
programTest
|
||||
|> focusInput
|
||||
|> ProgramTest.fillIn "" "Find a cocktail" "Chocolate"
|
||||
|> ProgramTest.advanceTime 300
|
||||
|> ProgramTest.simulateHttpOk "GET"
|
||||
@ -97,6 +100,18 @@ simulateConfig =
|
||||
}
|
||||
|
||||
|
||||
drinkSelect : Select Cocktail
|
||||
drinkSelect =
|
||||
App.init ()
|
||||
|> Tuple.first
|
||||
|> .select
|
||||
|
||||
|
||||
focusInput : ProgramTest model msg effect -> ProgramTest model msg effect
|
||||
focusInput =
|
||||
ProgramTest.simulateDomEvent (Query.find [ Selector.id (Select.toInputElementId drinkSelect) ]) Test.Html.Event.focus
|
||||
|
||||
|
||||
cocktailsResponse : String
|
||||
cocktailsResponse =
|
||||
"""{
|
||||
|
@ -10,6 +10,7 @@ module Internal.Model exposing
|
||||
, isOpen
|
||||
, isRequestFailed
|
||||
, openMenu
|
||||
, requiresNewFilteredOptions
|
||||
, selectOption
|
||||
, setElements
|
||||
, setFilteredOptions
|
||||
@ -128,20 +129,24 @@ wasHighlightedByMouse (Model { highlightedByMouse }) =
|
||||
|
||||
toFilteredOptions : Maybe Int -> (a -> String) -> Maybe (Filter a) -> Model a -> List (Option a)
|
||||
toFilteredOptions minInputLength itemToString filter (Model model) =
|
||||
case minInputLength of
|
||||
Just chars ->
|
||||
if String.length model.inputValue >= chars then
|
||||
toFilteredOptions_ itemToString filter (Model model)
|
||||
if not model.focused then
|
||||
[]
|
||||
|
||||
else
|
||||
[]
|
||||
else
|
||||
case minInputLength of
|
||||
Just chars ->
|
||||
if String.length model.inputValue >= chars then
|
||||
toFilteredOptions_ itemToString filter (Model model)
|
||||
|
||||
Nothing ->
|
||||
if model.applyFilter then
|
||||
toFilteredOptions_ itemToString filter (Model model)
|
||||
else
|
||||
[]
|
||||
|
||||
else
|
||||
List.map (Option.init itemToString) model.items
|
||||
Nothing ->
|
||||
if model.applyFilter then
|
||||
toFilteredOptions_ itemToString filter (Model model)
|
||||
|
||||
else
|
||||
List.map (Option.init itemToString) model.items
|
||||
|
||||
|
||||
toFilteredOptions_ : (a -> String) -> Maybe (Filter a) -> Model a -> List (Option a)
|
||||
@ -238,6 +243,11 @@ isRequestFailed (Model { requestState }) =
|
||||
requestState == Just Failed
|
||||
|
||||
|
||||
requiresNewFilteredOptions : Model a -> Bool
|
||||
requiresNewFilteredOptions (Model { filteredOptions }) =
|
||||
filteredOptions == Nothing
|
||||
|
||||
|
||||
|
||||
-- UPDATE
|
||||
|
||||
|
@ -8,6 +8,7 @@ type Msg a
|
||||
= InputChanged String (List (Option a))
|
||||
| OptionClicked (Option a)
|
||||
| InputFocused (Maybe Int)
|
||||
| GotNewFilteredOptions (List (Option a))
|
||||
| InputClicked (Maybe Int)
|
||||
| InputLostFocus
|
||||
{ clearInputValue : Bool
|
||||
|
@ -85,13 +85,19 @@ update_ { request, requestMinInputLength, debounceRequest, onFocus, onLoseFocus,
|
||||
, Effect.emitJust onLoseFocus
|
||||
)
|
||||
|
||||
GotNewFilteredOptions options ->
|
||||
( Model.setFilteredOptions options model
|
||||
, Effect.None
|
||||
)
|
||||
|
||||
MouseEnteredOption i ->
|
||||
( Model.highlightIndex (Just i) True model
|
||||
, Effect.none
|
||||
)
|
||||
|
||||
KeyDown selectOnTab filteredOptions key ->
|
||||
handleKey selectOnTab tagger model key filteredOptions
|
||||
Model.setFilteredOptions filteredOptions model
|
||||
|> handleKey selectOnTab tagger key filteredOptions
|
||||
|
||||
GotContainerAndMenuElements maybeIdx result ->
|
||||
( model
|
||||
@ -176,8 +182,8 @@ onFocusMenu tagger maybeOptionIdx hasRequest model =
|
||||
)
|
||||
|
||||
|
||||
handleKey : Bool -> (Msg a -> msg) -> Model a -> String -> List (Option a) -> ( Model a, Effect effect msg )
|
||||
handleKey selectOnTab tagger model key filteredOptions =
|
||||
handleKey : Bool -> (Msg a -> msg) -> String -> List (Option a) -> Model a -> ( Model a, Effect effect msg )
|
||||
handleKey selectOnTab tagger key filteredOptions model =
|
||||
let
|
||||
selectHighlighted =
|
||||
case Model.toHighlighted model |> Maybe.andThen (\idx -> getAt idx filteredOptions) of
|
||||
|
@ -89,58 +89,66 @@ toElement model config =
|
||||
toElement_ : Placement -> List (Option a) -> Model a -> ViewConfigInternal a msg -> Element msg
|
||||
toElement_ placement filteredOptions model config =
|
||||
Element.el
|
||||
([ Element.htmlAttribute (Html.Attributes.id <| Model.toContainerElementId model)
|
||||
, Element.width Element.fill
|
||||
, Element.below <|
|
||||
if
|
||||
List.length filteredOptions
|
||||
== 0
|
||||
&& Model.isOpen model
|
||||
&& (String.length (Model.toInputValue model) >= Maybe.withDefault 1 config.minInputLength)
|
||||
&& (Model.toRequestState model == Nothing || Model.toRequestState model == Just Success)
|
||||
then
|
||||
config.noMatchElement
|
||||
(List.concat
|
||||
[ [ Element.htmlAttribute (Html.Attributes.id <| Model.toContainerElementId model)
|
||||
, Element.width Element.fill
|
||||
, Element.below <|
|
||||
if
|
||||
List.length filteredOptions
|
||||
== 0
|
||||
&& Model.isOpen model
|
||||
&& (String.length (Model.toInputValue model) >= Maybe.withDefault 1 config.minInputLength)
|
||||
&& (Model.toRequestState model == Nothing || Model.toRequestState model == Just Success)
|
||||
then
|
||||
config.noMatchElement
|
||||
|
||||
else
|
||||
Element.none
|
||||
, Placement.toAttribute
|
||||
(if config.positionFixed then
|
||||
Placement.Below
|
||||
else
|
||||
Element.none
|
||||
, Placement.toAttribute
|
||||
(if config.positionFixed then
|
||||
Placement.Below
|
||||
|
||||
else
|
||||
placement
|
||||
)
|
||||
<|
|
||||
(if config.positionFixed then
|
||||
positionFixedEl placement (Model.toContainerElement model)
|
||||
|
||||
else
|
||||
identity
|
||||
)
|
||||
<|
|
||||
menuView
|
||||
(defaultMenuAttrs
|
||||
{ menuWidth = Model.toMenuMinWidth model
|
||||
, maxWidth = config.menuMaxWidth
|
||||
, menuHeight = Model.toMenuMaxHeight config.menuMaxHeight config.menuPlacement model
|
||||
}
|
||||
++ List.concatMap (\toAttrs -> toAttrs (Model.toMenuPlacement config.menuMaxHeight config.menuPlacement model)) config.menuAttributes
|
||||
else
|
||||
placement
|
||||
)
|
||||
{ menuId = Model.toMenuElementId model
|
||||
, toOptionId = Model.toOptionElementId model
|
||||
, toOptionState = Model.toOptionState model
|
||||
, onChange = config.onChange
|
||||
, menuOpen = Model.isOpen model
|
||||
, options = filteredOptions
|
||||
, optionElement = config.optionElement
|
||||
}
|
||||
]
|
||||
++ (if Model.isOpen model then
|
||||
[ Element.htmlAttribute <| Html.Attributes.style "z-index" "21" ]
|
||||
<|
|
||||
(if config.positionFixed then
|
||||
positionFixedEl placement (Model.toContainerElement model)
|
||||
|
||||
else
|
||||
[]
|
||||
)
|
||||
else
|
||||
identity
|
||||
)
|
||||
<|
|
||||
menuView
|
||||
(defaultMenuAttrs
|
||||
{ menuWidth = Model.toMenuMinWidth model
|
||||
, maxWidth = config.menuMaxWidth
|
||||
, menuHeight = Model.toMenuMaxHeight config.menuMaxHeight config.menuPlacement model
|
||||
}
|
||||
++ List.concatMap (\toAttrs -> toAttrs (Model.toMenuPlacement config.menuMaxHeight config.menuPlacement model)) config.menuAttributes
|
||||
)
|
||||
{ menuId = Model.toMenuElementId model
|
||||
, toOptionId = Model.toOptionElementId model
|
||||
, toOptionState = Model.toOptionState model
|
||||
, onChange = config.onChange
|
||||
, menuOpen = Model.isOpen model
|
||||
, options = filteredOptions
|
||||
, optionElement = config.optionElement
|
||||
}
|
||||
]
|
||||
, if Model.isOpen model then
|
||||
[ Element.htmlAttribute <| Html.Attributes.style "z-index" "21" ]
|
||||
|
||||
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)))
|
||||
]
|
||||
|
||||
else
|
||||
[]
|
||||
]
|
||||
)
|
||||
(inputView filteredOptions model config)
|
||||
|
||||
@ -153,10 +161,11 @@ inputView filteredOptions model config =
|
||||
|> Maybe.andThen (Option.findIndex filteredOptions)
|
||||
in
|
||||
Input.text
|
||||
(config.inputAttribs
|
||||
++ [ Events.onFocus (InputFocused selectedIdx |> config.onChange)
|
||||
, Events.onClick (InputClicked selectedIdx |> config.onChange)
|
||||
, Events.onLoseFocus
|
||||
(List.concat
|
||||
[ config.inputAttribs
|
||||
, [ Events.onFocus (InputFocused selectedIdx |> config.onChange)
|
||||
, Events.onClick (InputClicked selectedIdx |> config.onChange)
|
||||
, Events.onLoseFocus
|
||||
(config.onChange
|
||||
(InputLostFocus
|
||||
{ clearInputValue = config.clearInputValueOnBlur
|
||||
@ -165,16 +174,17 @@ inputView filteredOptions model config =
|
||||
filteredOptions
|
||||
)
|
||||
)
|
||||
, onKeyDown (Model.isOpen model) (KeyDown config.selectOnTab filteredOptions >> config.onChange)
|
||||
, Element.htmlAttribute (Html.Attributes.id <| Model.toInputElementId model)
|
||||
, Element.inFront <|
|
||||
, 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
|
||||
Maybe.withDefault Element.none config.clearButton
|
||||
|
||||
else
|
||||
Element.none
|
||||
]
|
||||
++ inputAccessibilityAttributes filteredOptions model
|
||||
]
|
||||
, inputAccessibilityAttributes filteredOptions model
|
||||
]
|
||||
)
|
||||
{ onChange =
|
||||
\v ->
|
||||
|
Loading…
Reference in New Issue
Block a user