elm-select
Select things in style!
This is a fork of https://github.com/Confidenceman02/elm-select due to a dependency problem. Once the issue is fixed, you should use that package instead.
Single select
Multi select
Single native
Accessibility
- Keyboard accessible
- Screen reader accessible
NOTE: The multi select variant has accessibility features missing that will be added in future versions. You can read what those are in the Still to come section.
Styled with elm-css
In the case your program is not using elm-css already, an extra step will be required to make everything work. You can see how to do that in the Unstyling elm-css section.
Usage
Set an initial state in your model.
import Select
import Html exposing (Html)
import Html.Styled as Styled
type Country
= Australia
| Japan
| Taiwan
-- other countries
type alias Model =
{ selectState : Select.State
, items : List (Select.MenuItem Country)
, selectedCountry : Maybe Country
}
init : Model
init =
{ selectState = Select.initState
, items =
[ { item = Australia, label = "Australia" }
, { item = Japan, label = "Japan" }
, { item = Taiwan, label = "Taiwan" }
]
, selectedCountry = Nothing
}
Add a branch in your update to handle Msg
's from the view.
When updating, elm-select will return a Maybe Select.Action
, Select.State
and Cmd Select.Msg
that need to be handled.
Ignoring the maybeAction
for now, the update below shows how to persist the updatedSelectState
and map the selectCmds
's to your programs Cmd Msg
's.
type Msg
= SelectMsg (Select.Msg Country)
-- your other Msg's
update : Msg -> Model -> (Model, Cmd Msg)
update msg model =
case msg of
SelectMsg selectMsg ->
let
(maybeAction, updatedSelectState, selectCmds) =
Select.update selectMsg model.selectState
in
({ model | selectState = updatedSelectState }
, Cmd.map SelectMsg selectCmds
)
Handle the Action
in your update
Adding to the example above, the update is handling the maybeAction
value. This Action
value represents an event that you may want to react to.
Because there is a Country
type to represent the menu list items of the Select, we presumably want to know what country someone is from. To know when
a country is selected we are interested in the Select
action.
The Select
action will contain the Country
value that you can persist in your model to track what has been selected.
update : Msg -> Model -> (Model, Cmd Msg)
update msg model =
case msg of
SelectMsg selectMsg ->
let
(maybeAction, selectState, selectCmds) =
Select.update selectMsg model.selectState
newModel =
case maybeAction of
Just (Select someCountry) ->
{ model | selectedCountry = Just someCountry }
Just (ClearSingleSelectItem someCountry) ->
-- handle cleared
Just (DeselectMultiItem) ->
-- handle deselected
Just (InputChange value) ->
-- handle InputChange
Nothing ->
model
in
(newModel, Cmd.map SelectMsg selectCmds)
Render your Select view
The select view functions first argument is a Select.Config Country
value which can be built using our model.
selectedCountryToMenuItem : Country -> Select.MenuItem Country
selectedCountryToMenuItem country =
case country of
Australia -> { item = Australia, label = "Australia" }
Japan -> { item = Japan, label = "Japan" }
Taiwan -> { item = Taiwan, label = "Taiwan" }
-- other countries
renderSelect : Model -> Styled.Html (Select.Msg Country)
renderSelect model =
Select.view
((Select.single <| Maybe.map selectedCountryToMenuItem model.selectedCountry)
|> Select.state model.selectState
|> Select.menuItems model.items
|> Select.placeholder "Select your country"
)
(selectIdentifier "CountrySelector")
It is required to map the Select.Msg
that the Select.view
outputs to a Msg
type that our view
is compatible with.
The single and only Msg
we have set up is the SelectMsg (Select.Msg Country)
which satisfies the renderSelect
messages.
view : Model -> Html Msg
view model =
Html.map SelectMsg (renderSelect model)
Unstyling elm-css
Because all Elm programs emit Html
type messages which are not directly compatible with elm-css Styles.Html
type messages, an extra step will be required
to make everything compatible.
Elm-css exposes a toUnstyled
function that will convert Styled.Html
messages to Html
messages.
Read more about toUnstyled.
view : Model -> Html Msg
view model =
Html.map SelectMsg
(Styled.toUnstyled <| renderSelect model)
NOTE: You can use elm-select examples as a resource to help you set up Elm programs with elm-css.
Advanced
Opt in Javascript optimisation
When using a searchable Select, elm-select renders an input element that can accept keyboard input to filter the menu down to a specific menu item.
For stylistic reasons, this input element dynamically adjusts its width to just accommodate the text.
Without a javascript optimization elm-select achieves this via the size attribute on the input element. It's performant but not a completely ideal solution.
You can see by the gif below, the input width is always a little wider than the text. This is because the size attribute sets the width of the input element to a value that relates to a characters average size.
To allow for characters that are above average in size, elm-select exaggerates the size value to ensure no text outgrows the dynamic input width, but there may exist some edge cases where this doesn't happen.
Other pure Elm ways to achieve this involved querying DOM elements but it was found not to be a performant way to dynamically size the input as someone types. This due to how slow DOM queries are compared to how fast someone can input text. The result's were usually an input that widens slower than the text is typed which creates a lagging feel.
When you opt in to the Javascript optimization you get a zero lag, performant, dynamically sized input with a guarantee that text will never outgrow the input element width.
Javascript size: (1.3kb minified + gzipped).
Optimized example
Opting in to Javascript optimization
Your project will need a package.json
file to use the @confidenceman02/elm-select npm package. You can use the
example code as a reference to set up your project.
Set the jsOptimize flag in your programs init function.
By default the flag is False
.
init : Model
init =
{ selectState = Select.initState |> Select.jsOptimize True
, items =
[ { item = Australia, label = "Australia" }
, { item = Japan, label = "Japan" }
, { item = Taiwan, label = "Taiwan" }
]
, selectedCountry = Nothing
}
Importing the package
In your projects root directory:
npm install @confidenceman02/elm-select
Using the package
Import the minified script directly into your projects html file.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Viewer</title>
<script src="/node_modules/@confidenceman02/elm-select/dist/dynamic.min.js"></script>
</head>
<body>
<main></main>
<script src="index.js"></script>
</body>
</html>
Alternatively, you can import the module wherever you are initiating your Elm program.
import { Elm } from "./src/Main";
import "@confidenceman02/elm-select"
Elm.Main.init({
node: document.querySelector("main"),
flags: // your flags
})
Still to come
Accessibility
- Multi select tags to be keyboard navigable and dismissible.
- Selected multi select items announced by screen reader.
Customisable view elements
Why?: Elm-select view elements are not very customisable. there is not much flexibility to allow consumers to "brand" the Select.
How?:
- Expand the Select configuration to allow for custom styling.
- Allow consumers to entirely replace Select view elements with their own custom views.
Native variant consistency
- Loading config support
- LabelledBy config support
- Disabled config support