@@ -11,7 +11,7 @@ module FormToolkit.Field exposing
1111 , selectionStart, selectionEnd
1212 , options, stringOptions, min, max, step, autogrow
1313 , class, classList
14- , disabled, hidden, noattr, pattern
14+ , disabled, hidden, noattr, pattern, accept
1515 , copies, repeatableMin, repeatableMax
1616 , updateAttribute, updateAttributes, updateWithId
1717 , updateValuesFromJson
@@ -45,7 +45,7 @@ their attributes, update, and render them.
4545@docs selectionStart, selectionEnd
4646@docs options, stringOptions, min, max, step, autogrow
4747@docs class, classList
48- @docs disabled, hidden, noattr, pattern
48+ @docs disabled, hidden, noattr, pattern, accept
4949
5050
5151# Groups
@@ -587,6 +587,7 @@ initAttributes fieldType =
587587 , disabled = False
588588 , hidden = False
589589 , pattern = []
590+ , acceptedMimeTypes = []
590591 }
591592
592593
@@ -913,6 +914,27 @@ pattern patternString =
913914 )
914915
915916
917+ {- | Sets accepted MIME types for file input. Supports wildcards like "image/*" or "*/*".
918+
919+ file
920+ [ label "Profile Picture"
921+ , accept [ "image/png", "image/jpeg", "image/gif" ]
922+ ]
923+
924+ file
925+ [ label "Any Image"
926+ , accept [ "image/*" ]
927+ ]
928+
929+ -}
930+ accept : List String -> Attribute id val
931+ accept mimeTypes =
932+ Attribute
933+ ( \ field ->
934+ { field | acceptedMimeTypes = mimeTypes }
935+ )
936+
937+
916938combineAttrs : Attribute id val -> Attribute id val -> Attribute id val
917939combineAttrs ( Attribute a) ( Attribute b) =
918940 Attribute ( b >> a)
@@ -1343,6 +1365,7 @@ validateNode node =
13431365 , checkRequired
13441366 , ifNotRequired checkInRange
13451367 , ifNotRequired checkEmail
1368+ , ifNotRequired checkMimeType
13461369 , ifNotRequired checkPattern
13471370 ]
13481371
@@ -1467,6 +1490,68 @@ validateUrl urlString =
14671490 |> Url . fromString
14681491
14691492
1493+ checkMimeType : Node id -> Node id
1494+ checkMimeType node =
1495+ let
1496+ attrs =
1497+ Tree . value node
1498+ in
1499+ case ( attrs. fieldType, attrs. acceptedMimeTypes ) of
1500+ ( File , [] ) ->
1501+ node
1502+
1503+ ( File , accepted ) ->
1504+ case Internal . Value . toFile attrs. value of
1505+ Just fileValue ->
1506+ let
1507+ fileMime =
1508+ File . mime fileValue
1509+ in
1510+ if isMimeTypeAccepted fileMime accepted then
1511+ node
1512+
1513+ else
1514+ setError
1515+ ( \ id ->
1516+ MimeTypeInvalid id
1517+ { mime = fileMime
1518+ , accepted = accepted
1519+ }
1520+ )
1521+ node
1522+
1523+ Nothing ->
1524+ node
1525+
1526+ _ ->
1527+ node
1528+
1529+
1530+ isMimeTypeAccepted : String -> List String -> Bool
1531+ isMimeTypeAccepted fileMime acceptedTypes =
1532+ List . any ( matchesMimeType fileMime) acceptedTypes
1533+
1534+
1535+ matchesMimeType : String -> String -> Bool
1536+ matchesMimeType fileMime mimePattern =
1537+ if mimePattern == " */*" || mimePattern == " *" then
1538+ True
1539+
1540+ else
1541+ case String . split " /" mimePattern of
1542+ [ typePattern, subTypePattern ] ->
1543+ case String . split " /" fileMime of
1544+ [ fileType, fileSubType ] ->
1545+ ( typePattern == " *" || typePattern == fileType)
1546+ && ( subTypePattern == " *" || subTypePattern == fileSubType)
1547+
1548+ _ ->
1549+ False
1550+
1551+ _ ->
1552+ fileMime == mimePattern
1553+
1554+
14701555checkPattern : Node id -> Node id
14711556checkPattern node =
14721557 let
@@ -1573,6 +1658,9 @@ mapError transformId error =
15731658 UrlInvalid id ->
15741659 UrlInvalid ( Maybe . map transformId id)
15751660
1661+ MimeTypeInvalid id data ->
1662+ MimeTypeInvalid ( Maybe . map transformId id) data
1663+
15761664 ParseError id ->
15771665 ParseError ( Maybe . map transformId id)
15781666
0 commit comments