git.iko.soy/typeable/elm-ui
on cd088cb help sign in
2205 lines 63 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
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")
947 ]
948 )
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
955 ]
956 ++ redistributed.input
957 )
958 (Internal.Unkeyed [])
960 wrappedInput =
961 case textInput.type_ of
962 TextArea ->
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.
966 Internal.element
967 Internal.asEl
968 Internal.div
969 ((if heightContent then
970 [ Element.width Element.fill
971 , Internal.htmlClass classes.focusedWithin
972 ]
974 else
975 [ Element.width Element.fill
976 , Internal.htmlClass classes.focusedWithin
977 , Element.scrollbarY
978 ]
979 )
980 ++ redistributed.parent
981 )
982 (Internal.Unkeyed
983 [ Internal.element
984 Internal.asParagraph
985 Internal.div
986 (Element.width Element.fill
987 :: Element.height Element.fill
988 :: Element.inFront inputElement
989 :: Internal.htmlClass classes.inputMultilineParent
990 :: redistributed.wrapper
991 )
992 (Internal.Unkeyed
993 (if textOptions.text == "" then
994 case textOptions.placeholder of
995 Nothing ->
996 []
998 Just place ->
999 [ renderPlaceholder place
1000 []
1001 (textOptions.text == "")
1004 else
1005 [ Internal.unstyled
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 ->
1018 Internal.element
1019 Internal.asEl
1020 Internal.div
1021 (Element.width Element.fill
1022 :: Internal.htmlClass classes.focusedWithin
1023 :: List.concat
1024 [ redistributed.parent
1025 , case textOptions.placeholder of
1026 Nothing ->
1027 []
1029 Just place ->
1030 [ Element.behindContent
1031 (renderPlaceholder place redistributed.cover (textOptions.text == ""))
1035 (Internal.Unkeyed [ inputElement ])
1036 in
1037 applyLabel
1038 (Internal.Class Flag.cursor classes.cursorText
1039 :: (if isHiddenLabel textOptions.label then
1040 Internal.NoAttribute
1042 else
1043 Element.spacing
1046 :: Region.announce
1047 :: redistributed.fullParent
1049 textOptions.label
1050 wrappedInput
1053negateBox box =
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 } =
1062 String.fromInt top
1063 ++ "px "
1064 ++ String.fromInt right
1065 ++ "px "
1066 ++ String.fromInt bottom
1067 ++ "px "
1068 ++ String.fromInt left
1069 ++ "px"
1072renderPlaceholder (Placeholder placeholderAttrs placeholderEl) forPlaceholder on =
1073 Element.el
1074 (forPlaceholder
1075 ++ [ Font.color charcoal
1076 , Internal.htmlClass (classes.noTextSelection ++ " " ++ classes.passPointerEvents)
1077 , Element.clip
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
1082 , Element.alpha
1083 (if on then
1086 else
1090 ++ placeholderAttrs
1092 placeholderEl
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 =
1100 let
1101 gatherSpacing attr found =
1102 case attr of
1103 Internal.StyleClass _ (Internal.SpacingStyle _ x y) ->
1104 case found of
1105 Nothing ->
1106 Just y
1108 _ ->
1109 found
1111 _ ->
1112 found
1113 in
1114 case List.foldr gatherSpacing Nothing attrs of
1115 Nothing ->
1116 Internal.NoAttribute
1118 Just vSpace ->
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
1133redistribute :
1134 Bool
1135 -> Bool
1136 -> List (Attribute msg)
1137 ->
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)
1146 { fullParent = []
1147 , parent = []
1148 , input = []
1149 , cover = []
1150 , wrapper = []
1152 attrs
1153 |> (\redist ->
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
1163isFill len =
1164 case len of
1165 Internal.Fill _ ->
1166 True
1168 Internal.Content ->
1169 False
1171 Internal.Px _ ->
1172 False
1174 Internal.Min _ l ->
1175 isFill l
1177 Internal.Max _ l ->
1178 isFill l
1181isShrink len =
1182 case len of
1183 Internal.Content ->
1184 True
1186 Internal.Px _ ->
1187 False
1189 Internal.Fill _ ->
1190 False
1192 Internal.Min _ l ->
1193 isShrink l
1195 Internal.Max _ l ->
1196 isShrink l
1199isPixel len =
1200 case len of
1201 Internal.Content ->
1202 False
1204 Internal.Px _ ->
1205 True
1207 Internal.Fill _ ->
1208 False
1210 Internal.Min _ l ->
1211 isPixel l
1213 Internal.Max _ l ->
1214 isPixel l
1217{-| isStacked means that the label is above or below
1219redistributeOver isMultiline stacked attr els =
1220 case attr of
1221 Internal.Nearby _ _ ->
1222 { els | parent = attr :: els.parent }
1224 Internal.Width width ->
1225 if stacked then
1226 { els | fullParent = attr :: els.fullParent }
1228 else if isFill width then
1229 { els
1230 | fullParent = attr :: els.fullParent
1231 , parent = attr :: els.parent
1234 else if isPixel width then
1235 { els | parent = attr :: els.parent }
1237 else
1238 { els
1239 | parent = attr :: els.parent
1242 Internal.Height height ->
1243 if not stacked then
1244 { els
1245 | fullParent = attr :: els.fullParent
1246 , parent = attr :: els.parent
1249 else if isFill height then
1250 { els
1251 | fullParent = attr :: els.fullParent
1252 , parent = attr :: els.parent
1255 else if isPixel height then
1256 { els | parent = attr :: els.parent }
1258 else
1259 { els
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 _ _ _) ->
1270 { els
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) ->
1278 if isMultiline then
1279 { els
1280 | parent = attr :: els.parent
1281 , cover = attr :: els.cover
1284 else
1285 let
1286 newHeight =
1287 Element.htmlAttribute
1288 (Html.Attributes.style
1289 "height"
1290 ("calc(1.0em + " ++ String.fromInt (2 * min t b) ++ "px)")
1293 newLineHeight =
1294 Element.htmlAttribute
1295 (Html.Attributes.style
1296 "line-height"
1297 ("calc(1.0em + " ++ String.fromInt (2 * min t b) ++ "px)")
1300 reducedVerticalPadding =
1301 Element.paddingEach
1302 { top = t - min t b
1303 , right = r
1304 , bottom = b - min t b
1305 , left = l
1307 in
1308 { els
1309 | parent = reducedVerticalPadding :: els.parent
1310 , input = newHeight :: newLineHeight :: els.input
1311 , cover = attr :: els.cover
1314 Internal.StyleClass _ (Internal.BorderWidth _ _ _ _ _) ->
1315 { els
1316 | parent = attr :: els.parent
1317 , cover = attr :: els.cover
1320 Internal.StyleClass _ (Internal.Transform _) ->
1321 { els
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 ->
1336 els
1338 Internal.Attr a ->
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 }
1351{-| -}
1352text :
1353 List (Attribute msg)
1354 ->
1355 { onChange : String -> msg
1356 , text : String
1357 , placeholder : Maybe (Placeholder msg)
1358 , label : Label msg
1360 -> Element msg
1361text =
1362 textHelper
1363 { type_ = TextInputNode "text"
1364 , spellchecked = False
1365 , autofill = Nothing
1369{-| If spell checking is available, this input will be spellchecked.
1371spellChecked :
1372 List (Attribute msg)
1373 ->
1374 { onChange : String -> msg
1375 , text : String
1376 , placeholder : Maybe (Placeholder msg)
1377 , label : Label msg
1379 -> Element msg
1380spellChecked =
1381 textHelper
1382 { type_ = TextInputNode "text"
1383 , spellchecked = True
1384 , autofill = Nothing
1388{-| -}
1389search :
1390 List (Attribute msg)
1391 ->
1392 { onChange : String -> msg
1393 , text : String
1394 , placeholder : Maybe (Placeholder msg)
1395 , label : Label msg
1397 -> Element msg
1398search =
1399 textHelper
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`)
1413newPassword :
1414 List (Attribute msg)
1415 ->
1416 { onChange : String -> msg
1417 , text : String
1418 , placeholder : Maybe (Placeholder msg)
1419 , label : Label msg
1420 , show : Bool
1422 -> Element msg
1423newPassword attrs pass =
1424 textHelper
1425 { type_ =
1426 TextInputNode <|
1427 if pass.show then
1428 "text"
1430 else
1431 "password"
1432 , spellchecked = False
1433 , autofill = Just "new-password"
1435 attrs
1436 { onChange = pass.onChange
1437 , text = pass.text
1438 , placeholder = pass.placeholder
1439 , label = pass.label
1443{-| -}
1444currentPassword :
1445 List (Attribute msg)
1446 ->
1447 { onChange : String -> msg
1448 , text : String
1449 , placeholder : Maybe (Placeholder msg)
1450 , label : Label msg
1451 , show : Bool
1453 -> Element msg
1454currentPassword attrs pass =
1455 textHelper
1456 { type_ =
1457 TextInputNode <|
1458 if pass.show then
1459 "text"
1461 else
1462 "password"
1463 , spellchecked = False
1464 , autofill = Just "current-password"
1466 attrs
1467 { onChange = pass.onChange
1468 , text = pass.text
1469 , placeholder = pass.placeholder
1470 , label = pass.label
1474{-| -}
1475username :
1476 List (Attribute msg)
1477 ->
1478 { onChange : String -> msg
1479 , text : String
1480 , placeholder : Maybe (Placeholder msg)
1481 , label : Label msg
1483 -> Element msg
1484username =
1485 textHelper
1486 { type_ = TextInputNode "text"
1487 , spellchecked = False
1488 , autofill = Just "username"
1492{-| -}
1493email :
1494 List (Attribute msg)
1495 ->
1496 { onChange : String -> msg
1497 , text : String
1498 , placeholder : Maybe (Placeholder msg)
1499 , label : Label msg
1501 -> Element msg
1502email =
1503 textHelper
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.
1515multiline :
1516 List (Attribute msg)
1517 ->
1518 { onChange : String -> msg
1519 , text : String
1520 , placeholder : Maybe (Placeholder msg)
1521 , label : Label msg
1522 , spellcheck : Bool
1524 -> Element msg
1525multiline attrs multi =
1526 textHelper
1527 { type_ =
1528 TextArea
1529 , spellchecked = multi.spellcheck
1530 , autofill = Nothing
1532 attrs
1533 { onChange = multi.onChange
1534 , text = multi.text
1535 , placeholder = multi.placeholder
1536 , label = multi.label
1540isHiddenLabel label =
1541 case label of
1542 HiddenLabel _ ->
1543 True
1545 _ ->
1546 False
1549applyLabel : List (Attribute msg) -> Label msg -> Element msg -> Element msg
1550applyLabel attrs label input =
1551 case label of
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
1555 Internal.element
1556 Internal.asColumn
1557 (Internal.NodeName "label")
1558 attrs
1559 (Internal.Unkeyed [ input ])
1561 Label position labelAttrs labelChild ->
1562 let
1563 labelElement =
1564 Internal.element
1565 Internal.asEl
1566 Internal.div
1567 labelAttrs
1568 (Internal.Unkeyed [ labelChild ])
1569 in
1570 case position of
1571 Above ->
1572 Internal.element
1573 Internal.asColumn
1574 (Internal.NodeName "label")
1575 attrs
1576 (Internal.Unkeyed [ labelElement, input ])
1578 Below ->
1579 Internal.element
1580 Internal.asColumn
1581 (Internal.NodeName "label")
1582 attrs
1583 (Internal.Unkeyed [ input, labelElement ])
1585 OnRight ->
1586 Internal.element
1587 Internal.asRow
1588 (Internal.NodeName "label")
1589 attrs
1590 (Internal.Unkeyed [ input, labelElement ])
1592 OnLeft ->
1593 Internal.element
1594 Internal.asRow
1595 (Internal.NodeName "label")
1596 attrs
1597 (Internal.Unkeyed [ labelElement, input ])
1600{-| -}
1601type Option value msg
1602 = Option value (OptionState -> Element msg)
1605{-| -}
1606type OptionState
1607 = Idle
1608 | Focused
1609 | Selected
1612{-| Add a choice to your radio element. This will be rendered with the default radio icon.
1614option : value -> Element msg -> Option value msg
1615option val txt =
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 =
1623 Option val view
1626{-| -}
1627radio :
1628 List (Attribute msg)
1629 ->
1630 { onChange : option -> msg
1631 , options : List (Option option msg)
1632 , selected : Maybe option
1633 , label : Label msg
1635 -> Element msg
1636radio =
1637 radioHelper Column
1640{-| Same as radio, but displayed as a row
1642radioRow :
1643 List (Attribute msg)
1644 ->
1645 { onChange : option -> msg
1646 , options : List (Option option msg)
1647 , selected : Maybe option
1648 , label : Label msg
1650 -> Element msg
1651radioRow =
1652 radioHelper Row
1655defaultRadioOption : Element msg -> OptionState -> Element msg
1656defaultRadioOption optionLabel status =
1657 Element.row
1658 [ Element.spacing 10
1659 , Element.alignLeft
1660 , Element.width Element.shrink
1662 [ Element.el
1663 [ Element.width (Element.px 14)
1664 , Element.height (Element.px 14)
1665 , Background.color white
1666 , Border.rounded 7
1667 , case status of
1668 Selected ->
1669 Internal.htmlClass "focusable"
1671 _ ->
1672 Internal.NoAttribute
1674 -- , Border.shadow <|
1675 -- -- case status of
1676 -- -- Idle ->
1677 -- -- { offset = ( 0, 0 )
1678 -- -- , blur =
1679 -- -- 1
1680 -- -- , color = Color.rgb 235 235 235
1681 -- -- }
1682 -- -- Focused ->
1683 -- -- { offset = ( 0, 0 )
1684 -- -- , blur =
1685 -- -- 0
1686 -- -- , color = Color.rgba 235 235 235 0
1687 -- -- }
1688 -- -- Selected ->
1689 -- { offset = ( 0, 0 )
1690 -- , blur =
1691 -- 1
1692 -- , color = Color.rgba 235 235 235 0
1693 -- }
1694 , Border.width <|
1695 case status of
1696 Idle ->
1699 Focused ->
1702 Selected ->
1704 , Border.color <|
1705 case status of
1706 Idle ->
1707 Element.rgb (208 / 255) (208 / 255) (208 / 255)
1709 Focused ->
1710 Element.rgb (208 / 255) (208 / 255) (208 / 255)
1712 Selected ->
1713 Element.rgb (59 / 255) (153 / 255) (252 / 255)
1715 Element.none
1716 , Element.el [ Element.width Element.fill, Internal.htmlClass "unfocusable" ] optionLabel
1720radioHelper :
1721 Orientation
1722 -> List (Attribute msg)
1723 ->
1724 { onChange : option -> msg
1725 , options : List (Option option msg)
1726 , selected : Maybe option
1727 , label : Label msg
1729 -> Element msg
1730radioHelper orientation attrs input =
1731 let
1732 renderOption (Option val view) =
1733 let
1734 status =
1735 if Just val == input.selected then
1736 Selected
1738 else
1739 Idle
1740 in
1741 Element.el
1742 [ Element.pointer
1743 , case orientation of
1744 Row ->
1745 Element.width Element.shrink
1747 Column ->
1748 Element.width Element.fill
1749 , Events.onClick (input.onChange val)
1750 , case status of
1751 Selected ->
1752 Internal.Attr <|
1753 Html.Attributes.attribute "aria-checked"
1754 "true"
1756 _ ->
1757 Internal.Attr <|
1758 Html.Attributes.attribute "aria-checked"
1759 "false"
1760 , Internal.Attr <|
1761 Html.Attributes.attribute "role" "radio"
1763 (view status)
1765 optionArea =
1766 case orientation of
1767 Row ->
1768 row (hiddenLabelAttribute input.label :: attrs)
1769 (List.map renderOption input.options)
1771 Column ->
1772 column (hiddenLabelAttribute input.label :: attrs)
1773 (List.map renderOption input.options)
1775 prevNext =
1776 case input.options of
1777 [] ->
1778 Nothing
1780 (Option val _) :: _ ->
1781 List.foldl track ( NotFound, val, val ) input.options
1782 |> (\( found, b, a ) ->
1783 case found of
1784 NotFound ->
1785 Just ( b, val )
1787 BeforeFound ->
1788 Just ( b, val )
1790 _ ->
1791 Just ( b, a )
1794 track opt ( found, prev, nxt ) =
1795 case opt of
1796 Option val _ ->
1797 case found of
1798 NotFound ->
1799 if Just val == input.selected then
1800 ( BeforeFound, prev, nxt )
1802 else
1803 ( found, val, nxt )
1805 BeforeFound ->
1806 ( AfterFound, prev, val )
1808 AfterFound ->
1809 ( found, prev, nxt )
1811 events =
1812 Internal.get
1813 attrs
1814 <|
1815 \attr ->
1816 case attr of
1817 Internal.Width (Internal.Fill _) ->
1818 True
1820 Internal.Height (Internal.Fill _) ->
1821 True
1823 Internal.Attr _ ->
1824 True
1826 _ ->
1827 False
1828 in
1829 applyLabel
1830 (List.filterMap identity
1831 [ Just Element.alignLeft
1832 , Just (tabindex 0)
1833 , Just (Internal.htmlClass "focus")
1834 , Just Region.announce
1835 , Just <|
1836 Internal.Attr <|
1837 Html.Attributes.attribute "role" "radiogroup"
1838 , case prevNext of
1839 Nothing ->
1840 Nothing
1842 Just ( prev, next ) ->
1843 Just
1844 (onKeyLookup <|
1845 \code ->
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
1860 Nothing ->
1861 Just (input.onChange prev)
1863 _ ->
1864 Nothing
1866 else
1867 Nothing
1870 ++ events
1871 -- ++ hideIfEverythingisInvisible
1873 input.label
1874 optionArea
1877type Found
1878 = NotFound
1879 | BeforeFound
1880 | AfterFound
1883type Orientation
1884 = Row
1885 | Column
1888column : List (Attribute msg) -> List (Internal.Element msg) -> Internal.Element msg
1889column attributes children =
1890 Internal.element
1891 Internal.asColumn
1892 Internal.div
1893 (Element.height Element.shrink
1894 :: Element.width Element.fill
1895 :: attributes
1897 (Internal.Unkeyed children)
1900row : List (Attribute msg) -> List (Internal.Element msg) -> Internal.Element msg
1901row attributes children =
1902 Internal.element
1903 Internal.asRow
1904 Internal.div
1905 (Element.width Element.fill
1906 :: attributes
1908 (Internal.Unkeyed children)
1912{- Event Handlers -}
1915{-| -}
1916onEnter : msg -> Attribute msg
1917onEnter msg =
1918 onKey enter msg
1921{-| -}
1922onSpace : msg -> Attribute msg
1923onSpace msg =
1924 onKey space msg
1927{-| -}
1928onUpArrow : msg -> Attribute msg
1929onUpArrow msg =
1930 onKey upArrow msg
1933{-| -}
1934onRightArrow : msg -> Attribute msg
1935onRightArrow msg =
1936 onKey rightArrow msg
1939{-| -}
1940onLeftArrow : msg -> Attribute msg
1941onLeftArrow msg =
1942 onKey leftArrow msg
1945{-| -}
1946onDownArrow : msg -> Attribute msg
1947onDownArrow msg =
1948 onKey downArrow msg
1951enter : String
1952enter =
1953 "Enter"
1956tab : String
1957tab =
1958 "Tab"
1961delete : String
1962delete =
1963 "Delete"
1966backspace : String
1967backspace =
1968 "Backspace"
1971upArrow : String
1972upArrow =
1973 "ArrowUp"
1976leftArrow : String
1977leftArrow =
1978 "ArrowLeft"
1981rightArrow : String
1982rightArrow =
1983 "ArrowRight"
1986downArrow : String
1987downArrow =
1988 "ArrowDown"
1991space : String
1992space =
1993 " "
1996{-| -}
1997onKey : String -> msg -> Attribute msg
1998onKey desiredCode msg =
1999 let
2000 decode code =
2001 if code == desiredCode then
2002 Json.succeed msg
2004 else
2005 Json.fail "Not the enter key"
2007 isKey =
2008 Json.field "key" Json.string
2009 |> Json.andThen decode
2010 in
2011 Internal.Attr <|
2012 Html.Events.preventDefaultOn "keyup"
2013 (Json.map (\fired -> ( fired, True )) isKey)
2017-- preventKeydown : String -> a -> Attribute a
2018-- preventKeydown desiredCode msg =
2019-- let
2020-- decode code =
2021-- if code == desiredCode then
2022-- Json.succeed msg
2023-- else
2024-- Json.fail "Not the enter key"
2025-- isKey =
2026-- Json.field "key" Json.string
2027-- |> Json.andThen decode
2028-- in
2029-- Events.onWithOptions "keydown"
2030-- { stopPropagation = False
2031-- , preventDefault = True
2032-- }
2033-- isKey
2036{-| -}
2037onKeyLookup : (String -> Maybe msg) -> Attribute msg
2038onKeyLookup lookup =
2039 let
2040 decode code =
2041 case lookup code of
2042 Nothing ->
2043 Json.fail "No key matched"
2045 Just msg ->
2046 Json.succeed msg
2048 isKey =
2049 Json.field "key" Json.string
2050 |> Json.andThen decode
2051 in
2052 Internal.Attr <| Html.Events.on "keyup" isKey
2055{-| -}
2056onFocusOut : msg -> Attribute msg
2057onFocusOut msg =
2058 Internal.Attr <| Html.Events.on "focusout" (Json.succeed msg)
2061{-| -}
2062onFocusIn : msg -> Attribute msg
2063onFocusIn msg =
2064 Internal.Attr <| Html.Events.on "focusin" (Json.succeed msg)
2067selected : Bool -> Attribute msg
2068selected =
2069 Internal.Attr << Html.Attributes.selected
2072name : String -> Attribute msg
2073name =
2074 Internal.Attr << Html.Attributes.name
2077value : String -> Attribute msg
2078value =
2079 Internal.Attr << Html.Attributes.value
2082tabindex : Int -> Attribute msg
2083tabindex =
2084 Internal.Attr << Html.Attributes.tabindex
2087disabled : Bool -> Attribute msg
2088disabled =
2089 Internal.Attr << Html.Attributes.disabled
2092spellcheck : Bool -> Attribute msg
2093spellcheck =
2094 Internal.Attr << Html.Attributes.spellcheck
2097readonly : Bool -> Attribute msg
2098readonly =
2099 Internal.Attr << Html.Attributes.readonly
2102autofill : String -> Attribute msg
2103autofill =
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
2113focusedOnLoad =
2114 Internal.Attr <| Html.Attributes.autofocus True
2118{- Style Defaults -}
2121defaultTextBoxStyle : List (Attribute msg)
2122defaultTextBoxStyle =
2123 [ defaultTextPadding
2124 , Border.rounded 3
2125 , Border.color darkGrey
2126 , Background.color white
2127 , Border.width 1
2128 , Element.spacing 5
2129 , Element.width Element.fill
2130 , Element.height Element.shrink
2134defaultTextPadding : Attribute msg
2135defaultTextPadding =
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 =
2146 Element.el
2147 [ Internal.htmlClass "focusable"
2148 , Element.width (Element.px 14)
2149 , Element.height (Element.px 14)
2150 , Font.color white
2151 , Element.centerY
2152 , Font.size 9
2153 , Font.center
2154 , Border.rounded 3
2155 , Border.color <|
2156 if checked then
2157 Element.rgb (59 / 255) (153 / 255) (252 / 255)
2159 else
2160 Element.rgb (211 / 255) (211 / 255) (211 / 255)
2161 , Border.shadow
2162 { offset = ( 0, 0 )
2163 , blur = 1
2164 , size = 1
2165 , color =
2166 if checked then
2167 Element.rgba (238 / 255) (238 / 255) (238 / 255) 0
2169 else
2170 Element.rgb (238 / 255) (238 / 255) (238 / 255)
2172 , Background.color <|
2173 if checked then
2174 Element.rgb (59 / 255) (153 / 255) (252 / 255)
2176 else
2177 white
2178 , Border.width <|
2179 if checked then
2182 else
2185 (if checked then
2186 Element.el
2187 [ Border.color white
2188 , Element.height (Element.px 6)
2189 , Element.width (Element.px 9)
2190 , Element.rotate (degrees -45)
2191 , Element.centerX
2192 , Element.centerY
2193 , Element.moveUp 1
2194 , Border.widthEach
2195 { top = 0
2196 , left = 2
2197 , bottom = 2
2198 , right = 0
2201 Element.none
2203 else
2204 Element.none