mirror of
https://github.com/mdgriffith/elm-ui.git
synced 2024-11-22 11:52:09 +03:00
a stylish new design toolkit
This commit is contained in:
parent
0abf848616
commit
25850370dc
13
.gitignore
vendored
13
.gitignore
vendored
@ -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/
|
202
CHANGES-FROM-STYLE-ELEMENTS.md
Normal file
202
CHANGES-FROM-STYLE-ELEMENTS.md
Normal 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
18
CSS-LOOKUP.md
Normal 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!
|
4
LICENSE
4
LICENSE
@ -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
57
README.md
Normal 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
26
elm.json
Normal 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
29
examples/Basic.elm
Normal 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
201
examples/Form.elm
Normal 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
65
examples/Table.elm
Normal 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
25
examples/elm.json
Normal 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": {}
|
||||
}
|
||||
}
|
57
experiments/Accumulator.elm
Normal file
57
experiments/Accumulator.elm
Normal 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
|
10
experiments/masks/Test.elm
Normal file
10
experiments/masks/Test.elm
Normal file
@ -0,0 +1,10 @@
|
||||
module Main exposing (..)
|
||||
|
||||
{-| -}
|
||||
|
||||
import Html
|
||||
import MaskedInput
|
||||
|
||||
|
||||
main =
|
||||
Html.text "Testing"
|
27
experiments/masks/elm.json
Normal file
27
experiments/masks/elm.json
Normal 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": {}
|
||||
}
|
||||
}
|
130
experiments/masks/src/MaskedInput.elm
Normal file
130
experiments/masks/src/MaskedInput.elm
Normal 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
|
189
experiments/masks/src/Notes.elm
Normal file
189
experiments/masks/src/Notes.elm
Normal 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
|
37
experiments/palette/PaletteTest.elm
Normal file
37
experiments/palette/PaletteTest.elm
Normal 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))
|
||||
]
|
||||
[]
|
||||
)
|
27
experiments/palette/elm.json
Normal file
27
experiments/palette/elm.json
Normal 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": {}
|
||||
}
|
||||
}
|
214
experiments/palette/src/Palette.elm
Normal file
214
experiments/palette/src/Palette.elm
Normal 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
1587
src/Element.elm
Normal file
File diff suppressed because it is too large
Load Diff
232
src/Element/Background.elm
Normal file
232
src/Element/Background.elm
Normal 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
221
src/Element/Border.elm
Normal 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
272
src/Element/Events.elm
Normal 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
332
src/Element/Font.elm
Normal 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
1834
src/Element/Input.elm
Normal file
File diff suppressed because it is too large
Load Diff
67
src/Element/Keyed.elm
Normal file
67
src/Element/Keyed.elm
Normal 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
117
src/Element/Lazy.elm
Normal 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
111
src/Element/Region.elm
Normal 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
260
src/Internal/Flag.elm
Normal 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
264
src/Internal/Grid.elm
Normal 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
2847
src/Internal/Model.elm
Normal file
File diff suppressed because it is too large
Load Diff
1628
src/Internal/Style.elm
Normal file
1628
src/Internal/Style.elm
Normal file
File diff suppressed because it is too large
Load Diff
54
tests/Tests/Basic.elm
Normal file
54
tests/Tests/Basic.elm
Normal 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!")
|
||||
]
|
176
tests/Tests/ColumnAlignment.elm
Normal file
176
tests/Tests/ColumnAlignment.elm
Normal 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 ]
|
||||
]
|
||||
]
|
||||
]
|
68
tests/Tests/ColumnSpacing.elm
Normal file
68
tests/Tests/ColumnSpacing.elm
Normal 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 []
|
||||
]
|
||||
]
|
||||
]
|
111
tests/Tests/ElementAlignment.elm
Normal file
111
tests/Tests/ElementAlignment.elm
Normal 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
|
||||
]
|
||||
)
|
||||
]
|
286
tests/Tests/Manual/AlignedForm.elm
Normal file
286
tests/Tests/Manual/AlignedForm.elm
Normal 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
1263
tests/Tests/Manual/All.elm
Normal file
File diff suppressed because it is too large
Load Diff
523
tests/Tests/Manual/LazyPerformance.elm
Normal file
523
tests/Tests/Manual/LazyPerformance.elm
Normal 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}
|
||||
""" ]
|
36
tests/Tests/Manual/OtherScrollbars.elm
Normal file
36
tests/Tests/Manual/OtherScrollbars.elm
Normal 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
|
||||
]
|
||||
[]
|
||||
]
|
16
tests/Tests/Manual/Overflow.elm
Normal file
16
tests/Tests/Manual/Overflow.elm
Normal 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 ] []
|
||||
]
|
62
tests/Tests/Manual/Scrollbars.elm
Normal file
62
tests/Tests/Manual/Scrollbars.elm
Normal 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")
|
||||
]
|
58
tests/Tests/Manual/ScrollbarsPls.elm
Normal file
58
tests/Tests/Manual/ScrollbarsPls.elm
Normal 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")
|
||||
]
|
43
tests/Tests/Manual/TextLayout.elm
Normal file
43
tests/Tests/Manual/TextLayout.elm
Normal 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
393
tests/Tests/Nearby.elm
Normal 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
41
tests/Tests/Palette.elm
Normal 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
|
212
tests/Tests/RowAlignment.elm
Normal file
212
tests/Tests/RowAlignment.elm
Normal 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
|
||||
]
|
||||
]
|
69
tests/Tests/RowSpacing.elm
Normal file
69
tests/Tests/RowSpacing.elm
Normal 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
28
tests/Tests/Run.elm
Normal 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
123
tests/Tests/Table.elm
Normal 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
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
57
tests/Tests/Transparency.elm
Normal file
57
tests/Tests/Transparency.elm
Normal 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 ]
|
||||
]
|
||||
]
|
61
tests/Tests/WidthHeight.elm
Normal file
61
tests/Tests/WidthHeight.elm
Normal 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")
|
||||
)
|
||||
]
|
59
tests/automation/selenium_test.py
Normal file
59
tests/automation/selenium_test.py
Normal 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
26
tests/elm.json
Normal 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
83
tests/gather-styles.html
Normal 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
1
tests/live.sh
Normal file
@ -0,0 +1 @@
|
||||
elm-live Main.elm --output=elm.js --open --pushstate
|
53
tests/src/Generator.elm
Normal file
53
tests/src/Generator.elm
Normal 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
730
tests/src/Testable.elm
Normal 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
|
1045
tests/src/Testable/Element.elm
Normal file
1045
tests/src/Testable/Element.elm
Normal file
File diff suppressed because it is too large
Load Diff
27
tests/src/Testable/Element/Background.elm
Normal file
27
tests/src/Testable/Element/Background.elm
Normal 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)
|
||||
}
|
48
tests/src/Testable/Element/Font.elm
Normal file
48
tests/src/Testable/Element/Font.elm
Normal 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)
|
||||
}
|
629
tests/src/Testable/FuzzMain.elm
Normal file
629
tests/src/Testable/FuzzMain.elm
Normal 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
|
444
tests/src/Testable/Runner.elm
Normal file
444
tests/src/Testable/Runner.elm
Normal 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
158
tests/suite/ClassNames.elm
Normal 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
89
tests/suite/Flags.elm
Normal 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
|
||||
]
|
16
tests/suite/elm-package.json
Normal file
16
tests/suite/elm-package.json
Normal 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"
|
||||
}
|
Loading…
Reference in New Issue
Block a user