1module Element.Input exposing
4 , checkbox, defaultCheckbox
6 , Placeholder, placeholder
7 , username, newPassword, currentPassword, email, search, spellChecked
8 , slider, Thumb, thumb, defaultThumb
9 , radio, radioRow, Option, option, optionWith, OptionState(..)
10 , Label, labelAbove, labelBelow, labelLeft, labelRight, labelHidden
13{-| Input elements have a lot of constraints!
15We want all of our input elements to:
17 - _Always be accessible_
18 - _Behave intuitively_
19 - _Be completely restyleable_
21While these three goals may seem pretty obvious, Html and CSS have made it surprisingly difficult to achieve!
23And incredibly difficult for developers to remember all the tricks necessary to make things work. If you've every tried to make a `<textarea>` be the height of it's content or restyle a radio button while maintaining accessibility, you may be familiar.
25This module is intended to be accessible by default. You shouldn't have to wade through docs, articles, and books to find out [exactly how accessible your html actually is](https://www.powermapper.com/tests/screen-readers/aria/index.html).
30All Elements can be styled on focus by using [`Element.focusStyle`](Element#focusStyle) to set a global focus style or [`Element.focused`](Element#focused) to set a focus style individually for an element.
42A checkbox requires you to store a `Bool` in your model.
44This is also the first input element that has a [`required label`](#Label).
46 import Element exposing (text)
47 import Element.Input as Input
50 = GuacamoleChecked Bool
54 { onChange = GuacamoleChecked
55 , icon = Input.defaultCheckbox
56 , checked = model.guacamole
59 (text "Do you want Guacamole?")
62@docs checkbox, defaultCheckbox
69@docs Placeholder, placeholder
74If we want to play nicely with a browser's ability to autofill a form, we need to be able to give it a hint about what we're expecting.
76The following inputs are very similar to `Input.text`, but they give the browser a hint to allow autofill to work correctly.
78@docs username, newPassword, currentPassword, email, search, spellChecked
83A slider is great for choosing between a range of numerical values.
85 - **thumb** - The icon that you click and drag to change the value.
86 - **track** - The line behind the thumb denoting where you can slide to.
88@docs slider, Thumb, thumb, defaultThumb
93The fact that we still call this a radio selection is fascinating. I can't remember the last time I actually used an honest-to-goodness button on a radio. Chalk it up along with the floppy disk save icon or the word [Dashboard](https://en.wikipedia.org/wiki/Dashboard).
95Perhaps a better name would be `Input.chooseOne`, because this allows you to select one of a set of options!
97Nevertheless, here we are. Here's how you put one together
103 { onChange = ChooseLunch
104 , selected = Just model.lunch
105 , label = Input.labelAbove [] (text "Lunch")
107 [ Input.option Burrito (text "Burrito")
108 , Input.option Taco (text "Taco!")
109 , Input.option Gyro (text "Gyro")
113**Note** we're using `Input.option`, which will render the default radio icon you're probably used to. If you want compeltely custom styling, use `Input.optionWith`!
115@docs radio, radioRow, Option, option, optionWith, OptionState
120Every input has a required `Label`.
122@docs Label, labelAbove, labelBelow, labelLeft, labelRight, labelHidden
127You might be wondering where something like `<form>` is.
129What I've found is that most people who want `<form>` usually want it for the [implicit submission behavior](https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#implicit-submission) or to be clearer, they want to do something when the `Enter` key is pressed.
131Instead of implicit submission behavior, [try making an `onEnter` event handler like in this Ellie Example](https://ellie-app.com/5X6jBKtxzdpa1). Then everything is explicit!
133And no one has to look up obtuse html documentation to understand the behavior of their code :).
138You also might be wondering how to disable an input.
140Disabled inputs can be a little problematic for user experience, and doubly so for accessibility. This is because it's now your priority to inform the user _why_ some field is disabled.
142If an input is truly disabled, meaning it's not focusable or doesn't send off a `Msg`, you actually lose your ability to help the user out! For those wary about accessibility [this is a big problem.](https://ux.stackexchange.com/questions/103239/should-disabled-elements-be-focusable-for-accessibility-purposes)
144Here are some alternatives to think about that don't involve explicitly disabling an input.
146**Disabled Buttons** - Change the `Msg` it fires, the text that is rendered, and optionally set a `Region.description` which will be available to screen readers.
148 import Element.Input as Input
149 import Element.Region as Region
154 [ Background.color blue
157 Just SaveButtonPressed
159 text "Save blog post"
164 [ Background.color grey
166 "A publish date is required before saving a blogpost."
169 Just DisabledSaveButtonPressed
174Consider showing a hint if `DisabledSaveButtonPressed` is sent.
176For other inputs such as `Input.text`, consider simply rendering it in a normal `paragraph` or `el` if it's not editable.
178Alternatively, see if it's reasonable to _not_ display an input if you'd normally disable it. Is there an option where it's only visible when it's editable?
182import Element exposing (Attribute, Color, Element)
183import Element.Background as Background
184import Element.Border as Border
185import Element.Events as Events
186import Element.Font as Font
187import Element.Region as Region
189import Html.Attributes
191import Internal.Flag as Flag
192import Internal.Model as Internal
193import Internal.Style exposing (classes)
194import Json.Decode as Json
199 = Placeholder (List (Attribute msg)) (Element msg)
207 Element.rgb (186 / 255) (189 / 255) (182 / 255)
218placeholder : List (Attribute msg) -> Element msg -> Placeholder msg
232 = Label LabelLocation (List (Attribute msg)) (Element msg)
236isStacked : Label msg -> Bool
258labelRight : List (Attribute msg) -> Element msg -> Label msg
264labelLeft : List (Attribute msg) -> Element msg -> Label msg
270labelAbove : List (Attribute msg) -> Element msg -> Label msg
276labelBelow : List (Attribute msg) -> Element msg -> Label msg
281{-| Sometimes you may need to have a label which is not visible, but is still accessible to screen readers.
283Seriously consider a visible label before using this.
285The situations where a hidden label makes sense:
287 - A searchbar with a `search` button right next to it.
288 - A `table` of inputs where the header gives the label.
290Basically, a hidden label works when there are other contextual clues that sighted people can pick up on.
293labelHidden : String -> Label msg
298hiddenLabelAttribute label =
300 HiddenLabel textLabel ->
301 Internal.Describe (Internal.Label textLabel)
307{-| A standard button.
309The `onPress` handler will be fired either `onClick` or when the element is focused and the `Enter` key has been pressed.
311 import Element exposing (rgb255, text)
312 import Element.Background as Background
313 import Element.Input as Input
316 Element.rgb255 238 238 238
320 [ Background.color blue
322 [ Background.color purple ]
324 { onPress = Just ClickMsg
325 , label = text "My Button"
332 { onPress : Maybe msg
333 , label : Element msg
336button attrs { onPress, label } =
339 -- We don't explicitly label this node as a button,
340 -- because buttons fire a bunch of times when you hold down the enter key.
341 -- We'd like to fire just once on the enter key, which means using keyup instead of keydown.
342 -- Because we have no way to disable keydown, though our messages get doubled.
344 (Element.width Element.shrink
345 :: Element.height Element.shrink
346 :: Internal.htmlClass
347 (classes.contentCenterX
349 ++ classes.contentCenterY
353 ++ classes.noTextSelection
356 :: focusDefault attrs
357 :: Internal.Describe Internal.Button
358 :: Internal.Attr (Html.Attributes.tabindex 0)
361 Internal.Attr (Html.Attributes.disabled True) :: attrs
369 (Internal.Unkeyed [ label ])
373 if List.any hasFocusStyle attrs then
377 Internal.htmlClass "focusable"
382 Internal.StyleClass _ (Internal.PseudoSelector Internal.Focus _) ->
390type alias Checkbox msg =
391 { onChange : Maybe (Bool -> msg)
392 , icon : Maybe (Element msg)
400 - **onChange** - The `Msg` to send.
401 - **icon** - The checkbox icon to show. This can be whatever you'd like, but `Input.defaultCheckbox` is included to get you started.
402 - **checked** - The current checked state.
403 - **label** - The [`Label`](#Label) for this checkbox
409 { onChange : Bool -> msg
410 , icon : Bool -> Element msg
415checkbox attrs { label, icon, checked, onChange } =
418 [ if isHiddenLabel label then
424 , Internal.Attr (Html.Events.onClick (onChange (not checked)))
428 if code == enter then
429 Just <| onChange (not checked)
431 else if code == space then
432 Just <| onChange (not checked)
439 , Element.width Element.fill
443 applyLabel attributes
449 Html.Attributes.attribute "role" "checkbox"
451 Html.Attributes.attribute "aria-checked" <|
457 , hiddenLabelAttribute label
459 , Element.height Element.fill
460 , Element.width Element.shrink
471 = Thumb (List (Attribute Never))
475thumb : List (Attribute Never) -> Thumb
484 [ Element.width (Element.px 16)
485 , Element.height (Element.px 16)
488 , Border.color (Element.rgb 0.5 0.5 0.5)
489 , Background.color (Element.rgb 1 1 1)
493{-| A slider input, good for capturing float values.
496 [ Element.height (Element.px 30)
498 -- Here is where we're creating/styling the "track"
499 , Element.behindContent
501 [ Element.width Element.fill
502 , Element.height (Element.px 2)
504 , Background.color grey
510 { onChange = AdjustValue
513 (text "My Slider Value")
517 , value = model.sliderValue
522`Element.behindContent` is used to render the track of the slider. Without it, no track would be rendered. The `thumb` is the icon that you can move around.
524The slider can be vertical or horizontal depending on the width/height of the slider.
526 - `height fill` and `width (px someWidth)` will cause the slider to be vertical.
527 - `height (px someHeight)` and `width (px someWidth)` where `someHeight` > `someWidth` will also do it.
528 - otherwise, the slider will be horizontal.
530**Note** If you want a slider for an `Int` value:
532 - set `step` to be `Just 1`, or some other whole value
533 - `value = toFloat model.myInt`
534 - And finally, round the value before making a message `onChange = round >> AdjustValue`
540 { onChange : Float -> msg
549slider attributes input =
551 (Thumb thumbAttributes) =
555 Internal.getWidth thumbAttributes
558 Internal.getHeight thumbAttributes
561 case ( trackWidth, trackHeight ) of
562 ( Nothing, Nothing ) ->
565 ( Just (Internal.Px w), Just (Internal.Px h) ) ->
568 ( Just (Internal.Px _), Just (Internal.Fill _) ) ->
575 Internal.getHeight attributes
578 Internal.getWidth attributes
580 ( spacingX, spacingY ) =
581 Internal.getSpacing attributes ( 5, 5 )
584 (input.value - input.min)
585 / (input.max - input.min)
590 - Width/Height of thumb so that the input can shadow it.
610 "thmb-" ++ thumbWidthString ++ "-" ++ thumbHeightString
617 Just (Internal.Px px) ->
618 String.fromInt px ++ "px"
628 Just (Internal.Px px) ->
629 String.fromInt px ++ "px"
635 [ Internal.Property "width"
637 , Internal.Property "height"
642 [ if isHiddenLabel input.label then
646 Element.spacingXY spacingX spacingY
653 Just (Internal.Px _) ->
664 Just (Internal.Px _) ->
674 (Maybe.withDefault Element.fill trackWidth)
676 (Maybe.withDefault (Element.px 20) trackHeight)
680 (Internal.NodeName "input")
681 [ hiddenLabelAttribute input.label
682 , Internal.StyleClass Flag.active
684 ("input[type=\"range\"]." ++ className ++ "::-moz-range-thumb")
687 , Internal.StyleClass Flag.hover
689 ("input[type=\"range\"]." ++ className ++ "::-webkit-slider-thumb")
692 , Internal.StyleClass Flag.focus
694 ("input[type=\"range\"]." ++ className ++ "::-ms-thumb")
697 , Internal.Attr (Html.Attributes.class (className ++ " focusable-parent"))
701 case String.toFloat str of
703 -- This should never happen because the browser
704 -- should always provide a Float.
712 Html.Attributes.type_ "range"
717 -- Note: If we set `any` here,
718 -- Firefox makes a single press of the arrows keys equal to 1
719 -- We could set the step manually to the effective range / 100
720 -- String.fromFloat ((input.max - input.min) / 100)
721 -- Which matches Chrome's default behavior
722 -- HOWEVER, that means manually moving a slider with the mouse will snap to that interval.
726 String.fromFloat step
729 Html.Attributes.min (String.fromFloat input.min)
731 Html.Attributes.max (String.fromFloat input.max)
733 Html.Attributes.value (String.fromFloat input.value)
736 Html.Attributes.attribute "orient" "vertical"
742 Maybe.withDefault (Element.px 20) trackHeight
745 Maybe.withDefault Element.fill trackWidth
748 Maybe.withDefault Element.fill trackWidth
751 Maybe.withDefault (Element.px 20) trackHeight
753 (Internal.Unkeyed [])
756 (Maybe.withDefault Element.fill trackWidth)
758 (Maybe.withDefault (Element.px 20) trackHeight)
760 -- This is after `attributes` because the thumb should be in front of everything.
761 ++ [ Element.behindContent <|
763 viewVerticalThumb factor thumbAttributes trackWidth
766 viewHorizontalThumb factor thumbAttributes trackHeight
774viewHorizontalThumb factor thumbAttributes trackHeight =
776 [ Element.width Element.fill
777 , Element.height (Maybe.withDefault Element.fill trackHeight)
781 [ Element.width (Element.fillPortion (round <| factor * 10000))
786 :: List.map (Internal.mapAttr Basics.never) thumbAttributes
790 [ Element.width (Element.fillPortion (round <| (abs <| 1 - factor) * 10000))
796viewVerticalThumb factor thumbAttributes trackWidth =
798 [ Element.height Element.fill
799 , Element.width (Maybe.withDefault Element.fill trackWidth)
803 [ Element.height (Element.fillPortion (round <| (abs <| 1 - factor) * 10000))
808 :: List.map (Internal.mapAttr Basics.never) thumbAttributes
812 [ Element.height (Element.fillPortion (round <| factor * 10000))
818type alias TextInput =
820 , spellchecked : Bool
821 , autofill : Maybe String
826 = TextInputNode String
832 { onChange : String -> msg
834 , placeholder : Maybe (Placeholder msg)
840textHelper : TextInput -> List (Attribute msg) -> Text msg -> Element msg
841textHelper textInput attrs textOptions =
844 defaultTextBoxStyle ++ attrs
847 redistribute (textInput.type_ == TextArea)
848 (isStacked textOptions.label)
853 Internal.StyleClass _ (Internal.SpacingStyle _ _ _) ->
861 Internal.StyleClass cls (Internal.PaddingStyle pad t r b l) ->
862 -- The - 3 is here to prevent accidental triggering of scrollbars
863 -- when things are off by a pixel or two.
864 -- (or at least when the browser *thinks* it's off by a pixel or two)
866 { top = max 0 (floor (toFloat t - 3))
867 , right = max 0 (floor (toFloat r - 3))
868 , bottom = max 0 (floor (toFloat b - 3))
869 , left = max 0 (floor (toFloat l - 3))
876 case textInput.type_ of
877 TextInputNode inputType ->
882 |> List.filterMap getHeight
885 |> Maybe.map isConstrained
886 |> Maybe.withDefault False
890 |> List.filterMap getPadding
903 (case textInput.type_ of
904 TextInputNode inputType ->
905 Internal.NodeName "input"
908 Internal.NodeName "textarea"
910 ((case textInput.type_ of
911 TextInputNode inputType ->
912 -- Note: Due to a weird edgecase in...Edge...
913 -- `type` needs to come _before_ `value`
914 -- More reading: https://github.com/mdgriffith/elm-ui/pull/94/commits/4f493a27001ccc3cf1f2baa82e092c35d3811876
915 [ Internal.Attr (Html.Attributes.type_ inputType)
916 , Internal.htmlClass classes.inputText
921 , Element.height Element.fill
922 , Internal.htmlClass classes.inputMultiline
923 , calcMoveToCompensateForPadding withDefaults
925 -- The only reason we do this padding trick is so that when the user clicks in the padding,
926 -- that the cursor will reset correctly.
927 -- This could probably be combined with the above `calcMoveToCompensateForPadding`
928 , Element.paddingEach parentPadding
929 , Internal.Attr (Html.Attributes.style "margin" (renderBox (negateBox parentPadding)))
930 , Internal.Attr (Html.Attributes.style "box-sizing" "content-box")
933 ++ [ value textOptions.text
934 , Internal.Attr (Html.Events.onInput textOptions.onChange)
935 , hiddenLabelAttribute textOptions.label
936 , spellcheck textInput.spellchecked
937 , Maybe.map autofill textInput.autofill
938 |> Maybe.withDefault Internal.NoAttribute
940 ++ redistributed.input
942 (Internal.Unkeyed [])
945 case textInput.type_ of
947 -- textarea with height-content means that
948 -- the input element is rendered `inFront` with a transparent background
949 -- Then the input text is rendered as the space filling element.
953 ((if heightConstrained then
954 (::) Element.scrollbarY
959 [ Element.width Element.fill
960 , Internal.htmlClass classes.focusedWithin
961 , Internal.htmlClass classes.inputMultilineWrapper
963 ++ redistributed.parent
969 (Element.width Element.fill
970 :: Element.height Element.fill
971 :: Element.inFront inputElement
972 :: Internal.htmlClass classes.inputMultilineParent
973 :: redistributed.wrapper
976 (if textOptions.text == "" then
977 case textOptions.placeholder of
979 -- Without this, firefox will make the text area lose focus
980 -- if the input is empty and you mash the keyboard
981 [ Element.text "\u{00A0}"
985 [ renderPlaceholder place
987 (textOptions.text == "")
992 (Html.span [ Html.Attributes.class classes.inputMultilineFiller ]
993 -- We append a non-breaking space to the end of the content so that newlines don't get chomped.
994 [ Html.text (textOptions.text ++ "\u{00A0}")
1003 TextInputNode inputType ->
1007 (Element.width Element.fill
1008 :: Internal.htmlClass classes.focusedWithin
1010 [ redistributed.parent
1011 , case textOptions.placeholder of
1016 [ Element.behindContent
1017 (renderPlaceholder place redistributed.cover (textOptions.text == ""))
1021 (Internal.Unkeyed [ inputElement ])
1024 (Internal.Class Flag.cursor classes.cursorText
1025 :: (if isHiddenLabel textOptions.label then
1026 Internal.NoAttribute
1033 :: redistributed.fullParent
1041 Internal.Height h ->
1049 { top = negate box.top
1050 , right = negate box.right
1051 , bottom = negate box.bottom
1052 , left = negate box.left
1056renderBox { top, right, bottom, left } =
1059 ++ String.fromInt right
1061 ++ String.fromInt bottom
1063 ++ String.fromInt left
1067renderPlaceholder (Placeholder placeholderAttrs placeholderEl) forPlaceholder on =
1070 ++ [ Font.color charcoal
1071 , Internal.htmlClass (classes.noTextSelection ++ " " ++ classes.passPointerEvents)
1073 , Border.color (Element.rgba 0 0 0 0)
1074 , Background.color (Element.rgba 0 0 0 0)
1075 , Element.height Element.fill
1076 , Element.width Element.fill
1090{-| Because textareas are now shadowed, where they're rendered twice,
1091we to move the literal text area up because spacing is based on line height.
1093calcMoveToCompensateForPadding : List (Attribute msg) -> Attribute msg
1094calcMoveToCompensateForPadding attrs =
1096 gatherSpacing attr found =
1098 Internal.StyleClass _ (Internal.SpacingStyle _ x y) ->
1109 case List.foldr gatherSpacing Nothing attrs of
1111 Internal.NoAttribute
1114 Element.moveUp (toFloat vSpace / 2)
1117{-| Given the list of attributes provided to `Input.multiline` or `Input.text`,
1119redistribute them to the parent, the input, or the cover.
1121 - fullParent -> Wrapper around label and input
1122 - parent -> parent of wrapper
1123 - wrapper -> the element that is here to take up space.
1124 - cover -> things like placeholders or text areas which are layered on top of input.
1125 - input -> actual input element
1131 -> List (Attribute msg)
1133 { fullParent : List (Attribute msg)
1134 , parent : List (Attribute msg)
1135 , wrapper : List (Attribute msg)
1136 , input : List (Attribute msg)
1137 , cover : List (Attribute msg)
1139redistribute isMultiline stacked attrs =
1140 List.foldl (redistributeOver isMultiline stacked)
1149 { parent = List.reverse redist.parent
1150 , fullParent = List.reverse redist.fullParent
1151 , wrapper = List.reverse redist.wrapper
1152 , input = List.reverse redist.input
1153 , cover = List.reverse redist.cover
1230{-| isStacked means that the label is above or below
1232redistributeOver isMultiline stacked attr els =
1234 Internal.Nearby _ _ ->
1235 { els | parent = attr :: els.parent }
1237 Internal.Width width ->
1239 { els | fullParent = attr :: els.fullParent }
1241 else if isFill width then
1243 | fullParent = attr :: els.fullParent
1244 , parent = attr :: els.parent
1247 else if isPixel width then
1248 { els | parent = attr :: els.parent }
1252 | parent = attr :: els.parent
1255 Internal.Height height ->
1258 | fullParent = attr :: els.fullParent
1259 , parent = attr :: els.parent
1262 else if isFill height then
1264 | fullParent = attr :: els.fullParent
1265 , parent = attr :: els.parent
1268 else if isPixel height then
1269 { els | parent = attr :: els.parent }
1273 | parent = attr :: els.parent
1276 Internal.AlignX _ ->
1277 { els | fullParent = attr :: els.fullParent }
1279 Internal.AlignY _ ->
1280 { els | fullParent = attr :: els.fullParent }
1282 Internal.StyleClass _ (Internal.SpacingStyle _ _ _) ->
1284 | fullParent = attr :: els.fullParent
1285 , parent = attr :: els.parent
1286 , input = attr :: els.input
1287 , wrapper = attr :: els.wrapper
1290 Internal.StyleClass cls (Internal.PaddingStyle pad t r b l) ->
1293 | parent = attr :: els.parent
1294 , cover = attr :: els.cover
1300 Element.htmlAttribute
1301 (Html.Attributes.style
1303 ("calc(1.0em + " ++ String.fromInt (2 * min t b) ++ "px)")
1307 Element.htmlAttribute
1308 (Html.Attributes.style
1310 ("calc(1.0em + " ++ String.fromInt (2 * min t b) ++ "px)")
1313 reducedVerticalPadding =
1317 , bottom = b - min t b
1322 | parent = reducedVerticalPadding :: els.parent
1323 , input = newHeight :: newLineHeight :: els.input
1324 , cover = attr :: els.cover
1327 Internal.StyleClass _ (Internal.BorderWidth _ _ _ _ _) ->
1329 | parent = attr :: els.parent
1330 , cover = attr :: els.cover
1333 Internal.StyleClass _ (Internal.Transform _) ->
1335 | parent = attr :: els.parent
1336 , cover = attr :: els.cover
1339 Internal.StyleClass _ (Internal.FontSize _) ->
1340 { els | fullParent = attr :: els.fullParent }
1342 Internal.StyleClass _ (Internal.FontFamily _ _) ->
1343 { els | fullParent = attr :: els.fullParent }
1345 Internal.StyleClass flag cls ->
1346 { els | parent = attr :: els.parent }
1348 Internal.NoAttribute ->
1352 { els | input = attr :: els.input }
1354 Internal.Describe _ ->
1355 { els | input = attr :: els.input }
1357 Internal.Class _ _ ->
1358 { els | parent = attr :: els.parent }
1360 Internal.TransformComponent _ _ ->
1361 { els | input = attr :: els.input }
1366 List (Attribute msg)
1368 { onChange : String -> msg
1370 , placeholder : Maybe (Placeholder msg)
1376 { type_ = TextInputNode "text"
1377 , spellchecked = False
1378 , autofill = Nothing
1382{-| If spell checking is available, this input will be spellchecked.
1385 List (Attribute msg)
1387 { onChange : String -> msg
1389 , placeholder : Maybe (Placeholder msg)
1395 { type_ = TextInputNode "text"
1396 , spellchecked = True
1397 , autofill = Nothing
1403 List (Attribute msg)
1405 { onChange : String -> msg
1407 , placeholder : Maybe (Placeholder msg)
1413 { type_ = TextInputNode "search"
1414 , spellchecked = False
1415 , autofill = Nothing
1419{-| A password input that allows the browser to autofill.
1421It's `newPassword` instead of just `password` because it gives the browser a hint on what type of password input it is.
1423A password takes all the arguments a normal `Input.text` would, and also **show**, which will remove the password mask (e.g. `****` vs `pass1234`)
1427 List (Attribute msg)
1429 { onChange : String -> msg
1431 , placeholder : Maybe (Placeholder msg)
1436newPassword attrs pass =
1445 , spellchecked = False
1446 , autofill = Just "new-password"
1449 { onChange = pass.onChange
1451 , placeholder = pass.placeholder
1452 , label = pass.label
1458 List (Attribute msg)
1460 { onChange : String -> msg
1462 , placeholder : Maybe (Placeholder msg)
1467currentPassword attrs pass =
1476 , spellchecked = False
1477 , autofill = Just "current-password"
1480 { onChange = pass.onChange
1482 , placeholder = pass.placeholder
1483 , label = pass.label
1489 List (Attribute msg)
1491 { onChange : String -> msg
1493 , placeholder : Maybe (Placeholder msg)
1499 { type_ = TextInputNode "text"
1500 , spellchecked = False
1501 , autofill = Just "username"
1507 List (Attribute msg)
1509 { onChange : String -> msg
1511 , placeholder : Maybe (Placeholder msg)
1517 { type_ = TextInputNode "email"
1518 , spellchecked = False
1519 , autofill = Just "email"
1523{-| A multiline text input.
1525By default it will have a minimum height of one line and resize based on it's contents.
1529 List (Attribute msg)
1531 { onChange : String -> msg
1533 , placeholder : Maybe (Placeholder msg)
1538multiline attrs multi =
1542 , spellchecked = multi.spellcheck
1543 , autofill = Nothing
1546 { onChange = multi.onChange
1548 , placeholder = multi.placeholder
1549 , label = multi.label
1553isHiddenLabel label =
1562applyLabel : List (Attribute msg) -> Label msg -> Element msg -> Element msg
1563applyLabel attrs label input =
1565 HiddenLabel labelText ->
1566 -- NOTE: This means that the label is applied outside of this function!
1567 -- It would be nice to unify this logic, but it's a little tricky
1570 (Internal.NodeName "label")
1572 (Internal.Unkeyed [ input ])
1574 Label position labelAttrs labelChild ->
1581 (Internal.Unkeyed [ labelChild ])
1587 (Internal.NodeName "label")
1589 (Internal.Unkeyed [ labelElement, input ])
1594 (Internal.NodeName "label")
1596 (Internal.Unkeyed [ input, labelElement ])
1601 (Internal.NodeName "label")
1603 (Internal.Unkeyed [ input, labelElement ])
1608 (Internal.NodeName "label")
1610 (Internal.Unkeyed [ labelElement, input ])
1614type Option value msg
1615 = Option value (OptionState -> Element msg)
1625{-| Add a choice to your radio element. This will be rendered with the default radio icon.
1627option : value -> Element msg -> Option value msg
1629 Option val (defaultRadioOption txt)
1632{-| Customize exactly what your radio option should look like in different states.
1634optionWith : value -> (OptionState -> Element msg) -> Option value msg
1635optionWith val view =
1641 List (Attribute msg)
1643 { onChange : option -> msg
1644 , options : List (Option option msg)
1645 , selected : Maybe option
1653{-| Same as radio, but displayed as a row
1656 List (Attribute msg)
1658 { onChange : option -> msg
1659 , options : List (Option option msg)
1660 , selected : Maybe option
1668defaultRadioOption : Element msg -> OptionState -> Element msg
1669defaultRadioOption optionLabel status =
1671 [ Element.spacing 10
1673 , Element.width Element.shrink
1676 [ Element.width (Element.px 14)
1677 , Element.height (Element.px 14)
1678 , Background.color white
1682 Internal.htmlClass "focusable"
1685 Internal.NoAttribute
1687 -- , Border.shadow <|
1688 -- -- case status of
1690 -- -- { offset = ( 0, 0 )
1693 -- -- , color = Color.rgb 235 235 235
1696 -- -- { offset = ( 0, 0 )
1699 -- -- , color = Color.rgba 235 235 235 0
1702 -- { offset = ( 0, 0 )
1705 -- , color = Color.rgba 235 235 235 0
1720 Element.rgb (208 / 255) (208 / 255) (208 / 255)
1723 Element.rgb (208 / 255) (208 / 255) (208 / 255)
1726 Element.rgb (59 / 255) (153 / 255) (252 / 255)
1729 , Element.el [ Element.width Element.fill, Internal.htmlClass "unfocusable" ] optionLabel
1735 -> List (Attribute msg)
1737 { onChange : option -> msg
1738 , options : List (Option option msg)
1739 , selected : Maybe option
1743radioHelper orientation attrs input =
1745 renderOption (Option val view) =
1748 if Just val == input.selected then
1756 , case orientation of
1758 Element.width Element.shrink
1761 Element.width Element.fill
1762 , Events.onClick (input.onChange val)
1766 Html.Attributes.attribute "aria-checked"
1771 Html.Attributes.attribute "aria-checked"
1774 Html.Attributes.attribute "role" "radio"
1781 row (hiddenLabelAttribute input.label :: attrs)
1782 (List.map renderOption input.options)
1785 column (hiddenLabelAttribute input.label :: attrs)
1786 (List.map renderOption input.options)
1789 case input.options of
1793 (Option val _) :: _ ->
1794 List.foldl track ( NotFound, val, val ) input.options
1795 |> (\( found, b, a ) ->
1807 track opt ( found, prev, nxt ) =
1812 if Just val == input.selected then
1813 ( BeforeFound, prev, nxt )
1819 ( AfterFound, prev, val )
1822 ( found, prev, nxt )
1830 Internal.Width (Internal.Fill _) ->
1833 Internal.Height (Internal.Fill _) ->
1843 (List.filterMap identity
1844 [ Just Element.alignLeft
1846 , Just (Internal.htmlClass "focus")
1847 , Just Region.announce
1850 Html.Attributes.attribute "role" "radiogroup"
1855 Just ( prev, next ) ->
1859 if code == leftArrow then
1860 Just (input.onChange prev)
1862 else if code == upArrow then
1863 Just (input.onChange prev)
1865 else if code == rightArrow then
1866 Just (input.onChange next)
1868 else if code == downArrow then
1869 Just (input.onChange next)
1871 else if code == space then
1872 case input.selected of
1874 Just (input.onChange prev)
1884 -- ++ hideIfEverythingisInvisible
1901column : List (Attribute msg) -> List (Internal.Element msg) -> Internal.Element msg
1902column attributes children =
1906 (Element.height Element.shrink
1907 :: Element.width Element.fill
1910 (Internal.Unkeyed children)
1913row : List (Attribute msg) -> List (Internal.Element msg) -> Internal.Element msg
1914row attributes children =
1918 (Element.width Element.fill
1921 (Internal.Unkeyed children)
1929onEnter : msg -> Attribute msg
1935onSpace : msg -> Attribute msg
1941onUpArrow : msg -> Attribute msg
1947onRightArrow : msg -> Attribute msg
1949 onKey rightArrow msg
1953onLeftArrow : msg -> Attribute msg
1959onDownArrow : msg -> Attribute msg
2010onKey : String -> msg -> Attribute msg
2011onKey desiredCode msg =
2014 if code == desiredCode then
2018 Json.fail "Not the enter key"
2021 Json.field "key" Json.string
2022 |> Json.andThen decode
2025 Html.Events.preventDefaultOn "keyup"
2026 (Json.map (\fired -> ( fired, True )) isKey)
2030-- preventKeydown : String -> a -> Attribute a
2031-- preventKeydown desiredCode msg =
2034-- if code == desiredCode then
2037-- Json.fail "Not the enter key"
2039-- Json.field "key" Json.string
2040-- |> Json.andThen decode
2042-- Events.onWithOptions "keydown"
2043-- { stopPropagation = False
2044-- , preventDefault = True
2050onKeyLookup : (String -> Maybe msg) -> Attribute msg
2056 Json.fail "No key matched"
2062 Json.field "key" Json.string
2063 |> Json.andThen decode
2065 Internal.Attr <| Html.Events.on "keyup" isKey
2069onFocusOut : msg -> Attribute msg
2071 Internal.Attr <| Html.Events.on "focusout" (Json.succeed msg)
2075onFocusIn : msg -> Attribute msg
2077 Internal.Attr <| Html.Events.on "focusin" (Json.succeed msg)
2080selected : Bool -> Attribute msg
2082 Internal.Attr << Html.Attributes.selected
2085name : String -> Attribute msg
2087 Internal.Attr << Html.Attributes.name
2090value : String -> Attribute msg
2092 Internal.Attr << Html.Attributes.value
2095tabindex : Int -> Attribute msg
2097 Internal.Attr << Html.Attributes.tabindex
2100disabled : Bool -> Attribute msg
2102 Internal.Attr << Html.Attributes.disabled
2105spellcheck : Bool -> Attribute msg
2107 Internal.Attr << Html.Attributes.spellcheck
2110readonly : Bool -> Attribute msg
2112 Internal.Attr << Html.Attributes.readonly
2115autofill : String -> Attribute msg
2117 Internal.Attr << Html.Attributes.attribute "autocomplete"
2120{-| Attach this attribute to any `Input` that you would like to be automatically focused when the page loads.
2122You should only have a maximum of one per page.
2125focusedOnLoad : Attribute msg
2127 Internal.Attr <| Html.Attributes.autofocus True
2134defaultTextBoxStyle : List (Attribute msg)
2135defaultTextBoxStyle =
2136 [ defaultTextPadding
2138 , Border.color darkGrey
2139 , Background.color white
2142 , Element.width Element.fill
2143 , Element.height Element.shrink
2147defaultTextPadding : Attribute msg
2149 Element.paddingXY 12 12
2152{-| The blue default checked box icon.
2154You'll likely want to make your own checkbox at some point that fits your design.
2157defaultCheckbox : Bool -> Element msg
2158defaultCheckbox checked =
2160 [ Internal.htmlClass "focusable"
2161 , Element.width (Element.px 14)
2162 , Element.height (Element.px 14)
2170 Element.rgb (59 / 255) (153 / 255) (252 / 255)
2173 Element.rgb (211 / 255) (211 / 255) (211 / 255)
2180 Element.rgba (238 / 255) (238 / 255) (238 / 255) 0
2183 Element.rgb (238 / 255) (238 / 255) (238 / 255)
2185 , Background.color <|
2187 Element.rgb (59 / 255) (153 / 255) (252 / 255)
2200 [ Border.color white
2201 , Element.height (Element.px 6)
2202 , Element.width (Element.px 9)
2203 , Element.rotate (degrees -45)
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list