git.iko.soy/typeable/elm-ui
on 7cedd3a help sign in
2194 lines 62 kB history raw
1module Element.Input exposing
2 ( focusedOnLoad
3 , button
4 , checkbox, defaultCheckbox
5 , text, multiline
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
11 )
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).
28# Focus Styling
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.
32@docs focusedOnLoad
35# Buttons
37@docs button
40# Checkboxes
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
49 type Msg
50 = GuacamoleChecked Bool
52 view model =
53 Input.checkbox []
54 { onChange = GuacamoleChecked
55 , icon = Input.defaultCheckbox
56 , checked = model.guacamole
57 , label =
58 Input.labelRight []
59 (text "Do you want Guacamole?")
60 }
62@docs checkbox, defaultCheckbox
65# Text
67@docs text, multiline
69@docs Placeholder, placeholder
72## Text with autofill
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
81# Sliders
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
91# Radio Selection
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
99 Input.radio
100 [ padding 10
101 , spacing 20
102 ]
103 { onChange = ChooseLunch
104 , selected = Just model.lunch
105 , label = Input.labelAbove [] (text "Lunch")
106 , options =
107 [ Input.option Burrito (text "Burrito")
108 , Input.option Taco (text "Taco!")
109 , Input.option Gyro (text "Gyro")
110 ]
111 }
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
118# Labels
120Every input has a required `Label`.
122@docs Label, labelAbove, labelBelow, labelLeft, labelRight, labelHidden
125# Form Elements
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 :).
136# Disabling Inputs
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
151 myButton ready =
152 if ready then
153 Input.button
154 [ Background.color blue
155 ]
156 { onPress =
157 Just SaveButtonPressed
158 , label =
159 text "Save blog post"
160 }
162 else
163 Input.button
164 [ Background.color grey
165 , Region.description
166 "A publish date is required before saving a blogpost."
167 ]
168 { onPress =
169 Just DisabledSaveButtonPressed
170 , label =
171 text "Save Blog "
172 }
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?
180-}
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
188import Html
189import Html.Attributes
190import Html.Events
191import Internal.Flag as Flag
192import Internal.Model as Internal
193import Internal.Style exposing (classes)
194import Json.Decode as Json
197{-| -}
198type Placeholder msg
199 = Placeholder (List (Attribute msg)) (Element msg)
202white =
203 Element.rgb 1 1 1
206darkGrey =
207 Element.rgb (186 / 255) (189 / 255) (182 / 255)
210charcoal =
211 Element.rgb
212 (136 / 255)
213 (138 / 255)
214 (133 / 255)
217{-| -}
218placeholder : List (Attribute msg) -> Element msg -> Placeholder msg
219placeholder =
220 Placeholder
223type LabelLocation
224 = OnRight
225 | OnLeft
226 | Above
227 | Below
230{-| -}
231type Label msg
232 = Label LabelLocation (List (Attribute msg)) (Element msg)
233 | HiddenLabel String
236isStacked : Label msg -> Bool
237isStacked label =
238 case label of
239 Label loc _ _ ->
240 case loc of
241 OnRight ->
242 False
244 OnLeft ->
245 False
247 Above ->
248 True
250 Below ->
251 True
253 HiddenLabel _ ->
254 True
257{-| -}
258labelRight : List (Attribute msg) -> Element msg -> Label msg
259labelRight =
260 Label OnRight
263{-| -}
264labelLeft : List (Attribute msg) -> Element msg -> Label msg
265labelLeft =
266 Label OnLeft
269{-| -}
270labelAbove : List (Attribute msg) -> Element msg -> Label msg
271labelAbove =
272 Label Above
275{-| -}
276labelBelow : List (Attribute msg) -> Element msg -> Label msg
277labelBelow =
278 Label Below
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.
292-}
293labelHidden : String -> Label msg
294labelHidden =
295 HiddenLabel
298hiddenLabelAttribute label =
299 case label of
300 HiddenLabel textLabel ->
301 Internal.Describe (Internal.Label textLabel)
303 Label _ _ _ ->
304 Internal.NoAttribute
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
315 blue =
316 Element.rgb255 238 238 238
318 myButton =
319 Input.button
320 [ Background.color blue
321 , Element.focused
322 [ Background.color purple ]
323 ]
324 { onPress = Just ClickMsg
325 , label = text "My Button"
326 }
328-}
329button :
330 List (Attribute msg)
331 ->
332 { onPress : Maybe msg
333 , label : Element msg
334 }
335 -> Element msg
336button attrs { onPress, label } =
337 Internal.element
338 Internal.asEl
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.
343 Internal.div
344 (Element.width Element.shrink
345 :: Element.height Element.shrink
346 :: Internal.htmlClass
347 (classes.contentCenterX
348 ++ " "
349 ++ classes.contentCenterY
350 ++ " "
351 ++ classes.seButton
352 ++ " "
353 ++ classes.noTextSelection
354 )
355 :: Element.pointer
356 :: focusDefault attrs
357 :: Internal.Describe Internal.Button
358 :: Internal.Attr (Html.Attributes.tabindex 0)
359 :: (case onPress of
360 Nothing ->
361 Internal.Attr (Html.Attributes.disabled True) :: attrs
363 Just msg ->
364 Events.onClick msg
365 :: onEnter msg
366 :: attrs
367 )
368 )
369 (Internal.Unkeyed [ label ])
372focusDefault attrs =
373 if List.any hasFocusStyle attrs then
374 Internal.NoAttribute
376 else
377 Internal.htmlClass "focusable"
380hasFocusStyle attr =
381 case attr of
382 Internal.StyleClass _ (Internal.PseudoSelector Internal.Focus _) ->
383 True
385 _ ->
386 False
389{-| -}
390type alias Checkbox msg =
391 { onChange : Maybe (Bool -> msg)
392 , icon : Maybe (Element msg)
393 , checked : Bool
394 , label : Label msg
395 }
398{-|
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
405-}
406checkbox :
407 List (Attribute msg)
408 ->
409 { onChange : Bool -> msg
410 , icon : Bool -> Element msg
411 , checked : Bool
412 , label : Label msg
413 }
414 -> Element msg
415checkbox attrs { label, icon, checked, onChange } =
416 let
417 attributes =
418 [ if isHiddenLabel label then
419 Internal.NoAttribute
421 else
422 Element.spacing
423 6
424 , Internal.Attr (Html.Events.onClick (onChange (not checked)))
425 , Region.announce
426 , onKeyLookup <|
427 \code ->
428 if code == enter then
429 Just <| onChange (not checked)
431 else if code == space then
432 Just <| onChange (not checked)
434 else
435 Nothing
436 , tabindex 0
437 , Element.pointer
438 , Element.alignLeft
439 , Element.width Element.fill
440 ]
441 ++ attrs
442 in
443 applyLabel attributes
444 label
445 (Internal.element
446 Internal.asEl
447 Internal.div
448 [ Internal.Attr <|
449 Html.Attributes.attribute "role" "checkbox"
450 , Internal.Attr <|
451 Html.Attributes.attribute "aria-checked" <|
452 if checked then
453 "true"
455 else
456 "false"
457 , hiddenLabelAttribute label
458 , Element.centerY
459 , Element.height Element.fill
460 , Element.width Element.shrink
461 ]
462 (Internal.Unkeyed
463 [ icon checked
464 ]
465 )
466 )
469{-| -}
470type Thumb
471 = Thumb (List (Attribute Never))
474{-| -}
475thumb : List (Attribute Never) -> Thumb
476thumb =
477 Thumb
480{-| -}
481defaultThumb : Thumb
482defaultThumb =
483 Thumb
484 [ Element.width (Element.px 16)
485 , Element.height (Element.px 16)
486 , Border.rounded 8
487 , Border.width 1
488 , Border.color (Element.rgb 0.5 0.5 0.5)
489 , Background.color (Element.rgb 1 1 1)
490 ]
493{-| A slider input, good for capturing float values.
495 Input.slider
496 [ Element.height (Element.px 30)
498 -- Here is where we're creating/styling the "track"
499 , Element.behindContent
500 (Element.el
501 [ Element.width Element.fill
502 , Element.height (Element.px 2)
503 , Element.centerY
504 , Background.color grey
505 , Border.rounded 2
506 ]
507 Element.none
508 )
509 ]
510 { onChange = AdjustValue
511 , label =
512 Input.labelAbove []
513 (text "My Slider Value")
514 , min = 0
515 , max = 75
516 , step = Nothing
517 , value = model.sliderValue
518 , thumb =
519 Input.defaultThumb
520 }
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`
536-}
537slider :
538 List (Attribute msg)
539 ->
540 { onChange : Float -> msg
541 , label : Label msg
542 , min : Float
543 , max : Float
544 , value : Float
545 , thumb : Thumb
546 , step : Maybe Float
547 }
548 -> Element msg
549slider attributes input =
550 let
551 (Thumb thumbAttributes) =
552 input.thumb
554 width =
555 Internal.getWidth thumbAttributes
557 height =
558 Internal.getHeight thumbAttributes
560 vertical =
561 case ( trackWidth, trackHeight ) of
562 ( Nothing, Nothing ) ->
563 False
565 ( Just (Internal.Px w), Just (Internal.Px h) ) ->
566 h > w
568 ( Just (Internal.Px _), Just (Internal.Fill _) ) ->
569 True
571 _ ->
572 False
574 trackHeight =
575 Internal.getHeight attributes
577 trackWidth =
578 Internal.getWidth attributes
580 ( spacingX, spacingY ) =
581 Internal.getSpacing attributes ( 5, 5 )
583 factor =
584 (input.value - input.min)
585 / (input.max - input.min)
587 {- Needed attributes
589 Thumb Attributes
590 - Width/Height of thumb so that the input can shadow it.
593 Attributes
595 OnParent ->
596 Spacing
599 On track ->
600 Everything else
605 The `<input>`
608 -}
609 className =
610 "thmb-" ++ thumbWidthString ++ "-" ++ thumbHeightString
612 thumbWidthString =
613 case width of
614 Nothing ->
615 "20px"
617 Just (Internal.Px px) ->
618 String.fromInt px ++ "px"
620 _ ->
621 "100%"
623 thumbHeightString =
624 case height of
625 Nothing ->
626 "20px"
628 Just (Internal.Px px) ->
629 String.fromInt px ++ "px"
631 _ ->
632 "100%"
634 thumbShadowStyle =
635 [ Internal.Property "width"
636 thumbWidthString
637 , Internal.Property "height"
638 thumbHeightString
639 ]
640 in
641 applyLabel
642 [ if isHiddenLabel input.label then
643 Internal.NoAttribute
645 else
646 Element.spacingXY spacingX spacingY
647 , Region.announce
648 , Element.width
649 (case trackWidth of
650 Nothing ->
651 Element.fill
653 Just (Internal.Px _) ->
654 Element.shrink
656 Just x ->
657 x
658 )
659 , Element.height
660 (case trackHeight of
661 Nothing ->
662 Element.shrink
664 Just (Internal.Px _) ->
665 Element.shrink
667 Just x ->
668 x
669 )
670 ]
671 input.label
672 (Element.row
673 [ Element.width
674 (Maybe.withDefault Element.fill trackWidth)
675 , Element.height
676 (Maybe.withDefault (Element.px 20) trackHeight)
677 ]
678 [ Internal.element
679 Internal.asEl
680 (Internal.NodeName "input")
681 [ hiddenLabelAttribute input.label
682 , Internal.StyleClass Flag.active
683 (Internal.Style
684 ("input[type=\"range\"]." ++ className ++ "::-moz-range-thumb")
685 thumbShadowStyle
686 )
687 , Internal.StyleClass Flag.hover
688 (Internal.Style
689 ("input[type=\"range\"]." ++ className ++ "::-webkit-slider-thumb")
690 thumbShadowStyle
691 )
692 , Internal.StyleClass Flag.focus
693 (Internal.Style
694 ("input[type=\"range\"]." ++ className ++ "::-ms-thumb")
695 thumbShadowStyle
696 )
697 , Internal.Attr (Html.Attributes.class (className ++ " focusable-parent"))
698 , Internal.Attr
699 (Html.Events.onInput
700 (\str ->
701 case String.toFloat str of
702 Nothing ->
703 -- This should never happen because the browser
704 -- should always provide a Float.
705 input.onChange 0
707 Just val ->
708 input.onChange val
709 )
710 )
711 , Internal.Attr <|
712 Html.Attributes.type_ "range"
713 , Internal.Attr <|
714 Html.Attributes.step
715 (case input.step of
716 Nothing ->
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.
723 "any"
725 Just step ->
726 String.fromFloat step
727 )
728 , Internal.Attr <|
729 Html.Attributes.min (String.fromFloat input.min)
730 , Internal.Attr <|
731 Html.Attributes.max (String.fromFloat input.max)
732 , Internal.Attr <|
733 Html.Attributes.value (String.fromFloat input.value)
734 , if vertical then
735 Internal.Attr <|
736 Html.Attributes.attribute "orient" "vertical"
738 else
739 Internal.NoAttribute
740 , Element.width <|
741 if vertical then
742 Maybe.withDefault (Element.px 20) trackHeight
744 else
745 Maybe.withDefault Element.fill trackWidth
746 , Element.height <|
747 if vertical then
748 Maybe.withDefault Element.fill trackWidth
750 else
751 Maybe.withDefault (Element.px 20) trackHeight
752 ]
753 (Internal.Unkeyed [])
754 , Element.el
755 (Element.width
756 (Maybe.withDefault Element.fill trackWidth)
757 :: Element.height
758 (Maybe.withDefault (Element.px 20) trackHeight)
759 :: attributes
760 -- This is after `attributes` because the thumb should be in front of everything.
761 ++ [ Element.behindContent <|
762 if vertical then
763 viewVerticalThumb factor thumbAttributes trackWidth
765 else
766 viewHorizontalThumb factor thumbAttributes trackHeight
767 ]
768 )
769 Element.none
770 ]
771 )
774viewHorizontalThumb factor thumbAttributes trackHeight =
775 Element.row
776 [ Element.width Element.fill
777 , Element.height (Maybe.withDefault Element.fill trackHeight)
778 , Element.centerY
779 ]
780 [ Element.el
781 [ Element.width (Element.fillPortion (round <| factor * 10000))
782 ]
783 Element.none
784 , Element.el
785 (Element.centerY
786 :: List.map (Internal.mapAttr Basics.never) thumbAttributes
787 )
788 Element.none
789 , Element.el
790 [ Element.width (Element.fillPortion (round <| (abs <| 1 - factor) * 10000))
791 ]
792 Element.none
793 ]
796viewVerticalThumb factor thumbAttributes trackWidth =
797 Element.column
798 [ Element.height Element.fill
799 , Element.width (Maybe.withDefault Element.fill trackWidth)
800 , Element.centerX
801 ]
802 [ Element.el
803 [ Element.height (Element.fillPortion (round <| (abs <| 1 - factor) * 10000))
804 ]
805 Element.none
806 , Element.el
807 (Element.centerX
808 :: List.map (Internal.mapAttr Basics.never) thumbAttributes
809 )
810 Element.none
811 , Element.el
812 [ Element.height (Element.fillPortion (round <| factor * 10000))
813 ]
814 Element.none
815 ]
818type alias TextInput =
819 { type_ : TextKind
820 , spellchecked : Bool
821 , autofill : Maybe String
822 }
825type TextKind
826 = TextInputNode String
827 | TextArea
830{-| -}
831type alias Text msg =
832 { onChange : String -> msg
833 , text : String
834 , placeholder : Maybe (Placeholder msg)
835 , label : Label msg
836 }
839{-| -}
840textHelper : TextInput -> List (Attribute msg) -> Text msg -> Element msg
841textHelper textInput attrs textOptions =
842 let
843 withDefaults =
844 defaultTextBoxStyle ++ attrs
846 redistributed =
847 redistribute (textInput.type_ == TextArea)
848 (isStacked textOptions.label)
849 withDefaults
851 onlySpacing attr =
852 case attr of
853 Internal.StyleClass _ (Internal.SpacingStyle _ _ _) ->
854 True
856 _ ->
857 False
859 getHeight attr =
860 case attr of
861 Internal.Height h ->
862 Just h
864 _ ->
865 Nothing
867 isHeightContent attr =
868 case attr of
869 Internal.Height Internal.Content ->
870 True
872 _ ->
873 False
875 getPadding attr =
876 case attr of
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)
881 Just
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))
886 }
888 _ ->
889 Nothing
891 heightContent =
892 case textInput.type_ of
893 TextInputNode inputType ->
894 False
896 TextArea ->
897 withDefaults
898 |> List.filterMap getHeight
899 |> List.reverse
900 |> List.head
901 |> Maybe.map ((==) Internal.Content)
902 |> Maybe.withDefault False
904 parentPadding =
905 withDefaults
906 |> List.filterMap getPadding
907 |> List.reverse
908 |> List.head
909 |> Maybe.withDefault
910 { top = 0
911 , right = 0
912 , bottom = 0
913 , left = 0
914 }
916 inputElement =
917 Internal.element
918 Internal.asEl
919 (case textInput.type_ of
920 TextInputNode inputType ->
921 Internal.NodeName "input"
923 TextArea ->
924 Internal.NodeName "textarea"
925 )
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
933 ]
935 TextArea ->
936 [ Element.clip
937 , Element.height Element.fill
938 , Internal.htmlClass classes.inputMultiline
939 , calcMoveToCompensateForPadding withDefaults
940 , Element.paddingEach parentPadding
941 , Internal.Attr (Html.Attributes.style "margin" (renderBox (negateBox parentPadding)))
942 , Internal.Attr (Html.Attributes.style "box-sizing" "content-box")
943 ]
944 )
945 ++ [ value textOptions.text
946 , Internal.Attr (Html.Events.onInput textOptions.onChange)
947 , hiddenLabelAttribute textOptions.label
948 , spellcheck textInput.spellchecked
949 , Maybe.map autofill textInput.autofill
950 |> Maybe.withDefault Internal.NoAttribute
951 ]
952 ++ redistributed.input
953 )
954 (Internal.Unkeyed [])
956 wrappedInput =
957 case textInput.type_ of
958 TextArea ->
959 -- textarea with height-content means that
960 -- the input element is rendered `inFront` and transparently.
961 -- Then the input text is rendered as the actual element.
962 Internal.element
963 Internal.asEl
964 Internal.div
965 ((if heightContent then
966 [ Element.width Element.fill
967 , Internal.htmlClass classes.focusedWithin
968 ]
970 else
971 [ Element.width Element.fill
972 , Internal.htmlClass classes.focusedWithin
973 , Element.scrollbarY
974 ]
975 )
976 ++ redistributed.parent
977 )
978 (Internal.Unkeyed
979 [ Internal.element
980 Internal.asParagraph
981 Internal.div
982 (Element.width Element.fill
983 :: Element.height Element.fill
984 :: Element.inFront inputElement
985 :: Internal.htmlClass classes.inputMultilineParent
986 :: redistributed.wrapper
987 )
988 (Internal.Unkeyed
989 (if textOptions.text == "" then
990 case textOptions.placeholder of
991 Nothing ->
992 []
994 Just place ->
995 [ renderPlaceholder place
996 []
997 (textOptions.text == "")
998 ]
1000 else
1001 [ Internal.unstyled (Html.text textOptions.text) ]
1007 TextInputNode inputType ->
1008 Internal.element
1009 Internal.asEl
1010 Internal.div
1011 (Element.width Element.fill
1012 :: List.concat
1013 [ redistributed.parent
1014 , case textOptions.placeholder of
1015 Nothing ->
1016 []
1018 Just place ->
1019 [ Element.behindContent
1020 (renderPlaceholder place redistributed.cover (textOptions.text == ""))
1024 (Internal.Unkeyed [ inputElement ])
1025 in
1026 applyLabel
1027 (Internal.Class Flag.cursor classes.cursorText
1028 :: (if isHiddenLabel textOptions.label then
1029 Internal.NoAttribute
1031 else
1032 Element.spacing
1035 :: Region.announce
1036 :: redistributed.fullParent
1038 textOptions.label
1039 wrappedInput
1042negateBox box =
1043 { top = negate box.top
1044 , right = negate box.right
1045 , bottom = negate box.bottom
1046 , left = negate box.left
1050renderBox { top, right, bottom, left } =
1051 String.fromInt top
1052 ++ "px "
1053 ++ String.fromInt right
1054 ++ "px "
1055 ++ String.fromInt bottom
1056 ++ "px "
1057 ++ String.fromInt left
1058 ++ "px"
1061renderPlaceholder (Placeholder placeholderAttrs placeholderEl) forPlaceholder on =
1062 Element.el
1063 (forPlaceholder
1064 ++ [ Font.color charcoal
1065 , Internal.htmlClass (classes.noTextSelection ++ " " ++ classes.passPointerEvents)
1066 , Element.clip
1067 , Border.color (Element.rgba 0 0 0 0)
1068 , Background.color (Element.rgba 0 0 0 0)
1069 , Element.height Element.fill
1070 , Element.width Element.fill
1071 , Element.alpha
1072 (if on then
1075 else
1079 ++ placeholderAttrs
1081 placeholderEl
1084{-| Because textareas are now shadowed, where they're rendered twice,
1085we to move the literal text area up because spacing is based on line height.
1087calcMoveToCompensateForPadding : List (Attribute msg) -> Attribute msg
1088calcMoveToCompensateForPadding attrs =
1089 let
1090 gatherSpacing attr found =
1091 case attr of
1092 Internal.StyleClass _ (Internal.SpacingStyle _ x y) ->
1093 case found of
1094 Nothing ->
1095 Just y
1097 _ ->
1098 found
1100 _ ->
1101 found
1102 in
1103 case List.foldr gatherSpacing Nothing attrs of
1104 Nothing ->
1105 Internal.NoAttribute
1107 Just vSpace ->
1108 Element.moveUp (toFloat vSpace / 2)
1111{-| Given the list of attributes provided to `Input.multiline` or `Input.text`,
1113redistribute them to the parent, the input, or the cover.
1115 - fullParent -> Wrapper around label and input
1116 - parent -> parent of wrapper
1117 - wrapper -> the element that is here to take up space.
1118 - cover -> things like placeholders or text areas which are layered on top of input.
1119 - input -> actual input element
1122redistribute :
1123 Bool
1124 -> Bool
1125 -> List (Attribute msg)
1126 ->
1127 { fullParent : List (Attribute msg)
1128 , parent : List (Attribute msg)
1129 , wrapper : List (Attribute msg)
1130 , input : List (Attribute msg)
1131 , cover : List (Attribute msg)
1133redistribute isMultiline stacked attrs =
1134 List.foldl (redistributeOver isMultiline stacked)
1135 { fullParent = []
1136 , parent = []
1137 , input = []
1138 , cover = []
1139 , wrapper = []
1141 attrs
1142 |> (\redist ->
1143 { parent = List.reverse redist.parent
1144 , fullParent = List.reverse redist.fullParent
1145 , wrapper = List.reverse redist.wrapper
1146 , input = List.reverse redist.input
1147 , cover = List.reverse redist.cover
1152isFill len =
1153 case len of
1154 Internal.Fill _ ->
1155 True
1157 Internal.Content ->
1158 False
1160 Internal.Px _ ->
1161 False
1163 Internal.Min _ l ->
1164 isFill l
1166 Internal.Max _ l ->
1167 isFill l
1170isShrink len =
1171 case len of
1172 Internal.Content ->
1173 True
1175 Internal.Px _ ->
1176 False
1178 Internal.Fill _ ->
1179 False
1181 Internal.Min _ l ->
1182 isShrink l
1184 Internal.Max _ l ->
1185 isShrink l
1188isPixel len =
1189 case len of
1190 Internal.Content ->
1191 False
1193 Internal.Px _ ->
1194 True
1196 Internal.Fill _ ->
1197 False
1199 Internal.Min _ l ->
1200 isPixel l
1202 Internal.Max _ l ->
1203 isPixel l
1206{-| isStacked means that the label is above or below
1208redistributeOver isMultiline stacked attr els =
1209 case attr of
1210 Internal.Nearby _ _ ->
1211 { els | parent = attr :: els.parent }
1213 Internal.Width width ->
1214 if stacked then
1215 { els | fullParent = attr :: els.fullParent }
1217 else if isFill width then
1218 { els
1219 | fullParent = attr :: els.fullParent
1220 , parent = attr :: els.parent
1223 else if isPixel width then
1224 { els | parent = attr :: els.parent }
1226 else
1227 { els
1228 | parent = attr :: els.parent
1231 Internal.Height height ->
1232 if not stacked then
1233 { els
1234 | fullParent = attr :: els.fullParent
1235 , parent = attr :: els.parent
1238 else if isFill height then
1239 { els
1240 | fullParent = attr :: els.fullParent
1241 , parent = attr :: els.parent
1244 else if isPixel height then
1245 { els | parent = attr :: els.parent }
1247 else
1248 { els
1249 | parent = attr :: els.parent
1252 Internal.AlignX _ ->
1253 { els | fullParent = attr :: els.fullParent }
1255 Internal.AlignY _ ->
1256 { els | fullParent = attr :: els.fullParent }
1258 Internal.StyleClass _ (Internal.SpacingStyle _ _ _) ->
1259 { els
1260 | fullParent = attr :: els.fullParent
1261 , parent = attr :: els.parent
1262 , input = attr :: els.input
1263 , wrapper = attr :: els.wrapper
1266 Internal.StyleClass cls (Internal.PaddingStyle pad t r b l) ->
1267 if isMultiline then
1268 { els
1269 | parent = attr :: els.parent
1270 , cover = attr :: els.cover
1273 else
1274 let
1275 newHeight =
1276 Element.htmlAttribute
1277 (Html.Attributes.style
1278 "height"
1279 ("calc(1.0em + " ++ String.fromInt (2 * min t b) ++ "px)")
1282 newLineHeight =
1283 Element.htmlAttribute
1284 (Html.Attributes.style
1285 "line-height"
1286 ("calc(1.0em + " ++ String.fromInt (2 * min t b) ++ "px)")
1289 reducedVerticalPadding =
1290 Element.paddingEach
1291 { top = t - min t b
1292 , right = r
1293 , bottom = b - min t b
1294 , left = l
1296 in
1297 { els
1298 | parent = reducedVerticalPadding :: els.parent
1299 , input = newHeight :: newLineHeight :: els.input
1300 , cover = attr :: els.cover
1303 Internal.StyleClass _ (Internal.BorderWidth _ _ _ _ _) ->
1304 { els
1305 | parent = attr :: els.parent
1306 , cover = attr :: els.cover
1309 Internal.StyleClass _ (Internal.Transform _) ->
1310 { els
1311 | parent = attr :: els.parent
1312 , cover = attr :: els.cover
1315 Internal.StyleClass _ (Internal.FontSize _) ->
1316 { els | fullParent = attr :: els.fullParent }
1318 Internal.StyleClass _ (Internal.FontFamily _ _) ->
1319 { els | fullParent = attr :: els.fullParent }
1321 Internal.StyleClass _ _ ->
1322 { els | parent = attr :: els.parent }
1324 Internal.NoAttribute ->
1325 els
1327 Internal.Attr a ->
1328 { els | input = attr :: els.input }
1330 Internal.Describe _ ->
1331 { els | input = attr :: els.input }
1333 Internal.Class _ _ ->
1334 { els | parent = attr :: els.parent }
1336 Internal.TransformComponent _ _ ->
1337 { els | input = attr :: els.input }
1340{-| -}
1341text :
1342 List (Attribute msg)
1343 ->
1344 { onChange : String -> msg
1345 , text : String
1346 , placeholder : Maybe (Placeholder msg)
1347 , label : Label msg
1349 -> Element msg
1350text =
1351 textHelper
1352 { type_ = TextInputNode "text"
1353 , spellchecked = False
1354 , autofill = Nothing
1358{-| If spell checking is available, this input will be spellchecked.
1360spellChecked :
1361 List (Attribute msg)
1362 ->
1363 { onChange : String -> msg
1364 , text : String
1365 , placeholder : Maybe (Placeholder msg)
1366 , label : Label msg
1368 -> Element msg
1369spellChecked =
1370 textHelper
1371 { type_ = TextInputNode "text"
1372 , spellchecked = True
1373 , autofill = Nothing
1377{-| -}
1378search :
1379 List (Attribute msg)
1380 ->
1381 { onChange : String -> msg
1382 , text : String
1383 , placeholder : Maybe (Placeholder msg)
1384 , label : Label msg
1386 -> Element msg
1387search =
1388 textHelper
1389 { type_ = TextInputNode "search"
1390 , spellchecked = False
1391 , autofill = Nothing
1395{-| A password input that allows the browser to autofill.
1397It's `newPassword` instead of just `password` because it gives the browser a hint on what type of password input it is.
1399A password takes all the arguments a normal `Input.text` would, and also **show**, which will remove the password mask (e.g. `****` vs `pass1234`)
1402newPassword :
1403 List (Attribute msg)
1404 ->
1405 { onChange : String -> msg
1406 , text : String
1407 , placeholder : Maybe (Placeholder msg)
1408 , label : Label msg
1409 , show : Bool
1411 -> Element msg
1412newPassword attrs pass =
1413 textHelper
1414 { type_ =
1415 TextInputNode <|
1416 if pass.show then
1417 "text"
1419 else
1420 "password"
1421 , spellchecked = False
1422 , autofill = Just "new-password"
1424 attrs
1425 { onChange = pass.onChange
1426 , text = pass.text
1427 , placeholder = pass.placeholder
1428 , label = pass.label
1432{-| -}
1433currentPassword :
1434 List (Attribute msg)
1435 ->
1436 { onChange : String -> msg
1437 , text : String
1438 , placeholder : Maybe (Placeholder msg)
1439 , label : Label msg
1440 , show : Bool
1442 -> Element msg
1443currentPassword attrs pass =
1444 textHelper
1445 { type_ =
1446 TextInputNode <|
1447 if pass.show then
1448 "text"
1450 else
1451 "password"
1452 , spellchecked = False
1453 , autofill = Just "current-password"
1455 attrs
1456 { onChange = pass.onChange
1457 , text = pass.text
1458 , placeholder = pass.placeholder
1459 , label = pass.label
1463{-| -}
1464username :
1465 List (Attribute msg)
1466 ->
1467 { onChange : String -> msg
1468 , text : String
1469 , placeholder : Maybe (Placeholder msg)
1470 , label : Label msg
1472 -> Element msg
1473username =
1474 textHelper
1475 { type_ = TextInputNode "text"
1476 , spellchecked = False
1477 , autofill = Just "username"
1481{-| -}
1482email :
1483 List (Attribute msg)
1484 ->
1485 { onChange : String -> msg
1486 , text : String
1487 , placeholder : Maybe (Placeholder msg)
1488 , label : Label msg
1490 -> Element msg
1491email =
1492 textHelper
1493 { type_ = TextInputNode "email"
1494 , spellchecked = False
1495 , autofill = Just "email"
1499{-| A multiline text input.
1501By default it will have a minimum height of one line and resize based on it's contents.
1504multiline :
1505 List (Attribute msg)
1506 ->
1507 { onChange : String -> msg
1508 , text : String
1509 , placeholder : Maybe (Placeholder msg)
1510 , label : Label msg
1511 , spellcheck : Bool
1513 -> Element msg
1514multiline attrs multi =
1515 textHelper
1516 { type_ =
1517 TextArea
1518 , spellchecked = multi.spellcheck
1519 , autofill = Nothing
1521 attrs
1522 { onChange = multi.onChange
1523 , text = multi.text
1524 , placeholder = multi.placeholder
1525 , label = multi.label
1529isHiddenLabel label =
1530 case label of
1531 HiddenLabel _ ->
1532 True
1534 _ ->
1535 False
1538applyLabel : List (Attribute msg) -> Label msg -> Element msg -> Element msg
1539applyLabel attrs label input =
1540 case label of
1541 HiddenLabel labelText ->
1542 -- NOTE: This means that the label is applied outside of this function!
1543 -- It would be nice to unify this logic, but it's a little tricky
1544 Internal.element
1545 Internal.asColumn
1546 (Internal.NodeName "label")
1547 attrs
1548 (Internal.Unkeyed [ input ])
1550 Label position labelAttrs labelChild ->
1551 let
1552 labelElement =
1553 Internal.element
1554 Internal.asEl
1555 Internal.div
1556 labelAttrs
1557 (Internal.Unkeyed [ labelChild ])
1558 in
1559 case position of
1560 Above ->
1561 Internal.element
1562 Internal.asColumn
1563 (Internal.NodeName "label")
1564 attrs
1565 (Internal.Unkeyed [ labelElement, input ])
1567 Below ->
1568 Internal.element
1569 Internal.asColumn
1570 (Internal.NodeName "label")
1571 attrs
1572 (Internal.Unkeyed [ input, labelElement ])
1574 OnRight ->
1575 Internal.element
1576 Internal.asRow
1577 (Internal.NodeName "label")
1578 attrs
1579 (Internal.Unkeyed [ input, labelElement ])
1581 OnLeft ->
1582 Internal.element
1583 Internal.asRow
1584 (Internal.NodeName "label")
1585 attrs
1586 (Internal.Unkeyed [ labelElement, input ])
1589{-| -}
1590type Option value msg
1591 = Option value (OptionState -> Element msg)
1594{-| -}
1595type OptionState
1596 = Idle
1597 | Focused
1598 | Selected
1601{-| Add a choice to your radio element. This will be rendered with the default radio icon.
1603option : value -> Element msg -> Option value msg
1604option val txt =
1605 Option val (defaultRadioOption txt)
1608{-| Customize exactly what your radio option should look like in different states.
1610optionWith : value -> (OptionState -> Element msg) -> Option value msg
1611optionWith val view =
1612 Option val view
1615{-| -}
1616radio :
1617 List (Attribute msg)
1618 ->
1619 { onChange : option -> msg
1620 , options : List (Option option msg)
1621 , selected : Maybe option
1622 , label : Label msg
1624 -> Element msg
1625radio =
1626 radioHelper Column
1629{-| Same as radio, but displayed as a row
1631radioRow :
1632 List (Attribute msg)
1633 ->
1634 { onChange : option -> msg
1635 , options : List (Option option msg)
1636 , selected : Maybe option
1637 , label : Label msg
1639 -> Element msg
1640radioRow =
1641 radioHelper Row
1644defaultRadioOption : Element msg -> OptionState -> Element msg
1645defaultRadioOption optionLabel status =
1646 Element.row
1647 [ Element.spacing 10
1648 , Element.alignLeft
1649 , Element.width Element.shrink
1651 [ Element.el
1652 [ Element.width (Element.px 14)
1653 , Element.height (Element.px 14)
1654 , Background.color white
1655 , Border.rounded 7
1656 , case status of
1657 Selected ->
1658 Internal.htmlClass "focusable"
1660 _ ->
1661 Internal.NoAttribute
1663 -- , Border.shadow <|
1664 -- -- case status of
1665 -- -- Idle ->
1666 -- -- { offset = ( 0, 0 )
1667 -- -- , blur =
1668 -- -- 1
1669 -- -- , color = Color.rgb 235 235 235
1670 -- -- }
1671 -- -- Focused ->
1672 -- -- { offset = ( 0, 0 )
1673 -- -- , blur =
1674 -- -- 0
1675 -- -- , color = Color.rgba 235 235 235 0
1676 -- -- }
1677 -- -- Selected ->
1678 -- { offset = ( 0, 0 )
1679 -- , blur =
1680 -- 1
1681 -- , color = Color.rgba 235 235 235 0
1682 -- }
1683 , Border.width <|
1684 case status of
1685 Idle ->
1688 Focused ->
1691 Selected ->
1693 , Border.color <|
1694 case status of
1695 Idle ->
1696 Element.rgb (208 / 255) (208 / 255) (208 / 255)
1698 Focused ->
1699 Element.rgb (208 / 255) (208 / 255) (208 / 255)
1701 Selected ->
1702 Element.rgb (59 / 255) (153 / 255) (252 / 255)
1704 Element.none
1705 , Element.el [ Element.width Element.fill, Internal.htmlClass "unfocusable" ] optionLabel
1709radioHelper :
1710 Orientation
1711 -> List (Attribute msg)
1712 ->
1713 { onChange : option -> msg
1714 , options : List (Option option msg)
1715 , selected : Maybe option
1716 , label : Label msg
1718 -> Element msg
1719radioHelper orientation attrs input =
1720 let
1721 renderOption (Option val view) =
1722 let
1723 status =
1724 if Just val == input.selected then
1725 Selected
1727 else
1728 Idle
1729 in
1730 Element.el
1731 [ Element.pointer
1732 , case orientation of
1733 Row ->
1734 Element.width Element.shrink
1736 Column ->
1737 Element.width Element.fill
1738 , Events.onClick (input.onChange val)
1739 , case status of
1740 Selected ->
1741 Internal.Attr <|
1742 Html.Attributes.attribute "aria-checked"
1743 "true"
1745 _ ->
1746 Internal.Attr <|
1747 Html.Attributes.attribute "aria-checked"
1748 "false"
1749 , Internal.Attr <|
1750 Html.Attributes.attribute "role" "radio"
1752 (view status)
1754 optionArea =
1755 case orientation of
1756 Row ->
1757 row (hiddenLabelAttribute input.label :: attrs)
1758 (List.map renderOption input.options)
1760 Column ->
1761 column (hiddenLabelAttribute input.label :: attrs)
1762 (List.map renderOption input.options)
1764 prevNext =
1765 case input.options of
1766 [] ->
1767 Nothing
1769 (Option val _) :: _ ->
1770 List.foldl track ( NotFound, val, val ) input.options
1771 |> (\( found, b, a ) ->
1772 case found of
1773 NotFound ->
1774 Just ( b, val )
1776 BeforeFound ->
1777 Just ( b, val )
1779 _ ->
1780 Just ( b, a )
1783 track opt ( found, prev, nxt ) =
1784 case opt of
1785 Option val _ ->
1786 case found of
1787 NotFound ->
1788 if Just val == input.selected then
1789 ( BeforeFound, prev, nxt )
1791 else
1792 ( found, val, nxt )
1794 BeforeFound ->
1795 ( AfterFound, prev, val )
1797 AfterFound ->
1798 ( found, prev, nxt )
1800 events =
1801 Internal.get
1802 attrs
1803 <|
1804 \attr ->
1805 case attr of
1806 Internal.Width (Internal.Fill _) ->
1807 True
1809 Internal.Height (Internal.Fill _) ->
1810 True
1812 Internal.Attr _ ->
1813 True
1815 _ ->
1816 False
1817 in
1818 applyLabel
1819 (List.filterMap identity
1820 [ Just Element.alignLeft
1821 , Just (tabindex 0)
1822 , Just (Internal.htmlClass "focus")
1823 , Just Region.announce
1824 , Just <|
1825 Internal.Attr <|
1826 Html.Attributes.attribute "role" "radiogroup"
1827 , case prevNext of
1828 Nothing ->
1829 Nothing
1831 Just ( prev, next ) ->
1832 Just
1833 (onKeyLookup <|
1834 \code ->
1835 if code == leftArrow then
1836 Just (input.onChange prev)
1838 else if code == upArrow then
1839 Just (input.onChange prev)
1841 else if code == rightArrow then
1842 Just (input.onChange next)
1844 else if code == downArrow then
1845 Just (input.onChange next)
1847 else if code == space then
1848 case input.selected of
1849 Nothing ->
1850 Just (input.onChange prev)
1852 _ ->
1853 Nothing
1855 else
1856 Nothing
1859 ++ events
1860 -- ++ hideIfEverythingisInvisible
1862 input.label
1863 optionArea
1866type Found
1867 = NotFound
1868 | BeforeFound
1869 | AfterFound
1872type Orientation
1873 = Row
1874 | Column
1877column : List (Attribute msg) -> List (Internal.Element msg) -> Internal.Element msg
1878column attributes children =
1879 Internal.element
1880 Internal.asColumn
1881 Internal.div
1882 (Element.height Element.shrink
1883 :: Element.width Element.fill
1884 :: attributes
1886 (Internal.Unkeyed children)
1889row : List (Attribute msg) -> List (Internal.Element msg) -> Internal.Element msg
1890row attributes children =
1891 Internal.element
1892 Internal.asRow
1893 Internal.div
1894 (Element.width Element.fill
1895 :: attributes
1897 (Internal.Unkeyed children)
1901{- Event Handlers -}
1904{-| -}
1905onEnter : msg -> Attribute msg
1906onEnter msg =
1907 onKey enter msg
1910{-| -}
1911onSpace : msg -> Attribute msg
1912onSpace msg =
1913 onKey space msg
1916{-| -}
1917onUpArrow : msg -> Attribute msg
1918onUpArrow msg =
1919 onKey upArrow msg
1922{-| -}
1923onRightArrow : msg -> Attribute msg
1924onRightArrow msg =
1925 onKey rightArrow msg
1928{-| -}
1929onLeftArrow : msg -> Attribute msg
1930onLeftArrow msg =
1931 onKey leftArrow msg
1934{-| -}
1935onDownArrow : msg -> Attribute msg
1936onDownArrow msg =
1937 onKey downArrow msg
1940enter : String
1941enter =
1942 "Enter"
1945tab : String
1946tab =
1947 "Tab"
1950delete : String
1951delete =
1952 "Delete"
1955backspace : String
1956backspace =
1957 "Backspace"
1960upArrow : String
1961upArrow =
1962 "ArrowUp"
1965leftArrow : String
1966leftArrow =
1967 "ArrowLeft"
1970rightArrow : String
1971rightArrow =
1972 "ArrowRight"
1975downArrow : String
1976downArrow =
1977 "ArrowDown"
1980space : String
1981space =
1982 " "
1985{-| -}
1986onKey : String -> msg -> Attribute msg
1987onKey desiredCode msg =
1988 let
1989 decode code =
1990 if code == desiredCode then
1991 Json.succeed msg
1993 else
1994 Json.fail "Not the enter key"
1996 isKey =
1997 Json.field "key" Json.string
1998 |> Json.andThen decode
1999 in
2000 Internal.Attr <|
2001 Html.Events.preventDefaultOn "keyup"
2002 (Json.map (\fired -> ( fired, True )) isKey)
2006-- preventKeydown : String -> a -> Attribute a
2007-- preventKeydown desiredCode msg =
2008-- let
2009-- decode code =
2010-- if code == desiredCode then
2011-- Json.succeed msg
2012-- else
2013-- Json.fail "Not the enter key"
2014-- isKey =
2015-- Json.field "key" Json.string
2016-- |> Json.andThen decode
2017-- in
2018-- Events.onWithOptions "keydown"
2019-- { stopPropagation = False
2020-- , preventDefault = True
2021-- }
2022-- isKey
2025{-| -}
2026onKeyLookup : (String -> Maybe msg) -> Attribute msg
2027onKeyLookup lookup =
2028 let
2029 decode code =
2030 case lookup code of
2031 Nothing ->
2032 Json.fail "No key matched"
2034 Just msg ->
2035 Json.succeed msg
2037 isKey =
2038 Json.field "key" Json.string
2039 |> Json.andThen decode
2040 in
2041 Internal.Attr <| Html.Events.on "keyup" isKey
2044{-| -}
2045onFocusOut : msg -> Attribute msg
2046onFocusOut msg =
2047 Internal.Attr <| Html.Events.on "focusout" (Json.succeed msg)
2050{-| -}
2051onFocusIn : msg -> Attribute msg
2052onFocusIn msg =
2053 Internal.Attr <| Html.Events.on "focusin" (Json.succeed msg)
2056selected : Bool -> Attribute msg
2057selected =
2058 Internal.Attr << Html.Attributes.selected
2061name : String -> Attribute msg
2062name =
2063 Internal.Attr << Html.Attributes.name
2066value : String -> Attribute msg
2067value =
2068 Internal.Attr << Html.Attributes.value
2071tabindex : Int -> Attribute msg
2072tabindex =
2073 Internal.Attr << Html.Attributes.tabindex
2076disabled : Bool -> Attribute msg
2077disabled =
2078 Internal.Attr << Html.Attributes.disabled
2081spellcheck : Bool -> Attribute msg
2082spellcheck =
2083 Internal.Attr << Html.Attributes.spellcheck
2086readonly : Bool -> Attribute msg
2087readonly =
2088 Internal.Attr << Html.Attributes.readonly
2091autofill : String -> Attribute msg
2092autofill =
2093 Internal.Attr << Html.Attributes.attribute "autocomplete"
2096{-| Attach this attribute to any `Input` that you would like to be automatically focused when the page loads.
2098You should only have a maximum of one per page.
2101focusedOnLoad : Attribute msg
2102focusedOnLoad =
2103 Internal.Attr <| Html.Attributes.autofocus True
2107{- Style Defaults -}
2110defaultTextBoxStyle : List (Attribute msg)
2111defaultTextBoxStyle =
2112 [ defaultTextPadding
2113 , Border.rounded 3
2114 , Border.color darkGrey
2115 , Background.color white
2116 , Border.width 1
2117 , Element.spacing 5
2118 , Element.width Element.fill
2119 , Element.height Element.shrink
2123defaultTextPadding : Attribute msg
2124defaultTextPadding =
2125 Element.paddingXY 12 12
2128{-| The blue default checked box icon.
2130You'll likely want to make your own checkbox at some point that fits your design.
2133defaultCheckbox : Bool -> Element msg
2134defaultCheckbox checked =
2135 Element.el
2136 [ Internal.htmlClass "focusable"
2137 , Element.width (Element.px 14)
2138 , Element.height (Element.px 14)
2139 , Font.color white
2140 , Element.centerY
2141 , Font.size 9
2142 , Font.center
2143 , Border.rounded 3
2144 , Border.color <|
2145 if checked then
2146 Element.rgb (59 / 255) (153 / 255) (252 / 255)
2148 else
2149 Element.rgb (211 / 255) (211 / 255) (211 / 255)
2150 , Border.shadow
2151 { offset = ( 0, 0 )
2152 , blur = 1
2153 , size = 1
2154 , color =
2155 if checked then
2156 Element.rgba (238 / 255) (238 / 255) (238 / 255) 0
2158 else
2159 Element.rgb (238 / 255) (238 / 255) (238 / 255)
2161 , Background.color <|
2162 if checked then
2163 Element.rgb (59 / 255) (153 / 255) (252 / 255)
2165 else
2166 white
2167 , Border.width <|
2168 if checked then
2171 else
2174 (if checked then
2175 Element.el
2176 [ Border.color white
2177 , Element.height (Element.px 6)
2178 , Element.width (Element.px 9)
2179 , Element.rotate (degrees -45)
2180 , Element.centerX
2181 , Element.centerY
2182 , Element.moveUp 1
2183 , Border.widthEach
2184 { top = 0
2185 , left = 2
2186 , bottom = 2
2187 , right = 0
2190 Element.none
2192 else
2193 Element.none