npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@confidenceman02/elm-select

v1.0.3

Published

JS optimizations for Confidenceman02/elm-select elm package

Downloads

59

Readme

elm-select

Select things in style! Inspired and built on top of the Kaizen design system Select component.

Single select

elm-select

Multi select

elm-select

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 elm-select view's 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 mdl =
    Html.map SelectMsg <| 
      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.

elm-select

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.

elm-select

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

elm-select

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.

Why not just use the Kaizen Select?

  • The Kaizen design system is a wonderful library with both react and elm components/views. Because the library makes many assumptions about the consuming project, it is not easily used by most Elm apps.

    Elm-select can be integrated into any Elm app with no special build requirements or non-Elm dependencies. In particular, it uses elm-css to provide it's own styles.

  • The Kaizen Elm Select has shortcomings around screen reader accessibility. Elm-select has addressed this by implementing the WAI-ARIA best practices for selects.