mirror of
https://github.com/nunntom/elm-ui-select.git
synced 2024-11-22 21:19:26 +03:00
Add UpdateConfig
This commit is contained in:
parent
672ddbae22
commit
d8cea06f84
3
elm.json
3
elm.json
@ -8,7 +8,8 @@
|
||||
"Select",
|
||||
"Select.Filter",
|
||||
"Select.Effect",
|
||||
"Select.Request"
|
||||
"Select.Request",
|
||||
"Select.UpdateConfig"
|
||||
],
|
||||
"elm-version": "0.19.0 <= v < 0.20.0",
|
||||
"dependencies": {
|
||||
|
@ -63,7 +63,13 @@ update : Msg -> Model -> ( Model, MyEffect )
|
||||
update msg model =
|
||||
case msg of
|
||||
SelectMsg subMsg ->
|
||||
Select.Effect.updateWithRequest (Select.Effect.request FetchCocktails) subMsg model.select
|
||||
Select.Effect.updateWith
|
||||
{ request = Just (Select.Effect.request FetchCocktails)
|
||||
, clearInputValueOnBlur = True
|
||||
, selectExactMatchOnBlur = False
|
||||
}
|
||||
subMsg
|
||||
model.select
|
||||
|> Tuple.mapBoth (\select -> { model | select = select }) SelectEffect
|
||||
|
||||
|
||||
|
@ -11,6 +11,7 @@ import Http
|
||||
import Json.Decode as Decode exposing (Decoder)
|
||||
import Resources.ClearButton
|
||||
import Select exposing (OptionState(..), Select)
|
||||
import Select.UpdateConfig as SelectConfig
|
||||
|
||||
|
||||
main : Program () Model Msg
|
||||
@ -62,7 +63,13 @@ update : Msg -> Model -> ( Model, Cmd Msg )
|
||||
update msg model =
|
||||
case msg of
|
||||
SelectMsg subMsg ->
|
||||
Select.updateWithRequest SelectMsg (Select.request fetchCocktails) subMsg model.select
|
||||
Select.updateWith
|
||||
(SelectConfig.default
|
||||
|> SelectConfig.withRequest (Select.request fetchCocktails)
|
||||
)
|
||||
SelectMsg
|
||||
subMsg
|
||||
model.select
|
||||
|> Tuple.mapFirst (\select -> { model | select = select })
|
||||
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
module Internal.Model exposing
|
||||
( Model
|
||||
, applyFilter
|
||||
, blur
|
||||
, clear
|
||||
, closeMenu
|
||||
, highlightIndex
|
||||
@ -36,10 +37,11 @@ module Internal.Model exposing
|
||||
|
||||
import Browser.Dom as Dom
|
||||
import Internal.Filter as Filter exposing (Filter)
|
||||
import Internal.Option exposing (Option)
|
||||
import Internal.Option as Option exposing (Option)
|
||||
import Internal.OptionState exposing (OptionState(..))
|
||||
import Internal.Placement exposing (Placement(..))
|
||||
import Internal.RequestState exposing (RequestState(..))
|
||||
import Select.UpdateConfig exposing (UpdateConfig)
|
||||
|
||||
|
||||
|
||||
@ -294,6 +296,36 @@ setFocused v (Model model) =
|
||||
Model { model | focused = v }
|
||||
|
||||
|
||||
blur : UpdateConfig effect -> List (Option a) -> Model a -> Model a
|
||||
blur { clearInputValueOnBlur, selectExactMatchOnBlur, request } filteredOptions (Model model) =
|
||||
(if model.selected == Nothing then
|
||||
case ( selectExactMatchOnBlur, Option.findByString filteredOptions model.inputValue ) of
|
||||
( True, Just option ) ->
|
||||
selectOption option (Model model)
|
||||
|
||||
_ ->
|
||||
if clearInputValueOnBlur then
|
||||
Model
|
||||
{ model
|
||||
| inputValue = ""
|
||||
, items =
|
||||
if request == Nothing then
|
||||
model.items
|
||||
|
||||
else
|
||||
[]
|
||||
}
|
||||
|
||||
else
|
||||
Model model
|
||||
|
||||
else
|
||||
Model model
|
||||
)
|
||||
|> setFocused False
|
||||
|> closeMenu
|
||||
|
||||
|
||||
|
||||
-- INTERNAL
|
||||
|
||||
|
@ -9,7 +9,7 @@ type Msg a
|
||||
| OptionClicked (Option a)
|
||||
| InputFocused
|
||||
| InputClicked
|
||||
| InputLostFocus
|
||||
| InputLostFocus (List (Option a))
|
||||
| MouseEnteredOption Int
|
||||
| KeyDown (List (Option a)) String
|
||||
| GotContainerAndMenuElements (Result Dom.Error { menu : Dom.Viewport, container : Dom.Element })
|
||||
|
@ -1,5 +1,33 @@
|
||||
module Internal.Option exposing (Option)
|
||||
module Internal.Option exposing (Option, findByString, findByValue)
|
||||
|
||||
|
||||
type alias Option a =
|
||||
( a, String )
|
||||
|
||||
|
||||
findByValue : List (Option a) -> a -> Maybe (Option a)
|
||||
findByValue list a =
|
||||
case list of
|
||||
[] ->
|
||||
Nothing
|
||||
|
||||
x :: xs ->
|
||||
if a == Tuple.first x then
|
||||
Just x
|
||||
|
||||
else
|
||||
findByValue xs a
|
||||
|
||||
|
||||
findByString : List (Option a) -> String -> Maybe (Option a)
|
||||
findByString list s =
|
||||
case list of
|
||||
[] ->
|
||||
Nothing
|
||||
|
||||
x :: xs ->
|
||||
if String.toLower s == String.toLower (Tuple.second x) then
|
||||
Just x
|
||||
|
||||
else
|
||||
findByString xs s
|
||||
|
@ -6,10 +6,11 @@ import Internal.Msg exposing (Msg(..))
|
||||
import Internal.Option exposing (Option)
|
||||
import Internal.Request as Request exposing (Request)
|
||||
import Internal.RequestState exposing (RequestState(..))
|
||||
import Select.UpdateConfig exposing (UpdateConfig)
|
||||
|
||||
|
||||
update : Maybe (Request effect) -> Msg a -> Model a -> ( Model a, Effect effect )
|
||||
update maybeRequest msg model =
|
||||
update : UpdateConfig effect -> Msg a -> Model a -> ( Model a, Effect effect )
|
||||
update ({ request } as config) msg model =
|
||||
case msg of
|
||||
InputChanged val ->
|
||||
( model
|
||||
@ -18,20 +19,20 @@ update maybeRequest msg model =
|
||||
|> Model.applyFilter True
|
||||
|> Model.setSelected Nothing
|
||||
|> Model.setItems
|
||||
(if maybeRequest /= Nothing && val == "" then
|
||||
(if request /= Nothing && val == "" then
|
||||
[]
|
||||
|
||||
else
|
||||
Model.toItems model
|
||||
)
|
||||
|> Model.setRequestState
|
||||
(if maybeRequest /= Nothing then
|
||||
(if request /= Nothing then
|
||||
Just NotRequested
|
||||
|
||||
else
|
||||
Nothing
|
||||
)
|
||||
, case maybeRequest of
|
||||
, case request of
|
||||
Just req ->
|
||||
if String.length val >= Request.toMinLength req then
|
||||
Effect.Debounce (Request.toDelay req) val
|
||||
@ -49,14 +50,13 @@ update maybeRequest msg model =
|
||||
)
|
||||
|
||||
InputFocused ->
|
||||
onFocusMenu maybeRequest model
|
||||
onFocusMenu request model
|
||||
|
||||
InputClicked ->
|
||||
onFocusMenu maybeRequest model
|
||||
onFocusMenu request model
|
||||
|
||||
InputLostFocus ->
|
||||
( Model.setFocused False model
|
||||
|> Model.closeMenu
|
||||
InputLostFocus filteredOptions ->
|
||||
( Model.blur config filteredOptions model
|
||||
, Effect.none
|
||||
)
|
||||
|
||||
@ -82,7 +82,7 @@ update maybeRequest msg model =
|
||||
( model
|
||||
|> Model.clear
|
||||
|> Model.setItems
|
||||
(if maybeRequest == Nothing then
|
||||
(if request == Nothing then
|
||||
Model.toItems model
|
||||
|
||||
else
|
||||
@ -94,7 +94,7 @@ update maybeRequest msg model =
|
||||
InputDebounceReturned val ->
|
||||
if val == Model.toInputValue model then
|
||||
( Model.setRequestState (Just Loading) model
|
||||
, Maybe.map (Request.toEffect >> (\effect -> Effect.Request (effect val))) maybeRequest
|
||||
, Maybe.map (Request.toEffect >> (\effect -> Effect.Request (effect val))) request
|
||||
|> Maybe.withDefault Effect.none
|
||||
)
|
||||
|
||||
@ -108,7 +108,11 @@ update maybeRequest msg model =
|
||||
( model
|
||||
|> Model.setItems items
|
||||
|> Model.setRequestState (Just Success)
|
||||
, getContainerAndMenuElementsEffect model
|
||||
, if Model.toValue model == Nothing then
|
||||
getContainerAndMenuElementsEffect model
|
||||
|
||||
else
|
||||
Effect.none
|
||||
)
|
||||
|
||||
Err _ ->
|
||||
|
@ -16,7 +16,7 @@ import Html.Events
|
||||
import Internal.Filter as Filter exposing (Filter)
|
||||
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.OptionState exposing (OptionState(..))
|
||||
import Internal.Placement as Placement exposing (Placement)
|
||||
import Internal.RequestState exposing (RequestState(..))
|
||||
@ -132,7 +132,7 @@ inputView filteredOptions model config =
|
||||
(config.inputAttribs
|
||||
++ [ Events.onFocus (config.onChange InputFocused)
|
||||
, Events.onClick (config.onChange InputClicked)
|
||||
, Events.onLoseFocus (config.onChange InputLostFocus)
|
||||
, Events.onLoseFocus (config.onChange (InputLostFocus filteredOptions))
|
||||
, onKeyDown (KeyDown filteredOptions >> config.onChange)
|
||||
, Element.htmlAttribute (Html.Attributes.id <| Model.toInputElementId model)
|
||||
, Element.inFront <|
|
||||
@ -359,16 +359,8 @@ hijackKey tagger key =
|
||||
|
||||
findOptionString : List (Option a) -> a -> Maybe String
|
||||
findOptionString list a =
|
||||
case list of
|
||||
[] ->
|
||||
Nothing
|
||||
|
||||
x :: xs ->
|
||||
if a == Tuple.first x then
|
||||
Just (Tuple.second x)
|
||||
|
||||
else
|
||||
findOptionString xs a
|
||||
Option.findByValue list a
|
||||
|> Maybe.map Tuple.second
|
||||
|
||||
|
||||
ariaLive : Int -> Element msg
|
||||
|
@ -3,8 +3,8 @@ module Select exposing
|
||||
, setItems, setSelected, setInputValue, closeMenu
|
||||
, toValue, toInputValue
|
||||
, isMenuOpen, isLoading, isRequestFailed
|
||||
, Msg, update, updateWithRequest, Request, request, gotRequestResponse
|
||||
, ViewConfig, view, withMenuAttributes, MenuPlacement(..), withMenuMaxHeight, withMenuMaxWidth, withNoMatchElement, OptionState, withOptionElement, ClearButton, withClearButton, clearButton, withFilter, withMenuAlwaysAbove, withMenuAlwaysBelow, withMenuPositionFixed
|
||||
, Msg, update, updateWith, Request, request, gotRequestResponse
|
||||
, ViewConfig, view, withMenuAttributes, MenuPlacement(..), withMenuMaxHeight, withMenuMaxWidth, withNoMatchElement, withOptionElement, OptionState, withClearButton, ClearButton, clearButton, withFilter, withMenuAlwaysAbove, withMenuAlwaysBelow, withMenuPositionFixed
|
||||
, toElement
|
||||
, Effect
|
||||
)
|
||||
@ -34,12 +34,12 @@ module Select exposing
|
||||
|
||||
# Update and Requests
|
||||
|
||||
@docs Msg, update, updateWithRequest, Request, request, gotRequestResponse
|
||||
@docs Msg, update, updateWith, Request, request, gotRequestResponse
|
||||
|
||||
|
||||
# Configure View
|
||||
|
||||
@docs ViewConfig, view, withMenuAttributes, MenuPlacement, withMenuMaxHeight, withMenuMaxWidth, withNoMatchElement, OptionState, withOptionElement, ClearButton, withClearButton, clearButton, withFilter, withMenuAlwaysAbove, withMenuAlwaysBelow, withMenuPositionFixed
|
||||
@docs ViewConfig, view, withMenuAttributes, MenuPlacement, withMenuMaxHeight, withMenuMaxWidth, withNoMatchElement, withOptionElement, OptionState, withClearButton, ClearButton, clearButton, withFilter, withMenuAlwaysAbove, withMenuAlwaysBelow, withMenuPositionFixed
|
||||
|
||||
|
||||
# Element
|
||||
@ -64,6 +64,7 @@ import Internal.Request as Request
|
||||
import Internal.Update as Update
|
||||
import Internal.View as View exposing (ViewConfigInternal)
|
||||
import Select.Filter exposing (Filter)
|
||||
import Select.UpdateConfig as UpdateConfig exposing (UpdateConfig)
|
||||
|
||||
|
||||
|
||||
@ -176,18 +177,25 @@ type alias Msg a =
|
||||
-}
|
||||
update : (Msg a -> msg) -> Msg a -> Select a -> ( Select a, Cmd msg )
|
||||
update tagger msg select =
|
||||
Update.update Nothing msg select
|
||||
Update.update UpdateConfig.default msg select
|
||||
|> Tuple.mapSecond (Effect.perform tagger (\_ -> Cmd.none))
|
||||
|
||||
|
||||
{-| Update with an HTTP request to retrieve matching remote results.
|
||||
{-| Update with configuration options, including using an HTTP request to retrieve matching remote results.
|
||||
Note that in order to avoid an elm/http dependency in this package, you will need to provide the request Cmd yourself.
|
||||
|
||||
update : Msg -> Model -> ( Model, Cmd Msg )
|
||||
update msg model =
|
||||
case msg of
|
||||
Request SelectMsg subMsg ->
|
||||
Select.updateWithRequest SelectMsg (Select.request fetchThings) subMsg model.select
|
||||
Select.updateWith
|
||||
{ request = Select.request fetchThings
|
||||
, clearInputValueOnBlur = False
|
||||
, selectExactMatchOnBlur = True
|
||||
}
|
||||
SelectMsg
|
||||
subMsg
|
||||
model.select
|
||||
|> Tuple.mapFirst (\select -> { model | select = select })
|
||||
|
||||
fetchThings : String -> Cmd (Select.Msg Thing)
|
||||
@ -197,10 +205,21 @@ Note that in order to avoid an elm/http dependency in this package, you will nee
|
||||
, expect = Http.expectJson (Select.gotRequestResponse query) (Decode.list thingDecoder)
|
||||
}
|
||||
|
||||
You can also use [Select.UpdateConfig](Select-UpdateConfig) to build up a config:
|
||||
|
||||
Select.updateWith
|
||||
(Select.UpdateConfig.default
|
||||
|> Select.UpdateConfig.withRequest (Select.request fetchThings)
|
||||
)
|
||||
SelectMsg
|
||||
subMsg
|
||||
model.select
|
||||
|> Tuple.mapFirst (\select -> { model | select = select })
|
||||
|
||||
-}
|
||||
updateWithRequest : (Msg a -> msg) -> Request a -> Msg a -> Select a -> ( Select a, Cmd msg )
|
||||
updateWithRequest tagger req msg select =
|
||||
Update.update (Just req) msg select
|
||||
updateWith : UpdateConfig (Cmd (Msg a)) -> (Msg a -> msg) -> Msg a -> Select a -> ( Select a, Cmd msg )
|
||||
updateWith config tagger msg select =
|
||||
Update.update config msg select
|
||||
|> Tuple.mapSecond (Effect.perform identity identity >> Cmd.map tagger)
|
||||
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
module Select.Effect exposing
|
||||
( Effect
|
||||
, update, updateWithRequest, Request, request
|
||||
, update, updateWith, Request, request
|
||||
, perform, performWithRequest
|
||||
)
|
||||
|
||||
@ -14,7 +14,7 @@ module Select.Effect exposing
|
||||
|
||||
# Update Effect
|
||||
|
||||
@docs update, updateWithRequest, Request, request
|
||||
@docs update, updateWith, Request, request
|
||||
|
||||
|
||||
# Perform Effect
|
||||
@ -28,6 +28,7 @@ import Internal.Model exposing (Model)
|
||||
import Internal.Msg exposing (Msg)
|
||||
import Internal.Request as Request
|
||||
import Internal.Update as Update
|
||||
import Select.UpdateConfig as UpdateConfig exposing (UpdateConfig)
|
||||
|
||||
|
||||
{-| The Effect type
|
||||
@ -62,7 +63,7 @@ type alias Effect effect =
|
||||
-}
|
||||
update : Msg a -> Select a -> ( Select a, Effect Never )
|
||||
update =
|
||||
Update.update Nothing
|
||||
Update.update UpdateConfig.default
|
||||
|
||||
|
||||
{-| Update with an HTTP request. Note that in order to avoid an elm/http dependency in this package, you will need to provide the request Effect yourself.
|
||||
@ -96,9 +97,9 @@ update =
|
||||
}
|
||||
|
||||
-}
|
||||
updateWithRequest : Request effect -> Msg a -> Select a -> ( Select a, Effect effect )
|
||||
updateWithRequest req =
|
||||
Update.update (Just req)
|
||||
updateWith : UpdateConfig effect -> Msg a -> Select a -> ( Select a, Effect effect )
|
||||
updateWith =
|
||||
Update.update
|
||||
|
||||
|
||||
{-| A request that uses your Effect type
|
||||
|
@ -6,11 +6,15 @@ module Select.Request exposing (withDelay, withMinLength)
|
||||
update msg model =
|
||||
case msg of
|
||||
SelectMsg subMsg ->
|
||||
Select.updateWithRequest SelectMsg
|
||||
(Select.request fetchThings
|
||||
|> Select.Request.withDelay 200
|
||||
|> Select.Request.withMinLength 4
|
||||
Select.updateWith
|
||||
(Select.UpdateConfig.default
|
||||
|> Select.UpdateConfig.withRequest
|
||||
(Select.request fetchThings
|
||||
|> Select.Request.withDelay 200
|
||||
|> Select.Request.withMinLength 4
|
||||
)
|
||||
)
|
||||
SelectMsg
|
||||
subMsg
|
||||
model.select
|
||||
|> Tuple.mapFirst (\select -> { model | select = select })
|
||||
|
85
src/Select/UpdateConfig.elm
Normal file
85
src/Select/UpdateConfig.elm
Normal file
@ -0,0 +1,85 @@
|
||||
module Select.UpdateConfig exposing
|
||||
( UpdateConfig, default
|
||||
, withRequest, withClearInputValueOnBlur, withSelectExactMatchOnBlur
|
||||
)
|
||||
|
||||
{-| Build a config for updating the select
|
||||
|
||||
|
||||
# Type
|
||||
|
||||
@docs UpdateConfig, default
|
||||
|
||||
|
||||
# Configure
|
||||
|
||||
@docs withRequest, withClearInputValueOnBlur, withSelectExactMatchOnBlur
|
||||
|
||||
-}
|
||||
|
||||
import Internal.Request exposing (Request)
|
||||
|
||||
|
||||
{-| The UpdateConfig type
|
||||
-}
|
||||
type alias UpdateConfig effect =
|
||||
{ request : Maybe (Request effect)
|
||||
, clearInputValueOnBlur : Bool
|
||||
, selectExactMatchOnBlur : Bool
|
||||
}
|
||||
|
||||
|
||||
{-| The default config. No request, clears the input on blur if no selection and does not select an exact string match on blur.
|
||||
-}
|
||||
default : UpdateConfig Never
|
||||
default =
|
||||
{ request = Nothing
|
||||
, clearInputValueOnBlur = False
|
||||
, selectExactMatchOnBlur = False
|
||||
}
|
||||
|
||||
|
||||
{-| Update with an HTTP request to retrieve matching remote results.
|
||||
Note that in order to avoid an elm/http dependency in this package, you will need to provide the request Cmd yourself.
|
||||
|
||||
update : Msg -> Model -> ( Model, Cmd Msg )
|
||||
update msg model =
|
||||
case msg of
|
||||
Request SelectMsg subMsg ->
|
||||
Select.updateWith
|
||||
(Select.UpdateConfig.default
|
||||
|> Select.UpdateConfig.withRequest (Select.request fetchThings)
|
||||
)
|
||||
SelectMsg
|
||||
subMsg
|
||||
model.select
|
||||
|> Tuple.mapFirst (\select -> { model | select = select })
|
||||
|
||||
fetchThings : String -> Cmd (Select.Msg Thing)
|
||||
fetchThings query =
|
||||
Http.get
|
||||
{ url = "https://awesome-thing.api/things?search=" ++ query
|
||||
, expect = Http.expectJson (Select.gotRequestResponse query) (Decode.list thingDecoder)
|
||||
}
|
||||
|
||||
-}
|
||||
withRequest : Request effect -> UpdateConfig eff1 -> UpdateConfig effect
|
||||
withRequest request config =
|
||||
{ request = Just request
|
||||
, clearInputValueOnBlur = config.clearInputValueOnBlur
|
||||
, selectExactMatchOnBlur = config.selectExactMatchOnBlur
|
||||
}
|
||||
|
||||
|
||||
{-| Should the input value be cleared when the input loses focus? Setting this to False can be useful to make the select more like an autocomplete
|
||||
-}
|
||||
withClearInputValueOnBlur : Bool -> UpdateConfig effect -> UpdateConfig effect
|
||||
withClearInputValueOnBlur v config =
|
||||
{ config | clearInputValueOnBlur = v }
|
||||
|
||||
|
||||
{-| If the input value exactly matches the valueToString value of one of the options, set is as selected when the input loses focus.
|
||||
-}
|
||||
withSelectExactMatchOnBlur : Bool -> UpdateConfig effect -> UpdateConfig effect
|
||||
withSelectExactMatchOnBlur v config =
|
||||
{ config | selectExactMatchOnBlur = v }
|
Loading…
Reference in New Issue
Block a user