a stylish new design toolkit

This commit is contained in:
Matthew Griffith 2018-08-28 19:41:08 -04:00
parent 0abf848616
commit 25850370dc
65 changed files with 18187 additions and 4 deletions

13
.gitignore vendored
View File

@ -1,4 +1,15 @@
# elm-package generated files
elm-stuff
elm-stuff/
# elm-repl generated files
repl-temp-*
.DS_Store
*/index.html
index.html
.elm-static-html
tests/html-generation/rendered/
env
elm.js
examples/temporary/

View File

@ -0,0 +1,202 @@
# Compared to Style Elements
This was a MAJOR rewrite of Style Elements.
* **Major Performance improvement** - Style Elements v5 is much faster than v4 due to a better rendering strategy and generating very minimal html. The rewritten architecture also allows me to explore a few other optimizations, so things may get even faster than they are now.
* **Lazy is here!** - It works with no weird caveats.
## No Stylesheet
You now define styles on the element itself.
```
el [ Background.color blue, Font.color white ] (text "I'm so stylish!")
```
These styles are gathered and rendered into a `stylesheet`. This has a few advantages:
1. Much faster than rendering as actual inline styles
2. More expressive power by allowing style-elements compile to pseudoclasses, css animations and the like.
3. Defining styles like this is a really nice workflow.
## Reorganization
* No `Style` modules anymore, it's all under `Element`.
* `Style.Color` and `Style.Shadow` have been merged into `Element.Font`, `Element.Border`, and `Element.background`. So things like `Font.color` and `Border.glow` now exist.
* No more `Element.Attributes`, everything has been either moved to `Element` or to a more appropriate module.
## Wait isn't Style Elements about separation of layout and style?!
It was! So why is there only `Element` and no `StyleSheet` now?
The key insight is that it's not so much the separation of layout and style that is important as it is that _properties affecting layout should all be in the view function_. The main thing is _not_ having layout specified through several layers of indirection, but having everything explict and in one place.
The new version moves to a more refined version of this idea: **Everything should be explicit and in your view!**
## Style Organization
Your next question might be "if we don't have a stylesheet, how to we capture our style logic in a nice way?"
The main thing I've found is that stylesheets in general seem to be pretty hard to maintain. Even well-typed stylesheets that only allow single classes! You have to manage names for everything, and in the case of large style refactors it's not obvious that style classes would be organized the same way.
So, I'm not sure that stylesheets or something like stylesheets are the way to go.
My current thinking is that you have 2 really powerful methods of capturing your styling logic.
1. **Just put it in a view function.** If you have a few button variations you want, just create a function for each variation. You probably don't need a huge number of variations. You don't need to think of it like recreating bootstrap in style-elements.
2. **Capture your colors, font sizes, etc.** Create a `Style` module that captures the **values** you use for your styling. Keep your colors there, as well as values for spacing, font names, and font sizes all in one place. You should consider using a scaling function for things like spacing and fontsizes.
## Element.Area
The `Element.Area` module is now how you can do accessibility markup.
You can do this by adding an area notation to an element's attributes.
```elm
import Element.Area as Area
row [ Area.navigation ]
[ --..my navigation links
]
```
Or you can make something an `<h1>`
```
el [ Area.heading 1 ] (text "Super important stuff")
```
This means your accessibility markup is separate from your layout markup, which turns out to be really nice.
## Alignment
Alignment got a _bunch_ of attention to make it more powerful and intuitive.
* _Alignment_ now applies to the element it's attached to, so Alignment on `row` and `column` does not apply to the children but to the `row` or `column` itself.
* _It works everywhere!_ Previously, if you set a child as `alignLeft` in a row, nothing would happen. The main weirdness that had to be resolved is what happens to the other elements when an el in the middle of a row is aligned. The answer I came up with is that it will push other elements to the side.
```
alignLeft(pushes element on the left of it, to the left)
| center(is the default now)
| | alignRight
v v v
|-el-|-el-|---------|-el-|---------------|-el-|
```
Also of note, is that if something is `center`, then it will truly be in the center.
* _Centered by Default_ - `el`s are centered by default.
## Things that have been removed/deprecated
**Percent** - `percent` is no longer there for width/height values. Just use `fill`, because you were probably just using `percent 100`, right? If you really need something like percent, you can manage it pretty easy with `fillPortion`. The main reason this goes away is that `percent` allows you to accidently overflow your element when trying to sum up multiple elements.
**Grids** - `grid`, and `namedGrid` are gone. The reason for this is that for 95% of cases, just composing something with `row` and `column` results in _much_ nicer code. I'm open to see arguments for `grid`, but I need to see specific realworld usecases that can't be done using `row` and `column`.
**WrappedRows/Columns** - `wrappedRow` and `wrappedColumn` are gone. From what I can see these cases don't show up very often. Removing them allows me to be cleaner with the internal style-elements code as well.
**When/WhenJust** - `when` and `whenJust` are removed, though you can easily make a convenience function for yourself! I wanted the library to place an emphasis on common elm constructs instead of library-specific ones. As far as shortcuts, they don't actually save you much anyway.
**Full, Spacer** - `full` and `spacer` have been removed in order to follow the libraries priority of explicitness. `full` would override the parents padding, while `spacer` would override the parent's `spacing`. Both can be achieved with the more common primities of `row`, `column` and `spacing`, and potentially some nesting of layouts.
# New Version of the Alpha
- `Font.weight` has been removed in favor of `Font.extraBold`, `Font.regular`, `Font.light`, etc. All weights from 100 - 900 are represented.
- `Background.image` and `Background.fittedImage` will place a centered background image, instead of anchoring at the top left.
- `fillBetween { min : Maybe Int, max : Maybe Int}` is now present for `min/max height/width` behavior. It works like fill, but with an optional top and lower bound.
- `transparent` - Set an element as transparent. It will take up space, but otherwise be transparent and unclickable.
- `alpha` can now be set for an element.
- `attribute` has been renamed `htmlAttribute` to better convey what it's used for.
- `Element.Area` has been renamed `Element.Region` to avoid confusion with `WAI ARIA` stuff.
- `center` has been renamed `centerX`
# New Default Behavior
The default logic has been made more consistent and hopefully more intuitive.
Al elements start with `width/height shrink`, which means that they are the size of their contents.
# PseudoClass Support
`Element.mouseOver`, `Element.focused`, and `Element.mouseDown` are available to style `:hover`, `:focus` and `:active`.
Only a small subset of properties are allowed here or else the compiler will give you an error.
This also introduced some new type aliases for attributes.
`Attribute msg` - What you're used to. This **cannot** be used in a mouseOver/focused/etc.
`Attr decorative msg` - A new attribute alias for attributes that can be used as a normal attribute or in `mouseOver`, `focused`, etc. I like to think of this as a *Decorative Attribute*.
# Input
`Input.select` has been removed. Ultimately this came down to it being recommended against for most UX purposes.
If you're looking for a replacement, consider any of these options which will likely create a better experience:
- Input.checkbox
- Input.radio/Input.radioRow with custom styling
- Input.text with some sort of suggestion/autocomplete attached to it.
If you still need to have a select menu, you can either:
- *Embed one* using `html`
- [Craft one by having a hidden `radio` that is shown on focus.](https://gist.github.com/mdgriffith/b99b7ee04eaabaac042572e328a85345) You'll have to store some state that indicates if the menu is open or not, but you'd have to do that anyway if this library was directly supporting `select`.
*Input.Notices* have been removed, which includes warnings and errors. Accessibility is important to this library and this change is actually meant to make it easier to have good form validation feedback.
You can just use `above`/`below` when you need to show a validation message and it will be announced politely to users using screen readers.
Notices were originally annotated as errors or warnings so that `aria-invalid` could be attached. However, it seems to me that having the changes be announced politely is better than having the screen reader just say "Yo, something's invalid". You now have more control over the feedback! Craft your messages well :)
Type aliases for the records used for inputs were also removed because it gives a nicer error message which references specific fields instead of the top level type alias.
# New Testing Capabilities
A test suite of ~1.6k layout tests was written(whew!). All of these tests pass on Chrome, Firefox, Safari, Edge, and IE11.
# Overview of other changes
- `Font.lineHeight` has been removed. Instead, `spacing` now works on paragraphs.
- `Element.empty` has been renamed `Element.none` to be more consistent with other elm libraries.
- `Device` no longer includes `window.width` and `window.height`. Previously every view function that depends on `device` was forced to rerender when the window was resized, which meant you couldn't take advantage of lazy. If you do need the window coordinates you can save them separately.
- *Fewer nodes rendered* - So, things should be faster!
- `fillBetween` has been replaced by `Element.minimum` and `Element.maximum`.
So now you can do things like
```elm
view =
el
[ width
(fill
|> minimum 20
|> maximum 200
)
]
(text "woohoo, I have a min and max")
```

18
CSS-LOOKUP.md Normal file
View File

@ -0,0 +1,18 @@
# CSS Concepts and where to find them
This library creates a new language around layout and style, though if you're already used to CSS, you're probably wondering where certain concepts lie.
> I know how I can do it in CSS, but how could I approach the problem using Style Elements?
CSS | Style Elements | Description
-------|------------------|------------
`position:absolute` | `above`, `below`, `onRight`, `onLeft`, `inFront`, `behindContent` | In Style Elements we can attach elements relative to another element. They won't affect normal flow, just like `position:absolute`
`position:fixed` | `inFront` if it's attached to the `Element.layout` element. | `position:fixed` needs to be at the top of your view or else it can break in seemingly random ways. Did you know `position:fixed` will position something relative to the viewport *OR* any parent that uses `filter`, `transform` or `perspective`? So you add a blur effect and your layout breaks...
`z-index` | __N/A__ | One of the goals of the library was to make `z-index` a behind-the-scenes detail. If you ever encounter a situation where you feel like you actually need it, let me know on slack or through the issues.
`float:left` `float:right` | `alignLeft` or `alignRight` when inside a `paragraph` or a `textColumn` |
`opacity` | `alpha` |
`margin` | __N/A__ Instead, check out `padding` and `spacing` | `margin` in CSS was designed to fight with `padding`. This library was designed to minimize override logic and properties that fight with each other in order to create a layout language that is predictable and easy to think about. The result is that in style elements, there's generally only *one place* where an effect can happen.
`:hover`, `:focus`, `:active` | `mouseOver`, `focused`, `mouseDown` | Only certain styles are allowed to be in a pseudo state. They have the type `Attr decorative msg`, which means they can be either an `Attribute` or a `Decoration`.
`<form>` | __N/a__ | __Elm__ already has a mechanism for submiting data to a server, namely the `Http` package. There has been some mention that the `form` element might be beneficial accessibility-wise, which I'm definitely open to hearing about!
`onSubmit` | __N/A__ | Similar to `<form>`, there is no `onSubmit` behavior. Likely if you're attempting to capture some of the keybaord related behavior of `onSubmit`, you're likely better just crafting a keyboard even handler in the first place!

View File

@ -1,5 +1,3 @@
BSD 3-Clause License
Copyright (c) 2018, Matthew Griffith
All rights reserved.
@ -13,7 +11,7 @@ modification, are permitted provided that the following conditions are met:
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of the copyright holder nor the names of its
* Neither the name of Elm UI nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.

57
README.md Normal file
View File

@ -0,0 +1,57 @@
# A New Language for Layout and Interface
CSS and HTML are actually quite difficult to use when you're trying to do the layout and styling of a web page.
This library is a complete alternative to HTML and CSS. Basically you can just write your app using this library and (mostly) never have to think about HTML and CSS again.
The high level goal of this library is to be a **design toolkit** that draws inspiration from the domains of design, layout, and typography, as opposed to drawing from the ideas as implemented in CSS and HTML.
This means:
* Writing and designing your layout and `view` should be as **simple and as fun** as possible.
* Many layout errors (like you'd run into using CSS) **are just not possible to write** in the first place!
* Everything should just **run fast.**
* **Layout and style are explicit and easy to modify.** CSS and HTML as tools for a layout language are hard to modify because there's no central place that represents your layout. You're generally forced to bounce back and forth between multiple definitions in multiple files in order to adjust layout, even though it's probably the most common thing you'll do.
```elm
import Color exposing (blue, darkBlue)
import Element exposing (Element, el, text, row, alignRight)
import Element.Background as Background
import Element.Border as Border
main =
Element.layout []
myElement
myRowOfStuff =
row [ width fill ]
[ myElement
, myElement
, el [ alignRight ] myElement
]
myElement : Element msg
myElement =
el
[ Background.color blue
, Border.color darkBlue
]
(text "You've made a stylish element!")
```
## History
The work is based off of a rewrite of the [Style Elements](https://github.com/mdgriffith/style-elements) library. A lot of that work was orignally released under the [Stylish Elephants](https://github.com/mdgriffith/stylish-elephants) project.

26
elm.json Normal file
View File

@ -0,0 +1,26 @@
{
"type": "package",
"name": "mdgriffith/elm-ui",
"summary": "Layout and style that's easy to refactor, all without thinking about CSS.",
"license": "BSD-3-Clause",
"version": "1.0.0",
"exposed-modules": [
"Element",
"Element.Input",
"Element.Events",
"Element.Background",
"Element.Border",
"Element.Font",
"Element.Lazy",
"Element.Keyed",
"Element.Region"
],
"elm-version": "0.19.0 <= v < 0.20.0",
"dependencies": {
"elm/core": "1.0.0 <= v < 2.0.0",
"elm/html": "1.0.0 <= v < 2.0.0",
"elm/json": "1.0.0 <= v < 2.0.0",
"elm/virtual-dom": "1.0.0 <= v < 2.0.0"
},
"test-dependencies": {}
}

29
examples/Basic.elm Normal file
View File

@ -0,0 +1,29 @@
module Main exposing (..)
{-| -}
import Element exposing (..)
import Element.Background as Background
import Element.Font as Font
import Element.Input
import Element.Lazy
main =
Element.layout
[ Background.color (rgba 0 0 0 1)
, Font.color (rgba 1 1 1 1)
, Font.italic
, Font.size 32
, Font.family
[ Font.external
{ url = "https://fonts.googleapis.com/css?family=EB+Garamond"
, name = "EB Garamond"
}
, Font.sansSerif
]
]
<|
el
[ centerX, centerY ]
(text "Hello stylish friend!")

201
examples/Form.elm Normal file
View File

@ -0,0 +1,201 @@
module Main exposing (..)
{-| -}
import Browser
import Element exposing (..)
import Element.Background as Background
import Element.Border as Border
import Element.Font as Font
import Element.Input as Input
import Element.Region as Region
white =
Element.rgb 1 1 1
grey =
Element.rgb 0.9 0.9 0.9
blue =
Element.rgb 0 0 0.8
red =
Element.rgb 0.8 0 0
darkBlue =
Element.rgb 0 0 0.9
main =
Browser.sandbox
{ init = init
, view = view
, update = update
}
init =
{ username = ""
, password = ""
, agreeTOS = False
, comment = "Extra hot sauce?\n\n\nYes pls"
, lunch = Gyro
, spiciness = 2
}
type alias Form =
{ username : String
, password : String
, agreeTOS : Bool
, comment : String
, lunch : Lunch
, spiciness : Float
}
type Msg
= Update Form
update msg model =
case Debug.log "msg" msg of
Update new ->
new
type Lunch
= Burrito
| Taco
| Gyro
view model =
Element.layout
[ Font.size 20
]
<|
Element.column [ width (px 800), height shrink, centerY, centerX, spacing 36, padding 10, explain Debug.todo ]
[ el
[ Region.heading 1
, alignLeft
, Font.size 36
]
(text "Welcome to the Stylish Elephants Lunch Emporium")
, Input.radio
[ spacing 12
, Background.color grey
]
{ selected = Just model.lunch
, onChange = \new -> Update { model | lunch = new }
, label = Input.labelAbove [ Font.size 14, paddingXY 0 12 ] (text "What would you like for lunch?")
, options =
[ Input.option Gyro (text "Gyro")
, Input.option Burrito (text "Burrito")
, Input.option Taco (text "Taco")
]
}
, Input.username
[ spacing 12
, below
(el
[ Font.color red
, Font.size 14
, alignRight
, moveDown 6
]
(text "This one is wrong")
)
]
{ text = model.username
, placeholder = Just (Input.placeholder [] (text "username"))
, onChange = \new -> Update { model | username = new }
, label = Input.labelAbove [ Font.size 14 ] (text "Username")
}
, Input.currentPassword [ spacing 12, width shrink ]
{ text = model.password
, placeholder = Nothing
, onChange = \new -> Update { model | password = new }
, label = Input.labelAbove [ Font.size 14 ] (text "Password")
, show = False
}
, Input.multiline
[ height shrink
, spacing 12
-- , padding 6
]
{ text = model.comment
, placeholder = Just (Input.placeholder [] (text "Extra hot sauce?\n\n\nYes pls"))
, onChange = \new -> Update { model | comment = new }
, label = Input.labelAbove [ Font.size 14 ] (text "Leave a comment!")
, spellcheck = False
}
, Input.checkbox []
{ checked = model.agreeTOS
, onChange = \new -> Update { model | agreeTOS = new }
, icon = Input.defaultCheckbox
, label = Input.labelRight [] (text "Agree to Terms of Service")
}
, Input.slider
[ Element.height (Element.px 30)
, Element.behindContent
(Element.el
[ Element.width Element.fill
, Element.height (Element.px 2)
, Element.centerY
, Background.color grey
, Border.rounded 2
]
Element.none
)
]
{ onChange = \new -> Update { model | spiciness = new }
, label = Input.labelAbove [] (text ("Spiciness: " ++ String.fromFloat model.spiciness))
, min = 0
, max = 3.2
, step = Nothing
, value = model.spiciness
, thumb =
Input.defaultThumb
}
, Input.slider
[ Element.width (Element.px 40)
, Element.height (Element.px 200)
, Element.behindContent
(Element.el
[ Element.height Element.fill
, Element.width (Element.px 2)
, Element.centerX
, Background.color grey
, Border.rounded 2
]
Element.none
)
]
{ onChange = \new -> Update { model | spiciness = new }
, label = Input.labelAbove [] (text ("Spiciness: " ++ String.fromFloat model.spiciness))
, min = 0
, max = 3.2
, step = Nothing
, value = model.spiciness
, thumb =
Input.defaultThumb
}
, Input.button
[ Background.color blue
, Font.color white
, Border.color darkBlue
, paddingXY 32 16
, Border.rounded 3
, width fill
]
{ onPress = Nothing
, label = Element.text "Place your lunch order!"
}
]

65
examples/Table.elm Normal file
View File

@ -0,0 +1,65 @@
module Main exposing (..)
{-| -}
import Element exposing (..)
import Element.Background as Background
import Element.Font as Font
import Element.Input
import Element.Lazy
type alias Person =
{ firstName : String
, lastName : String
}
persons : List Person
persons =
[ { firstName = "David"
, lastName = "Bowie"
}
, { firstName = "Florence"
, lastName = "Welch"
}
]
main =
Element.layout
[ Background.color (rgba 0 0 0 1)
, Font.color (rgba 1 1 1 1)
, Font.italic
, Font.size 32
, Font.family
[ Font.external
{ url = "https://fonts.googleapis.com/css?family=EB+Garamond"
, name = "EB Garamond"
}
, Font.sansSerif
]
]
<|
Element.table
[ Element.centerX
, Element.centerY
, Element.spacing 5
, Element.padding 10
]
{ data = persons
, columns =
[ { header = Element.text "First Name"
, width = px 200
, view =
\person ->
Element.text person.firstName
}
, { header = Element.text "Last Name"
, width = fill
, view =
\person ->
Element.text person.lastName
}
]
}

25
examples/elm.json Normal file
View File

@ -0,0 +1,25 @@
{
"type": "application",
"source-directories": [
".",
"../src/"
],
"elm-version": "0.19.0",
"dependencies": {
"direct": {
"elm/browser": "1.0.0",
"elm/core": "1.0.0",
"elm/html": "1.0.0",
"elm/json": "1.0.0",
"elm/virtual-dom": "1.0.0"
},
"indirect": {
"elm/time": "1.0.0",
"elm/url": "1.0.0"
}
},
"test-dependencies": {
"direct": {},
"indirect": {}
}
}

View File

@ -0,0 +1,57 @@
module Accumulation exposing (..)
{-| -}
import Set
type Style
= Color String Float Float Float Float
| Spacing String Float
| Font String (List String)
key style =
case style of
Color k _ _ _ _ ->
k
Spacing k _ ->
k
Font k _ ->
k
type Element
= Element (List Style) (List Element)
| None
render element =
case element of
None ->
[]
Element styles children ->
let
childrenStyles =
List.foldr render [] children
in
styles ++ childrenStyles
finalize styles =
List.foldl deduplicate ( Set.empty, [] ) styles
deduplicate style ( cached, styles ) =
if Set.member (key style) then
( cached, styles )
else
( Set.insert (key style), style :: styles )
toHtml element =
render element
|> finalize

View File

@ -0,0 +1,10 @@
module Main exposing (..)
{-| -}
import Html
import MaskedInput
main =
Html.text "Testing"

View File

@ -0,0 +1,27 @@
{
"type": "application",
"source-directories": [
".",
"src"
],
"elm-version": "0.19.0",
"dependencies": {
"direct": {
"elm/browser": "1.0.0",
"elm/core": "1.0.0",
"elm/html": "1.0.0",
"elm/json": "1.0.0",
"elm/parser": "1.0.0",
"elm/virtual-dom": "1.0.0",
"elm-community/list-extra": "8.0.0"
},
"indirect": {
"elm/time": "1.0.0",
"elm/url": "1.0.0"
}
},
"test-dependencies": {
"direct": {},
"indirect": {}
}
}

View File

@ -0,0 +1,130 @@
module MaskedInput exposing (..)
{-| When we've created a Mask, it should be able to:
- The validator's job is only to say if an entire string is valid.
- If a string is valid, then a message is sent out with the updated value.
- If a string is not valid, then the last(i.e. the current) value is sent out on a message.
- We have to send out a message on every input or else we'll get out of sync.(?)
- The formatter's job is to take an input string and to format it into a view-string
- The capturing parser's job is to transform the input string into the desired value.
- If the parser fails, we keep returning `Partial`
- As soon as it succeeds, we can return `Full`, which can have a value extracted.
First pass at full description.
Mask is defined in a view.
- An initial string is potentially given.
- String is formatted and displayed
- onInput handler is registered which parses the value, and creates either a Partial or a Full
- The onInput handler will return the formatted string.
->? we could diff it against a previous formatted string to see what changed. Maybe too complicated
->? Capture keyboard events directly and make modifications there. What about pasting?
->! Have the parser operate directly on the formatted string.
-}
import Parser exposing (Parser)
type Masked input
= Mask
{ capture : Parser input -- String -> input
, format : List Formatter -- tel: "86" -> "(86 ) - "
, validate : List Validator -- Is a character allowed to be typed
}
type Validator
= Validator Int (Char -> Bool)
type Formatter
= Exactly String
| FromInput Int (Maybe Hint) -- hint is shown if no string can be retrieved
type alias Hint =
String
type Captured thing
= Partial String
| Full thing String
capture : input -> Masked input
capture value =
Mask
{ capture = Parser.succeed value
, format = []
, validate = []
}
{-| Shows a static string.
-}
show str (Mask mask) =
Mask
{ mask
| format = mask.format ++ [ Exactly str ]
, capture = Parser.token str
}
type alias Match =
{ length : Int
, valid : Char -> Bool
, hint : Maybe String
}
{-| -}
match matcher (Mask mask) =
Mask
{ mask
| format = mask.format ++ [ FromInput matcher.length matcher.hint ]
, validate = mask.validate ++ [ Validator matcher.length matcher.valid ]
, capture = Parser.exactly matcher.length matcher.valid
}
-- map : (a -> b) -> Masked a -> Masked b
-- map =
-- Debug.crash "TODO"
-- map2 :
-- (a -> b -> value)
-- -> Masked a
-- -> Masked b
-- -> Masked value
-- map2 =
-- Debug.crash "TODO"
view : String -> Masked input -> Element msg
view =
Debug.crash
captureValue : String -> Masked input -> Captured input
captureValue input (Mask mask) =
case Parser.run mask.capture input of
Ok val ->
Full val input
Err _ ->
Partial input
valid : Captured input -> Maybe input
valid cap =
case cap of
Partial _ ->
Nothing
Full result _ ->
Just result

View File

@ -0,0 +1,189 @@
module Element.Input.Mask exposing (..)
{-| With masked input we can simultaneously descibe:
- a parser to only allow specific arguments
- We have a set of expected characters and how many of them to expect.
- Static formating. Meaning show a `/` between two numbers, but it's not part of the input
- Hints. These are mini placeholder values for a section of your input.
- Autocomplete suggestions. These are suggestions that can be selected and completed with `tab`.
-}
type Masked input
= Mask (List Pattern)
type Pattern
= Capture Limit (Char -> Bool)
| Decimal
| Decoration String
type Limit
= NoLimit
| Min Int
| Max Int
| MinMax Int Int
type Input thing
= Partial String
| Full thing String
type alias CreditCard =
{ number : String
, expMonth : String
, expYear : String
, ccv : String
}
type alias CreditCardNumber =
String
example =
capture (\one two three four -> CreditCardNumber (one ++ two ++ three ++ four))
|> Mask.stringWith
{ length = 4
, valid = String.isDigit
, hint = "1234"
}
|> Mask.show " "
|> Mask.stringWith
{ length = 4
, valid = String.isDigit
, hint = "1234"
}
|> Mask.show " "
|> Mask.stringWith
{ length = 4
, valid = String.isDigit
, hint = "1234"
}
|> Mask.space
|> Mask.stringWith
{ length = 4
, valid = String.isDigit
, hint = "1234"
}
|> Mask.andThen
(\creditcard ->
capture (CreditCard creditNumber)
|> Mask.show "1234"
-- last four digits of credit card
|> Mask.space
|> Mask.stringWith
{ length = 2
, valid = String.isDigit
, hint = "MM"
}
|> Mask.show "/"
|> Mask.stringWith
{ length = 2
, valid = String.isDigit
, hint = "YY"
}
|> Mask.space
|> Mask.stringWith
{ length = 4
, valid = String.isDigit
, hint = "CCV"
}
)
float =
Mask.int
|> Mask.token "."
|> Mask.andThen
(\one ->
masked (\two -> one + two)
--combine ints in a way
|> Mask.token (toString one)
|> Mask.token "."
|> Mask.stringWith
{ length = 3 -- No length restrictions
, valid = String.isDigit
, hint = "CCV"
}
)
{-| <https://ellie-app.com/JJxYGFKptVa1>
<https://ellie-app.com/JKMJgSTtn2a1>
<https://ellie-app.com/KdY8X99fqba1>
-}
-- type Masked input
-- = Mask
-- { capture : Parser input -- String -> input
-- , format : List Formatter -- tel: "86" -> "(86 ) - "
-- , validate : List Validator -- Is a character allowed to be typed
-- }
-- type Validator
-- = Match Int (Char -> Bool)
-- type Formatter
-- = Exactly String
-- | FromInput Int Hint -- hint is shown if no string can be retrieved
-- type alias Hint =
-- String
-- -- type Masked input =
-- -- { parser : Parser input
-- -- ,
-- -- }
-- {-| We want this to be in
-- ---> formatting
-- -x-> result
-- -}
-- show str mask =
-- mask
-- {-| ---> formatting
-- ---> result
-- | if nothing's parsed
-- hint ---> formatting
-- hint -x-> result
-- -}
-- stringWith options mask =
-- mask
-- type Pattern input
-- = Capture Limit (Char -> Bool)
-- | Decimal
-- | Show String
-- type Limit
-- = NoLimit
-- | Min Int
-- | Max Int
-- | MinMax Int Int
-- type Captured input
-- = Partial String
-- | Full input String
-- {-| A placeholder ot represent different pieces having different styles
-- -}
-- type Styled
-- = Styled String String
-- render : String -> Masked input -> List Styled
-- captureValue : String -> Masked input -> Captured input
-- capture : input -> Masked input
-- map : (a -> b) -> Masked a -> Masked b
-- map2 :
-- (a -> b -> value)
-- -> Masked a
-- -> Masked b
-- -> Masked value
-- andThen : (a -> Masked b) -> Masked a -> Masked b
-- valid : Captured input -> Maybe input
-- valid cap =
-- case cap of
-- Partial _ ->
-- Nothing
-- Full result _ ->
-- Just result

View File

@ -0,0 +1,37 @@
module Main exposing (..)
import Element exposing (..)
import Html
import Palette
{- First, Define our Palette
Usually this would be in a different file.
-}
type alias MyColorPalette =
{ primary : Palette.Protected Color
, secondary : Palette.Protected Color
}
myColors : Palette.Colors MyColorPalette
myColors =
Palette.colors MyColorPalette
|> Palette.color (rgb 1 0 0)
|> Palette.color (rgb 1 0 1)
main =
Palette.layout myColors
[]
(Palette.Element
[
-- Palette.bgColor red
--
Palette.bgColor .primary
-- Palette.bgColor (Palette.dynamic (rgb 0 1 0))
]
[]
)

View File

@ -0,0 +1,27 @@
{
"type": "application",
"source-directories": [
".",
"src",
"../src/"
],
"elm-version": "0.19.0",
"dependencies": {
"direct": {
"elm/browser": "1.0.0",
"elm/core": "1.0.0",
"elm/html": "1.0.0",
"elm/json": "1.0.0",
"elm/virtual-dom": "1.0.0",
"elm-community/list-extra": "8.0.0"
},
"indirect": {
"elm/time": "1.0.0",
"elm/url": "1.0.0"
}
},
"test-dependencies": {
"direct": {},
"indirect": {}
}
}

View File

@ -0,0 +1,214 @@
module Palette exposing (..)
{-| With palettes, we want the dev to be able to
- specify some colors
- those colors then need to be rendered by the layout
- those colors can then be referred to by name in the layout
- A lookup is done to retrieve the classname
-}
import Element exposing (Color)
import Html exposing (Html)
import Html.Attributes
import Internal.Model as Internal
{- Declare a Palette of Colors (or any value, but colors for not) -}
{- -}
layout : Colors colors -> List (Attribute colors msg) -> Element colors msg -> Html msg
layout colorPalette attrs child =
-- Render a "stylesheet"
let
( html, dynamicStyles ) =
renderPaletteElement colorPalette (Element attrs [ child ])
in
Html.div [ Html.Attributes.class "root" ]
[ Html.text "static stylesheet"
, Html.div
[ Html.Attributes.style "padding" "20px"
, Html.Attributes.style "whitespace" "pre"
, Html.Attributes.style "border-radius" "4px"
, Html.Attributes.style "background-color" "#CCDDCC"
, Html.Attributes.style "font-family" "Open Sans"
]
[ Html.text (String.join "\n" (renderColorPalette colorPalette)) ]
, Html.node "style"
[]
[ Html.text ("html,body,.root {width: 100%; height: 100%;}.ui {min-width:100px; min-height: 100px;}" ++ String.join "\n" (renderColorPalette colorPalette))
]
, Html.text "dynamic stylesheet"
, Html.div
[ Html.Attributes.style "padding" "20px"
, Html.Attributes.style "whitespace" "pre"
, Html.Attributes.style "border-radius" "4px"
, Html.Attributes.style "background-color" "#FFDDCC"
, Html.Attributes.style "font-family" "Open Sans"
]
[ Html.text (String.join "\n" dynamicStyles) ]
, Html.node "style" [] [ Html.text (String.join "\n" dynamicStyles) ]
, Html.text "content:"
, html
]
renderColor clr =
Internal.formatColor clr
colorRule clr =
".bg-clr-"
++ Internal.formatColorClass clr
++ "{ background-color:"
++ Internal.formatColor clr
++ " }"
renderColorPalette : Colors colors -> List String
renderColorPalette (Colors palette) =
List.map
colorRule
palette.values
renderPaletteElement : Colors colors -> InternalElement (colors -> Protected Color) msg -> ( Html msg, List String )
renderPaletteElement colorPalette (Element attrs children) =
let
gatherAttr attr ( attributes, myAttrStyles ) =
let
( cls, style ) =
renderPaletteAttribute colorPalette attr
in
( cls ++ " " ++ attributes, style :: myAttrStyles )
( classes, attrStyles ) =
List.foldl gatherAttr ( "", [] ) attrs
( renderedChildren, styles ) =
List.foldr gather ( [], attrStyles ) children
gather child ( rendered, existingStyles ) =
let
( childHtml, childStyles ) =
renderPaletteElement colorPalette child
in
( childHtml :: rendered, childStyles ++ existingStyles )
in
( Html.div [ Html.Attributes.class ("ui " ++ classes) ]
renderedChildren
, styles
)
renderPaletteAttribute : Colors colors -> InternalAttribute (colors -> Protected Color) msg -> ( String, String )
renderPaletteAttribute (Colors palette) attribute =
case attribute of
InternalAttribute ->
( "ui", "" )
ColorStyle colorLookup ->
case colorLookup palette.protected of
Dynamic clr ->
( "bg-clr-" ++ Internal.formatColorClass clr, colorRule clr )
Protected clr ->
( "bg-clr-" ++ Internal.formatColorClass clr, "" )
renderAttribute : Colors color -> InternalAttribute Color msg -> String
renderAttribute (Colors palette) attribute =
case attribute of
InternalAttribute ->
""
ColorStyle clr ->
"dynamic clr"
bgColor : color -> InternalAttribute color msg
bgColor clr =
ColorStyle clr
{-| Concrete Values
-}
type alias Attr msg =
InternalAttribute Color msg
{-| Palette based attributes
-}
type alias Attribute colors msg =
InternalAttribute (colors -> Protected Color) msg
type alias Element colors msg =
InternalElement (colors -> Protected Color) msg
type alias El msg =
InternalElement Color msg
type InternalAttribute color msg
= InternalAttribute
| ColorStyle color
type InternalElement color msg
= Element (List (InternalAttribute color msg)) (List (InternalElement color msg))
{-| This is how we keep track of something that's already rendered, (Protected), or needs to be rendered
-}
type Protected thing
= Protected thing
| Dynamic thing
-- dynamic : Color -> Colors colors -> Protected Color
dynamic value palette =
Dynamic value
{- COLORS -}
type Colors a
= Colors
{ protected : a
, values : List Color
}
colors : a -> Colors a
colors a =
Colors
{ protected = a
, values = []
}
color : Color -> Colors (Protected Color -> a) -> Colors a
color clr pal =
let
addColor p =
{ protected = p.protected (Protected clr)
, values = clr :: p.values
}
in
map addColor pal
map : ({ protected : a, values : List Color } -> { protected : a1, values : List Color }) -> Colors a -> Colors a1
map fn pal =
case pal of
Colors a ->
Colors (fn a)

1587
src/Element.elm Normal file

File diff suppressed because it is too large Load Diff

232
src/Element/Background.elm Normal file
View File

@ -0,0 +1,232 @@
module Element.Background
exposing
( color
, gradient
, image
, tiled
, tiledX
, tiledY
, uncropped
)
{-|
@docs color, gradient
# Images
@docs image, uncropped, tiled, tiledX, tiledY
**Note** if you want more control over a background image than is provided here, you should try just using a normal `Element.image` with something like `Element.behind`.
-}
import Element exposing (Attr, Attribute, Color)
import Internal.Flag as Flag
import Internal.Model as Internal
import VirtualDom
{-| -}
color : Color -> Attr decorative msg
color clr =
Internal.StyleClass Flag.bgColor (Internal.Colored ("bg-" ++ Internal.formatColorClass clr) "background-color" clr)
{-| Resize the image to fit the containing element while maintaining proportions and cropping the overflow.
-}
image : String -> Attribute msg
image src =
Internal.Attr (VirtualDom.style "background" ("url(\"" ++ src ++ "\") center / cover no-repeat"))
{-| A centered background image that keeps it's natural propostions, but scales to fit the space.
-}
uncropped : String -> Attribute msg
uncropped src =
Internal.Attr (VirtualDom.style "background" ("url(\"" ++ src ++ "\") center / contain no-repeat"))
{-| Tile an image in the x and y axes.
-}
tiled : String -> Attribute msg
tiled src =
Internal.Attr (VirtualDom.style "background" ("url(\"" ++ src ++ "\") repeat"))
{-| Tile an image in the x axis.
-}
tiledX : String -> Attribute msg
tiledX src =
Internal.Attr (VirtualDom.style "background" ("url(\"" ++ src ++ "\") repeat-x"))
{-| Tile an image in the y axis.
-}
tiledY : String -> Attribute msg
tiledY src =
Internal.Attr (VirtualDom.style "background" ("url(\"" ++ src ++ "\") repeat-y"))
type Direction
= ToUp
| ToDown
| ToRight
| ToTopRight
| ToBottomRight
| ToLeft
| ToTopLeft
| ToBottomLeft
| ToAngle Float
type Step
= ColorStep Color
| PercentStep Float Color
| PxStep Int Color
{-| -}
step : Color -> Step
step =
ColorStep
{-| -}
percent : Float -> Color -> Step
percent =
PercentStep
{-| -}
px : Int -> Color -> Step
px =
PxStep
{-| A linear gradient.
First you need to specify what direction the gradient is going by providing an angle in radians. `0` is up and `pi` is down.
The colors will be evenly spaced.
-}
gradient :
{ angle : Float
, steps : List Color
}
-> Attr decorative msg
gradient { angle, steps } =
case steps of
[] ->
Internal.NoAttribute
clr :: [] ->
Internal.StyleClass Flag.bgColor
(Internal.Colored ("bg-" ++ Internal.formatColorClass clr) "background-color" clr)
_ ->
Internal.StyleClass Flag.bgGradient <|
Internal.Single ("bg-grad-" ++ (String.join "-" <| Internal.floatClass angle :: List.map Internal.formatColorClass steps))
"background-image"
("linear-gradient(" ++ (String.join ", " <| (String.fromFloat angle ++ "rad") :: List.map Internal.formatColor steps) ++ ")")
-- {-| -}
-- gradientWith : { direction : Direction, steps : List Step } -> Attribute msg
-- gradientWith { direction, steps } =
-- StyleClass <|
-- Single ("bg-gradient-" ++ (String.join "-" <| renderDirectionClass direction :: List.map renderStepClass steps))
-- "background"
-- ("linear-gradient(" ++ (String.join ", " <| renderDirection direction :: List.map renderStep steps) ++ ")")
-- {-| -}
-- renderStep : Step -> String
-- renderStep step =
-- case step of
-- ColorStep color ->
-- formatColor color
-- PercentStep percent color ->
-- formatColor color ++ " " ++ toString percent ++ "%"
-- PxStep px color ->
-- formatColor color ++ " " ++ toString px ++ "px"
-- {-| -}
-- renderStepClass : Step -> String
-- renderStepClass step =
-- case step of
-- ColorStep color ->
-- formatColorClass color
-- PercentStep percent color ->
-- formatColorClass color ++ "-" ++ floatClass percent ++ "p"
-- PxStep px color ->
-- formatColorClass color ++ "-" ++ toString px ++ "px"
-- toUp : Direction
-- toUp =
-- ToUp
-- toDown : Direction
-- toDown =
-- ToDown
-- toRight : Direction
-- toRight =
-- ToRight
-- toTopRight : Direction
-- toTopRight =
-- ToTopRight
-- toBottomRight : Direction
-- toBottomRight =
-- ToBottomRight
-- toLeft : Direction
-- toLeft =
-- ToLeft
-- toTopLeft : Direction
-- toTopLeft =
-- ToTopLeft
-- toBottomLeft : Direction
-- toBottomLeft =
-- ToBottomLeft
-- angle : Float -> Direction
-- angle rad =
-- ToAngle rad
-- renderDirection : Direction -> String
-- renderDirection dir =
-- case dir of
-- ToUp ->
-- "to top"
-- ToDown ->
-- "to bottom"
-- ToRight ->
-- "to right"
-- ToTopRight ->
-- "to top right"
-- ToBottomRight ->
-- "to bottom right"
-- ToLeft ->
-- "to left"
-- ToTopLeft ->
-- "to top left"
-- ToBottomLeft ->
-- "to bottom left"
-- ToAngle angle ->
-- toString angle ++ "rad"
-- renderDirectionClass : Direction -> String
-- renderDirectionClass dir =
-- case dir of
-- ToUp ->
-- "to-top"
-- ToDown ->
-- "to-bottom"
-- ToRight ->
-- "to-right"
-- ToTopRight ->
-- "to-top-right"
-- ToBottomRight ->
-- "to-bottom-right"
-- ToLeft ->
-- "to-left"
-- ToTopLeft ->
-- "to-top-left"
-- ToBottomLeft ->
-- "to-bottom-left"
-- ToAngle angle ->
-- floatClass angle ++ "rad"

221
src/Element/Border.elm Normal file
View File

@ -0,0 +1,221 @@
module Element.Border
exposing
( color
, dashed
, dotted
, glow
, innerGlow
, innerShadow
, roundEach
, rounded
, shadow
, solid
, width
, widthEach
, widthXY
)
{-|
@docs color
## Border Widths
@docs width, widthXY, widthEach
## Border Styles
@docs solid, dashed, dotted
## Rounded Corners
@docs rounded, roundEach
## Shadows
@docs glow, innerGlow, shadow, innerShadow
-}
import Element exposing (Attr, Attribute, Color)
import Internal.Flag as Flag
import Internal.Model as Internal
import Internal.Style as Style exposing (classes)
{-| -}
color : Color -> Attr decorative msg
color clr =
Internal.StyleClass Flag.borderColor (Internal.Colored ("border-color-" ++ Internal.formatColorClass clr) "border-color" clr)
{-| -}
width : Int -> Attribute msg
width v =
Internal.StyleClass Flag.borderWidth (Internal.Single ("border-" ++ String.fromInt v) "border-width" (String.fromInt v ++ "px"))
{-| Set horizontal and vertical borders.
-}
widthXY : Int -> Int -> Attribute msg
widthXY x y =
Internal.StyleClass Flag.borderWidth (Internal.Single ("border-" ++ String.fromInt x ++ "-" ++ String.fromInt y) "border-width" (String.fromInt y ++ "px " ++ String.fromInt x ++ "px"))
{-| -}
widthEach : { bottom : Int, left : Int, right : Int, top : Int } -> Attribute msg
widthEach { bottom, top, left, right } =
Internal.StyleClass Flag.borderWidth
(Internal.Single ("border-" ++ String.fromInt top ++ "-" ++ String.fromInt right ++ String.fromInt bottom ++ "-" ++ String.fromInt left)
"border-width"
(String.fromInt top
++ "px "
++ String.fromInt right
++ "px "
++ String.fromInt bottom
++ "px "
++ String.fromInt left
++ "px"
)
)
-- {-| No Borders
-- -}
-- none : Attribute msg
-- none =
-- Class "border" "border-none"
{-| -}
solid : Attribute msg
solid =
Internal.Class Flag.borderStyle classes.borderSolid
{-| -}
dashed : Attribute msg
dashed =
Internal.Class Flag.borderStyle classes.borderDashed
{-| -}
dotted : Attribute msg
dotted =
Internal.Class Flag.borderStyle classes.borderDotted
{-| Round all corners.
-}
rounded : Int -> Attribute msg
rounded radius =
Internal.StyleClass Flag.borderRound (Internal.Single ("border-radius-" ++ String.fromInt radius) "border-radius" (String.fromInt radius ++ "px"))
{-| -}
roundEach : { topLeft : Int, topRight : Int, bottomLeft : Int, bottomRight : Int } -> Attribute msg
roundEach { topLeft, topRight, bottomLeft, bottomRight } =
Internal.StyleClass Flag.borderRound
(Internal.Single ("border-radius-" ++ String.fromInt topLeft ++ "-" ++ String.fromInt topRight ++ String.fromInt bottomLeft ++ "-" ++ String.fromInt bottomRight)
"border-radius"
(String.fromInt topLeft
++ "px "
++ String.fromInt topRight
++ "px "
++ String.fromInt bottomRight
++ "px "
++ String.fromInt bottomLeft
++ "px"
)
)
{-| A simple glow by specifying the color and size.
-}
glow : Color -> Float -> Attr decorative msg
glow clr size =
shadow
{ offset = ( 0, 0 )
, size = size
, blur = size * 2
, color = clr
}
{-| -}
innerGlow : Color -> Float -> Attr decorative msg
innerGlow clr size =
innerShadow
{ offset = ( 0, 0 )
, size = size
, blur = size * 2
, color = clr
}
{-| -}
shadow :
{ offset : ( Float, Float )
, size : Float
, blur : Float
, color : Color
}
-> Attr decorative msg
shadow almostShade =
let
shade =
{ inset = False
, offset = almostShade.offset
, size = almostShade.size
, blur = almostShade.blur
, color = almostShade.color
}
in
Internal.StyleClass Flag.shadows <|
Internal.Single (Internal.boxShadowName shade) "box-shadow" (Internal.formatBoxShadow shade)
{-| -}
innerShadow :
{ offset : ( Float, Float )
, size : Float
, blur : Float
, color : Color
}
-> Attr decorative msg
innerShadow almostShade =
let
shade =
{ inset = True
, offset = almostShade.offset
, size = almostShade.size
, blur = almostShade.blur
, color = almostShade.color
}
in
Internal.StyleClass Flag.shadows <|
Internal.Single (Internal.boxShadowName shade) "box-shadow" (Internal.formatBoxShadow shade)
-- {-| -}
-- shadow :
-- { offset : ( Float, Float )
-- , blur : Float
-- , size : Float
-- , color : Color
-- }
-- -> Attr decorative msg
-- shadow shade =
-- Internal.BoxShadow
-- { inset = False
-- , offset = shade.offset
-- , size = shade.size
-- , blur = shade.blur
-- , color = shade.color
-- }

272
src/Element/Events.elm Normal file
View File

@ -0,0 +1,272 @@
module Element.Events
exposing
( onClick
-- , onClickCoords
-- , onClickPageCoords
-- , onClickScreenCoords
, onDoubleClick
, onFocus
, onLoseFocus
-- , onMouseCoords
, onMouseDown
, onMouseEnter
, onMouseLeave
, onMouseMove
-- , onMousePageCoords
-- , onMouseScreenCoords
, onMouseUp
)
{-|
## Mouse Events
@docs onClick, onDoubleClick, onMouseDown, onMouseUp, onMouseEnter, onMouseLeave, onMouseMove
## Focus Events
@docs onFocus, onLoseFocus
-}
import Element exposing (Attribute)
import Html.Events
import Internal.Model as Internal
import Json.Decode as Json
import VirtualDom
-- MOUSE EVENTS
{-| -}
onMouseDown : msg -> Attribute msg
onMouseDown =
Internal.Attr << Html.Events.onMouseDown
{-| -}
onMouseUp : msg -> Attribute msg
onMouseUp =
Internal.Attr << Html.Events.onMouseUp
{-| -}
onClick : msg -> Attribute msg
onClick =
Internal.Attr << Html.Events.onClick
{-| -}
onDoubleClick : msg -> Attribute msg
onDoubleClick =
Internal.Attr << Html.Events.onDoubleClick
{-| -}
onMouseEnter : msg -> Attribute msg
onMouseEnter =
Internal.Attr << Html.Events.onMouseEnter
{-| -}
onMouseLeave : msg -> Attribute msg
onMouseLeave =
Internal.Attr << Html.Events.onMouseLeave
{-| -}
onMouseMove : msg -> Attribute msg
onMouseMove msg =
on "mousemove" (Json.succeed msg)
-- onClickWith
-- { button = primary
-- , send = localCoords Button
-- }
-- type alias Click =
-- { button : Button
-- , send : Track
-- }
-- type Button = Primary | Secondary
-- type Track
-- = ElementCoords
-- | PageCoords
-- | ScreenCoords
-- |
{-| -}
onClickCoords : (Coords -> msg) -> Attribute msg
onClickCoords msg =
on "click" (Json.map msg localCoords)
{-| -}
onClickScreenCoords : (Coords -> msg) -> Attribute msg
onClickScreenCoords msg =
on "click" (Json.map msg screenCoords)
{-| -}
onClickPageCoords : (Coords -> msg) -> Attribute msg
onClickPageCoords msg =
on "click" (Json.map msg pageCoords)
{-| -}
onMouseCoords : (Coords -> msg) -> Attribute msg
onMouseCoords msg =
on "mousemove" (Json.map msg localCoords)
{-| -}
onMouseScreenCoords : (Coords -> msg) -> Attribute msg
onMouseScreenCoords msg =
on "mousemove" (Json.map msg screenCoords)
{-| -}
onMousePageCoords : (Coords -> msg) -> Attribute msg
onMousePageCoords msg =
on "mousemove" (Json.map msg pageCoords)
type alias Coords =
{ x : Int
, y : Int
}
screenCoords : Json.Decoder Coords
screenCoords =
Json.map2 Coords
(Json.field "screenX" Json.int)
(Json.field "screenY" Json.int)
{-| -}
localCoords : Json.Decoder Coords
localCoords =
Json.map2 Coords
(Json.field "offsetX" Json.int)
(Json.field "offsetY" Json.int)
pageCoords : Json.Decoder Coords
pageCoords =
Json.map2 Coords
(Json.field "pageX" Json.int)
(Json.field "pageY" Json.int)
-- FOCUS EVENTS
{-| -}
onLoseFocus : msg -> Attribute msg
onLoseFocus =
Internal.Attr << Html.Events.onBlur
{-| -}
onFocus : msg -> Attribute msg
onFocus =
Internal.Attr << Html.Events.onFocus
-- CUSTOM EVENTS
{-| Create a custom event listener. Normally this will not be necessary, but
you have the power! Here is how `onClick` is defined for example:
import Json.Decode as Json
onClick : msg -> Attribute msg
onClick message =
on "click" (Json.succeed message)
The first argument is the event name in the same format as with JavaScript's
[`addEventListener`][aEL] function.
The second argument is a JSON decoder. Read more about these [here][decoder].
When an event occurs, the decoder tries to turn the event object into an Elm
value. If successful, the value is routed to your `update` function. In the
case of `onClick` we always just succeed with the given `message`.
If this is confusing, work through the [Elm Architecture Tutorial][tutorial].
It really does help!
[aEL]: <https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener>
[decoder]: <http://package.elm-lang.org/packages/elm-lang/core/latest/Json-Decode>
[tutorial]: <https://github.com/evancz/elm-architecture-tutorial/>
-}
on : String -> Json.Decoder msg -> Attribute msg
on event decode =
Internal.Attr <| Html.Events.on event decode
-- {-| Same as `on` but you can set a few options.
-- -}
-- onWithOptions : String -> Html.Events.Options -> Json.Decoder msg -> Attribute msg
-- onWithOptions event options decode =
-- Internal.Attr <| Html.Events.onWithOptions event options decode
-- COMMON DECODERS
{-| A `Json.Decoder` for grabbing `event.target.value`. We use this to define
`onInput` as follows:
import Json.Decode as Json
onInput : (String -> msg) -> Attribute msg
onInput tagger =
on "input" (Json.map tagger targetValue)
You probably will never need this, but hopefully it gives some insights into
how to make custom event handlers.
-}
targetValue : Json.Decoder String
targetValue =
Json.at [ "target", "value" ] Json.string
{-| A `Json.Decoder` for grabbing `event.target.checked`. We use this to define
`onCheck` as follows:
import Json.Decode as Json
onCheck : (Bool -> msg) -> Attribute msg
onCheck tagger =
on "input" (Json.map tagger targetChecked)
-}
targetChecked : Json.Decoder Bool
targetChecked =
Json.at [ "target", "checked" ] Json.bool
{-| A `Json.Decoder` for grabbing `event.keyCode`. This helps you define
keyboard listeners like this:
import Json.Decode as Json
onKeyUp : (Int -> msg) -> Attribute msg
onKeyUp tagger =
on "keyup" (Json.map tagger keyCode)
**Note:** It looks like the spec is moving away from `event.keyCode` and
towards `event.key`. Once this is supported in more browsers, we may add
helpers here for `onKeyUp`, `onKeyDown`, `onKeyPress`, etc.
-}
keyCode : Json.Decoder Int
keyCode =
Json.field "keyCode" Json.int

332
src/Element/Font.elm Normal file
View File

@ -0,0 +1,332 @@
module Element.Font
exposing
( Font
, alignLeft
, alignRight
, bold
, center
, color
, external
, extraBold
, extraLight
, family
, glow
, hairline
, heavy
, italic
, justify
, letterSpacing
, light
, medium
, monospace
, regular
, sansSerif
, semiBold
, serif
, shadow
, size
, strike
, typeface
, underline
, unitalicized
, wordSpacing
)
{-|
import Color exposing (blue)
import Element
import Element.Font as Font
view =
Element.el
[ Font.color blue
, Font.size 18
, Font.family
[ Font.typeface "Open Sans"
, Font.sansSerif
]
]
(Element.text "Woohoo, I'm stylish text")
**Note**: `Font.color`, `Font.size`, and `Font.family` are inherited, meaning you can set them at the top of your view and all subsequent nodes will have that value.
@docs color, size
## Typefaces
@docs family, Font, typeface, serif, sansSerif, monospace
@docs external
`Font.external` can be used to import font files. Let's say you found a neat font on <http://fonts.google.com>:
import Element
import Element.Font as Font
view =
Element.el
[ Font.family
[ Font.external
{ name = "Roboto"
, url = "https://fonts.googleapis.com/css?family=Roboto"
}
, Font.sansSerif
]
]
(Element.text "Woohoo, I'm stylish text")
## Alignment and Spacing
@docs alignLeft, alignRight, center, justify, letterSpacing, wordSpacing
## Font Styles
@docs underline, strike, italic, unitalicized
## Font Weight
@docs heavy, extraBold, bold, semiBold, medium, regular, light, extraLight, hairline
## Shadows
@docs glow, shadow
-}
import Element exposing (Attr, Attribute, Color)
import Internal.Flag as Flag
import Internal.Model as Internal
import Internal.Style exposing (classes)
{-| -}
type alias Font =
Internal.Font
{-| -}
color : Color -> Attr decorative msg
color fontColor =
Internal.StyleClass Flag.fontColor (Internal.Colored ("fc-" ++ Internal.formatColorClass fontColor) "color" fontColor)
{-|
import Element
import Element.Font as Font
myElement =
Element.el
[ Font.family
[ Font.typeface "Helvetica"
, Font.sansSerif
]
]
(text "")
-}
family : List Font -> Attribute msg
family families =
Internal.StyleClass Flag.fontFamily <| Internal.FontFamily (List.foldl Internal.renderFontClassName "ff-" families) families
{-| -}
serif : Font
serif =
Internal.Serif
{-| -}
sansSerif : Font
sansSerif =
Internal.SansSerif
{-| -}
monospace : Font
monospace =
Internal.Monospace
{-| -}
typeface : String -> Font
typeface =
Internal.Typeface
{-| -}
external : { url : String, name : String } -> Font
external { url, name } =
Internal.ImportFont name url
{-| Font sizes are always given as `px`.
-}
size : Int -> Attr decorative msg
size i =
Internal.StyleClass Flag.fontSize (Internal.FontSize i)
{-| In `px`.
-}
letterSpacing : Float -> Attribute msg
letterSpacing offset =
Internal.StyleClass Flag.letterSpacing <|
Internal.Single
("ls-" ++ Internal.floatClass offset)
"letter-spacing"
(String.fromFloat offset ++ "px")
{-| In `px`.
-}
wordSpacing : Float -> Attribute msg
wordSpacing offset =
Internal.StyleClass Flag.wordSpacing <|
Internal.Single ("ws-" ++ Internal.floatClass offset) "word-spacing" (String.fromFloat offset ++ "px")
{-| Align the font to the left.
-}
alignLeft : Attribute msg
alignLeft =
Internal.Class Flag.fontAlignment classes.textLeft
{-| Align the font to the right.
-}
alignRight : Attribute msg
alignRight =
Internal.Class Flag.fontAlignment classes.textRight
{-| Center align the font.
-}
center : Attribute msg
center =
Internal.Class Flag.fontAlignment classes.textCenter
{-| -}
justify : Attribute msg
justify =
Internal.Class Flag.fontAlignment classes.textJustify
-- {-| -}
-- justifyAll : Attribute msg
-- justifyAll =
-- Internal.class classesTextJustifyAll
{-| -}
underline : Attribute msg
underline =
Internal.htmlClass classes.underline
{-| -}
strike : Attribute msg
strike =
Internal.htmlClass classes.strike
{-| -}
italic : Attribute msg
italic =
Internal.htmlClass classes.italic
{-| -}
bold : Attribute msg
bold =
Internal.Class Flag.fontWeight classes.bold
{-| -}
light : Attribute msg
light =
Internal.Class Flag.fontWeight classes.textLight
{-| -}
hairline : Attribute msg
hairline =
Internal.Class Flag.fontWeight classes.textThin
{-| -}
extraLight : Attribute msg
extraLight =
Internal.Class Flag.fontWeight classes.textExtraLight
{-| -}
regular : Attribute msg
regular =
Internal.Class Flag.fontWeight classes.textNormalWeight
{-| -}
semiBold : Attribute msg
semiBold =
Internal.Class Flag.fontWeight classes.textSemiBold
{-| -}
medium : Attribute msg
medium =
Internal.Class Flag.fontWeight classes.textMedium
{-| -}
extraBold : Attribute msg
extraBold =
Internal.Class Flag.fontWeight classes.textExtraBold
{-| -}
heavy : Attribute msg
heavy =
Internal.Class Flag.fontWeight classes.textHeavy
{-| This will reset bold and italic.
-}
unitalicized : Attribute msg
unitalicized =
Internal.htmlClass classes.textUnitalicized
{-| -}
shadow :
{ offset : ( Float, Float )
, blur : Float
, color : Color
}
-> Attr decorative msg
shadow shade =
Internal.StyleClass Flag.txtShadows <|
Internal.Single (Internal.textShadowName shade) "text-shadow" (Internal.formatTextShadow shade)
{-| A glow is just a simplified shadow
-}
glow : Color -> Float -> Attr decorative msg
glow clr i =
let
shade =
{ offset = ( 0, 0 )
, blur = i * 2
, color = clr
}
in
Internal.StyleClass Flag.txtShadows <|
Internal.Single (Internal.textShadowName shade) "text-shadow" (Internal.formatTextShadow shade)

1834
src/Element/Input.elm Normal file

File diff suppressed because it is too large Load Diff

67
src/Element/Keyed.elm Normal file
View File

@ -0,0 +1,67 @@
module Element.Keyed exposing (column, el, row)
{-| Notes from the `Html.Keyed` on how keyed works:
---
A keyed node helps optimize cases where children are getting added, moved, removed, etc. Common examples include:
- The user can delete items from a list.
- The user can create new items in a list.
- You can sort a list based on name or date or whatever.
When you use a keyed node, every child is paired with a string identifier. This makes it possible for the underlying diffing algorithm to reuse nodes more efficiently.
This means if a key is changed between renders, then the diffing step will be skipped and the node will be forced to rerender.
---
@docs el, column, row
-}
import Element exposing (Attribute, Element, fill, height, width)
import Internal.Model as Internal
import Internal.Style exposing (classes)
{-| -}
el : List (Attribute msg) -> ( String, Element msg ) -> Element msg
el attrs child =
Internal.element
Internal.asEl
Internal.div
(width Element.shrink
:: height Element.shrink
:: attrs
)
(Internal.Keyed [ child ])
{-| -}
row : List (Attribute msg) -> List ( String, Element msg ) -> Element msg
row attrs children =
Internal.element
Internal.asRow
Internal.div
(Internal.htmlClass classes.contentLeft
:: Internal.htmlClass classes.contentCenterY
:: width fill
:: attrs
)
(Internal.Keyed children)
{-| -}
column : List (Attribute msg) -> List ( String, Element msg ) -> Element msg
column attrs children =
Internal.element
Internal.asColumn
Internal.div
(Internal.htmlClass classes.contentTop
:: Internal.htmlClass classes.contentLeft
:: height fill
:: width fill
:: attrs
)
(Internal.Keyed children)

117
src/Element/Lazy.elm Normal file
View File

@ -0,0 +1,117 @@
module Element.Lazy exposing (lazy, lazy2, lazy3, lazy4, lazy5)
{-| Same as `Html.lazy`. In case you're unfamiliar, here's a note from the `Html` library!
---
Since all Elm functions are pure we have a guarantee that the same input
will always result in the same output. This module gives us tools to be lazy
about building `Html` that utilize this fact.
Rather than immediately applying functions to their arguments, the `lazy`
functions just bundle the function and arguments up for later. When diffing
the old and new virtual DOM, it checks to see if all the arguments are equal
by reference. If so, it skips calling the function!
This is a really cheap test and often makes things a lot faster, but definitely
benchmark to be sure!
---
@docs lazy, lazy2, lazy3, lazy4, lazy5
-}
import Internal.Model exposing (..)
import VirtualDom
{-| -}
lazy : (a -> Element msg) -> a -> Element msg
lazy fn a =
Unstyled <| VirtualDom.lazy3 apply1 fn a
{-| -}
lazy2 : (a -> b -> Element msg) -> a -> b -> Element msg
lazy2 fn a b =
Unstyled <| VirtualDom.lazy4 apply2 fn a b
{-| -}
lazy3 : (a -> b -> c -> Element msg) -> a -> b -> c -> Element msg
lazy3 fn a b c =
Unstyled <| VirtualDom.lazy5 apply3 fn a b c
{-| -}
lazy4 : (a -> b -> c -> d -> Element msg) -> a -> b -> c -> d -> Element msg
lazy4 fn a b c d =
Unstyled <| VirtualDom.lazy6 apply4 fn a b c d
{-| -}
lazy5 : (a -> b -> c -> d -> e -> Element msg) -> a -> b -> c -> d -> e -> Element msg
lazy5 fn a b c d e =
Unstyled <| VirtualDom.lazy7 apply5 fn a b c d e
apply1 fn a =
embed (fn a)
apply2 fn a b =
embed (fn a b)
apply3 fn a b c =
embed (fn a b c)
apply4 fn a b c d =
embed (fn a b c d)
apply5 fn a b c d e =
embed (fn a b c d e)
{-| -}
embed : Element msg -> LayoutContext -> VirtualDom.Node msg
embed x =
case x of
Unstyled html ->
html
Styled styled ->
styled.html
(Internal.Model.OnlyDynamic
{ hover = AllowHover
, focus =
{ borderColor = Nothing
, shadow = Nothing
, backgroundColor = Nothing
}
, mode = Layout
}
styled.styles
)
-- -- (Just
-- -- (toStyleSheetString
-- { hover = AllowHover
-- , focus =
-- { borderColor = Nothing
-- , shadow = Nothing
-- , backgroundColor = Nothing
-- }
-- , mode = Layout
-- }
-- -- styled.styles
-- -- )
-- -- )
Text text ->
always (VirtualDom.text text)
Empty ->
always (VirtualDom.text "")

111
src/Element/Region.elm Normal file
View File

@ -0,0 +1,111 @@
module Element.Region
exposing
( announce
, announceUrgently
, aside
, description
, footer
, heading
, mainContent
, navigation
)
{-| This module is meant to make accessibility easy! They're sign posts that accessibility software like screen readers can use to navigate your app.
All you have to do is add them to elements in your app where you see fit.
Here's an example of annotating your navigation region:
import Element.Region as Region
myNavigation =
Element.row [ Region.navigation ]
[-- ..your navigation links
]
@docs mainContent, navigation, heading, aside, footer
@docs description
@docs announce, announceUrgently
-}
import Element exposing (Attribute)
import Internal.Model as Internal exposing (Description(..))
{-| -}
mainContent : Attribute msg
mainContent =
Internal.Describe Main
{-| -}
aside : Attribute msg
aside =
Internal.Describe Complementary
{-| -}
navigation : Attribute msg
navigation =
Internal.Describe Navigation
-- form : Attribute msg
-- form =
-- Internal.Describe Form
-- search : Attribute msg
-- search =
-- Internal.Describe Search
{-| -}
footer : Attribute msg
footer =
Internal.Describe ContentInfo
{-| This will mark an element as `h1`, `h2`, etc where possible.
Though it's also smart enough to not conflict with existing nodes.
So, this code
link [ Region.heading 1 ]
{ url = "http://fruits.com"
, label = text "Best site ever"
}
will generate
<a href="http://fruits.com">
<h1>Best site ever</h1>
</a>
-}
heading : Int -> Attribute msg
heading =
Internal.Describe << Heading
{-| Screen readers will announce changes to this element and potentially interrupt any other announcement.
-}
announceUrgently : Attribute msg
announceUrgently =
Internal.Describe LiveAssertive
{-| Screen readers will announce when changes to this element are made.
-}
announce : Attribute msg
announce =
Internal.Describe LivePolite
{-| -}
description : String -> Attribute msg
description =
Internal.Describe << Internal.Label

260
src/Internal/Flag.elm Normal file
View File

@ -0,0 +1,260 @@
module Internal.Flag exposing (..)
{-| -}
import Bitwise
type Field
= Field Int Int
type Flag
= Flag Int
| Second Int
none : Field
none =
Field 0 0
value myFlag =
case myFlag of
Flag first ->
round (logBase 2 (toFloat first))
Second second ->
round (logBase 2 (toFloat second)) + 32
{-| If the query is in the truth, return True
-}
present : Flag -> Field -> Bool
present myFlag (Field fieldOne fieldTwo) =
case myFlag of
Flag first ->
Bitwise.and first fieldOne == first
Second second ->
Bitwise.and second fieldTwo == second
{-| Add a flag to a field.
-}
add : Flag -> Field -> Field
add myFlag (Field one two) =
case myFlag of
Flag first ->
Field (Bitwise.or first one) two
Second second ->
Field one (Bitwise.or second two)
{-| Generally you want to use `add`, which keeps a distinction between Fields and Flags.
Merging will combine two fields
-}
merge : Field -> Field -> Field
merge (Field one two) (Field three four) =
Field (Bitwise.or one three) (Bitwise.or two four)
flag : Int -> Flag
flag i =
if i > 31 then
Second
(Bitwise.shiftLeftBy (i - 32) 1)
else
Flag
(Bitwise.shiftLeftBy i 1)
{- Used for Style invalidation -}
transparency =
flag 0
padding =
flag 2
spacing =
flag 3
fontSize =
flag 4
fontFamily =
flag 5
width =
flag 6
height =
flag 7
bgColor =
flag 8
bgImage =
flag 9
bgGradient =
flag 10
borderStyle =
flag 11
fontAlignment =
flag 12
fontWeight =
flag 13
fontColor =
flag 14
wordSpacing =
flag 15
letterSpacing =
flag 16
borderRound =
flag 17
txtShadows =
flag 18
shadows =
flag 19
overflow =
flag 20
cursor =
flag 21
scale =
flag 23
rotate =
flag 24
moveX =
flag 25
moveY =
flag 26
borderWidth =
flag 27
borderColor =
flag 28
yAlign =
flag 29
xAlign =
flag 30
focus =
flag 31
active =
flag 32
hover =
flag 33
gridTemplate =
flag 34
gridPosition =
flag 35
{- Notes -}
heightContent =
flag 36
heightFill =
flag 37
widthContent =
flag 38
widthFill =
flag 39
alignRight =
flag 40
alignBottom =
flag 41
centerX =
flag 42
centerY =
flag 43
widthBetween =
flag 44
heightBetween =
flag 45
behind =
flag 46

264
src/Internal/Grid.elm Normal file
View File

@ -0,0 +1,264 @@
module Internal.Grid exposing (..)
{-| Relative positioning within a grid.
A relatively positioned grid, means a 3x3 grid with the primary element in the center.
-}
import Element
import Internal.Flag as Flag
import Internal.Model as Internal
type RelativePosition
= OnRight
| OnLeft
| Above
| Below
| InFront
type Layout
= GridElement
| Row
| Column
type alias Around alignment msg =
{ right : Maybe (PositionedElement alignment msg)
, left : Maybe (PositionedElement alignment msg)
, primary : ( Maybe String, List (Internal.Attribute alignment msg), List (Internal.Element msg) )
-- , primaryWidth : Internal.Length
, defaultWidth : Internal.Length
, below : Maybe (PositionedElement alignment msg)
, above : Maybe (PositionedElement alignment msg)
, inFront : Maybe (PositionedElement alignment msg)
}
type alias PositionedElement alignment msg =
{ layout : Layout
, child : List (Internal.Element msg)
, attrs : List (Internal.Attribute alignment msg)
, width : Int
, height : Int
}
relative : Maybe String -> List (Internal.Attribute alignment msg) -> Around alignment msg -> Internal.Element msg
relative node attributes around =
let
( sX, sY ) =
Internal.getSpacing attributes ( 7, 7 )
make positioned =
Internal.element Internal.noStyleSheet
Internal.asEl
Nothing
positioned.attrs
(Internal.Unkeyed positioned.child)
( template, children ) =
createGrid ( sX, sY ) around
in
Internal.element Internal.noStyleSheet
Internal.asGrid
node
(template ++ attributes)
(Internal.Unkeyed
children
)
createGrid : ( Int, Int ) -> Around alignment msg -> ( List (Internal.Attribute alignment msg1), List (Element.Element msg) )
createGrid ( spacingX, spacingY ) nearby =
let
rowCount =
List.sum
[ 1
, if Nothing == nearby.above then
0
else
1
, if Nothing == nearby.below then
0
else
1
]
colCount =
List.sum
[ 1
, if Nothing == nearby.left then
0
else
1
, if Nothing == nearby.right then
0
else
1
]
rows =
if nearby.above == Nothing then
{ above = 0
, primary = 1
, below = 2
}
else
{ above = 1
, primary = 2
, below = 3
}
columns =
if Nothing == nearby.left then
{ left = 0
, primary = 1
, right = 2
}
else
{ left = 1
, primary = 2
, right = 3
}
rowCoord pos =
case pos of
Above ->
rows.above
Below ->
rows.below
OnRight ->
rows.primary
OnLeft ->
rows.primary
InFront ->
rows.primary
colCoord pos =
case pos of
Above ->
columns.primary
Below ->
columns.primary
OnRight ->
columns.right
OnLeft ->
columns.left
InFront ->
columns.primary
place pos el =
build (rowCoord pos) (colCoord pos) spacingX spacingY el
in
( [ Internal.StyleClass Flag.gridTemplate
(Internal.GridTemplateStyle
{ spacing = ( Internal.Px spacingX, Internal.Px spacingY )
, columns =
List.filterMap identity
[ nearby.left
|> Maybe.map (\el -> Maybe.withDefault nearby.defaultWidth (getWidth el.attrs))
, nearby.primary
|> (\( node, attrs, el ) -> getWidth attrs)
|> Maybe.withDefault nearby.defaultWidth
|> Just
, nearby.right
|> Maybe.map (\el -> Maybe.withDefault nearby.defaultWidth (getWidth el.attrs))
]
, rows = List.map (always Internal.Content) (List.range 1 rowCount)
}
)
]
, List.filterMap identity
[ Just <|
case nearby.primary of
( primaryNode, primaryAttrs, primaryChildren ) ->
Internal.element Internal.noStyleSheet
Internal.asEl
primaryNode
(Internal.StyleClass Flag.gridPosition
(Internal.GridPosition
{ row = rows.primary
, col = columns.primary
, width = 1
, height = 1
}
)
:: primaryAttrs
)
(Internal.Unkeyed primaryChildren)
, Maybe.map (place OnLeft) nearby.left
, Maybe.map (place OnRight) nearby.right
, Maybe.map (place Above) nearby.above
, Maybe.map (place Below) nearby.below
, Maybe.map (place InFront) nearby.inFront
]
)
build : Int -> Int -> Int -> Int -> { a | attrs : List (Internal.Attribute alignment msg), height : Int, layout : Layout, width : Int, child : List (Internal.Element msg) } -> Internal.Element msg
build rowCoord colCoord spacingX spacingY positioned =
let
attributes =
Internal.StyleClass Flag.gridPosition
(Internal.GridPosition
{ row = rowCoord
, col = colCoord
, width = positioned.width
, height = positioned.height
}
)
:: Internal.StyleClass Flag.spacing (Internal.SpacingStyle spacingX spacingY)
:: positioned.attrs
in
case positioned.layout of
GridElement ->
Internal.element Internal.noStyleSheet
Internal.asEl
Nothing
attributes
(Internal.Unkeyed <| positioned.child)
Row ->
Internal.element Internal.noStyleSheet
Internal.asRow
Nothing
attributes
(Internal.Unkeyed positioned.child)
Column ->
Internal.element Internal.noStyleSheet
Internal.asColumn
Nothing
attributes
(Internal.Unkeyed positioned.child)
getWidth : List (Internal.Attribute align msg) -> Maybe Internal.Length
getWidth attrs =
let
widthPlease attr found =
case found of
Just x ->
Just x
Nothing ->
case attr of
Internal.Width w ->
Just w
_ ->
Nothing
in
List.foldr widthPlease Nothing attrs

2847
src/Internal/Model.elm Normal file

File diff suppressed because it is too large Load Diff

1628
src/Internal/Style.elm Normal file

File diff suppressed because it is too large Load Diff

54
tests/Tests/Basic.elm Normal file
View File

@ -0,0 +1,54 @@
module Tests.Basic exposing (view)
{-| -}
import Element as Actual
import Html
import Testable
import Testable.Element as Element exposing (..)
import Testable.Element.Background as Background
import Testable.Element.Font as Font
import Testable.Runner
import Tests.Palette as Palette
{-| -}
main : Html.Html msg
main =
Testable.Runner.show view
{-| -}
view : Testable.Element msg
view =
row [ spacing 50, alignTop ]
[ el
[ width (px 200)
, height (px 200)
, Background.color Palette.blue
, Font.color Palette.white
]
(text "Hello!")
, el
[ width (px 200)
, height (px 200)
, Background.color Palette.blue
, Font.color Palette.white
]
(text "Hello!")
, el
[ width (px 200)
, height (px 200)
, Background.color (Actual.rgba 0 0 1 1)
, Font.color Palette.white
, below
(el
[ Background.color Palette.grey
, width (px 50)
, height (px 50)
]
none
)
]
(text "Hello!")
]

View File

@ -0,0 +1,176 @@
module Tests.ColumnAlignment exposing (..)
import Html
import Testable
import Testable.Element as Element exposing (..)
import Testable.Element.Background as Background
import Testable.Element.Font as Font
import Testable.Runner
import Tests.Palette as Palette exposing (..)
{-| -}
main : Html.Html msg
main =
Testable.Runner.show view
box attrs =
el
([ width (px 50)
, height (px 50)
, Background.color blue
]
++ attrs
)
none
container =
el [ width (px 100), height (px 100) ]
view =
let
colContainer attrs children =
column ([ Background.color lightGrey, spacing 20, width (px 100), height (px 500) ] ++ attrs) children
tallContainer attrs children =
column ([ Background.color lightGrey, spacing 20, width (px 100), height fill ] ++ attrs) children
in
column
[ width fill, spacing 20 ]
[ el [] (text "Alignment Within a Column")
, row [ spacing 20 ]
[ colContainer []
[ box [] ]
, colContainer []
[ box [ centerY ]
]
, colContainer []
[ box [ alignBottom, label "first" ]
]
]
, row [ spacing 20 ]
[ colContainer []
[ box [] ]
, colContainer []
[ box []
, box []
, box []
]
, colContainer []
[ box [ alignLeft, label "first" ]
, box [ centerX, label "second" ]
, box [ alignRight, label "third" ]
]
]
, row [ spacing 20 ]
[ colContainer []
[ box []
, box []
, box []
]
, colContainer []
[ box []
, box []
, box [ alignBottom, label "third" ]
]
, colContainer []
[ box []
, box [ alignBottom, label "second" ]
, box []
]
, colContainer []
[ box [ alignBottom, label "first" ]
, box []
, box []
]
]
, text "centerY"
, row [ spacing 20 ]
[ colContainer [ height fill ]
[ box [ centerY, label "solo" ]
]
, colContainer []
[ box []
, box [ centerY, label "middle" ]
, box []
]
, colContainer []
[ box []
, box []
, box [ centerY, label "last" ]
]
]
, text "multiple centerY"
, row [ height (px 800), spacing 20 ]
[ tallContainer []
[ box []
, box [ centerY ]
, box [ centerY ]
, box [ centerY ]
, box [ centerY ]
, box [ alignBottom ]
]
, tallContainer []
[ box []
, box [ centerY ]
, box []
]
, tallContainer []
[ box []
, box []
, box [ centerY ]
]
]
, text "top, center, bottom"
, row [ spacing 20 ]
[ colContainer []
[ box [ alignTop, label "first" ]
, box []
, box [ alignBottom, label "last" ]
]
, colContainer []
[ box [ alignTop ]
, box [ centerY ]
, box [ alignBottom ]
]
, colContainer []
[ box [ alignLeft, alignTop ]
, box [ centerX, centerY ]
, box [ alignRight, alignBottom ]
]
]
, el [ width fill ] (text "Column in a Row")
, row [ width fill, spacing 20, label "row" ]
[ box [ alignLeft, alignTop, label "box" ]
, column
[ alignLeft
, alignTop
, spacing 20
, label "column"
, Background.color lightGrey
]
[ box []
, box []
, box []
]
, column
[ spacing 20
, width (px 100)
, alignLeft
, alignTop
, Background.color lightGrey
]
[ box []
, box []
, box []
]
, colContainer []
[ box [ alignRight ]
, box [ centerX ]
, box [ alignLeft ]
]
]
]

View File

@ -0,0 +1,68 @@
module Tests.ColumnSpacing exposing (..)
import Html
import Testable
import Testable.Element as Element exposing (..)
import Testable.Element.Background as Background
import Testable.Element.Font as Font
import Testable.Runner
import Tests.Palette as Palette exposing (..)
{-| -}
main : Html.Html msg
main =
Testable.Runner.show view
box attrs =
el
([ width (px 50)
, height (px 50)
, Background.color blue
]
++ attrs
)
none
tinyBox attrs =
el
([ width (px 20)
, height (px 20)
-- , centerY
, Background.color darkCharcoal
]
++ attrs
)
none
container =
el [ width (px 100), height (px 100) ]
view =
let
colContainer attrs children =
column ([ spacing 20, width (px 100), height (px 500) ] ++ attrs) children
in
column
[]
[ el [] (text "Spacing within a column")
, row []
[ colContainer []
[ box [] ]
, colContainer []
[ box []
, box []
, box []
]
, colContainer [ onRight (tinyBox []) ]
[ box []
, box []
, box []
]
]
]

View File

@ -0,0 +1,111 @@
module Tests.ElementAlignment exposing (..)
import Generator
import Html
import Testable
import Testable.Element as Element exposing (..)
import Testable.Element.Background as Background
import Testable.Element.Font as Font
import Testable.Runner
import Tests.Palette as Palette exposing (..)
{-| -}
main : Html.Html msg
main =
Testable.Runner.show view
box : List (Testable.Attr msg) -> Testable.Element msg
box attrs =
el
([ width (px 50)
, height (px 50)
, Background.color blue
]
++ attrs
)
none
container : Testable.Element msg -> Testable.Element msg
container =
el
[ width (px 200)
, height (px 200)
, Background.color lightGrey
]
view : Testable.Element msg
view =
column []
[ el [] (text "Alignment Within an El")
, container <|
box []
, text "alignLeft, centerX, alignRight"
, column [ spacing 20 ] <|
Generator.sizes
(\resizeable ->
row [ spacing 20 ]
[ container <|
resizeable [ Background.color blue, alignLeft ] none
, container <|
resizeable [ Background.color blue, centerX ] none
, container <|
resizeable [ Background.color blue, alignRight ] none
]
)
, text "top, centerY, bottom"
, column [ spacing 20 ] <|
Generator.sizes
(\resizeable ->
row [ spacing 20 ]
[ container <|
resizeable [ alignTop ] none
, container <|
resizeable [ centerY ] none
, container <|
resizeable [ alignBottom ] none
]
)
, text "align top ++ alignments"
, column [ spacing 20 ] <|
Generator.sizes
(\resizeable ->
row [ spacing 20 ]
[ container <|
resizeable [ alignTop, alignLeft ] none
, container <|
resizeable [ alignTop, centerX ] none
, container <|
resizeable [ alignTop, alignRight ] none
]
)
, text "centerY ++ alignments"
, column [ spacing 20 ] <|
Generator.sizes
(\resizeable ->
row [ spacing 20 ]
[ container <|
resizeable [ centerY, alignLeft ] none
, container <|
resizeable [ centerY, centerX ] none
, container <|
resizeable [ centerY, alignRight ] none
]
)
, text "alignBottom ++ alignments"
, column [ spacing 20 ] <|
Generator.sizes
(\resizeable ->
row [ spacing 20 ]
[ container <|
resizeable [ alignBottom, alignLeft ] none
, container <|
resizeable [ alignBottom, centerX ] none
, container <|
resizeable [ alignBottom, alignRight ] none
]
)
]

View File

@ -0,0 +1,286 @@
module Main exposing (..)
{-| -}
import Tests.Palette as Palette exposing (..)
import Element exposing (..)
import Element.Background as Background
import Element.Border as Border
import Element.Font as Font
import Element.Input as Input
import Element.Region as Region
import Html
main =
Html.program
{ init = ( init, Cmd.none )
, view = view
, update = update
, subscriptions = \_ -> Sub.none
}
init =
{ username = ""
, password = ""
, agreeTOS = False
, comment = ""
, lunch = Gyro
}
type alias Form =
{ username : String
, password : String
, agreeTOS : Bool
, comment : String
, lunch : Lunch
}
type Msg
= Update Form
update msg model =
case Debug.log "msg" msg of
Update new ->
( new, Cmd.none )
type Lunch
= Burrito
| Taco
| Gyro
view model =
let
label str =
Input.labelLeft
[ width (fillPortion 1)
, Font.alignRight
, paddingXY 12 7
, Font.bold
, focused
[ Background.color blue
, Font.size 40
, Font.color white
]
]
(text str)
testRadio =
Input.radio
[ width (fillPortion 4)
, transparent True
, spacing 15
, focused
[ transparent False
]
]
{ selected = Just model.lunch
, onChange = Just (\new -> Update { model | lunch = new })
, label =
Input.labelAbove
[ transparent True
, focused
[ transparent False
]
]
(text "What would you like for lunch?")
, options =
[ Input.option Gyro (text "Gyro")
, Input.option Burrito (text "Burrito")
, Input.option Taco (text "Taco")
]
}
in
Element.layout
[ Font.size 20
]
<|
Element.column
[ width (px 800)
, height shrink
, centerY
, centerX
, spacing 36
, padding 10
]
[ el
[ Region.heading 1
, alignLeft
, Font.size 36
-- , above True
-- [ focused
-- [ transparent False ]
-- ]
-- (text "show me")
-- , mouseOver
-- [ Font.size 36
-- , above True
-- (el
-- [ Font.color red
-- , Font.size 14
-- , alignLeft
-- ]
-- (text "This one is le wrong")
-- )
-- -- Decoration.backgroundColor Color.blue
-- -- , Decoration.fontSize 40
-- -- , Decoration.fontColor Color.white
-- ]
, above True
(el
[ Font.color red
, Font.size 14
, alignLeft
]
(text "This one is le wrong")
)
-- [ Decoration.fontSize 40
-- , Decoration.backgroundColor Color.blue
-- ]
]
(text "Welcome to the Stylish Elephants Lunch Emporium")
-- , Input.radioRow [ width (fillPortion 4), spacing 15 ]
-- { selected = Just model.lunch
-- , onChange = Just (\new -> Update { model | lunch = new })
-- , label = label "What would you like for lunch?"
-- , options =
-- [ Input.option Gyro (text "Gyro")
-- , Input.option Burrito (text "Burrito")
-- , Input.option Taco (text "Taco")
-- ]
-- }
-- , Input.username
-- [ width (fillPortion 4)
-- , focused
-- [ Decoration.backgroundColor Color.blue
-- , Decoration.fontSize 40
-- , Decoration.fontColor Color.white
-- ]
-- , below True
-- (el
-- [ Font.color red
-- , Font.size 14
-- , alignLeft
-- ]
-- (text "This one is le wrong")
-- )
-- ]
-- { text = model.username
-- , placeholder = Nothing --Just (Input.placeholder [] (text "Extra hot sauce?"))
-- , onChange = Just (\new -> Update { model | username = new })
-- , label =
-- Input.labelAbove
-- []
-- (text "username")
-- }
-- , Input.username
-- [ below True
-- (el
-- [ Font.color red
-- , Font.size 14
-- , alignLeft
-- ]
-- (text "This one is le wrong")
-- )
-- , width (fillPortion 4)
-- -- , Decoration.focused
-- -- [ Decoration.backgroundColor Color.blue
-- -- , Decoration.fontSize 40
-- -- , Decoration.fontColor Color.white
-- -- ]
-- ]
-- { text = model.username
-- , placeholder = Nothing --Just (Input.placeholder [] (text "Extra hot sauce?"))
-- , onChange = Just (\new -> Update { model | username = new })
-- , label =
-- Input.labelAbove
-- [ moveDown 30
-- , Font.size 20
-- , Decoration.focused
-- [ Decoration.fontSize 15
-- , Decoration.moveUp 500
-- ]
-- ]
-- (text "Username")
-- }
-- , Input.currentPassword [ width (fillPortion 4) ]
-- { text = model.password
-- , placeholder = Nothing
-- , onChange = Just (\new -> Update { model | password = new })
-- , label = label "Password"
-- , show = False
-- }
, Input.multiline
[ height shrink
, width (fillPortion 4)
, below True
(el
[ Font.color red
, Font.size 14
, alignLeft
]
(text "This one is le wrong")
)
]
{ text = model.comment
, placeholder = Just (Input.placeholder [] (text "Extra hot sauce?"))
, onChange = Just (\new -> Update { model | comment = new })
, label =
Input.labelLeft
[ width (fillPortion 1)
, Font.alignRight
, Font.bold
, transparent True
, paddingXY 12 7
, focused
[ Background.color blue
, Font.size 40
, Font.color white
]
]
(text "Question")
, spellcheck = False
}
-- , testRadio
, Element.row
[ Font.bold
, alignLeft
, below True <|
testRadio
]
[ el [ alignLeft, Font.bold ] <| text "Selection"
]
-- , Element.row []
-- [ Element.el [ Element.width Element.fill ] Element.none
-- , Input.checkbox
-- [ width (fillPortion 4) ]
-- { checked = model.agreeTOS
-- , onChange = Just (\new -> Update { model | agreeTOS = new })
-- , icon = Nothing
-- , label = Input.labelRight [] (text "Agree to Terms of Service")
-- }
-- ]
-- , Input.button
-- [ Background.color blue
-- , Font.color white
-- , Border.color darkBlue
-- , paddingXY 15 5
-- , Border.rounded 3
-- , alignLeft
-- -- , width fill
-- ]
-- { onPress = Nothing
-- , label = Element.text "Place your lunch order!"
-- }
]

1263
tests/Tests/Manual/All.elm Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,523 @@
module Tests.Manual.LazyPerformance exposing (..)
{-
We want to make sure that lazy is able to perform correctly in style-elements.
For the setup:
Render an expensive thing in html.
Rerender with Lazy.
-}
import Element
import Element.Lazy
import Html exposing (Html)
import Html.Attributes
import Html.Events
import Html.Lazy
import Internal.Model
subscriptions : Model -> Sub Msg
subscriptions model =
Sub.none
init : ( Model, Cmd Msg )
init =
( { renderAs = NothingPlease, count = 0 }, Cmd.none )
main : Program Never Model Msg
main =
Html.program
{ init = init
, view = view
, update = update
, subscriptions = subscriptions
}
type alias Model =
{ renderAs : Render
, count : Int
}
type Render
= HtmlPlease
| StylePlease
| NothingPlease
type Msg
= RenderAs Render
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
RenderAs mode ->
( { model
| renderAs = mode
, count =
if mode == model.renderAs then
model.count + 1
else
0
}
, Cmd.none
)
view : Model -> Html Msg
view model =
Html.div []
[ Html.button [ Html.Events.onClick <| RenderAs HtmlPlease ] [ Html.text "Html, Please" ]
, Html.button [ Html.Events.onClick <| RenderAs StylePlease ] [ Html.text "Style, Please" ]
, Html.div [] [ Html.text (toString model.count) ]
, case model.renderAs of
HtmlPlease ->
Html.div []
[ staticStyleSheet
, Html.Lazy.lazy viewHtml 10000
]
StylePlease ->
Element.layout []
(Element.Lazy.lazy viewStyle 10000)
NothingPlease ->
Html.text "Nothing rendered.."
]
-- on second render, js time goes down to single digit ms, small bit of updating layout, and updating layout tree.
viewHtml x =
Html.div [ Html.Attributes.class "se column spacing-20-20 content-top height-fill width-fill" ]
(List.repeat x (Html.div [ Html.Attributes.class "se el width-content height-content self-center-y self-center-x" ] [ Html.div [ Html.Attributes.class "se text width-fill" ] [ Html.text "hello!" ] ]))
viewStyle x =
Element.column []
(List.repeat x (Element.el [] (Element.text "hello!")))
staticStyleSheet =
Html.node "style"
[]
[ Html.text """
html {
height: 100%;
}
body {
height: 100%;
}
input {
border: none;
}
a {
text-decoration: none;
color: inherit;
}
.style-elements {
width: 100%;
height: auto;
min-height: 100%;
}
.se {
position: relative;
display: flex;
flex-direction: row;
box-sizing: border-box;
margin: 0;
padding: 0;
border-width: 0;
border-style: solid;
font: inherit;
}
.se.above {
position: absolute;
display: block;
top: 0;
height: 0;
z-index: 10;
}
.se.above > .height-fill {
height: auto;
}
.se.below {
position: absolute;
display: block;
bottom: 0;
height: 0;
z-index: 10;
}
.se.below > .height-fill {
height: auto;
}
.se.bold {
font-weight: 700;
}
.se.border-dashed {
border-style: dashed;
}
.se.border-dotted {
border-style: dotted;
}
.se.border-none {
border-width: 0;
}
.se.border-solid {
border-style: solid;
}
.se.italic {
font-style: italic;
}
.se.on-left {
position: absolute;
display: block;
right: 100%;
width: 0;
z-index: 10;
}
.se.on-right {
position: absolute;
display: block;
left: 100%;
width: 0;
z-index: 10;
}
.se.overlay {
position: absolute;
display: block;
left: 0;
top: 0;
z-index: 10;
}
.se.strike {
text-decoration: line-through;
}
.se.text-center {
text-align: center;
}
.se.text-justify {
text-align: justify;
}
.se.text-justify-all {
text-align: justify-all;
}
.se.text-left {
text-align: left;
}
.se.text-light {
font-weight: 300;
}
.se.text-right {
text-align: right;
}
.se.underline {
text-decoration: underline;
}
.se.width-content {
width: auto;
}
.text {
white-space: pre;
display: inline-block;
}
.spacer + .se {
margin-top: 0;
margin-left: 0;
}
.el {
display: flex;
flex-direction: row;
}
.el > .height-fill {
height: 100%;
}
.el > .se.self-bottom {
align-self: flex-end;
}
.el > .se.self-center-x {
margin-left: auto;
margin-right: auto;
}
.el > .se.self-center-y {
align-self: center;
}
.el > .se.self-left {
margin-right: auto;
}
.el > .se.self-right {
margin-left: auto;
}
.el > .se.self-top {
align-self: flex-start;
}
.el > .width-fill {
width: 100%;
}
.el.content-bottom {
align-items: flex-end;
}
.el.content-center-x {
justify-content: center;
}
.el.content-center-y {
align-items: center;
}
.el.content-left {
justify-content: flex-start;
}
.el.content-right {
justify-content: flex-end;
}
.el.content-top {
align-items: flex-start;
}
.nearby {
position: absolute;
width: 100%;
height: 100%;
pointer-events: none;
}
.row {
display: flex;
flex-direction: row;
}
.row > .height-fill {
height: 100%;
}
.row > .se.self-bottom {
align-self: flex-end;
}
.row > .se.self-center-y {
align-self: center;
}
.row > .se.self-top {
align-self: flex-start;
}
.row > .width-fill {
flex-grow: 1;
}
.row.content-bottom {
align-items: flex-end;
}
.row.content-center-x {
justify-content: center;
}
.row.content-center-y {
align-items: center;
}
.row.content-left {
justify-content: flex-start;
}
.row.content-right {
justify-content: flex-end;
}
.row.content-top {
align-items: flex-start;
}
.row.space-evenly {
justify-content: space-between;
}
.column {
display: flex;
flex-direction: column;
}
.column > .height-fill {
flex-grow: 1;
}
.column > .se.self-center-x {
align-self: center;
}
.column > .se.self-left {
align-self: flex-start;
}
.column > .se.self-right {
align-self: flex-end;
}
.column > .width-fill {
width: 100%;
}
.column.content-bottom {
justify-content: flex-end;
}
.column.content-center-x {
align-items: center;
}
.column.content-center-y {
justify-content: center;
}
.column.content-left {
align-items: flex-start;
}
.column.content-right {
align-items: flex-end;
}
.column.content-top {
justify-content: flex-start;
}
.page {
display: block;
}
.page > .se.self-left {
float: left;
}
.page > .se.self-left:after: {
content: "";
display: table;
clear: both;
}
.page > .se.self-left:first-child + .se {
margin: 0 !important;
}
.page > .se.self-right {
float: right;
}
.page > .se.self-right:after: {
content: "";
display: table;
clear: both;
}
.page > .se.self-right:first-child + .se {
margin: 0 !important;
}
.paragraph {
display: block;
}
.paragraph > .column {
display: inline-flex;
}
.paragraph > .el {
display: inline-flex;
}
.paragraph > .el > .text {
display: inline;
white-space: normal;
}
.paragraph > .grid {
display: inline-grid;
}
.paragraph > .row {
display: inline-flex;
}
.paragraph > .se.self-left {
float: left;
}
.paragraph > .se.self-right {
float: right;
}
.paragraph > .text {
display: inline;
white-space: normal;
}
.hidden {
display: none;
}
.bg-52-101-164-100{background-color:rgba(52,101,164,1)}
.text-color-255-255-255-100{color:rgba(255,255,255,1)}
.font-size-20{font-size:20px}
.font-opensansgeorgiaserif{font-family:"Open Sans", "georgia", serif}
""" ]

View File

@ -0,0 +1,36 @@
module Tests.Manual.OtherScrollbars exposing (main)
import Element exposing (..)
import Element.Background as Bg
import Html exposing (Html)
import Tests.Palette as Palette
main : Html msg
main =
layout [ height Element.shrink ] <|
row
[ height fill
]
[ Element.el [ scrollbarY, width fill, height fill ] <|
column
[ height <| px 1000
, Bg.color Color.red
, width fill
]
[]
, Element.el [ scrollbarY, width fill, height fill ] <|
column
[ height <| px 1500
, Bg.color Color.green
, width fill
]
[]
, Element.el [ scrollbarY, width fill, height fill ] <|
column
[ height <| px 2000
, Bg.color Color.blue
, width fill
]
[]
]

View File

@ -0,0 +1,16 @@
module Main exposing (main)
import Tests.Palette as Palette
import Element exposing (..)
import Element.Background as Bg
import Html exposing (Html)
main : Html msg
main =
layout [ height Element.shrink ] <|
row [ height fill ]
[ Element.el [ scrollbarY, width fill, height fill ] <| column [ height <| px 1000, Bg.color Color.red ] []
, Element.el [ scrollbarY, width fill, height fill ] <| column [ height <| px 1500, Bg.color Color.green ] []
, Element.el [ scrollbarY, width fill, height fill ] <| column [ height <| px 2000, Bg.color Color.blue ] []
]

View File

@ -0,0 +1,62 @@
module Tests.Manual.Scrollbars exposing (main)
import Tests.Palette as Palette
import Element as El exposing (Element)
import Element.Background as Background
import Element.Border as Border
import Element.Events as Events
import Element.Font as Font
import Html exposing (Html)
main : Html msg
main =
El.layout [ El.height El.shrink ] <|
El.column
[ --El.height El.fill
El.width El.fill
]
[ header
, content
]
header =
El.el
[ El.height (El.px 80)
, Background.color Color.red
, El.width El.fill
]
El.none
content =
El.column
[ --El.height El.fill
El.scrollbarY
, El.width El.fill
]
[ item
, item
, item
, item
, item
, item
, item
, item
]
item =
El.column
[ El.alignLeft
, Background.color Color.green
, El.paddingXY 10 0
, El.width El.fill
]
[ El.el
[ El.height (El.px 500)
, El.width El.fill
]
(El.text "some content")
]

View File

@ -0,0 +1,58 @@
module Main exposing (main)
import Tests.Palette as Palette
import Element as El exposing (Element)
import Element.Background as Background
import Element.Border as Border
import Element.Events as Events
import Element.Font as Font
import Html exposing (Html)
main : Html msg
main =
El.layout [ El.height El.shrink ] <|
El.column
[--El.height El.fill
]
[ header
, content
]
header =
El.el
[ El.height (El.px 80)
, Background.color Color.red
, El.width El.fill
]
El.none
content =
El.column
[ --El.height El.fill
El.scrollbarY
]
[ item
, item
, item
, item
, item
, item
, item
, item
]
item =
El.column
[ El.alignLeft
, Background.color Color.green
, El.paddingXY 10 0
]
[ El.el
[ El.height (El.px 500)
]
(El.text "some content")
]

View File

@ -0,0 +1,43 @@
module Main exposing (main)
import Tests.Palette as Palette exposing (..)
import Element exposing (..)
import Element.Background as Background
import Element.Font as Font
import Element.Input as Input
import Html exposing (Html)
main : Html msg
main =
layout
[ Font.size 20
, Font.lineHeight 1.3
, Font.color darkCharcoal
]
<|
textColumn
[ spacing 56 ]
[ paragraph []
[ el
[ alignLeft
, Font.size 60
, Font.lineHeight 0.8
, moveUp 5
, paddingEach
{ right = 5
, top = 0
, bottom = 0
, left = 0
}
]
(text "S")
, text "tylish elephants are on the loose. By day they prowl, by night they sleep."
, text "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam dictum turpis sem, sed commodo felis blandit quis. Etiam gravida velit a felis accumsan, ut finibus risus bibendum. Donec eget augue lorem. Curabitur neque sem, fermentum sed nisl at, semper feugiat nibh. Morbi mollis tempor turpis. Suspendisse est urna, sodales sed molestie semper, rhoncus eu nisl. Fusce ultrices leo sit amet arcu maximus, in scelerisque ante egestas. Suspendisse dictum augue eu venenatis molestie. Duis ullamcorper magna ut ex placerat fringilla. In mollis efficitur tellus, sit amet tincidunt mauris accumsan non. Morbi vel dapibus velit. Nullam quam sem, mattis vel feugiat et, sagittis vulputate libero. Maecenas posuere dui semper mollis hendrerit. Sed sit amet dolor tempus, tristique enim in, porttitor sem. Ut lobortis egestas lorem ut ornare."
]
, paragraph []
[ text "Vivamus luctus ex eros, ac accumsan lectus ornare eget. Phasellus mattis dapibus tortor, ut rutrum dolor pulvinar eget. Sed vulputate metus vel sapien dignissim, ac ornare justo porta. Praesent dolor leo, varius id nisl ut, pretium dapibus purus. Curabitur at dolor et augue accumsan venenatis et non urna. Aenean rutrum augue nulla, a tempus sapien aliquam vel. Cras sodales nulla sed dolor mollis, ac consectetur lorem consectetur." ]
, paragraph []
[ text "Maecenas ultricies felis ipsum, quis rhoncus libero malesuada eget. Maecenas sagittis quis ipsum at convallis. Suspendisse potenti. Donec feugiat ligula nunc, id pellentesque erat tristique sed. In hac habitasse platea dictumst. Sed rutrum, urna vel efficitur pretium, lacus risus blandit turpis, sed finibus nisi odio pretium diam. Etiam elementum ante non nibh semper, quis gravida elit fermentum."
]
]

393
tests/Tests/Nearby.elm Normal file
View File

@ -0,0 +1,393 @@
module Tests.Nearby exposing (view)
{-| Testing nearby elements such as those defined with `above`, `below`, etc.
-}
import Html
import Testable
import Testable.Element exposing (..)
import Testable.Element.Background as Background
import Testable.Element.Font as Font
import Testable.Runner
import Tests.Palette as Palette exposing (..)
box attrs =
el
([ width (px 50)
, height (px 50)
, Background.color blue
]
++ attrs
)
none
p attrs =
paragraph
([ Background.color blue
, Font.color white
, padding 20
]
++ attrs
)
[ text "Lorem Ipsum or something or other." ]
{-| -}
main : Html.Html msg
main =
Testable.Runner.show view
{-| -}
view : Testable.Element msg
view =
let
transparentBox attrs =
el
([ Font.color white
, width (px 50)
, height (px 50)
, Background.color (rgba 52 101 164 0.8)
]
++ attrs
)
(text "hi")
-- single location name box =
-- row [ height (px 100), width fill, spacing 50 ]
-- [ box
-- [ location
-- (el
-- [ width (px 20)
-- , height (px 20)
-- , Background.color darkCharcoal
-- ]
-- none
-- )
-- ]
-- , box
-- [ location
-- (el
-- [ width (px 20)
-- , height (px 20)
-- , alignLeft
-- , Background.color darkCharcoal
-- ]
-- none
-- )
-- ]
-- ]
little name attrs =
el
([ label name
, width (px 5)
, height (px 5)
, Background.color darkCharcoal
]
++ attrs
)
none
nearby location name render =
column [ spacing 32, label "column" ]
[ el [ padding 20, Background.color green, Font.color white ] (text name)
, row [ height (px 100), width fill, spacing 50 ]
[ render
[ location
(el
[ label name
, width (px 20)
, height (px 20)
, Background.color darkCharcoal
]
none
)
]
, render
[ location
(el
[ label name
, width (px 20)
, height (px 20)
, alignLeft
, Background.color darkCharcoal
]
none
)
]
, render
[ location
(el
[ label name
, width (px 20)
, height (px 20)
, centerX
, Background.color darkCharcoal
]
none
)
]
, render
[ location
(el
[ label name
, width (px 20)
, height (px 20)
, alignRight
, Background.color darkCharcoal
]
none
)
]
, render
[ location
(el
[ label name
, width (px 20)
, height (px 20)
, alignTop
, Background.color darkCharcoal
]
none
)
]
, render
[ location
(el
[ label name
, width (px 20)
, height (px 20)
, centerY
, Background.color darkCharcoal
]
none
)
]
, render
[ location
(el
[ label name
, width (px 20)
, height (px 20)
, alignBottom
, Background.color darkCharcoal
]
none
)
]
]
, text "widths/heights"
, row [ height (px 100), width fill, spacing 50, label "Row" ]
[ render
[ location
(el
[ label name
, width fill
, height fill
, Background.color darkCharcoal
]
none
)
]
, render
[ location
(el
[ label name
, width (px 20)
, height fill
, Background.color darkCharcoal
]
none
)
]
, render
[ label "render"
, location
(el
[ label name
, width fill
, height (px 20)
, Background.color darkCharcoal
]
none
)
]
, render
[ location
(el
[ label name
, width (px 20)
, height shrink
, Background.color darkCharcoal
, Font.color white
]
(text "h-shrink")
)
]
, render
[ location
(el
[ label name
, width shrink
, height (px 20)
, Background.color darkCharcoal
, Font.color white
]
(text "w-shrink")
)
]
]
, text "on paragraph"
, row [ width fill, spacing 50, label "Row" ]
[ p
[ location
(el
[ label name
, width (px 20)
, height (px 20)
, Background.color darkCharcoal
]
none
)
]
, p
[ location
(el
[ label name
, width (px 20)
, height (px 20)
, alignLeft
, Background.color darkCharcoal
]
none
)
]
, p
[ location
(el
[ label name
, width (px 20)
, height (px 20)
, centerX
, Background.color darkCharcoal
]
none
)
]
, p
[ location
(el
[ label name
, width (px 20)
, height (px 20)
, alignRight
, Background.color darkCharcoal
]
none
)
]
, p
[ location
(el
[ label name
, width (px 20)
, height (px 20)
, alignTop
, Background.color darkCharcoal
]
none
)
]
, p
[ location
(el
[ label name
, width (px 20)
, height (px 20)
, centerY
, Background.color darkCharcoal
]
none
)
]
, p
[ location
(el
[ label name
, width (px 20)
, height (px 20)
, alignBottom
, Background.color darkCharcoal
]
none
)
]
]
]
master =
el [ padding 20 ] <|
box
[ above (little "above-left" [ alignLeft ])
, above (little "above-center" [ centerX ])
, above (little "above-right" [ alignRight ])
, below (little "below-left" [ alignLeft ])
, below (little "below-center" [ centerX ])
, below (little "below-right" [ alignRight ])
, onRight (little "onRight-left" [ alignTop ])
, onRight (little "onRight-center" [ centerY ])
, onRight (little "onRight-right" [ alignBottom ])
, onLeft (little "onLeft-left" [ alignTop ])
, onLeft (little "onLeft-center" [ centerY ])
, onLeft (little "onLeft-right" [ alignBottom ])
, inFront (little "infront-left-top" [ alignTop, alignLeft ])
, inFront (little "infront-center-top" [ alignTop, centerX ])
, inFront (little "infront-right-top" [ alignTop, alignRight ])
, inFront (little "infront-left-center" [ centerY, alignLeft ])
, inFront (little "infront-center-center" [ centerY, centerX ])
, inFront (little "infront-right-center" [ centerY, alignRight ])
, inFront (little "infront-left-bottom" [ alignBottom, alignLeft ])
, inFront (little "infront-center-bottom" [ alignBottom, centerX ])
, inFront (little "infront-right-bottom" [ alignBottom, alignRight ])
]
masterParagraph =
el [ padding 20 ] <|
p
[ above (little "above-left" [ alignLeft ])
, above (little "above-center" [ centerX ])
, above (little "above-right" [ alignRight ])
, below (little "below-left" [ alignLeft ])
, below (little "below-center" [ centerX ])
, below (little "below-right" [ alignRight ])
, onRight (little "onRight-left" [ alignTop ])
, onRight (little "onRight-center" [ centerY ])
, onRight (little "onRight-right" [ alignBottom ])
, onLeft (little "onLeft-left" [ alignTop ])
, onLeft (little "onLeft-center" [ centerY ])
, onLeft (little "onLeft-right" [ alignBottom ])
, inFront (little "infront-left-top" [ alignTop, alignLeft ])
, inFront (little "infront-center-top" [ alignTop, centerX ])
, inFront (little "infront-right-top" [ alignTop, alignRight ])
, inFront (little "infront-left-center" [ centerY, alignLeft ])
, inFront (little "infront-center-center" [ centerY, centerX ])
, inFront (little "infront-right-center" [ centerY, alignRight ])
, inFront (little "infront-left-bottom" [ alignBottom, alignLeft ])
, inFront (little "infront-center-bottom" [ alignBottom, centerX ])
, inFront (little "infront-right-bottom" [ alignBottom, alignRight ])
]
in
column
[ centerX, label "Nearby Elements", spacing 100 ]
[ master
, masterParagraph
, nearby above "above" box
, nearby below "below" box
, nearby inFront "inFront" box
, nearby onRight "onRight" box
, nearby onLeft "onLeft" box
, nearby behind "behind" transparentBox
, text "all nearbys, all alignments"
]

41
tests/Tests/Palette.elm Normal file
View File

@ -0,0 +1,41 @@
module Tests.Palette exposing (..)
{-| -}
import Element
rgba r g b a =
Element.rgba (r / 255) (g / 255) (b / 255) a
darkGrey =
Element.rgb 0.8 0.8 0.8
darkCharcoal =
Element.rgb 0.9 0.9 0.9
lightGrey =
Element.rgb 0.5 0.5 0.5
grey =
darkGrey
red =
Element.rgb 1 0 0
green =
Element.rgb 0 1 0
white =
Element.rgb 1 1 1
blue =
Element.rgb 0 0 1

View File

@ -0,0 +1,212 @@
module Tests.RowAlignment exposing (..)
import Generator
import Html
import Testable
import Testable.Element as Element exposing (..)
import Testable.Element.Background as Background
import Testable.Element.Font as Font
import Testable.Runner
import Tests.Palette as Palette exposing (..)
{-| -}
main : Html.Html msg
main =
Testable.Runner.show view
box attrs =
el
([ width (px 50)
, height (px 50)
, Background.color blue
]
++ attrs
)
none
view =
let
rowContainer attrs children =
row
([ spacing 20
, height (px 100)
, Background.color lightGrey
]
++ attrs
)
children
in
column [ width (px 500), spacing 20 ]
[ el [] (text "Alignment Within a Row")
, column [ spacing 20 ] <|
Generator.sizes <|
\resizeable ->
rowContainer
[ label "single child" ]
[ resizeable [] none ]
, column [ spacing 20 ] <|
Generator.sizes <|
\resizeable ->
rowContainer
[ label "single child" ]
[ resizeable [ centerX ] none ]
, column [ spacing 20 ] <|
Generator.sizes <|
\resizeable ->
rowContainer [ label "single child" ]
[ resizeable [ alignRight ] none ]
, column [ spacing 20 ] <|
Generator.sizes <|
\resizeable ->
rowContainer []
[ resizeable [] none
, resizeable [] none
, resizeable [] none
]
, column [ spacing 20 ] <|
Generator.sizes <|
\resizeable ->
rowContainer []
[ resizeable [] none
, resizeable [] none
, resizeable [ alignRight, label "Right Child in Row" ] none
]
, column [ spacing 20 ] <|
Generator.sizes <|
\resizeable ->
rowContainer
[]
[ resizeable [] none
, resizeable [ alignRight, label "Middle Child in Row" ] none
, resizeable [] none
]
, column [ spacing 20 ] <|
Generator.sizes <|
\resizeable ->
rowContainer []
[ resizeable [ alignRight, label "Left Child in Row" ] none
, resizeable [] none
, resizeable [] none
]
, text "center X"
, column [ spacing 20 ] <|
Generator.sizes <|
\resizeable ->
rowContainer []
[ resizeable [ centerX, label "Left Child in Row" ] none
, resizeable [] none
, resizeable [] none
]
, column [ spacing 20 ] <|
Generator.sizes <|
\resizeable ->
rowContainer []
[ resizeable [] none
, resizeable [ centerX, label "Middle Child in Row" ] none
, resizeable [] none
]
, column [ spacing 20 ] <|
Generator.sizes <|
\resizeable ->
rowContainer []
[ resizeable [] none
, resizeable [] none
, resizeable [ centerX, label "Right Child in Row" ] none
]
, column [ spacing 20 ] <|
Generator.sizes <|
\resizeable ->
rowContainer []
[ resizeable [] none
, resizeable [] none
, resizeable [ centerX, label "Middle-Right Child in Row" ] none
, resizeable [] none
]
, column [ spacing 20 ] <|
Generator.sizes <|
\resizeable ->
rowContainer []
[ resizeable [] none
, resizeable [] none
, resizeable [ centerX, label "Middle-Right Child in Row" ] none
, resizeable [ centerX, label "Middle-Right Child in Row" ] none
, resizeable [] none
]
, column [ spacing 20 ] <|
Generator.sizes <|
\resizeable ->
rowContainer []
[ resizeable [] none
, resizeable [] none
, resizeable [ centerX, label "Middle-Right Child in Row" ] none
, resizeable [ centerX, label "Middle-Right Child in Row" ] none
, resizeable [ centerX, label "Middle-Right Child in Row" ] none
, resizeable [] none
]
, text "left x right"
, column [ spacing 20 ] <|
Generator.sizes <|
\resizeable ->
rowContainer []
[ resizeable [ alignLeft, label "Left Child in Row" ] none
, resizeable [] none
, resizeable [ alignRight, label "Right Child in Row" ] none
]
, text "left center right"
, column [ spacing 20 ] <|
Generator.sizes <|
\resizeable ->
rowContainer []
[ resizeable [ alignLeft, label "Left Child in Row" ] none
, resizeable [ centerX, label "Middle Child in Row" ] none
, resizeable [ alignRight, label "Right Child in Row" ] none
]
, text "vertical alignment"
, column [ spacing 20 ] <|
Generator.sizes <|
\resizeable ->
rowContainer []
[ resizeable [ alignTop, label "Left Child in Row" ] none
, resizeable [ centerY, label "Middle Child in Row" ] none
, resizeable [ alignBottom, label "Right Child in Row" ] none
]
, text "x and y alignments"
, column [ spacing 20 ] <|
Generator.sizes <|
\resizeable ->
rowContainer []
[ resizeable [ alignLeft, alignTop, label "Left Child" ] none
, resizeable [ centerX, centerY, label "Middle Child" ] none
, resizeable [ alignRight, alignBottom, label "Right Child" ] none
]
, text "align Top and X alignments "
, column [ spacing 20 ] <|
Generator.sizes <|
\resizeable ->
rowContainer []
[ resizeable [ alignLeft, alignTop, label "Left Child" ] none
, resizeable [ centerX, alignTop, label "Middle Child" ] none
, resizeable [ alignRight, alignTop, label "Right Child" ] none
]
, text "align Bottom and X alignments "
, column [ spacing 20 ] <|
Generator.sizes <|
\resizeable ->
rowContainer []
[ resizeable [ alignLeft, alignBottom, label "Left Child" ] none
, resizeable [ centerX, alignBottom, label "Middle Child" ] none
, resizeable [ alignRight, alignBottom, label "Right Child" ] none
]
, text "centerY and X alignments "
, column [ spacing 20 ] <|
Generator.sizes <|
\resizeable ->
rowContainer []
[ resizeable [ alignLeft, centerY, label "Left Child" ] none
, resizeable [ centerX, centerY, label "Middle Child" ] none
, resizeable [ alignRight, centerY, label "Right Child" ] none
]
]

View File

@ -0,0 +1,69 @@
module Tests.RowSpacing exposing (..)
import Html
import Testable
import Testable.Element as Element exposing (..)
import Testable.Element.Background as Background
import Testable.Element.Font as Font
import Testable.Runner
import Tests.Palette as Palette exposing (..)
{-| -}
main : Html.Html msg
main =
Testable.Runner.show view
box attrs =
el
([ width (px 50)
, height (px 50)
, Background.color blue
]
++ attrs
)
none
tinyBox attrs =
el
([ width (px 20)
, height (px 20)
-- , centerY
, Background.color darkCharcoal
]
++ attrs
)
none
container =
el [ width (px 100), height (px 100) ]
view =
let
colContainer attrs children =
row ([ spacing 20, width (px 500), height (px 120) ] ++ attrs) children
in
column
[]
[ el [] (text "Spacing within a row")
, column []
[ box []
, colContainer []
[ box [] ]
, colContainer []
[ box []
, box []
, box []
]
, colContainer [ below (tinyBox []) ]
[ box []
, box []
, box []
]
]
]

28
tests/Tests/Run.elm Normal file
View File

@ -0,0 +1,28 @@
port module Tests.Run exposing (..)
{-| -}
import Testable.Runner
import Tests.Basic
import Tests.ColumnAlignment
import Tests.ColumnSpacing
import Tests.ElementAlignment
import Tests.Nearby
import Tests.RowAlignment
import Tests.RowSpacing
import Tests.Transparency
main : Testable.Runner.TestableProgram
main =
Testable.Runner.program
[ --Tuple.pair "Basic Element" Tests.Basic.view
Tuple.pair "Nearby" Tests.Nearby.view
, Tuple.pair "Element Alignment" Tests.ElementAlignment.view
, Tuple.pair "Transparency" Tests.Transparency.view
, Tuple.pair "Column Alignment" Tests.ColumnAlignment.view
-- , Tuple.pair "Row Alignment" Tests.RowAlignment.view
, Tuple.pair "Column Spacing" Tests.ColumnSpacing.view
, Tuple.pair "Row Spacing" Tests.RowSpacing.view
]

123
tests/Tests/Table.elm Normal file
View File

@ -0,0 +1,123 @@
module Tests.Table exposing (..)
import Tests.Palette as Palette exposing (..)
import Html
import Testable
import Testable.Element as Element exposing (..)
import Testable.Element.Background as Background
import Testable.Element.Font as Font
import Testable.Runner
{-| -}
main : Html.Html msg
main =
Testable.Runner.show view
box attrs =
el
([ width (px 50)
, height (px 50)
, Background.color blue
]
++ attrs
)
none
container =
el [ width (px 100), height (px 100) ]
view =
let
data =
[ { firstName = "David"
, lastName = "Bowie"
}
, { firstName = "Florence"
, lastName = "Welch"
}
]
in
column [ spacing 20, width fill, paddingXY 1 30 ]
[ text "Tables With Headers"
, table []
{ data = data
, columns =
[ { header = text "First Name"
, view =
\row -> text row.firstName
}
, { header = text "Last Name"
, view =
\row -> text row.lastName
}
]
}
, text "Without Headers"
, table []
{ data = data
, columns =
[ { header = none
, view =
\row -> text row.firstName
}
, { header = none
, view =
\row -> text row.lastName
}
]
}
, text "With Spacing and Styling"
, table
[ Background.color blue
, spacing 20
, padding 30
]
{ data = data
, columns =
[ { header =
el [ Font.color white ] <|
text "First Name"
, view =
\row -> el [ Background.color lightGrey ] <| text row.firstName
}
, { header = el [ Font.color white ] <| text "Last Name"
, view =
\row ->
el [ Background.color lightGrey ] <|
text row.lastName
}
]
}
, text "Indexed Table With Spacing and Styling"
, indexedTable
[ Background.color blue
, spacing 20
, padding 30
]
{ data = data
, columns =
[ { header =
el [ Font.color white ] <|
text "Index"
, view =
\i row -> el [ Background.color lightGrey, width fill ] <| text (toString i)
}
, { header =
el [ Font.color white ] <|
text "First Name"
, view =
\i row -> el [ Background.color lightGrey, width fill ] <| text row.firstName
}
, { header = el [ Font.color white ] <| text "Last Name"
, view =
\i row ->
el [ Background.color lightGrey, width fill ] <|
text row.lastName
}
]
}
]

View File

@ -0,0 +1,57 @@
module Tests.Transparency exposing (..)
import Html
import Testable
import Testable.Element exposing (..)
import Testable.Element.Background as Background
import Testable.Element.Font as Font
import Testable.Runner
import Tests.Palette as Palette exposing (..)
box attrs =
el
([ width (px 50)
, height (px 50)
, Background.color blue
]
++ attrs
)
none
{-| -}
main : Html.Html msg
main =
Testable.Runner.show view
view =
column [ paddingXY 0 100, spacing 16 ]
[ text "transparency"
, row [ spacing 16 ]
[ box [ transparent True ]
, box [ transparent False ]
]
, text "transparency with hover"
, row [ spacing 16 ]
[ box
[ transparent True
-- , mouseOver [ Background.color green ]
]
, box
[ transparent False
-- , mouseOver [ Background.color green ]
]
]
, text "all opacities"
, row [ spacing 16 ]
[ box [ alpha 0 ]
, box [ alpha 0.25 ]
, box [ alpha 0.5 ]
, box [ alpha 0.75 ]
, box [ alpha 1.0 ]
]
]

View File

@ -0,0 +1,61 @@
module Tests.Basic exposing (view)
{-| -}
import Html
import Testable
import Testable.Element as Element exposing (..)
import Testable.Element.Background as Background
import Testable.Element.Font as Font
import Testable.Runner
import Tests.Palette as Palette exposing (..)
{-| -}
main : Html.Html msg
main =
Testable.Runner.show view
container attrs child =
el
(Background.color lightGrey
:: attrs
)
child
box attrs =
el (Background.color blue :: attrs) none
{-| -}
view : Testable.Element msg
view =
column [ spacing 50, alignTop, Font.color white ]
[ el [ Font.color black ] (text "fill")
, el
[ width (px 600)
, Background.color lightGrey
]
(el
[ Background.color blue
, width fill
, height (px 200)
]
none
)
, el [ Font.color black ] (text "shrink")
, el
[ width (px 600)
, height (px 100)
, Background.color lightGrey
]
(el
[ Background.color blue
, width shrink
, height shrink
]
(text "Hello")
)
]

View File

@ -0,0 +1,59 @@
from selenium import webdriver
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
import os
import time
def browsers():
capabilities = [
{'platform': "Mac OS X 10.9",
'browserName': "chrome",
'version': "31",
}
]
return capabilies
def run_local():
driver = webdriver.Firefox()
local_file = "file://" + os.getcwd() + "/gather-styles.html"
run_test(driver, local_file)
def get_credentials():
return {'username': os.environ['SAUCE_USERNAME'], 'access_key': os.environ['SAUCE_ACCESS_KEY']}
def run_remote(desired_cap):
creds = get_credentials()
driver = webdriver.Remote(
command_executor='http://{username}:{key}@ondemand.saucelabs.com:80/wd/hub'.format(
username=creds['username'],
key=creds['access_key']),
desired_capabilities=desired_cap)
run_test(driver)
def run_test(driver, url):
try:
driver.get(local_file)
for x in range(5):
results = driver.execute_script('return test_results')
if results != "waiting..":
print(results)
break
time.sleep(1)
except Exception as inst:
print(type(inst))
print(inst.args)
print(inst)
driver.quit()
else:
print("quitting browser")
driver.quit()
if __name__ == "__main__":
run_local()

26
tests/elm.json Normal file
View File

@ -0,0 +1,26 @@
{
"type": "application",
"source-directories": [
"src",
"suite",
"../src"
],
"elm-version": "0.19.0",
"dependencies": {
"direct": {
"elm/browser": "1.0.0",
"elm/core": "1.0.0",
"elm/html": "1.0.0",
"elm/json": "1.0.0",
"elm/virtual-dom": "1.0.0"
},
"indirect": {
"elm/time": "1.0.0",
"elm/url": "1.0.0"
}
},
"test-dependencies": {
"direct": {},
"indirect": {}
}
}

83
tests/gather-styles.html Normal file
View File

@ -0,0 +1,83 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="UTF-8">
<title>Rendering Benchmark Viewer</title>
<script src="elm.js"></script>
</head>
<style>
</style>
<body id="root"></body>
<script type="text/javascript">
//var app = Elm.Main.fullscreen();
var node = document.getElementById('root');
var app = Elm.Tests.Run.embed(node);
var test_results = "waiting.."
app.ports.report.subscribe(function (results) {
test_results = results;
})
app.ports.analyze.subscribe(function (ids) {
// ids : List String
var idsLength = ids.length;
var results = [];
for (var i = 0; i < idsLength; i++) {
var id = ids[i];
var element = document.getElementById(id);
if (element == null) {
console.log("id " + id + " not found");
}
var style = getStyle(element);
var bbox = getBoundingBox(element);
var result = { "bbox": bbox, "style": style, "id": id };
results.push(result);
}
app.ports.styles.send(results);
});
function getStyle(element) {
var props = []
var style = window.getComputedStyle(element);
for (var i = style.length; i--;) {
var name = style.item(i);
var value = style.getPropertyValue(name);
props.push([name, value]);
}
return props;
}
function getBoundingBox(element) {
var bbox = element.getBoundingClientRect();
var style = window.getComputedStyle(element, null);
var padding = {
'top': parseFloat(style.getPropertyValue('padding-top'))
, 'bottom': parseFloat(style.getPropertyValue('padding-bottom'))
, 'left': parseFloat(style.getPropertyValue('padding-left'))
, 'right': parseFloat(style.getPropertyValue('padding-right'))
};
return {
'top': bbox.top
, 'bottom': bbox.bottom
, 'left': bbox.left
, 'right': bbox.right
, 'width': bbox.width
, 'height': bbox.height
, 'padding': padding
}
}
</script>
</html>

1
tests/live.sh Normal file
View File

@ -0,0 +1 @@
elm-live Main.elm --output=elm.js --open --pushstate

53
tests/src/Generator.elm Normal file
View File

@ -0,0 +1,53 @@
module Generator exposing (..)
{-| -}
import Testable
import Testable.Element as Element exposing (..)
-- sizes : (List (Testable.Attr msg) -> Testable.Element msg -> Testable.Element msg) -> List (Testable.Element msg)
sizes render =
List.concatMap
(\( widthLen, heightLen ) ->
[ text (Debug.toString ( widthLen, heightLen ))
, render
(\attrs children ->
el
(width widthLen
:: height heightLen
:: attrs
)
children
)
]
)
allLengthPairs
allLengthPairs : List ( Length, Length )
allLengthPairs =
let
crossProduct len =
List.map (Tuple.pair len) lengths
in
List.concatMap crossProduct lengths
lengths =
[ px 50
, fill
, shrink
, fill
|> maximum 100
, fill
|> maximum 100
|> minimum 50
, shrink
|> maximum 100
, shrink
|> maximum 100
|> minimum 50
]

730
tests/src/Testable.elm Normal file
View File

@ -0,0 +1,730 @@
module Testable exposing (..)
{-| -}
import Dict exposing (Dict)
import Element exposing (Color)
import Expect
import Html exposing (Html)
import Html.Attributes
import Internal.Model as Internal
import Random
import Test exposing (Test)
import Test.Runner
import Test.Runner.Failure
type Element msg
= El (List (Attr msg)) (Element msg)
| Row (List (Attr msg)) (List (Element msg))
| Column (List (Attr msg)) (List (Element msg))
| TextColumn (List (Attr msg)) (List (Element msg))
| Paragraph (List (Attr msg)) (List (Element msg))
| Text String
| Empty
type Attr msg
= Attr (Element.Attribute msg)
| AttrTest (Surroundings -> Test)
| Batch (List (Attr msg))
| Spacing Int
| Nearby
{ location : Location
, element : Element msg
, test : Surroundings -> () -> Expect.Expectation
, label : String
}
| Label String
| LabeledTest
{ test : Surroundings -> () -> Expect.Expectation
, label : String
, attr : Element.Attribute msg
}
type Location
= Above
| Below
| OnRight
| OnLeft
| InFront
| Behind
type LayoutContext
= IsNearby Location
| InRow
| InEl
| InColumn
type alias Surroundings =
{ siblings : List Found
, parent : Found
, children : List Found
, self : Found
-- These values are needed to perform some types of tests.
, location : LayoutContext
, parentSpacing : Int
}
type alias Found =
{ bbox : BoundingBox
, style : Style
}
{-| -}
type alias Style =
Dict String String
type alias BoundingBox =
{ width : Float
, height : Float
, left : Float
, top : Float
, right : Float
, bottom : Float
, padding :
{ left : Float
, right : Float
, top : Float
, bottom : Float
}
}
{- Retrieve Ids -}
getIds : Element msg -> List String
getIds el =
"se-0" :: getElementId [ 0, 0 ] el
getElementId : List Int -> Element msg -> List String
getElementId level el =
let
id =
levelToString level
attrID attrIndex attr =
case attr of
Nearby nearby ->
getElementId (attrIndex :: -1 :: level) nearby.element
_ ->
[]
attributeIDs attrs =
attrs
|> List.indexedMap attrID
|> List.concat
childrenIDs children =
List.concat <| List.indexedMap (\i -> getElementId (i :: level)) children
in
case el of
El attrs child ->
id :: getElementId (0 :: level) child ++ attributeIDs attrs
Row attrs children ->
id :: childrenIDs children ++ attributeIDs attrs
Column attrs children ->
id :: childrenIDs children ++ attributeIDs attrs
TextColumn attrs children ->
id :: childrenIDs children ++ attributeIDs attrs
Paragraph attrs children ->
id :: childrenIDs children ++ attributeIDs attrs
Empty ->
[]
Text _ ->
[]
{- Render as Html -}
render : Element msg -> Html msg
render el =
Element.layout [ idAttr "0" ] <|
renderElement [ 0, 0 ] el
idAttr : String -> Element.Attribute msg
idAttr id =
Element.htmlAttribute (Html.Attributes.id ("se-" ++ id))
renderElement : List Int -> Element msg -> Element.Element msg
renderElement level el =
let
id =
level
|> List.map String.fromInt
|> String.join "-"
|> idAttr
makeAttributes attrs =
attrs
|> List.indexedMap (renderAttribute level)
|> List.concat
in
case el of
El attrs child ->
Element.el
(id :: makeAttributes attrs)
(renderElement (0 :: level) child)
Row attrs children ->
Element.row
(id :: makeAttributes attrs)
(List.indexedMap (\i -> renderElement (i :: level)) children)
Column attrs children ->
Element.column
(id :: makeAttributes attrs)
(List.indexedMap (\i -> renderElement (i :: level)) children)
TextColumn attrs children ->
Element.textColumn
(id :: makeAttributes attrs)
(List.indexedMap (\i -> renderElement (i :: level)) children)
Paragraph attrs children ->
Element.paragraph
(id :: makeAttributes attrs)
(List.indexedMap (\i -> renderElement (i :: level)) children)
Empty ->
Element.none
Text str ->
Element.text str
renderAttribute : List Int -> Int -> Attr msg -> List (Element.Attribute msg)
renderAttribute level attrIndex attr =
case attr of
Attr attribute ->
[ attribute ]
AttrTest _ ->
[]
Spacing _ ->
[]
Label _ ->
[]
Nearby { location, element } ->
case location of
Above ->
[ Element.above (renderElement (attrIndex :: -1 :: level) element) ]
Below ->
[ Element.below (renderElement (attrIndex :: -1 :: level) element) ]
OnRight ->
[ Element.onRight (renderElement (attrIndex :: -1 :: level) element) ]
OnLeft ->
[ Element.onLeft (renderElement (attrIndex :: -1 :: level) element) ]
InFront ->
[ Element.inFront (renderElement (attrIndex :: -1 :: level) element) ]
Behind ->
[ Element.behindContent (renderElement (attrIndex :: -1 :: level) element) ]
Batch batch ->
List.indexedMap (renderAttribute (attrIndex :: level)) batch
|> List.concat
LabeledTest tested ->
[ tested.attr ]
{- Convert to Test -}
toTest : String -> Dict String Found -> Element msg -> Test
toTest label harvested el =
let
maybeFound =
Dict.get "se-0" harvested
in
case maybeFound of
Nothing ->
Test.describe label
[ Test.test "Find Root" <|
\_ -> Expect.fail "unable to find root"
]
Just root ->
Test.describe label <|
createTest
{ siblings = []
, parent = root
, cache = harvested
, parentSpacing = 0
, level = [ 0, 0 ]
, element = el
, location = InEl
}
levelToString : List Int -> String
levelToString level =
level
|> List.map String.fromInt
|> String.join "-"
|> (\x -> "se-" ++ x)
createTest :
{ siblings : List Found
, parent : Found
, cache : Dict String Found
, level : List Int
, element : Element msg
, location : LayoutContext
, parentSpacing : Int
}
-> List Test
createTest { siblings, parent, cache, level, element, location, parentSpacing } =
let
spacing =
getSpacing element
|> Maybe.withDefault 0
id =
-- Debug.log "create Test" <|
levelToString level
testChildren : Found -> List (Element msg) -> List Test
testChildren found children =
let
childrenFound =
-- Should check taht this lookup doesn't fail.
-- Thoug if it does, it'll fail when the element itself is tested
List.filterMap
(\x ->
Dict.get (levelToString (x :: level)) cache
)
(List.range 0 (List.length children))
in
List.foldl (applyChildTest found)
{ index = 0
, upcoming = childrenFound
, previous = []
, tests = []
}
children
|> .tests
applyChildTest :
Found
-> Element msg
->
{ a
| index : Int
, previous : List Found
, tests : List Test
, upcoming : List Found
}
->
{ index : Int
, previous : List Found
, tests : List Test
, upcoming : List Found
}
applyChildTest found child childTest =
-- { index, upcoming, previous, tests }
let
surroundingChildren =
case childTest.upcoming of
[] ->
childTest.previous
x :: remaining ->
remaining ++ childTest.previous
childrenTests =
createTest
{ siblings = surroundingChildren
, parent = found
, cache = cache
, level = childTest.index :: level
, element = child
, parentSpacing = spacing
, location =
case element of
El _ _ ->
InEl
Row _ _ ->
InRow
Column _ _ ->
InColumn
TextColumn _ _ ->
InColumn
Paragraph _ _ ->
InRow
Text _ ->
InEl
Empty ->
InEl
}
in
{ index = childTest.index + 1
, tests = childTest.tests ++ childrenTests
, previous =
case childTest.upcoming of
[] ->
childTest.previous
x :: _ ->
x :: childTest.previous
, upcoming =
case childTest.upcoming of
[] ->
[]
_ :: rest ->
rest
}
tests : Found -> List (Attr msg) -> List (Element msg) -> List Test
tests self attributes children =
let
findBBox elem ( i, gathered ) =
case elem of
Empty ->
( i + 1
, gathered
)
Text _ ->
( i + 1
, gathered
)
_ ->
case Dict.get (levelToString (i :: level)) cache of
Nothing ->
let
_ =
Debug.log "el failed to find" elem
_ =
Debug.log "Failed to find child" (levelToString (i :: level))
in
( i + 1
, gathered
)
Just found ->
( i + 1
, found :: gathered
)
childrenFoundData =
List.foldl findBBox ( 0, [] ) children
|> Tuple.second
attributeTests =
attributes
|> applyLabels
|> List.indexedMap
-- Found -> Dict String Found -> List Int -> Int -> Surroundings -> Attr msg -> List Test
(\i attr ->
createAttributeTest self
cache
level
i
{ siblings = siblings
, parent = parent
, self = self
, children = childrenFoundData
, location = location
, parentSpacing = parentSpacing
}
attr
)
|> List.concat
in
attributeTests
++ testChildren self children
in
case Dict.get id cache of
Nothing ->
case element of
Empty ->
[]
Text _ ->
[]
_ ->
[ Test.test ("Unable to find " ++ id) (always <| Expect.fail "failed id lookup") ]
Just self ->
case element of
El attrs child ->
tests self attrs [ child ]
Row attrs children ->
tests self attrs children
Column attrs children ->
tests self attrs children
TextColumn attrs children ->
tests self attrs children
Paragraph attrs children ->
tests self attrs children
Empty ->
[]
Text str ->
[]
applyLabels : List (Attr msg) -> List (Attr msg)
applyLabels attrs =
let
toLabel attr =
case attr of
Label label ->
Just label
_ ->
Nothing
newLabels =
attrs
|> List.filterMap toLabel
|> String.join ", "
applyLabel newLabel attr =
case attr of
LabeledTest labeled ->
LabeledTest
{ labeled
| label =
if newLabel == "" then
labeled.label
else
newLabel ++ ", " ++ labeled.label
}
x ->
x
in
List.map (applyLabel newLabels) attrs
createAttributeTest : Found -> Dict String Found -> List Int -> Int -> Surroundings -> Attr msg -> List Test
createAttributeTest parent cache level attrIndex surroundings attr =
let
indexLabel =
levelToString (attrIndex :: level)
in
case attr of
Attr _ ->
[]
Label _ ->
[]
Spacing _ ->
[]
AttrTest test ->
[ test surroundings
]
Nearby nearby ->
createTest
{ siblings = []
, parent = parent
, cache = cache
, parentSpacing = 0
, level = attrIndex :: -1 :: level
, location = IsNearby nearby.location
, element = addAttribute (AttrTest (\context -> Test.test (nearby.label ++ " #" ++ indexLabel) (nearby.test context))) nearby.element
}
Batch batch ->
batch
|> List.indexedMap (\i attribute -> createAttributeTest parent cache (attrIndex :: level) i surroundings attribute)
|> List.concat
LabeledTest { label, test } ->
[ Test.test (label ++ " #" ++ indexLabel) (test surroundings)
]
addAttribute : Attr msg -> Element msg -> Element msg
addAttribute attr el =
case el of
El attrs child ->
El (attr :: attrs) child
Row attrs children ->
Row (attr :: attrs) children
Column attrs children ->
Column (attr :: attrs) children
TextColumn attrs children ->
TextColumn (attr :: attrs) children
Paragraph attrs children ->
Paragraph (attr :: attrs) children
Empty ->
Empty
Text str ->
Text str
runTests :
Random.Seed
-> Test
->
List
( String
, Maybe
{ given : Maybe String
, description : String
, reason : Test.Runner.Failure.Reason
}
)
runTests seed tests =
let
run runner =
let
ran =
List.map Test.Runner.getFailureReason (runner.run ())
in
List.map2 Tuple.pair runner.labels ran
results =
case Test.Runner.fromTest 100 seed tests of
Test.Runner.Plain rnrs ->
List.map run rnrs
Test.Runner.Only rnrs ->
List.map run rnrs
Test.Runner.Skipping rnrs ->
List.map run rnrs
Test.Runner.Invalid invalid ->
let
_ =
Debug.log "Invalid tests" invalid
in
[]
in
List.concat results
compareFormattedColor : Color -> String -> Bool
compareFormattedColor color expected =
formatColor color == expected || formatColorWithAlpha color == expected
formatColorWithAlpha : Color -> String
formatColorWithAlpha (Internal.Rgba red green blue alpha) =
if alpha == 1 then
("rgba(" ++ String.fromInt (round (red * 255)))
++ (", " ++ String.fromInt (round (green * 255)))
++ (", " ++ String.fromInt (round (blue * 255)))
++ ", 1"
++ ")"
else
("rgba(" ++ String.fromInt (round (red * 255)))
++ (", " ++ String.fromInt (round (green * 255)))
++ (", " ++ String.fromInt (round (blue * 255)))
++ (", " ++ String.fromFloat alpha ++ ")")
formatColor : Color -> String
formatColor (Internal.Rgba red green blue alpha) =
if alpha == 1 then
("rgb(" ++ String.fromInt (round (red * 255)))
++ (", " ++ String.fromInt (round (green * 255)))
++ (", " ++ String.fromInt (round (blue * 255)))
++ ")"
else
("rgb(" ++ String.fromInt (round (red * 255)))
++ (", " ++ String.fromInt (round (green * 255)))
++ (", " ++ String.fromInt (round (blue * 255)))
++ ")"
getSpacing : Element msg -> Maybe Int
getSpacing el =
let
getSpacingAttr attr found =
if found /= Nothing then
found
else
case attr of
Spacing i ->
Just i
Batch attrs ->
List.foldr getSpacingAttr Nothing attrs
_ ->
Nothing
filterAttrs attrs =
List.foldr getSpacingAttr Nothing attrs
in
case el of
El attrs _ ->
filterAttrs attrs
Row attrs _ ->
filterAttrs attrs
Column attrs _ ->
filterAttrs attrs
TextColumn attrs _ ->
filterAttrs attrs
Paragraph attrs _ ->
filterAttrs attrs
Empty ->
Nothing
Text _ ->
Nothing

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,27 @@
module Testable.Element.Background exposing (color)
{-| -}
import Dict
import Element exposing (Color)
import Element.Background as Background
import Expect
import Testable
color : Color -> Testable.Attr msg
color clr =
Testable.LabeledTest
{ label = "background color-" ++ Testable.formatColor clr
, attr = Background.color clr
, test =
\context _ ->
let
selfBackgroundColor =
context.self.style
|> Dict.get "background-color"
|> Maybe.withDefault "notfound"
in
Expect.true ("Color Match - " ++ (Testable.formatColor clr ++ " vs " ++ selfBackgroundColor))
(Testable.compareFormattedColor clr selfBackgroundColor)
}

View File

@ -0,0 +1,48 @@
module Testable.Element.Font exposing (color)
{-| -}
import Dict
import Element exposing (Color)
import Element.Font as Font
import Expect
import Testable
color : Color -> Testable.Attr msg
color clr =
Testable.LabeledTest
{ label = "font color-" ++ Testable.formatColor clr
, attr = Font.color clr
, test =
\context _ ->
let
selfFontColor =
context.self.style
|> Dict.get "color"
|> Maybe.withDefault "notfound"
in
Expect.true ("Color Match - " ++ (Testable.formatColor clr ++ " vs " ++ selfFontColor))
(Testable.compareFormattedColor clr selfFontColor)
}
size : Int -> Testable.Attr msg
size i =
Testable.LabeledTest
{ label = "font size-" ++ String.fromInt i
, attr = Font.size i
, test =
\context _ ->
let
selfFontSize =
context.self.style
|> Dict.get "fontsize"
|> Maybe.withDefault "notfound"
formattedInt =
String.fromInt i
in
Expect.true ("Size Match - " ++ (formattedInt ++ " vs " ++ selfFontSize))
(formattedInt == selfFontSize)
}

View File

@ -0,0 +1,629 @@
port module Main exposing (..)
{-| -}
import AnimationFrame
import Color
import Dict exposing (Dict)
import Element
import Element.Background as Background
import Element.Border as Border
import Element.Font as Font
import Expect
import Fuzz
import Html exposing (Html)
import List.Extra
import Random
import Test
import Test.Runner
import Test.Runner.Failure
import Testable
import Tests exposing (Testable)
import Time exposing (Time)
init : ( Model Msg, Cmd Msg )
init =
( { current = Nothing
, upcoming =
Tests.tests
-- generateTests (Random.initialSeed 227852860) 1
, finished = []
, stage = BeginRendering
}
, Cmd.none
)
main : Program Never (Model Msg) Msg
main =
Html.program
{ init = init
, view = view
, update = update
, subscriptions = subscriptions
}
type alias Model msg =
{ current : Maybe (Testable msg)
, upcoming : List (Testable msg)
, finished : List (Testable msg)
, stage : Stage
}
type Msg
= NoOp
| Tick Time
| RefreshBoundingBox
(List
{ id : String
, bbox : Testable.BoundingBox
, style : Testable.Style
}
)
type Stage
= Rendered
| BeginRendering
| GatherData
| Finished
runTest : Dict String Testable.Found -> Testable msg -> Testable msg
runTest boxes test =
let
tests =
Testable.toTest test.label boxes test.element
seed =
Random.initialSeed 227852860
results =
Testable.runTests seed (Debug.log "created Tests" tests)
|> Debug.log "run tests"
in
{ test | results = Just results }
generateTests : Random.Seed -> Int -> List (Testable msg)
generateTests seed num =
if num <= 0 then
[]
else
let
( testable, newSeed ) =
generateTest seed
in
testable :: generateTests newSeed (num - 1)
generateTest : Random.Seed -> ( Testable msg, Random.Seed )
generateTest seed =
testableLayout
|> Test.Runner.fuzz
|> flip Random.step seed
|> (\( ( val, shrinker ), newSeed ) -> ( val, seed ))
update : Msg -> Model Msg -> ( Model Msg, Cmd Msg )
update msg model =
case msg of
NoOp ->
( model, Cmd.none )
RefreshBoundingBox boxes ->
case model.current of
Nothing ->
( { model | stage = Finished }
, Cmd.none
)
Just current ->
let
toTuple box =
( box.id, { style = box.style, bbox = box.bbox } )
foundData =
boxes
|> List.map toTuple
|> Dict.fromList
currentResults =
runTest foundData current
in
case model.upcoming of
[] ->
( { model
| stage = Finished
, current = Nothing
, finished = currentResults :: model.finished
}
, Cmd.none
)
newCurrent :: remaining ->
( { model
| finished = currentResults :: model.finished
, stage = BeginRendering
}
, Cmd.none
)
Tick time ->
case model.stage of
BeginRendering ->
case model.upcoming of
[] ->
( { model | stage = Rendered }
, Cmd.none
)
current :: remaining ->
( { model
| stage = Rendered
, upcoming = remaining
, current = Just current
}
, Cmd.none
)
Rendered ->
case model.current of
Nothing ->
( { model | stage = Finished }
, Cmd.none
)
Just test ->
( { model | stage = GatherData }
, analyze (Testable.getIds test.element)
)
_ ->
( { model | stage = Rendered }
, Cmd.none
)
subscriptions : { a | stage : Stage } -> Sub Msg
subscriptions model =
Sub.batch
[ styles RefreshBoundingBox
, case model.stage of
BeginRendering ->
AnimationFrame.times Tick
Rendered ->
AnimationFrame.times Tick
_ ->
Sub.none
]
port analyze : List String -> Cmd msg
port styles : (List { id : String, bbox : Testable.BoundingBox, style : Testable.Style } -> msg) -> Sub msg
view : Model Msg -> Html Msg
view model =
case model.current of
Nothing ->
if model.stage == Finished then
Element.layout [] <|
Element.column
[ Element.spacing 20
, Element.padding 20
, Element.width (Element.px 800)
-- , Background.color Color.grey
]
(List.map viewResult model.finished)
else
Html.text "running?"
Just current ->
Testable.render current.element
viewResult : Testable Msg -> Element.Element Msg
viewResult testable =
let
viewSingle result =
case result of
( label, Nothing ) ->
Element.el
[ Background.color Color.green
, Font.color Color.white
, Element.paddingXY 20 10
, Element.alignLeft
, Border.rounded 3
]
<|
Element.text (label ++ " - " ++ "Success!")
( label, Just { given, description, reason } ) ->
Element.row
[ Background.color Color.red
, Font.color Color.white
, Element.paddingXY 20 10
, Element.alignLeft
, Element.spacing 25
, Border.rounded 3
]
[ Element.el [ Element.width Element.fill ] <| Element.text label
-- , Element.el [ Element.width Element.fill ] <| Element.text description
, Element.el [ Element.width Element.fill ] <| Element.text (toString reason)
]
in
Element.column
[ Border.width 1
, Border.color Color.lightGrey
, Element.padding 20
, Element.height Element.shrink
, Element.alignLeft
]
[ Element.el [ Font.bold ] (Element.text testable.label)
, Element.column [ Element.alignLeft, Element.spacing 20 ]
(case testable.results of
Nothing ->
[ Element.text "no results" ]
Just results ->
List.map viewSingle results
)
]
{--}
type alias Testable msg =
{ element : Testable.Element msg
, label : String
, results :
Maybe
(List
( String
, Maybe
{ given : Maybe String
, description : String
, reason : Test.Runner.Failure.Reason
}
)
)
}
attr tag label attr test =
Testable.Batch
[ Testable.Attr attr
, Testable.AttrTest
(\found ->
Test.test (tag ++ ": " ++ label) <|
\_ -> test found
)
]
widthFill tag =
attr tag
"width fill"
(Element.width Element.fill)
(\context ->
let
spacePerPortion =
context.parent.bbox.width / toFloat (List.length context.siblings + 1)
in
Expect.equal spacePerPortion context.self.bbox.width
)
heightFill tag =
attr tag
"height fill"
(Element.height Element.fill)
(\context ->
let
spacePerPortion =
context.parent.bbox.height / toFloat (List.length context.siblings + 1)
in
Expect.equal spacePerPortion context.self.bbox.height
)
heightPx px tag =
attr tag
("height at " ++ toString px ++ "px")
(Element.height (Element.px (round px)))
(\found ->
Expect.equal found.self.bbox.height px
)
widthPx px tag =
attr tag
("width at " ++ toString px ++ "px")
(Element.width (Element.px (round px)))
(\found ->
Expect.equal found.self.bbox.width px
)
type alias PossibleAttributes msg =
{ width : String -> Testable.Attr msg
, height : String -> Testable.Attr msg
}
possibleToList str { width, height } =
width str :: height str :: []
availableAttributes : { width : Float, height : Float } -> Fuzz.Fuzzer (PossibleAttributes msg)
availableAttributes bounds =
Fuzz.map2 PossibleAttributes
(Fuzz.oneOf
(List.map Fuzz.constant (widths bounds.width))
)
(Fuzz.oneOf
(List.map Fuzz.constant (heights bounds.height))
)
widths : Float -> List (String -> Testable.Attr msg)
widths width =
[ \tag ->
attr tag
(" with width at " ++ toString width ++ "px")
(Element.width (Element.px (round width)))
(\found ->
Expect.equal found.self.bbox.width width
)
, \tag ->
attr tag
" with width fill"
(Element.width Element.fill)
(\context ->
let
-- {-|
-- Calculation is
-- totalparentwidth - (paddingLeft ++ paddingRight)
-- minus widths of all the other children
-- -}
otherWidth =
-- Debug.log "width" <|
List.sum <|
-- Debug.log "widths" <|
List.map (\child -> child.bbox.width) context.siblings
spacePerPortion =
context.parent.bbox.width - otherWidth
in
Expect.equal spacePerPortion context.self.bbox.width
)
]
heights : Float -> List (String -> Testable.Attr msg)
heights height =
[ \tag ->
attr tag
(" with height at " ++ toString height ++ "px")
(Element.height (Element.px (round height)))
(\found ->
Expect.equal found.self.bbox.height height
)
, \tag ->
attr tag
" with height fill"
(Element.height Element.fill)
(\context ->
let
-- {-|
-- Calculation is
-- totalparentwidth - (paddingLeft ++ paddingRight)
-- minus widths of all the other children
-- -}
otherHeight =
List.sum <|
List.map (\child -> child.bbox.height) context.siblings
spacePerPortion =
context.parent.bbox.height - otherHeight
in
Expect.equal spacePerPortion context.self.bbox.height
)
]
content : Testable.Element msg
content =
Testable.El
[ Testable.Attr <| Element.width (Element.px 20)
, Testable.Attr <| Element.height (Element.px 20)
]
Testable.Empty
tests : List (Testable msg)
tests =
[ { label = "Width/Height 200"
, results = Nothing
, element =
Testable.El
[ widthPx 200 "single element"
, heightPx 200 "single element"
]
Testable.Empty
}
, { label = "Width Fill/Height Fill - 1"
, results = Nothing
, element =
Testable.El
[ widthFill "single element"
, heightFill "single element"
]
<|
content
}
, { label = "Width Fill/Height Fill - 2"
, results = Nothing
, element =
Testable.El
[ widthFill "top element"
, heightFill "top element"
]
<|
Testable.El
[ widthFill "embedded element"
, heightFill "embedded element"
]
<|
content
}
, { label = "Row Width Fill/Height Fill"
, results = Nothing
, element =
Testable.Row
[ widthFill "on row"
, heightFill "on row"
]
[ Testable.El
[ widthFill "row - 1"
, heightFill "row - 1"
]
content
, Testable.El
[ widthFill "row - 2"
, heightFill "row - 2"
]
content
, Testable.El
[ widthFill "row - 3"
, heightFill "row - 3"
]
content
]
}
]
{- Generating A Complete Test Suite
-}
type TestableElement
= El
| Row
| Column
allIndividualTestables : List (Fuzz.Fuzzer TestableElement)
allIndividualTestables =
[ Fuzz.constant El
, Fuzz.constant Row
, Fuzz.constant Column
]
testableLayout : Fuzz.Fuzzer (Testable msg)
testableLayout =
let
asTestable layout =
{ element = layout
, label = "A Sweet Test"
, results = Nothing
}
in
Fuzz.map asTestable layout
layout : Fuzz.Fuzzer (Testable.Element msg)
layout =
Fuzz.map4 createContainer
(Fuzz.tuple3
( availableAttributes { width = 20, height = 20 }
, availableAttributes { width = 20, height = 20 }
, availableAttributes { width = 20, height = 20 }
)
)
(availableAttributes { width = 200, height = 200 })
(Fuzz.oneOf allIndividualTestables)
(Fuzz.oneOf allIndividualTestables)
createElement : String -> TestableElement -> PossibleAttributes msg -> Testable.Element msg
createElement tag layout possibleAttrs =
let
attrs =
possibleToList tag possibleAttrs
in
case layout of
El ->
Testable.El attrs content
Row ->
Testable.Row attrs
[ content
, content
, content
]
Column ->
Testable.Column attrs
[ content
, content
, content
]
createContainer : ( PossibleAttributes msg, PossibleAttributes msg, PossibleAttributes msg ) -> PossibleAttributes msg -> TestableElement -> TestableElement -> Testable.Element msg
createContainer ( a1, a2, a3 ) possibleAttrs parent child =
let
attrs =
possibleToList "0" possibleAttrs
in
case parent of
El ->
Testable.El attrs (createElement "0-0" child a1)
Row ->
Testable.Row attrs
[ createElement "0-0" child a1
, createElement "0-1" child a2
, createElement "0-2" child a3
]
Column ->
Testable.Column attrs
[ createElement "0-0" child a1
, createElement "0-1" child a2
, createElement "0-2" child a3
]
{-| Given a list of a list of possibilities,
choose once from each list.
[ [width (px 100), width fill]
, [height (px 100), height fill]
]
->
[ [width (px 100), height (px 100)]
, [width (px 100), height fill]
]
-}
allPossibilities : List (List a) -> List (List a)
allPossibilities =
List.Extra.cartesianProduct

View File

@ -0,0 +1,444 @@
port module Testable.Runner exposing (TestableProgram, program, show)
{-| -}
import Browser
import Char
import Dict exposing (Dict)
import Element
import Element.Background as Background
import Element.Border as Border
import Element.Font as Font
import Html exposing (Html)
import Parser exposing ((|.), (|=))
import Process
import Random
import Set
import Task
import Test.Runner
import Test.Runner.Failure as Failure
import Testable
import Time
show : Testable.Element msg -> Html msg
show =
Testable.render
type alias TestableProgram =
Program () (Model Msg) Msg
palette =
{ white = Element.rgb 1 1 1
, red = Element.rgb 1 0 0
, green = Element.rgb 0 1 0
, lightGrey = Element.rgb 0.7 0.7 0.7
}
program : List ( String, Testable.Element Msg ) -> TestableProgram
program tests =
let
( current, upcoming ) =
case tests of
[] ->
( Nothing, [] )
cur :: remaining ->
( Just cur, remaining )
in
Browser.embed
{ init =
always
( { current = current
, upcoming = upcoming
, finished = []
}
, Task.perform (always Analyze)
(Process.sleep 32
|> Task.andThen
(always Time.now)
)
)
, view = view
, update = update
, subscriptions = subscriptions
}
subscriptions : Model Msg -> Sub Msg
subscriptions model =
Sub.batch
[ styles RefreshBoundingBox
]
type alias Model msg =
{ current : Maybe ( String, Testable.Element msg )
, upcoming : List ( String, Testable.Element msg )
, finished : List (WithResults (Testable.Element msg))
}
type alias WithResults thing =
{ element : thing
, label : String
, results :
List
( String
, Maybe
{ given : Maybe String
, description : String
, reason : Failure.Reason
}
)
}
prepareResults :
List (WithResults (Testable.Element msg))
->
List
{ label : String
, results :
List
( String
, Maybe
{ given : Maybe String
, description : String
}
)
}
prepareResults withResults =
let
prepareNode ( x, maybeResult ) =
( x
, case maybeResult of
Nothing ->
Nothing
Just res ->
Just
{ given = res.given
, description = res.description
}
)
prepare { label, results } =
{ label = label
, results = List.map prepareNode results
}
in
List.map prepare withResults
type Msg
= NoOp
| Analyze
| RefreshBoundingBox
(List
{ id : String
, bbox : Testable.BoundingBox
, style : List ( String, String )
}
)
runTest : Dict String Testable.Found -> String -> Testable.Element msg -> WithResults (Testable.Element msg)
runTest boxes label element =
let
tests =
Testable.toTest label boxes element
seed =
Random.initialSeed 227852860
results =
Testable.runTests seed tests
in
{ element = element
, label = label
, results = results
}
update : Msg -> Model Msg -> ( Model Msg, Cmd Msg )
update msg model =
case msg of
NoOp ->
( model, Cmd.none )
RefreshBoundingBox boxes ->
case model.current of
Nothing ->
( model
, Cmd.none
)
Just ( label, current ) ->
let
toTuple box =
( box.id, { style = Dict.fromList box.style, bbox = box.bbox } )
foundData =
boxes
|> List.map toTuple
|> Dict.fromList
currentResults =
runTest foundData label current
in
case model.upcoming of
[] ->
( { model
| current = Nothing
, finished = model.finished ++ [ currentResults ]
}
, report (prepareResults (currentResults :: model.finished))
)
newCurrent :: remaining ->
( { model
| finished = model.finished ++ [ currentResults ]
, current = Just newCurrent
, upcoming = remaining
}
, Task.perform (always Analyze)
(Process.sleep 32
|> Task.andThen
(always Time.now)
)
)
Analyze ->
case model.current of
Nothing ->
( model
, Cmd.none
)
Just ( label, current ) ->
( model
, analyze (Testable.getIds current)
)
view : Model Msg -> Html Msg
view model =
case model.current of
Nothing ->
if List.isEmpty model.upcoming then
-- (List.map viewResult model.finished)
case model.finished of
[] ->
Element.layout [] <|
Element.column
[ Element.spacing 20
, Element.padding 20
, Element.width (Element.px 800)
-- , Background.color Color.grey
]
[ Element.none ]
finished :: remaining ->
if False then
viewResultsInline finished
else
Element.layout [] <|
Element.column
[ Element.spacing 20
, Element.padding 20
, Element.width (Element.px 800)
]
(List.map viewResult (finished :: remaining))
else
Html.text "running?"
Just ( label, current ) ->
Testable.render current
viewResultsInline : WithResults (Testable.Element Msg) -> Html Msg
viewResultsInline testable =
Html.div
[]
[ viewResultsAnnotationStylesheet testable.results
, Testable.render testable.element
]
{-| Our ID is part of our label. This could be fixed farther down the chain, but I think it'd be pretty involved.
So, now we can just parse the id out of the label.
-}
parseId str =
str
|> Parser.run
(Parser.succeed identity
|. Parser.chompWhile (\c -> c /= '#')
|= Parser.variable
{ start = \c -> c == '#'
, inner = \c -> Char.isAlphaNum c || c == '-'
, reserved = Set.empty
}
)
|> Result.toMaybe
viewResultsAnnotationStylesheet results =
let
toStyleClass ( label, maybeFailure ) =
case maybeFailure of
Nothing ->
""
Just failure ->
case parseId label of
Nothing ->
Debug.log "NO ID FOUND" label
Just id ->
id ++ " { background-color:red; outline: dashed; };"
styleSheet =
results
|> List.map toStyleClass
|> String.join ""
in
Html.node "style"
[]
[ Html.text styleSheet
]
viewResult : WithResults (Testable.Element Msg) -> Element.Element Msg
viewResult testable =
let
isPassing result =
case Tuple.second result of
Nothing ->
True
Just _ ->
False
( passing, failing ) =
List.partition isPassing testable.results
viewSingle result =
case result of
( label, Nothing ) ->
Element.el
[ Background.color palette.green
, Font.color palette.white
, Element.paddingXY 20 10
, Element.alignLeft
, Border.rounded 3
]
<|
Element.text ("Success! - " ++ label)
( label, Just ({ given, description } as reason) ) ->
Element.column
[ Background.color palette.red
, Font.color palette.white
, Element.paddingXY 20 10
, Element.alignLeft
, Element.width Element.shrink
-- , Element.spacing 25
, Border.rounded 3
]
[ Element.el [ Element.width Element.fill ] <| Element.text label
, Element.el [ Element.width Element.fill ] <| Element.text (viewReason reason)
]
in
Element.column
[ Border.width 1
, Border.color palette.lightGrey
, Element.padding 20
, Element.height Element.shrink
, Element.alignLeft
, Element.spacing 16
]
[ Element.el [ Font.bold, Font.size 64 ] (Element.text testable.label)
, Element.column [ Element.alignLeft, Element.spacing 20 ]
(failing
|> List.map viewSingle
)
, Element.el
[ Element.alignLeft
, Element.spacing 20
, Background.color palette.green
, Font.color palette.white
, Element.paddingXY 20 10
, Element.alignLeft
, Border.rounded 3
]
(Element.text (String.fromInt (List.length passing) ++ " tests passing!"))
]
viewReason { description, reason } =
case reason of
Failure.Custom ->
description
Failure.Equality one two ->
description ++ " " ++ one ++ " " ++ two
Failure.Comparison one two ->
description ++ " " ++ one ++ " " ++ two
Failure.ListDiff expected actual ->
"expected\n"
++ String.join " \n" expected
++ "actual\n"
++ String.join " \n" actual
Failure.CollectionDiff { expected, actual, extra, missing } ->
String.join "\n"
[ formatKeyValue "expected" expected
, formatKeyValue "actual" actual
, formatKeyValue "extra" (String.join ", " extra)
, formatKeyValue "missing" (String.join ", " missing)
]
Failure.TODO ->
description
Failure.Invalid _ ->
description
formatKeyValue : String -> String -> String
formatKeyValue key val =
key ++ ": " ++ val
port report :
List
{ label : String
, results :
List
( String
, Maybe
{ given : Maybe String
, description : String
}
)
}
-> Cmd msg
port test : String -> Cmd msg
port analyze : List String -> Cmd msg
port styles : (List { id : String, bbox : Testable.BoundingBox, style : List ( String, String ) } -> msg) -> Sub msg

158
tests/suite/ClassNames.elm Normal file
View File

@ -0,0 +1,158 @@
module Main exposing (..)
import Html
import Internal.Flag as Flag
import Internal.Style
main =
Html.div []
[ Html.text "The following names collide"
, Html.div []
(List.map viewPair onlyDuplicates)
]
viewPair ( name, description ) =
Html.div []
[ Html.text name
, Html.text ": "
, Html.text description
]
onlyDuplicates =
List.filter findDuplicates allClassNames
findDuplicates ( name, description ) =
List.any
(\( checkName, checkDescription ) ->
checkName == name && description /= checkDescription
)
allClassNames
allClassNames =
List.map (Tuple.mapFirst (\fn -> fn Internal.Style.classes)) allClassNameFns
allClassNameFns =
[ ( .root, "root" )
, ( .any, "any" )
, ( .single, "single" )
, ( .row, "row" )
, ( .column, "column" )
, ( .page, "page" )
, ( .paragraph, "paragraph" )
, ( .text, "text" )
, ( .grid, "grid" )
, ( .imageContainer, "imageContainer" )
-- widhts/heights
, ( .widthFill, "widthFill" )
, ( .widthContent, "widthContent" )
, ( .widthExact, "widthExact" )
, ( .widthFillPortion, "widthFillPortion" )
, ( .heightFill, "heightFill" )
, ( .heightContent, "heightContent" )
, ( .heightFillPortion, "heightFillPortion" )
, ( .seButton, "seButton" )
-- nearby elements
, ( .above, "above" )
, ( .below, "below" )
, ( .onRight, "onRight" )
, ( .onLeft, "onLeft" )
, ( .inFront, "inFront" )
, ( .behind, "behind" )
-- alignments
, ( .alignTop, "alignTop" )
, ( .alignBottom, "alignBottom" )
, ( .alignRight, "alignRight" )
, ( .alignLeft, "alignLeft" )
, ( .alignCenterX, "alignCenterX" )
, ( .alignCenterY, "alignCenterY" )
, ( .alignedHorizontally, "alignedHorizontally" )
, ( .alignedVertically, "alignedVertically" )
-- space evenly
, ( .spaceEvenly, "spaceEvenly" )
, ( .container, "container" )
, ( .alignContainerRight, "alignContainerRight" )
, ( .alignContainerBottom, "alignContainerBottom" )
, ( .alignContainerCenterX, "alignContainerCenterX" )
, ( .alignContainerCenterY, "alignContainerCenterY" )
-- content alignments
, ( .contentTop, "contentTop" )
, ( .contentBottom, "contentBottom" )
, ( .contentRight, "contentRight" )
, ( .contentLeft, "contentLeft" )
, ( .contentCenterX, "contentCenterX" )
, ( .contentCenterY, "contentCenterY" )
-- selection
, ( .noTextSelection, "noTextSelection" )
, ( .cursorPointer, "cursorPointer" )
, ( .cursorText, "cursorText" )
-- pointer events
, ( .passPointerEvents, "passPointerEvents" )
, ( .capturePointerEvents, "capturePointerEvents" )
, ( .transparent, "transparent" )
, ( .opaque, "opaque" )
, ( .overflowHidden, "overflowHidden" )
-- special state classes
, ( .hover, "hover" )
-- , ( .hoverOpaque, "hoverOpaque" )
, ( .focus, "focus" )
-- , ( .focusOpaque, "focusOpaque" )
, ( .active, "active" )
-- , ( .activeOpaque, "activeOpaque" )
--scrollbars
, ( .scrollbars, "scrollbars" )
, ( .scrollbarsX, "scrollbarsX" )
, ( .scrollbarsY, "scrollbarsY" )
, ( .clip, "clip" )
, ( .clipX, "clipX" )
, ( .clipY, "clipY" )
-- borders
, ( .borderNone, "borderNone" )
, ( .borderDashed, "borderDashed" )
, ( .borderDotted, "borderDotted" )
, ( .borderSolid, "borderSolid" )
-- text weight
, ( .textThin, "textThin" )
, ( .textExtraLight, "textExtraLight" )
, ( .textLight, "textLight" )
, ( .textNormalWeight, "textNormalWeight" )
, ( .textMedium, "textMedium" )
, ( .textSemiBold, "textSemiBold" )
, ( .bold, "bold" )
, ( .textExtraBold, "textExtraBold" )
, ( .textHeavy, "textHeavy" )
, ( .italic, "italic" )
, ( .strike, "strike" )
, ( .underline, "underline" )
, ( .textUnitalicized, "textUnitalicized" )
-- text alignment
, ( .textJustify, "textJustify" )
, ( .textJustifyAll, "textJustifyAll" )
, ( .textCenter, "textCenter" )
, ( .textRight, "textRight" )
, ( .textLeft, "textLeft" )
, ( .transition, "transition" )
-- inputText
, ( .inputText, "inputText" )
, ( .inputMultiline, "inputMultiline" )
]

89
tests/suite/Flags.elm Normal file
View File

@ -0,0 +1,89 @@
module Main exposing (..)
import Html
import Internal.Flag as Flag
main =
Html.div []
[ Html.text "Verify All Flags invalidate themselves"
, Html.div []
(List.indexedMap invalidateSelf allFlags)
, Html.text "Verify All Flags don't interfere with other flags"
, Html.div []
(List.indexedMap doesntInvalidateOthers allFlags)
]
invalidateSelf i flag =
if Flag.present flag (Flag.add flag Flag.none) then
Html.text ""
else
Html.div [] [ Html.text (toString (Flag.value flag) ++ " at index " ++ toString i ++ " does not invalidate itself") ]
doesntInvalidateOthers i flag =
let
withFlag =
Flag.none
|> Flag.add flag
passing =
List.all identity <|
List.indexedMap
(\j otherFlag ->
Flag.present otherFlag (Flag.add otherFlag withFlag)
)
allFlags
in
if passing then
Html.text ""
else
Html.div []
[ Html.text (toString (Flag.value flag) ++ " at index " ++ toString i ++ " invalidates other flags!")
]
allFlags =
[ Flag.transparency
, Flag.padding
, Flag.spacing
, Flag.fontSize
, Flag.fontFamily
, Flag.width
, Flag.height
, Flag.bgColor
, Flag.bgImage
, Flag.bgGradient
, Flag.borderStyle
, Flag.fontAlignment
, Flag.fontWeight
, Flag.fontColor
, Flag.wordSpacing
, Flag.letterSpacing
, Flag.borderRound
, Flag.shadows
, Flag.overflow
, Flag.cursor
, Flag.scale
, Flag.rotate
, Flag.moveX
, Flag.moveY
, Flag.borderWidth
, Flag.borderColor
, Flag.yAlign
, Flag.xAlign
, Flag.focus
, Flag.active
, Flag.hover
, Flag.gridTemplate
, Flag.gridPosition
, Flag.heightContent
, Flag.heightFill
, Flag.widthContent
, Flag.widthFill
, Flag.alignRight
, Flag.alignBottom
, Flag.centerX
, Flag.centerY
]

View File

@ -0,0 +1,16 @@
{
"version": "1.0.0",
"summary": "helpful summary of your project, less than 80 characters",
"repository": "https://github.com/user/project.git",
"license": "BSD3",
"source-directories": [
".",
"../../src"
],
"exposed-modules": [],
"dependencies": {
"elm-lang/core": "5.1.1 <= v < 6.0.0",
"elm-lang/html": "2.0.0 <= v < 3.0.0"
},
"elm-version": "0.18.0 <= v < 0.19.0"
}