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 _ _ _) ->
867 isHeightContent attr =
869 Internal.Height Internal.Content ->
877 Internal.StyleClass cls (Internal.PaddingStyle pad t r b l) ->
878 -- The - 3 is here to prevent accidental triggering of scrollbars
879 -- when things are off by a pixel or two.
880 -- (or at least when the browser *thinks* it's off by a pixel or two)
882 { top = max 0 (floor (toFloat t - 3))
883 , right = max 0 (floor (toFloat r - 3))
884 , bottom = max 0 (floor (toFloat b - 3))
885 , left = max 0 (floor (toFloat l - 3))
892 case textInput.type_ of
893 TextInputNode inputType ->
898 |> List.filterMap getHeight
901 |> Maybe.map ((==) Internal.Content)
902 |> Maybe.withDefault False
906 |> List.filterMap getPadding
919 (case textInput.type_ of
920 TextInputNode inputType ->
921 Internal.NodeName "input"
924 Internal.NodeName "textarea"
926 ((case textInput.type_ of
927 TextInputNode inputType ->
928 -- Note: Due to a weird edgecase in...Edge...
929 -- `type` needs to come _before_ `value`
930 -- More reading: https://github.com/mdgriffith/elm-ui/pull/94/commits/4f493a27001ccc3cf1f2baa82e092c35d3811876
931 [ Internal.Attr (Html.Attributes.type_ inputType)
932 , Internal.htmlClass classes.inputText
937 , Element.height Element.fill
938 , Internal.htmlClass classes.inputMultiline
939 , calcMoveToCompensateForPadding withDefaults
941 -- The only reason we do this padding trick is so that when the user clicks in the padding,
942 -- that the cursor will reset correctly.
943 -- This could probably be combined with the above `calcMoveToCompensateForPadding`
944 , Element.paddingEach parentPadding
945 , Internal.Attr (Html.Attributes.style "margin" (renderBox (negateBox parentPadding)))
946 , Internal.Attr (Html.Attributes.style "box-sizing" "content-box")
949 ++ [ value textOptions.text
950 , Internal.Attr (Html.Events.onInput textOptions.onChange)
951 , hiddenLabelAttribute textOptions.label
952 , spellcheck textInput.spellchecked
953 , Maybe.map autofill textInput.autofill
954 |> Maybe.withDefault Internal.NoAttribute
956 ++ redistributed.input
958 (Internal.Unkeyed [])
961 case textInput.type_ of
963 -- textarea with height-content means that
964 -- the input element is rendered `inFront` with a transparent background
965 -- Then the input text is rendered as the space filling element.
969 ((if heightContent then
970 [ Element.width Element.fill
971 , Internal.htmlClass classes.focusedWithin
975 [ Element.width Element.fill
976 , Internal.htmlClass classes.focusedWithin
980 ++ redistributed.parent
986 (Element.width Element.fill
987 :: Element.height Element.fill
988 :: Element.inFront inputElement
989 :: Internal.htmlClass classes.inputMultilineParent
990 :: redistributed.wrapper
993 (if textOptions.text == "" then
994 case textOptions.placeholder of
999 [ renderPlaceholder place
1001 (textOptions.text == "")
1006 (Html.span [ Html.Attributes.class classes.inputMultilineFiller ]
1007 -- We append a non-breaking space to the end of the content so that newlines don't get chomped.
1008 [ Html.text (textOptions.text ++ "\u{00A0}")
1017 TextInputNode inputType ->
1021 (Element.width Element.fill
1022 :: Internal.htmlClass classes.focusedWithin
1024 [ redistributed.parent
1025 , case textOptions.placeholder of
1030 [ Element.behindContent
1031 (renderPlaceholder place redistributed.cover (textOptions.text == ""))
1035 (Internal.Unkeyed [ inputElement ])
1038 (Internal.Class Flag.cursor classes.cursorText
1039 :: (if isHiddenLabel textOptions.label then
1040 Internal.NoAttribute
1047 :: redistributed.fullParent
1054 { top = negate box.top
1055 , right = negate box.right
1056 , bottom = negate box.bottom
1057 , left = negate box.left
1061renderBox { top, right, bottom, left } =
1064 ++ String.fromInt right
1066 ++ String.fromInt bottom
1068 ++ String.fromInt left
1072renderPlaceholder (Placeholder placeholderAttrs placeholderEl) forPlaceholder on =
1075 ++ [ Font.color charcoal
1076 , Internal.htmlClass (classes.noTextSelection ++ " " ++ classes.passPointerEvents)
1078 , Border.color (Element.rgba 0 0 0 0)
1079 , Background.color (Element.rgba 0 0 0 0)
1080 , Element.height Element.fill
1081 , Element.width Element.fill
1095{-| Because textareas are now shadowed, where they're rendered twice,
1096we to move the literal text area up because spacing is based on line height.
1098calcMoveToCompensateForPadding : List (Attribute msg) -> Attribute msg
1099calcMoveToCompensateForPadding attrs =
1101 gatherSpacing attr found =
1103 Internal.StyleClass _ (Internal.SpacingStyle _ x y) ->
1114 case List.foldr gatherSpacing Nothing attrs of
1116 Internal.NoAttribute
1119 Element.moveUp (toFloat vSpace / 2)
1122{-| Given the list of attributes provided to `Input.multiline` or `Input.text`,
1124redistribute them to the parent, the input, or the cover.
1126 - fullParent -> Wrapper around label and input
1127 - parent -> parent of wrapper
1128 - wrapper -> the element that is here to take up space.
1129 - cover -> things like placeholders or text areas which are layered on top of input.
1130 - input -> actual input element
1136 -> List (Attribute msg)
1138 { fullParent : List (Attribute msg)
1139 , parent : List (Attribute msg)
1140 , wrapper : List (Attribute msg)
1141 , input : List (Attribute msg)
1142 , cover : List (Attribute msg)
1144redistribute isMultiline stacked attrs =
1145 List.foldl (redistributeOver isMultiline stacked)
1154 { parent = List.reverse redist.parent
1155 , fullParent = List.reverse redist.fullParent
1156 , wrapper = List.reverse redist.wrapper
1157 , input = List.reverse redist.input
1158 , cover = List.reverse redist.cover
1217{-| isStacked means that the label is above or below
1219redistributeOver isMultiline stacked attr els =
1221 Internal.Nearby _ _ ->
1222 { els | parent = attr :: els.parent }
1224 Internal.Width width ->
1226 { els | fullParent = attr :: els.fullParent }
1228 else if isFill width then
1230 | fullParent = attr :: els.fullParent
1231 , parent = attr :: els.parent
1234 else if isPixel width then
1235 { els | parent = attr :: els.parent }
1239 | parent = attr :: els.parent
1242 Internal.Height height ->
1245 | fullParent = attr :: els.fullParent
1246 , parent = attr :: els.parent
1249 else if isFill height then
1251 | fullParent = attr :: els.fullParent
1252 , parent = attr :: els.parent
1255 else if isPixel height then
1256 { els | parent = attr :: els.parent }
1260 | parent = attr :: els.parent
1263 Internal.AlignX _ ->
1264 { els | fullParent = attr :: els.fullParent }
1266 Internal.AlignY _ ->
1267 { els | fullParent = attr :: els.fullParent }
1269 Internal.StyleClass _ (Internal.SpacingStyle _ _ _) ->
1271 | fullParent = attr :: els.fullParent
1272 , parent = attr :: els.parent
1273 , input = attr :: els.input
1274 , wrapper = attr :: els.wrapper
1277 Internal.StyleClass cls (Internal.PaddingStyle pad t r b l) ->
1280 | parent = attr :: els.parent
1281 , cover = attr :: els.cover
1287 Element.htmlAttribute
1288 (Html.Attributes.style
1290 ("calc(1.0em + " ++ String.fromInt (2 * min t b) ++ "px)")
1294 Element.htmlAttribute
1295 (Html.Attributes.style
1297 ("calc(1.0em + " ++ String.fromInt (2 * min t b) ++ "px)")
1300 reducedVerticalPadding =
1304 , bottom = b - min t b
1309 | parent = reducedVerticalPadding :: els.parent
1310 , input = newHeight :: newLineHeight :: els.input
1311 , cover = attr :: els.cover
1314 Internal.StyleClass _ (Internal.BorderWidth _ _ _ _ _) ->
1316 | parent = attr :: els.parent
1317 , cover = attr :: els.cover
1320 Internal.StyleClass _ (Internal.Transform _) ->
1322 | parent = attr :: els.parent
1323 , cover = attr :: els.cover
1326 Internal.StyleClass _ (Internal.FontSize _) ->
1327 { els | fullParent = attr :: els.fullParent }
1329 Internal.StyleClass _ (Internal.FontFamily _ _) ->
1330 { els | fullParent = attr :: els.fullParent }
1332 Internal.StyleClass flag cls ->
1333 { els | parent = attr :: els.parent }
1335 Internal.NoAttribute ->
1339 { els | input = attr :: els.input }
1341 Internal.Describe _ ->
1342 { els | input = attr :: els.input }
1344 Internal.Class _ _ ->
1345 { els | parent = attr :: els.parent }
1347 Internal.TransformComponent _ _ ->
1348 { els | input = attr :: els.input }
1353 List (Attribute msg)
1355 { onChange : String -> msg
1357 , placeholder : Maybe (Placeholder msg)
1363 { type_ = TextInputNode "text"
1364 , spellchecked = False
1365 , autofill = Nothing
1369{-| If spell checking is available, this input will be spellchecked.
1372 List (Attribute msg)
1374 { onChange : String -> msg
1376 , placeholder : Maybe (Placeholder msg)
1382 { type_ = TextInputNode "text"
1383 , spellchecked = True
1384 , autofill = Nothing
1390 List (Attribute msg)
1392 { onChange : String -> msg
1394 , placeholder : Maybe (Placeholder msg)
1400 { type_ = TextInputNode "search"
1401 , spellchecked = False
1402 , autofill = Nothing
1406{-| A password input that allows the browser to autofill.
1408It's `newPassword` instead of just `password` because it gives the browser a hint on what type of password input it is.
1410A password takes all the arguments a normal `Input.text` would, and also **show**, which will remove the password mask (e.g. `****` vs `pass1234`)
1414 List (Attribute msg)
1416 { onChange : String -> msg
1418 , placeholder : Maybe (Placeholder msg)
1423newPassword attrs pass =
1432 , spellchecked = False
1433 , autofill = Just "new-password"
1436 { onChange = pass.onChange
1438 , placeholder = pass.placeholder
1439 , label = pass.label
1445 List (Attribute msg)
1447 { onChange : String -> msg
1449 , placeholder : Maybe (Placeholder msg)
1454currentPassword attrs pass =
1463 , spellchecked = False
1464 , autofill = Just "current-password"
1467 { onChange = pass.onChange
1469 , placeholder = pass.placeholder
1470 , label = pass.label
1476 List (Attribute msg)
1478 { onChange : String -> msg
1480 , placeholder : Maybe (Placeholder msg)
1486 { type_ = TextInputNode "text"
1487 , spellchecked = False
1488 , autofill = Just "username"
1494 List (Attribute msg)
1496 { onChange : String -> msg
1498 , placeholder : Maybe (Placeholder msg)
1504 { type_ = TextInputNode "email"
1505 , spellchecked = False
1506 , autofill = Just "email"
1510{-| A multiline text input.
1512By default it will have a minimum height of one line and resize based on it's contents.
1516 List (Attribute msg)
1518 { onChange : String -> msg
1520 , placeholder : Maybe (Placeholder msg)
1525multiline attrs multi =
1529 , spellchecked = multi.spellcheck
1530 , autofill = Nothing
1533 { onChange = multi.onChange
1535 , placeholder = multi.placeholder
1536 , label = multi.label
1540isHiddenLabel label =
1549applyLabel : List (Attribute msg) -> Label msg -> Element msg -> Element msg
1550applyLabel attrs label input =
1552 HiddenLabel labelText ->
1553 -- NOTE: This means that the label is applied outside of this function!
1554 -- It would be nice to unify this logic, but it's a little tricky
1557 (Internal.NodeName "label")
1559 (Internal.Unkeyed [ input ])
1561 Label position labelAttrs labelChild ->
1568 (Internal.Unkeyed [ labelChild ])
1574 (Internal.NodeName "label")
1576 (Internal.Unkeyed [ labelElement, input ])
1581 (Internal.NodeName "label")
1583 (Internal.Unkeyed [ input, labelElement ])
1588 (Internal.NodeName "label")
1590 (Internal.Unkeyed [ input, labelElement ])
1595 (Internal.NodeName "label")
1597 (Internal.Unkeyed [ labelElement, input ])
1601type Option value msg
1602 = Option value (OptionState -> Element msg)
1612{-| Add a choice to your radio element. This will be rendered with the default radio icon.
1614option : value -> Element msg -> Option value msg
1616 Option val (defaultRadioOption txt)
1619{-| Customize exactly what your radio option should look like in different states.
1621optionWith : value -> (OptionState -> Element msg) -> Option value msg
1622optionWith val view =
1628 List (Attribute msg)
1630 { onChange : option -> msg
1631 , options : List (Option option msg)
1632 , selected : Maybe option
1640{-| Same as radio, but displayed as a row
1643 List (Attribute msg)
1645 { onChange : option -> msg
1646 , options : List (Option option msg)
1647 , selected : Maybe option
1655defaultRadioOption : Element msg -> OptionState -> Element msg
1656defaultRadioOption optionLabel status =
1658 [ Element.spacing 10
1660 , Element.width Element.shrink
1663 [ Element.width (Element.px 14)
1664 , Element.height (Element.px 14)
1665 , Background.color white
1669 Internal.htmlClass "focusable"
1672 Internal.NoAttribute
1674 -- , Border.shadow <|
1675 -- -- case status of
1677 -- -- { offset = ( 0, 0 )
1680 -- -- , color = Color.rgb 235 235 235
1683 -- -- { offset = ( 0, 0 )
1686 -- -- , color = Color.rgba 235 235 235 0
1689 -- { offset = ( 0, 0 )
1692 -- , color = Color.rgba 235 235 235 0
1707 Element.rgb (208 / 255) (208 / 255) (208 / 255)
1710 Element.rgb (208 / 255) (208 / 255) (208 / 255)
1713 Element.rgb (59 / 255) (153 / 255) (252 / 255)
1716 , Element.el [ Element.width Element.fill, Internal.htmlClass "unfocusable" ] optionLabel
1722 -> List (Attribute msg)
1724 { onChange : option -> msg
1725 , options : List (Option option msg)
1726 , selected : Maybe option
1730radioHelper orientation attrs input =
1732 renderOption (Option val view) =
1735 if Just val == input.selected then
1743 , case orientation of
1745 Element.width Element.shrink
1748 Element.width Element.fill
1749 , Events.onClick (input.onChange val)
1753 Html.Attributes.attribute "aria-checked"
1758 Html.Attributes.attribute "aria-checked"
1761 Html.Attributes.attribute "role" "radio"
1768 row (hiddenLabelAttribute input.label :: attrs)
1769 (List.map renderOption input.options)
1772 column (hiddenLabelAttribute input.label :: attrs)
1773 (List.map renderOption input.options)
1776 case input.options of
1780 (Option val _) :: _ ->
1781 List.foldl track ( NotFound, val, val ) input.options
1782 |> (\( found, b, a ) ->
1794 track opt ( found, prev, nxt ) =
1799 if Just val == input.selected then
1800 ( BeforeFound, prev, nxt )
1806 ( AfterFound, prev, val )
1809 ( found, prev, nxt )
1817 Internal.Width (Internal.Fill _) ->
1820 Internal.Height (Internal.Fill _) ->
1830 (List.filterMap identity
1831 [ Just Element.alignLeft
1833 , Just (Internal.htmlClass "focus")
1834 , Just Region.announce
1837 Html.Attributes.attribute "role" "radiogroup"
1842 Just ( prev, next ) ->
1846 if code == leftArrow then
1847 Just (input.onChange prev)
1849 else if code == upArrow then
1850 Just (input.onChange prev)
1852 else if code == rightArrow then
1853 Just (input.onChange next)
1855 else if code == downArrow then
1856 Just (input.onChange next)
1858 else if code == space then
1859 case input.selected of
1861 Just (input.onChange prev)
1871 -- ++ hideIfEverythingisInvisible
1888column : List (Attribute msg) -> List (Internal.Element msg) -> Internal.Element msg
1889column attributes children =
1893 (Element.height Element.shrink
1894 :: Element.width Element.fill
1897 (Internal.Unkeyed children)
1900row : List (Attribute msg) -> List (Internal.Element msg) -> Internal.Element msg
1901row attributes children =
1905 (Element.width Element.fill
1908 (Internal.Unkeyed children)
1916onEnter : msg -> Attribute msg
1922onSpace : msg -> Attribute msg
1928onUpArrow : msg -> Attribute msg
1934onRightArrow : msg -> Attribute msg
1936 onKey rightArrow msg
1940onLeftArrow : msg -> Attribute msg
1946onDownArrow : msg -> Attribute msg
1997onKey : String -> msg -> Attribute msg
1998onKey desiredCode msg =
2001 if code == desiredCode then
2005 Json.fail "Not the enter key"
2008 Json.field "key" Json.string
2009 |> Json.andThen decode
2012 Html.Events.preventDefaultOn "keyup"
2013 (Json.map (\fired -> ( fired, True )) isKey)
2017-- preventKeydown : String -> a -> Attribute a
2018-- preventKeydown desiredCode msg =
2021-- if code == desiredCode then
2024-- Json.fail "Not the enter key"
2026-- Json.field "key" Json.string
2027-- |> Json.andThen decode
2029-- Events.onWithOptions "keydown"
2030-- { stopPropagation = False
2031-- , preventDefault = True
2037onKeyLookup : (String -> Maybe msg) -> Attribute msg
2043 Json.fail "No key matched"
2049 Json.field "key" Json.string
2050 |> Json.andThen decode
2052 Internal.Attr <| Html.Events.on "keyup" isKey
2056onFocusOut : msg -> Attribute msg
2058 Internal.Attr <| Html.Events.on "focusout" (Json.succeed msg)
2062onFocusIn : msg -> Attribute msg
2064 Internal.Attr <| Html.Events.on "focusin" (Json.succeed msg)
2067selected : Bool -> Attribute msg
2069 Internal.Attr << Html.Attributes.selected
2072name : String -> Attribute msg
2074 Internal.Attr << Html.Attributes.name
2077value : String -> Attribute msg
2079 Internal.Attr << Html.Attributes.value
2082tabindex : Int -> Attribute msg
2084 Internal.Attr << Html.Attributes.tabindex
2087disabled : Bool -> Attribute msg
2089 Internal.Attr << Html.Attributes.disabled
2092spellcheck : Bool -> Attribute msg
2094 Internal.Attr << Html.Attributes.spellcheck
2097readonly : Bool -> Attribute msg
2099 Internal.Attr << Html.Attributes.readonly
2102autofill : String -> Attribute msg
2104 Internal.Attr << Html.Attributes.attribute "autocomplete"
2107{-| Attach this attribute to any `Input` that you would like to be automatically focused when the page loads.
2109You should only have a maximum of one per page.
2112focusedOnLoad : Attribute msg
2114 Internal.Attr <| Html.Attributes.autofocus True
2121defaultTextBoxStyle : List (Attribute msg)
2122defaultTextBoxStyle =
2123 [ defaultTextPadding
2125 , Border.color darkGrey
2126 , Background.color white
2129 , Element.width Element.fill
2130 , Element.height Element.shrink
2134defaultTextPadding : Attribute msg
2136 Element.paddingXY 12 12
2139{-| The blue default checked box icon.
2141You'll likely want to make your own checkbox at some point that fits your design.
2144defaultCheckbox : Bool -> Element msg
2145defaultCheckbox checked =
2147 [ Internal.htmlClass "focusable"
2148 , Element.width (Element.px 14)
2149 , Element.height (Element.px 14)
2157 Element.rgb (59 / 255) (153 / 255) (252 / 255)
2160 Element.rgb (211 / 255) (211 / 255) (211 / 255)
2167 Element.rgba (238 / 255) (238 / 255) (238 / 255) 0
2170 Element.rgb (238 / 255) (238 / 255) (238 / 255)
2172 , Background.color <|
2174 Element.rgb (59 / 255) (153 / 255) (252 / 255)
2187 [ Border.color white
2188 , Element.height (Element.px 6)
2189 , Element.width (Element.px 9)
2190 , 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