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 , if List.any hasFocusStyle withDefaults then
964 Internal.htmlClass classes.focusedWithin
965 , Internal.htmlClass classes.inputMultilineWrapper
967 ++ redistributed.parent
973 (Element.width Element.fill
974 :: Element.height Element.fill
975 :: Element.inFront inputElement
976 :: Internal.htmlClass classes.inputMultilineParent
977 :: redistributed.wrapper
980 (if textOptions.text == "" then
981 case textOptions.placeholder of
983 -- Without this, firefox will make the text area lose focus
984 -- if the input is empty and you mash the keyboard
985 [ Element.text "\u{00A0}"
989 [ renderPlaceholder place
991 (textOptions.text == "")
996 (Html.span [ Html.Attributes.class classes.inputMultilineFiller ]
997 -- We append a non-breaking space to the end of the content so that newlines don't get chomped.
998 [ Html.text (textOptions.text ++ "\u{00A0}")
1007 TextInputNode inputType ->
1011 (Element.width Element.fill
1012 :: (if List.any hasFocusStyle withDefaults then
1013 Internal.NoAttribute
1016 Internal.htmlClass classes.focusedWithin
1019 [ redistributed.parent
1020 , case textOptions.placeholder of
1025 [ Element.behindContent
1026 (renderPlaceholder place redistributed.cover (textOptions.text == ""))
1030 (Internal.Unkeyed [ inputElement ])
1033 (Internal.Class Flag.cursor classes.cursorText
1034 :: (if isHiddenLabel textOptions.label then
1035 Internal.NoAttribute
1042 :: redistributed.fullParent
1050 Internal.Height h ->
1058 { top = negate box.top
1059 , right = negate box.right
1060 , bottom = negate box.bottom
1061 , left = negate box.left
1065renderBox { top, right, bottom, left } =
1068 ++ String.fromInt right
1070 ++ String.fromInt bottom
1072 ++ String.fromInt left
1076renderPlaceholder (Placeholder placeholderAttrs placeholderEl) forPlaceholder on =
1079 ++ [ Font.color charcoal
1080 , Internal.htmlClass (classes.noTextSelection ++ " " ++ classes.passPointerEvents)
1082 , Border.color (Element.rgba 0 0 0 0)
1083 , Background.color (Element.rgba 0 0 0 0)
1084 , Element.height Element.fill
1085 , Element.width Element.fill
1099{-| Because textareas are now shadowed, where they're rendered twice,
1100we to move the literal text area up because spacing is based on line height.
1102calcMoveToCompensateForPadding : List (Attribute msg) -> Attribute msg
1103calcMoveToCompensateForPadding attrs =
1105 gatherSpacing attr found =
1107 Internal.StyleClass _ (Internal.SpacingStyle _ x y) ->
1118 case List.foldr gatherSpacing Nothing attrs of
1120 Internal.NoAttribute
1123 Element.moveUp (toFloat (floor (toFloat vSpace / 2)))
1126{-| Given the list of attributes provided to `Input.multiline` or `Input.text`,
1128redistribute them to the parent, the input, or the cover.
1130 - fullParent -> Wrapper around label and input
1131 - parent -> parent of wrapper
1132 - wrapper -> the element that is here to take up space.
1133 - cover -> things like placeholders or text areas which are layered on top of input.
1134 - input -> actual input element
1140 -> List (Attribute msg)
1142 { fullParent : List (Attribute msg)
1143 , parent : List (Attribute msg)
1144 , wrapper : List (Attribute msg)
1145 , input : List (Attribute msg)
1146 , cover : List (Attribute msg)
1148redistribute isMultiline stacked attrs =
1149 List.foldl (redistributeOver isMultiline stacked)
1158 { parent = List.reverse redist.parent
1159 , fullParent = List.reverse redist.fullParent
1160 , wrapper = List.reverse redist.wrapper
1161 , input = List.reverse redist.input
1162 , cover = List.reverse redist.cover
1239{-| isStacked means that the label is above or below
1241redistributeOver isMultiline stacked attr els =
1243 Internal.Nearby _ _ ->
1244 { els | parent = attr :: els.parent }
1246 Internal.Width width ->
1247 if isFill width then
1249 | fullParent = attr :: els.fullParent
1250 , parent = attr :: els.parent
1251 , input = attr :: els.input
1254 else if stacked then
1256 | fullParent = attr :: els.fullParent
1261 | parent = attr :: els.parent
1264 Internal.Height height ->
1267 | fullParent = attr :: els.fullParent
1268 , parent = attr :: els.parent
1271 else if isFill height then
1273 | fullParent = attr :: els.fullParent
1274 , parent = attr :: els.parent
1277 else if isPixel height then
1278 { els | parent = attr :: els.parent }
1282 | parent = attr :: els.parent
1285 Internal.AlignX _ ->
1286 { els | fullParent = attr :: els.fullParent }
1288 Internal.AlignY _ ->
1289 { els | fullParent = attr :: els.fullParent }
1291 Internal.StyleClass _ (Internal.SpacingStyle _ _ _) ->
1293 | fullParent = attr :: els.fullParent
1294 , parent = attr :: els.parent
1295 , input = attr :: els.input
1296 , wrapper = attr :: els.wrapper
1299 Internal.StyleClass cls (Internal.PaddingStyle pad t r b l) ->
1302 | parent = attr :: els.parent
1303 , cover = attr :: els.cover
1309 Element.htmlAttribute
1310 (Html.Attributes.style
1312 ("calc(1.0em + " ++ String.fromInt (2 * min t b) ++ "px)")
1316 Element.htmlAttribute
1317 (Html.Attributes.style
1319 ("calc(1.0em + " ++ String.fromInt (2 * min t b) ++ "px)")
1322 reducedVerticalPadding =
1326 , bottom = b - min t b
1331 | parent = reducedVerticalPadding :: els.parent
1332 , input = newHeight :: newLineHeight :: els.input
1333 , cover = attr :: els.cover
1336 Internal.StyleClass _ (Internal.BorderWidth _ _ _ _ _) ->
1338 | parent = attr :: els.parent
1339 , cover = attr :: els.cover
1342 Internal.StyleClass _ (Internal.Transform _) ->
1344 | parent = attr :: els.parent
1345 , cover = attr :: els.cover
1348 Internal.StyleClass _ (Internal.FontSize _) ->
1349 { els | fullParent = attr :: els.fullParent }
1351 Internal.StyleClass _ (Internal.FontFamily _ _) ->
1352 { els | fullParent = attr :: els.fullParent }
1354 Internal.StyleClass flag cls ->
1355 { els | parent = attr :: els.parent }
1357 Internal.NoAttribute ->
1361 { els | input = attr :: els.input }
1363 Internal.Describe _ ->
1364 { els | input = attr :: els.input }
1366 Internal.Class _ _ ->
1367 { els | parent = attr :: els.parent }
1369 Internal.TransformComponent _ _ ->
1370 { els | input = attr :: els.input }
1375 List (Attribute msg)
1377 { onChange : String -> msg
1379 , placeholder : Maybe (Placeholder msg)
1385 { type_ = TextInputNode "text"
1386 , spellchecked = False
1387 , autofill = Nothing
1391{-| If spell checking is available, this input will be spellchecked.
1394 List (Attribute msg)
1396 { onChange : String -> msg
1398 , placeholder : Maybe (Placeholder msg)
1404 { type_ = TextInputNode "text"
1405 , spellchecked = True
1406 , autofill = Nothing
1412 List (Attribute msg)
1414 { onChange : String -> msg
1416 , placeholder : Maybe (Placeholder msg)
1422 { type_ = TextInputNode "search"
1423 , spellchecked = False
1424 , autofill = Nothing
1428{-| A password input that allows the browser to autofill.
1430It's `newPassword` instead of just `password` because it gives the browser a hint on what type of password input it is.
1432A password takes all the arguments a normal `Input.text` would, and also **show**, which will remove the password mask (e.g. `****` vs `pass1234`)
1436 List (Attribute msg)
1438 { onChange : String -> msg
1440 , placeholder : Maybe (Placeholder msg)
1445newPassword attrs pass =
1454 , spellchecked = False
1455 , autofill = Just "new-password"
1458 { onChange = pass.onChange
1460 , placeholder = pass.placeholder
1461 , label = pass.label
1467 List (Attribute msg)
1469 { onChange : String -> msg
1471 , placeholder : Maybe (Placeholder msg)
1476currentPassword attrs pass =
1485 , spellchecked = False
1486 , autofill = Just "current-password"
1489 { onChange = pass.onChange
1491 , placeholder = pass.placeholder
1492 , label = pass.label
1498 List (Attribute msg)
1500 { onChange : String -> msg
1502 , placeholder : Maybe (Placeholder msg)
1508 { type_ = TextInputNode "text"
1509 , spellchecked = False
1510 , autofill = Just "username"
1516 List (Attribute msg)
1518 { onChange : String -> msg
1520 , placeholder : Maybe (Placeholder msg)
1526 { type_ = TextInputNode "email"
1527 , spellchecked = False
1528 , autofill = Just "email"
1532{-| A multiline text input.
1534By default it will have a minimum height of one line and resize based on it's contents.
1538 List (Attribute msg)
1540 { onChange : String -> msg
1542 , placeholder : Maybe (Placeholder msg)
1547multiline attrs multi =
1551 , spellchecked = multi.spellcheck
1552 , autofill = Nothing
1555 { onChange = multi.onChange
1557 , placeholder = multi.placeholder
1558 , label = multi.label
1562isHiddenLabel label =
1571applyLabel : List (Attribute msg) -> Label msg -> Element msg -> Element msg
1572applyLabel attrs label input =
1574 HiddenLabel labelText ->
1575 -- NOTE: This means that the label is applied outside of this function!
1576 -- It would be nice to unify this logic, but it's a little tricky
1579 (Internal.NodeName "label")
1581 (Internal.Unkeyed [ input ])
1583 Label position labelAttrs labelChild ->
1590 (Internal.Unkeyed [ labelChild ])
1596 (Internal.NodeName "label")
1598 (Internal.Unkeyed [ labelElement, input ])
1603 (Internal.NodeName "label")
1605 (Internal.Unkeyed [ input, labelElement ])
1610 (Internal.NodeName "label")
1612 (Internal.Unkeyed [ input, labelElement ])
1617 (Internal.NodeName "label")
1619 (Internal.Unkeyed [ labelElement, input ])
1623type Option value msg
1624 = Option value (OptionState -> Element msg)
1634{-| Add a choice to your radio element. This will be rendered with the default radio icon.
1636option : value -> Element msg -> Option value msg
1638 Option val (defaultRadioOption txt)
1641{-| Customize exactly what your radio option should look like in different states.
1643optionWith : value -> (OptionState -> Element msg) -> Option value msg
1644optionWith val view =
1650 List (Attribute msg)
1652 { onChange : option -> msg
1653 , options : List (Option option msg)
1654 , selected : Maybe option
1662{-| Same as radio, but displayed as a row
1665 List (Attribute msg)
1667 { onChange : option -> msg
1668 , options : List (Option option msg)
1669 , selected : Maybe option
1677defaultRadioOption : Element msg -> OptionState -> Element msg
1678defaultRadioOption optionLabel status =
1680 [ Element.spacing 10
1682 , Element.width Element.shrink
1685 [ Element.width (Element.px 14)
1686 , Element.height (Element.px 14)
1687 , Background.color white
1691 Internal.htmlClass "focusable"
1694 Internal.NoAttribute
1696 -- , Border.shadow <|
1697 -- -- case status of
1699 -- -- { offset = ( 0, 0 )
1702 -- -- , color = Color.rgb 235 235 235
1705 -- -- { offset = ( 0, 0 )
1708 -- -- , color = Color.rgba 235 235 235 0
1711 -- { offset = ( 0, 0 )
1714 -- , color = Color.rgba 235 235 235 0
1729 Element.rgb (208 / 255) (208 / 255) (208 / 255)
1732 Element.rgb (208 / 255) (208 / 255) (208 / 255)
1735 Element.rgb (59 / 255) (153 / 255) (252 / 255)
1738 , Element.el [ Element.width Element.fill, Internal.htmlClass "unfocusable" ] optionLabel
1744 -> List (Attribute msg)
1746 { onChange : option -> msg
1747 , options : List (Option option msg)
1748 , selected : Maybe option
1752radioHelper orientation attrs input =
1754 renderOption (Option val view) =
1757 if Just val == input.selected then
1765 , case orientation of
1767 Element.width Element.shrink
1770 Element.width Element.fill
1771 , Events.onClick (input.onChange val)
1775 Html.Attributes.attribute "aria-checked"
1780 Html.Attributes.attribute "aria-checked"
1783 Html.Attributes.attribute "role" "radio"
1790 row (hiddenLabelAttribute input.label :: attrs)
1791 (List.map renderOption input.options)
1794 column (hiddenLabelAttribute input.label :: attrs)
1795 (List.map renderOption input.options)
1798 case input.options of
1802 (Option val _) :: _ ->
1803 List.foldl track ( NotFound, val, val ) input.options
1804 |> (\( found, b, a ) ->
1816 track opt ( found, prev, nxt ) =
1821 if Just val == input.selected then
1822 ( BeforeFound, prev, nxt )
1828 ( AfterFound, prev, val )
1831 ( found, prev, nxt )
1839 Internal.Width (Internal.Fill _) ->
1842 Internal.Height (Internal.Fill _) ->
1852 (List.filterMap identity
1853 [ Just Element.alignLeft
1855 , Just (Internal.htmlClass "focus")
1856 , Just Region.announce
1859 Html.Attributes.attribute "role" "radiogroup"
1864 Just ( prev, next ) ->
1868 if code == leftArrow then
1869 Just (input.onChange prev)
1871 else if code == upArrow then
1872 Just (input.onChange prev)
1874 else if code == rightArrow then
1875 Just (input.onChange next)
1877 else if code == downArrow then
1878 Just (input.onChange next)
1880 else if code == space then
1881 case input.selected of
1883 Just (input.onChange prev)
1893 -- ++ hideIfEverythingisInvisible
1910column : List (Attribute msg) -> List (Internal.Element msg) -> Internal.Element msg
1911column attributes children =
1915 (Element.height Element.shrink
1916 :: Element.width Element.fill
1919 (Internal.Unkeyed children)
1922row : List (Attribute msg) -> List (Internal.Element msg) -> Internal.Element msg
1923row attributes children =
1927 (Element.width Element.fill
1930 (Internal.Unkeyed children)
1938onEnter : msg -> Attribute msg
1944onSpace : msg -> Attribute msg
1950onUpArrow : msg -> Attribute msg
1956onRightArrow : msg -> Attribute msg
1958 onKey rightArrow msg
1962onLeftArrow : msg -> Attribute msg
1968onDownArrow : msg -> Attribute msg
2019onKey : String -> msg -> Attribute msg
2020onKey desiredCode msg =
2023 if code == desiredCode then
2027 Json.fail "Not the enter key"
2030 Json.field "key" Json.string
2031 |> Json.andThen decode
2034 Html.Events.preventDefaultOn "keyup"
2035 (Json.map (\fired -> ( fired, True )) isKey)
2039-- preventKeydown : String -> a -> Attribute a
2040-- preventKeydown desiredCode msg =
2043-- if code == desiredCode then
2046-- Json.fail "Not the enter key"
2048-- Json.field "key" Json.string
2049-- |> Json.andThen decode
2051-- Events.onWithOptions "keydown"
2052-- { stopPropagation = False
2053-- , preventDefault = True
2059onKeyLookup : (String -> Maybe msg) -> Attribute msg
2065 Json.fail "No key matched"
2071 Json.field "key" Json.string
2072 |> Json.andThen decode
2074 Internal.Attr <| Html.Events.on "keyup" isKey
2078onFocusOut : msg -> Attribute msg
2080 Internal.Attr <| Html.Events.on "focusout" (Json.succeed msg)
2084onFocusIn : msg -> Attribute msg
2086 Internal.Attr <| Html.Events.on "focusin" (Json.succeed msg)
2089selected : Bool -> Attribute msg
2091 Internal.Attr << Html.Attributes.selected
2094name : String -> Attribute msg
2096 Internal.Attr << Html.Attributes.name
2099value : String -> Attribute msg
2101 Internal.Attr << Html.Attributes.value
2104tabindex : Int -> Attribute msg
2106 Internal.Attr << Html.Attributes.tabindex
2109disabled : Bool -> Attribute msg
2111 Internal.Attr << Html.Attributes.disabled
2114spellcheck : Bool -> Attribute msg
2116 Internal.Attr << Html.Attributes.spellcheck
2119readonly : Bool -> Attribute msg
2121 Internal.Attr << Html.Attributes.readonly
2124autofill : String -> Attribute msg
2126 Internal.Attr << Html.Attributes.attribute "autocomplete"
2129{-| Attach this attribute to any `Input` that you would like to be automatically focused when the page loads.
2131You should only have a maximum of one per page.
2134focusedOnLoad : Attribute msg
2136 Internal.Attr <| Html.Attributes.autofocus True
2143defaultTextBoxStyle : List (Attribute msg)
2144defaultTextBoxStyle =
2145 [ defaultTextPadding
2147 , Border.color darkGrey
2148 , Background.color white
2151 , Element.width Element.fill
2152 , Element.height Element.shrink
2156defaultTextPadding : Attribute msg
2158 Element.paddingXY 12 12
2161{-| The blue default checked box icon.
2163You'll likely want to make your own checkbox at some point that fits your design.
2166defaultCheckbox : Bool -> Element msg
2167defaultCheckbox checked =
2169 [ Internal.htmlClass "focusable"
2170 , Element.width (Element.px 14)
2171 , Element.height (Element.px 14)
2179 Element.rgb (59 / 255) (153 / 255) (252 / 255)
2182 Element.rgb (211 / 255) (211 / 255) (211 / 255)
2189 Element.rgba (238 / 255) (238 / 255) (238 / 255) 0
2192 Element.rgb (238 / 255) (238 / 255) (238 / 255)
2194 , Background.color <|
2196 Element.rgb (59 / 255) (153 / 255) (252 / 255)
2209 [ Border.color white
2210 , Element.height (Element.px 6)
2211 , Element.width (Element.px 9)
2212 , 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