on master help sign in
Merge branch 'dev' into json
commit832c1984612da89030cbee231f20350dbf6f9b04 authorEvan Czaplicki <info@elm-lang.org> dateFri 21 Feb 2014, 12:45 (13 years ago) parent139f73afea6361
11 files changed+638−482
changelog.txt+13−4 compiler/Parse/Literal.hs+30−12 libraries/Debug.elm+17 libraries/Graphics/Element.elm+5−2 libraries/Graphics/Input.elm+185−156 libraries/List.elm+1−1 libraries/Native/Debug.js+22 libraries/Native/Graphics/Input.js+324−299 libraries/Native/Trampoline.js+4−2 libraries/String.elm+1−1 runtime/Render/Element.js+36−5
changelog.txt+13−4
@@ -1,14 +1,23 @@
11
22## next
33
4+Breaking Changes:
5+
6+ * Overhaul Graphics.Input library
7+ * Overhaul Regex library
48 * Change syntax for "import open List" to "import List (..)"
5− * Support Trampolining (thanks to Max New and Tim Hobbs)
6− * Drastically improved performance on markdown parsing (thanks to @Dandandan)
79 * Improved JSON format for types generated by elm-doc
10+ * Remove problematic Mouse.isClicked signal
811 * Revise the semantics of keepWhen and dropWhen to only update when
9− * the filtered signal changes (thanks Max New and Janis Voigtländer)
12+ the filtered signal changes (thanks Max New and Janis Voigtländer)
13+
14+Improvements:
15+
16+ * Add Trampoline library (thanks to @maxsnew and @timthelion)
17+ * Add Debug library (inspired by @timthelion)
18+ * Drastically improved performance on markdown parsing (thanks to @Dandandan)
1019 * Add Date.fromTime function
11− * Remove problematic Mouse.isClicked signal
20+ * Use pointer-events to detect hovers on layered elements (thanks to @Xashili)
1221 * Fix serious bugs in Bitwise library
1322 * Fix bug in exporting Maybe values through ports
1423
compiler/Parse/Literal.hs+30−12
@@ -1,4 +1,4 @@
1−{-# OPTIONS_GHC -Wall -fno-warn-unused-do-bind #-}
1+{-# OPTIONS_GHC -W #-}
22module Parse.Literal (literal) where
33
44import Control.Applicative ((<$>))
@@ -7,16 +7,34 @@ import Parse.Helpers
77import SourceSyntax.Literal
88
99literal :: IParser Literal
10−literal = num <|> (Str <$> str) <|> (Chr <$> chr)
10+literal = num <|> (Str <$> str) <|> (Chr <$> chr) <?> "literal"
1111
1212num :: IParser Literal
13−num = fmap toLit (preNum <?> "number")
14− where toLit n | '.' `elem` n = FloatNum (read n)
15− | otherwise = IntNum (read n)
16− preNum = concat <$> sequence [ option "" minus, many1 digit, option "" postNum ]
17− postNum = do try $ lookAhead (string "." >> digit)
18− string "."
19− ('.':) <$> many1 digit
20− minus = try $ do string "-"
21− lookAhead digit
22− return "-"
13+num = toLit <$> (number <?> "number")
14+ where
15+ toLit n
16+ | any (`elem` ".eE") n = FloatNum (read n)
17+ | otherwise = IntNum (read n)
18+
19+ number = concat <$> sequence
20+ [ option "" minus
21+ , many1 digit
22+ , option "" decimals
23+ , option "" exponent ]
24+
25+ minus = try $ do
26+ string "-"
27+ lookAhead digit
28+ return "-"
29+
30+ decimals = do
31+ try $ lookAhead (string "." >> digit)
32+ string "."
33+ n <- many1 digit
34+ return ('.' : n)
35+
36+ exponent = do
37+ string "e" <|> string "E"
38+ op <- option "" (string "+" <|> string "-")
39+ n <- many1 digit
40+ return ('e' : op ++ n)
libraries/Debug.elmnew file+17
@@ -0,0 +1,17 @@
1+module Debug where
2+{-| This library is for investigating bugs or performance problems. It should
3+*not* be used in production code.
4+-}
5+
6+import Native.Debug
7+
8+{-| Log a tagged value on the developer console, and then return the value.
9+
10+ 1 + log "number" 1 -- equals 2, logs "number: 1"
11+ length (log "start" []) -- equals 0, logs "start: []"
12+
13+Notice that `log` is not a pure function! It should *only* be used for
14+investigating bugs or performance problems.
15+-}
16+log : String -> a -> a
17+log = Native.Debug.log
libraries/Graphics/Element.elm+5−2
@@ -53,7 +53,8 @@ type Properties = {
5353 color : Maybe Color,
5454 href : JSString,
5555 tag : JSString,
56− hover : ()
56+ hover : (),
57+ click : ()
5758 }
5859
5960type Element = { props : Properties, element : ElementPrim }
@@ -128,7 +129,9 @@ link href e = let p = e.props in
128129
129130emptyStr = JS.fromString ""
130131newElement w h e =
131− { props = Properties (Native.Utils.guid ()) w h 1 Nothing emptyStr emptyStr (), element = e }
132+ { props = Properties (Native.Utils.guid ()) w h 1 Nothing emptyStr emptyStr () ()
133+ , element = e
134+ }
132135
133136data ElementPrim
134137 = Image ImageStyle Int Int JSString
libraries/Graphics/Input.elm+185−156
@@ -1,199 +1,228 @@
11module Graphics.Input where
2−
32{-| This module is for creating standard input widgets such as buttons and
4−text boxes. In general, functions in this library return a signal representing
5−events from the user.
3+text fields. All functions in this library follow a general pattern in which
4+you create an `Input` that many elements can report to:
5+
6+```haskell
7+clicks : Input ()
8+clicks = input ()
69
7−The simplest inputs are *one-way inputs*, meaning the user can update
8−them, but the programmer cannot. If you need to update an input from
9−within the program you want the slightly more complex *two-way inputs*.
10−This document will always show the one-way inputs first, *then* the
11−two-way inputs.
10+clickableYogi : Element
11+clickableYogi = clickable clicks.handle () (image 40 40 "/yogi.jpg")
12+```
1213
13−# Buttons
14−@docs button, customButton, buttons, customButtons
14+Whenever the user clicks on the resulting `clickableYogi` element, it sends an
15+update to the `clicks` input. You will see this pattern again and again in
16+examples in this library, so just read on to get a better idea of how it works!
1517
16−# Fields
17−@docs field, password, email, fields, FieldState, emptyFieldState
18+# Creating Inputs
19+@docs Input, input
1820
19−# Checkboxes
20−@docs checkbox, checkboxes
21+# Basic Input Elements
22+Text fields come later.
23+@docs button, customButton, checkbox, dropDown
2124
22−# Drop Downs
23−@docs stringDropDown, dropDown
25+# Clicks and Hovers
26+@docs clickable, hoverable
2427
25−# Mouse Hover
26−@docs hoverable, hoverables
28+# Text Fields
29+@docs field, password, email, noContent, FieldContent, Selection, Direction
2730-}
2831
29−import Basics (String)
30−import Signal (Signal,lift,dropRepeats)
31−import Native.Graphics.Input
32−import List
32+import Signal (Signal)
3333import Graphics.Element (Element)
34−import Maybe (Maybe)
35−import JavaScript (JSString)
34+import Native.Graphics.Input
3635
37−id x = x
36+{-| This is the key abstraction of this library. An `Input` is a record
37+of two fields:
3838
39−{-| Create a group of buttons.
39+ 1. `signal` &mdash; all values coming to this input from &ldquo;the world&rdquo;
40+ 2. `handle` &mdash; a way to refer to this particular input and send it values
4041
41− * The first argument is the default value of the `events` signal.
42− * The `events` signal represents all of the activity in this group
43− of buttons.
44− * The `button` function creates a button
45− with the given name, like &ldquo;Submit&rdquo; or &ldquo;Cancel&rdquo;.
46− The `a` value is sent to `events` whenever the button is pressed.
42+This will make more sense as you see more examples.
4743-}
48−buttons : a -> { events : Signal a,
49− button : a -> String -> Element }
50−buttons = Native.Graphics.Input.buttons
44+type Input a = { signal : Signal a, handle : Handle a }
45+
46+data Handle a = Handle
47+
48+{-| Create a new `Input`. You just give the initial value for the
49+input&rsquo;s `signal`.
5150
52−{-| Create a button with a given label. The result is an `Element` and
53−a signal of units. This signal triggers whenever the button is pressed.
51+Note for Advanced Users: creating an `Input` is an inherently imperative
52+action, so this is one of very few impure functions in Elm. That means
53+`(input ())` and `(input ())` are actually two different inputs with different
54+signals and handles. By design, Elm&rsquo;s impure functions can only be useful
55+as you build your signal graph at startup, so Elm is still pure at runtime.
5456-}
55−button : String -> (Element, Signal ())
56−button txt =
57− let pool = buttons ()
58− in (pool.button () txt, pool.events)
59−
60−{-| Create a group of custom buttons.
61−
62− * The first argument is the default value of the `events` signal.
63− * The `events` signal represents all of the activity in this group
64− of custom buttons.
65− * The `customButton` function creates a button with three different visual
66− states, one for up, hovering, and down. The resulting button has dimensions
67− large enough to fit all three possible `Elements`.
68− The `a` value is sent to `events` whenever the button is pressed.
57+input : a -> Input a
58+input = Native.Graphics.Input.input
59+
60+{-| Create a standard button. The following example begins making a basic
61+calculator:
62+
63+ data Keys = Number Int | Plus | Minus | Clear
64+
65+ keys : Input Keys
66+ keys = input Clear
67+
68+ calculator : Element
69+ calculator =
70+ flow right [ button keys.handle (Number 1) "1"
71+ , button keys.handle (Number 2) "2"
72+ , button keys.handle Plus "+"
73+ ]
74+
75+If the user presses the "+" button, `keys.signal` will update to `Plus`. If the
76+users presses "2", `keys.signal` will update to `(Number 2)`.
6977-}
70−customButtons : a -> { events : Signal a,
71− customButton : a -> Element -> Element -> Element -> Element }
72−customButtons = Native.Graphics.Input.customButtons
78+button : Handle a -> a -> String -> Element
79+button = Native.Graphics.Input.button
7380
74−{-| Create a button with custom states for up, hovering, and down
75−(given in that order). The result is an `Element` and a signal of
76−units. This signal triggers whenever the button is pressed.
81+{-| Same as `button` but lets you customize buttons to look however you want.
82+
83+ click : Input ()
84+ click = input ()
85+
86+ prettyButton : Element
87+ prettyButton =
88+ customButton click.handle
89+ (image 100 40 "/button_up.jpg")
90+ (image 100 40 "/button_hover.jpg")
91+ (image 100 40 "/button_down.jpg")
7792-}
78−customButton : Element -> Element -> Element -> (Element, Signal ())
79−customButton up hover down =
80− let pool = customButtons ()
81− in (pool.customButton () up hover down, pool.events)
82−
83−{-| Create a group of checkboxes.
84−
85− * The first argument is the default value of the `events` signal.
86− * The `events` signal represents all of the activity in this group
87− of checkboxes.
88− * The `checkbox` function creates a
89− checkbox with a given state. The `(Bool -> a)` function is used
90− when the checkbox is modified. It takes the new state and turns
91− it into a value that can be sent to `events`. For example, this
92− lets you add an ID to distinguish between checkboxes.
93+customButton : Handle a -> a -> Element -> Element -> Element -> Element
94+customButton = Native.Graphics.Input.customButton
95+
96+{-| Create a checkbox. The following example creates three synced checkboxes:
97+
98+ check : Input Bool
99+ check = input False
100+
101+ boxes : Bool -> Element
102+ boxes checked =
103+ let box = container 40 40 middle (checkbox check.handle id checked)
104+ in flow right [ box, box, box ]
105+
106+ main : Signal Element
107+ main = boxes <~ check.signal
93108-}
94−checkboxes : a -> { events : Signal a,
95− checkbox : (Bool -> a) -> Bool -> Element }
96−checkboxes = Native.Graphics.Input.checkboxes
97−
98−{-| Create a checkbox with a given start state. Unlike `button`, this
99−result is a *signal* of elements. That is because a checkbox has state
100−that updates based on user input. The boolean signal represents the
101−current state of the checkbox.
109+checkbox : Handle a -> (Bool -> a) -> Bool -> Element
110+checkbox = Native.Graphics.Input.checkbox
111+
112+{-| Create a drop-down menu. The following drop-down lets you choose your
113+favorite British sport:
114+
115+ data Sport = Football | Cricket | Snooker
116+
117+ sport : Input Sport
118+ sport = input Nothing
119+
120+ sportDropDown : Element
121+ sportDropDown =
122+ dropDown sport.handle
123+ [ ("" , Nothing)
124+ , ("Football", Just Football)
125+ , ("Cricket" , Just Cricket)
126+ , ("Snooker" , Just Snooker)
127+ ]
128+
129+If the user selects "Football" from the drop down menue, `sport.signal`
130+will update to `Just Football`.
102131-}
103−checkbox : Bool -> (Signal Element, Signal Bool)
104−checkbox b =
105− let cbs = checkboxes b
106− in (lift (cbs.checkbox id) cbs.events, cbs.events)
107−
108−{-| Detect when the mouse is hovering over some elements. This
109−allows you to create and destroy elements dynamically and still
110−detect hover information.
132+dropDown : Handle a -> [(String,a)] -> Element
133+dropDown = Native.Graphics.Input.dropDown
134+
135+{-| Detect mouse hovers over a specific `Element`. In the following example,
136+we will create a hoverable picture called `cat`.
137+
138+ hover : Input Bool
139+ hover = input False
140+
141+ cat : Element
142+ cat = image 30 30 "/cat.jpg"
143+ |> hoverable hover.handle id
144+
145+When the mouse hovers above the `cat` element, `hover.signal` will become
146+`True`. When the mouse leaves it, `hover.signal` will become `False`.
111147-}
112−hoverables : a -> { events : Signal a,
113− hoverable : (Bool -> a) -> Element -> Element }
114−hoverables = Native.Graphics.Input.hoverables
148+hoverable : Handle a -> (Bool -> a) -> Element -> Element
149+hoverable = Native.Graphics.Input.hoverable
115150
116−{-| Detect when the mouse is hovering over a specific `Element`. -}
117−hoverable : Element -> (Element, Signal Bool)
118−hoverable elem =
119− let pool = hoverables False
120− in (pool.hoverable id elem, pool.events)
151+{-| Detect mouse clicks on a specific `Element`. In the following example,
152+we will create a clickable picture called `cat`.
121153
122−{-| Represents the current state of a text field. The `string` represents the
123−characters filling the text field. The `selectionStart` and `selectionEnd`
124−values represent what the user has selected with their mouse or keyboard.
125−For example:
154+ data Picture = Cat | Hat
155+
156+ picture : Input Picture
157+ picture = input Cat
158+
159+ cat : Element
160+ cat = image 30 30 "/cat.jpg"
161+ |> clickable picture.handle Cat
162+
163+ hat : Element
164+ hat = image 30 30 "/hat.jpg"
165+ |> clickable picture.handle Hat
166+
167+When the user clicks on the `cat` element, `picture.signal` receives
168+an update containing the value `Cat`. When the user clicks on the `hat` element,
169+`picture.signal` receives an update containing the value `Hat`. This lets you
170+distinguish which element was clicked. In a more complex example, they could be
171+distinguished with IDs or more complex data structures.
172+-}
173+clickable : Handle a -> a -> Element -> Element
174+clickable = Native.Graphics.Input.clickable
126175
127− { string="She sells sea shells", selectionStart=3, selectionEnd=0 }
176+{-| Represents the current content of a text field. For example:
177+
178+ FieldContent "She sells sea shells" (Selection 0 3 Backward)
128179
129180This means the user highlighted the substring `"She"` backwards.
130181-}
131−type FieldState = { string:String, selectionStart:Int, selectionEnd:Int }
132−
133−{-| Create a group of text input fields.
134−
135− * The first argument is the default value of the `events` signal.
136− * The `events` signal represents all of the activity in this group
137− of text fields.
138− * The `field` function creates a
139− field with the given ghost text and initial field state.
140− When the field is modified, the `(FieldState -> a)` function
141− takes the new state and turns
142− it into a value that can be sent to `events`. For example, this
143− lets you add an ID to distinguish between input fields.
182+type FieldContent = { string:String, selection:Selection }
183+
184+{-| The selection within a text field. `start` is never greater than `end`:
185+
186+ Selection 0 0 Forward -- cursor precedes all characters
187+
188+ Selection 5 9 Backward -- highlighting characters starting after
189+ -- the 5th and ending after the 9th
144190-}
145−fields : a -> { events : Signal a,
146− field : (FieldState -> a) -> String -> FieldState -> Element }
147−fields = Native.Graphics.Input.fields
191+type Selection = { start:Int, end:Int, direction:Direction }
192+
193+{-| The direction of selection.-}
194+data Direction = Forward | Backward
148195
149−{-| The empty field state:
196+{-| A field with no content:
150197
151− { string="", selectionStart=0, selectionEnd=0 }
198+ FieldContent "" (Selection 0 0 Forward)
152199-}
153−emptyFieldState : FieldState
154−emptyFieldState = { string="", selectionStart=0, selectionEnd=0 }
200+noContent : FieldContent
201+noContent = FieldContent "" (Selection 0 0 Forward)
155202
156−{-| Create a field with the given default text. The output is an element
157−that updates to match the user input and a signal of strings representing
158−the content of the field.
203+{-| Create a text field. The following example creates a time-varying element
204+called `nameField`. As the user types their name, the field will be updated
205+to match what they have entered.
206+
207+ name : Input FieldContent
208+ name = input noContent
209+
210+ nameField : Signal Element
211+ nameField = field name.handle (\content -> content) "Name" <~ nameContent
159212-}
160−field : String -> (Signal Element, Signal String)
161−field placeHolder =
162− let tfs = fields emptyFieldState
163− changes = dropRepeats tfs.events
164− in (lift (tfs.field id placeHolder) changes,
165− dropRepeats (lift .string changes))
213+field : Handle a -> (FieldContent -> a) -> String -> FieldContent -> Element
214+field = Native.Graphics.Input.field
166215
167216{-| Same as `field` but the UI element blocks out each characters. -}
168−password : String -> (Signal Element, Signal String)
169−password placeHolder =
170− let tfs = Native.Graphics.Input.passwords emptyFieldState
171− changes = dropRepeats tfs.events
172− in (lift (tfs.field id placeHolder) changes,
173− dropRepeats (lift .string changes))
217+password : Handle a -> (FieldContent -> a) -> String -> FieldContent -> Element
218+password = Native.Graphics.Input.password
174219
175220{-| Same as `field` but it adds an annotation that this field is for email
176221addresses. This is helpful for auto-complete and for mobile users who may
177222get a custom keyboard with an `@` and `.com` button.
178223-}
179−email : String -> (Signal Element, Signal String)
180−email placeHolder =
181− let tfs = Native.Graphics.Input.emails emptyFieldState
182− changes = dropRepeats tfs.events
183− in (lift (tfs.field id placeHolder) changes,
184− dropRepeats (lift .string changes))
185−
186−{-| Create a drop-down menu. When the user selects a string,
187−the current state of the drop-down is set to the associated
188−value. This lets you avoid manually mapping the string onto
189−functions and values.
190−-}
191−dropDown : [(String,a)] -> (Signal Element, Signal a)
192−dropDown = Native.Graphics.Input.dropDown
224+email : Handle a -> (FieldContent -> a) -> String -> FieldContent -> Element
225+email = Native.Graphics.Input.email
193226
194−{-| Create a drop-down menu for selecting strings. The resulting
195−signal of strings represents the string that is currently selected.
196−-}
197−stringDropDown : [String] -> (Signal Element, Signal String)
198−stringDropDown strs =
199− dropDown (List.map (\s -> (s,s)) strs)
227+-- area : Handle a -> (FieldContent -> a) -> Handle b -> ((Int,Int) -> b) -> (Int,Int) -> String -> FieldContent -> Element
228+-- area = Native.Graphics.Input.area
libraries/List.elm+1−1
@@ -10,7 +10,7 @@ list must have the same type.
1010@docs head, tail, last, filter, take, drop
1111
1212# Putting Lists Together
13−@docs concat, concatMap, join, intersperse, zip, zipWith
13+@docs concat, concatMap, join, intersperse, zip, zipWith, repeat
1414
1515# Taking Lists Apart
1616@docs partition, unzip
libraries/Native/Debug.jsnew file+22
@@ -0,0 +1,22 @@
1+Elm.Native.Debug = {};
2+Elm.Native.Debug.make = function(elm) {
3+ elm.Native = elm.Native || {};
4+ elm.Native.Debug = elm.Native.Debug || {};
5+ if (elm.Native.Debug.values) return elm.Native.Debug.values;
6+
7+ var show = Elm.Native.Show.make(elm).show;
8+
9+ function log(tag,value) {
10+ if (process && process.stdout) {
11+ process.stdout.write(tag + ': ' + show(value));
12+ } else {
13+ console.log(tag + ': ' + show(value));
14+ }
15+ return value;
16+ }
17+
18+ return elm.Native.Debug.values = {
19+ log: F2(log)
20+ };
21+
22+};
libraries/Native/Graphics/Input.js+324−299
@@ -1,303 +1,328 @@
11Elm.Native.Graphics.Input = {};
22Elm.Native.Graphics.Input.make = function(elm) {
3−
4− elm.Native = elm.Native || {};
5− elm.Native.Graphics = elm.Native.Graphics || {};
6− elm.Native.Graphics.Input = elm.Native.Graphics.Input || {};
7− if (elm.Native.Graphics.Input.values) return elm.Native.Graphics.Input.values;
8−
9− var Render = ElmRuntime.use(ElmRuntime.Render.Element);
10− var newNode = ElmRuntime.use(ElmRuntime.Render.Utils).newElement;
11−
12− var Signal = Elm.Signal.make(elm);
13− var newElement = Elm.Graphics.Element.make(elm).newElement;
14− var JS = Elm.Native.JavaScript.make(elm);
15− var Utils = Elm.Native.Utils.make(elm);
16− var Tuple2 = Utils.Tuple2;
17−
18− function dropDown(values) {
19− var entries = JS.fromList(values);
20− var events = Signal.constant(entries[0]._1);
21−
22− var drop = newNode('select');
23− drop.style.border = '0 solid';
24− for (var i = 0; i < entries.length; ++i) {
25− var option = newNode('option');
26− var name = JS.fromString(entries[i]._0);
27− option.value = name;
28− option.innerHTML = name;
29− drop.appendChild(option);
30− }
31− drop.addEventListener('change', function() {
32− elm.notify(events.id, entries[drop.selectedIndex]._1);
33− });
34−
35− var t = drop.cloneNode(true);
36− t.style.visibility = "hidden";
37−
38− elm.node.appendChild(t);
39− var style = window.getComputedStyle(t, null);
40− var w = Math.ceil(style.getPropertyValue("width").slice(0,-2) - 0);
41− var h = Math.ceil(style.getPropertyValue("height").slice(0,-2) - 0);
42− elm.node.removeChild(t);
43−
44− var element = A3(newElement, w, h, {
45− ctor: 'Custom',
46− type: 'DropDown',
47− render: function render(model) { return drop; },
48− update: function update(node, oldModel, newModel) {},
49− model: {}
50− });
51−
52− return Tuple2(Signal.constant(element), events);
53− }
54−
55− function buttons(defaultValue) {
56− var events = Signal.constant(defaultValue);
57−
58− function render(model) {
59− var b = newNode('button');
60− b.style.display = 'block';
61− b.elmEvent = model.event;
62− function click() { elm.notify(events.id, b.elmEvent); }
63− b.addEventListener('click', click);
64− b.innerHTML = model.text;
65− return b;
66− }
67−
68− function update(node, oldModel, newModel) {
69− node.elmEvent = newModel.event;
70− var txt = newModel.text;
71− if (oldModel.text !== txt) node.innerHTML = txt;
72− }
73−
74− function button(evnt, txt) {
75− return A3(newElement, 100, 40, {
76− ctor: 'Custom',
77− type: 'Button',
78− render: render,
79− update: update,
80− model: { event:evnt, text:JS.fromString(txt) }
81− });
82− }
83−
84− return { _:{}, button:F2(button), events:events };
85− }
86−
87− function customButtons(defaultValue) {
88− var events = Signal.constant(defaultValue);
89−
90− function render(model) {
91− var btn = newNode('div');
92− btn.elmEvent = model.event;
93−
94− btn.elmUp = Render.render(model.up);
95− btn.elmHover = Render.render(model.hover);
96− btn.elmDown = Render.render(model.down);
97−
98− function replace(node) {
99− if (node !== btn.firstChild) btn.replaceChild(node, btn.firstChild);
100− }
101− var overCount = 0;
102− function over(e) {
103− if (overCount++ > 0) return;
104− replace(btn.elmHover);
105− }
106− function out(e) {
107− if (btn.contains(e.toElement || e.relatedTarget)) return;
108− overCount = 0;
109− replace(btn.elmUp);
110− }
111− function up() {
112− replace(btn.elmHover);
113− elm.notify(events.id, btn.elmEvent);
114− }
115− function down() { replace(btn.elmDown); }
116− btn.addEventListener('mouseover', over);
117− btn.addEventListener('mouseout' , out);
118− btn.addEventListener('mousedown', down);
119− btn.addEventListener('mouseup' , up);
120−
121− btn.appendChild(btn.elmUp);
122−
123− var clicker = newNode('div');
124− clicker.style.width = btn.elmUp.style.width;
125− clicker.style.height = btn.elmUp.style.height;
126− clicker.style.position = 'absolute';
127− clicker.style.top = 0;
128− btn.appendChild(clicker);
129−
130− return btn;
131− }
132−
133− function update(node, oldModel, newModel) {
134− node.elmEvent = newModel.event;
135− Render.update(node.elmUp, oldModel.up, newModel.up)
136− Render.update(node.elmHover, oldModel.hover, newModel.hover)
137− Render.update(node.elmDown, oldModel.down, newModel.down)
138− }
139−
140− function button(evnt, up, hover, down) {
141− return A3(newElement,
142− Math.max(up.props.width, hover.props.width, down.props.width),
143− Math.max(up.props.height, hover.props.height, down.props.height),
144− { ctor: 'Custom',
145− type: 'CustomButton',
146− render: render,
147− update: update,
148− model: { event:evnt, up:up, hover:hover, down:down }
149− });
150− }
151−
152− return { _:{}, customButton:F4(button), events:events };
153− }
154−
155−
156− function hoverables(defaultValue) {
157− var events = Signal.constant(defaultValue);
158− function hoverable(handler, elem) {
159− function onHover(bool) {
160− elm.notify(events.id, handler(bool));
161− }
162− var props = Utils.replace([['hover',onHover]], elem.props);
163− return { props:props, element:elem.element };
164− }
165− return { _:{}, hoverable:F2(hoverable), events:events };
166− }
167−
168−
169− function checkboxes(defaultValue) {
170− var events = Signal.constant(defaultValue);
171−
172− function render(model) {
173− var b = newNode('input');
174− b.type = 'checkbox';
175− b.checked = model.checked;
176− b.style.display = 'block';
177− b.elmHandler = model.handler;
178− function change() { elm.notify(events.id, b.elmHandler(b.checked)); }
179− b.addEventListener('change', change);
180− return b;
181− }
182−
183− function update(node, oldModel, newModel) {
184− node.elmHandler = newModel.handler;
185− node.checked = newModel.checked;
186− return true;
187− }
188−
189− function box(handler, checked) {
190− return A3(newElement, 13, 13, {
191− ctor: 'Custom',
192− type: 'CheckBox',
193− render: render,
194− update: update,
195− model: { checked:checked, handler:handler }
196− });
197− }
198−
199− return { _:{}, checkbox:F2(box), events:events };
200− }
201−
202− function setRange(node, start, end, dir) {
203− if (node.parentNode) {
204− node.setSelectionRange(start, end, dir);
205− } else {
206− setTimeout(function(){node.setSelectionRange(start, end, dir);}, 0);
207− }
208− }
209−
210− function mkTextPool(type) { return function fields(defaultValue) {
211− var events = Signal.constant(defaultValue);
212−
213− var state = null;
214−
215− function render(model) {
216− var field = newNode('input');
217− field.elmHandler = model.handler;
218−
219− field.id = 'test';
220− field.type = type;
221− field.placeholder = JS.fromString(model.placeHolder);
222− field.value = JS.fromString(model.state.string);
223− setRange(field, model.state.selectionStart, model.state.selectionEnd, 'forward');
224− field.style.border = 'none';
225− state = model.state;
226−
227− function update() {
228− var start = field.selectionStart,
229− end = field.selectionEnd;
230− if (field.selectionDirection === 'backward') {
231− start = end;
232− end = field.selectionStart;
233− }
234− state = { _:{},
235− string:JS.toString(field.value),
236− selectionStart:start,
237− selectionEnd:end };
238− elm.notify(events.id, field.elmHandler(state));
239− }
240− function mousedown() {
241− update();
242− elm.node.addEventListener('mouseup', mouseup);
243− }
244− function mouseup() {
245− update();
246− elm.node.removeEventListener('mouseup', mouseup)
247− }
248− field.addEventListener('keyup', update);
249− field.addEventListener('mousedown', mousedown);
250−
251− return field;
252− }
253−
254− function update(node, oldModel, newModel) {
255− node.elmHandler = newModel.handler;
256− if (state === newModel.state) return;
257− var newStr = JS.fromString(newModel.state.string);
258− if (node.value !== newStr) node.value = newStr;
259−
260− var start = newModel.state.selectionStart;
261− var end = newModel.state.selectionEnd;
262− var direction = 'forward';
263− if (end < start) {
264− start = end;
265− end = newModel.state.selectionStart;
266− direction = 'backward';
267− }
268−
269− if (node.selectionStart !== start
270− || node.selectionEnd !== end
271− || node.selectionDirection !== direction) {
272− setRange(node, start, end, direction);
273− }
274− }
275−
276− function field(handler, placeHolder, state) {
277− return A3(newElement, 200, 30,
278− { ctor: 'Custom',
279− type: type + 'Input',
280− render: render,
281− update: update,
282− model: { handler:handler,
283− placeHolder:placeHolder,
284− state:state }
285− });
286− }
287−
288− return { _:{}, field:F3(field), events:events };
289− }
290− }
291−
292− return elm.Native.Graphics.Input.values = {
293− buttons:buttons,
294− customButtons:customButtons,
295− hoverables:hoverables,
296− checkboxes:checkboxes,
297− fields:mkTextPool('text'),
298− emails:mkTextPool('email'),
299− passwords:mkTextPool('password'),
300− dropDown:dropDown
301− };
3+ elm.Native = elm.Native || {};
4+ elm.Native.Graphics = elm.Native.Graphics || {};
5+ elm.Native.Graphics.Input = elm.Native.Graphics.Input || {};
6+ if (elm.Native.Graphics.Input.values) return elm.Native.Graphics.Input.values;
7+
8+ var Render = ElmRuntime.use(ElmRuntime.Render.Element);
9+ var newNode = ElmRuntime.use(ElmRuntime.Render.Utils).newElement;
10+
11+ var Signal = Elm.Signal.make(elm);
12+ var newElement = Elm.Graphics.Element.make(elm).newElement;
13+ var JS = Elm.Native.JavaScript.make(elm);
14+ var Utils = Elm.Native.Utils.make(elm);
15+ var Tuple2 = Utils.Tuple2;
16+
17+ function input(initialValue) {
18+ var signal = Signal.constant(initialValue);
19+ return { _:{}, signal:signal, handle:signal };
20+ }
21+
22+ function renderDropDown(signal, values) {
23+ return function(_) {
24+ var entries = JS.fromList(values);
25+
26+ var drop = newNode('select');
27+ drop.style.border = '0 solid';
28+ drop.style.pointerEvents = 'auto';
29+ for (var i = 0; i < entries.length; ++i) {
30+ var option = newNode('option');
31+ var name = JS.fromString(entries[i]._0);
32+ option.value = name;
33+ option.innerHTML = name;
34+ drop.appendChild(option);
35+ }
36+ drop.addEventListener('change', function() {
37+ elm.notify(signal.id, entries[drop.selectedIndex]._1);
38+ });
39+
40+ var t = drop.cloneNode(true);
41+ t.style.visibility = "hidden";
42+
43+ elm.node.appendChild(t);
44+ var style = window.getComputedStyle(t, null);
45+ var w = Math.ceil(style.getPropertyValue("width").slice(0,-2) - 0);
46+ var h = Math.ceil(style.getPropertyValue("height").slice(0,-2) - 0);
47+ elm.node.removeChild(t);
48+ return drop;
49+ };
50+ }
51+
52+ function updateDropDown(node, oldModel, newModel) {
53+ }
54+
55+ function dropDown(signal, values) {
56+ return A3(newElement, 100, 24, {
57+ ctor: 'Custom',
58+ type: 'DropDown',
59+ render: renderDropDown(signal,values),
60+ update: updateDropDown,
61+ model: {}
62+ });
63+ }
64+
65+ function renderButton(model) {
66+ var node = newNode('button');
67+ node.style.display = 'block';
68+ node.style.pointerEvents = 'auto';
69+ node.elm_signal = model.signal;
70+ node.elm_value = model.value;
71+ function click() {
72+ elm.notify(node.elm_signal.id, node.elm_value);
73+ }
74+ node.addEventListener('click', click);
75+ node.innerHTML = model.text;
76+ return node;
77+ }
78+
79+ function updateButton(node, oldModel, newModel) {
80+ node.elm_signal = newModel.signal;
81+ node.elm_value = nemModel.value;
82+ var txt = newModel.text;
83+ if (oldModel.text !== txt) node.innerHTML = txt;
84+ }
85+
86+ function button(signal, value, text) {
87+ return A3(newElement, 100, 40, {
88+ ctor: 'Custom',
89+ type: 'Button',
90+ render: renderButton,
91+ update: updateButton,
92+ model: { signal:signal, value:value, text:JS.fromString(text) }
93+ });
94+ }
95+
96+ function renderCustomButton(model) {
97+ var btn = newNode('div');
98+ btn.style.pointerEvents = 'auto';
99+ btn.elm_signal = model.signal;
100+ btn.elm_value = model.value;
101+
102+ btn.elm_up = Render.render(model.up);
103+ btn.elm_hover = Render.render(model.hover);
104+ btn.elm_down = Render.render(model.down);
105+
106+ function replace(node) {
107+ if (node !== btn.firstChild) {
108+ btn.replaceChild(node, btn.firstChild);
109+ }
110+ }
111+ var overCount = 0;
112+ function over(e) {
113+ if (overCount++ > 0) return;
114+ replace(btn.elm_hover);
115+ }
116+ function out(e) {
117+ if (btn.contains(e.toElement || e.relatedTarget)) return;
118+ overCount = 0;
119+ replace(btn.elm_up);
120+ }
121+ function up() {
122+ replace(btn.elm_hover);
123+ elm.notify(btn.elm_signal.id, btn.elm_value);
124+ }
125+ function down() {
126+ replace(btn.elm_down);
127+ }
128+ btn.addEventListener('mouseover', over);
129+ btn.addEventListener('mouseout' , out);
130+ btn.addEventListener('mousedown', down);
131+ btn.addEventListener('mouseup' , up);
132+
133+ btn.appendChild(btn.elm_up);
134+
135+ var clicker = newNode('div');
136+ clicker.style.width = btn.elm_up.style.width;
137+ clicker.style.height = btn.elm_up.style.height;
138+ clicker.style.position = 'absolute';
139+ clicker.style.top = 0;
140+ btn.appendChild(clicker);
141+
142+ return btn;
143+ }
144+
145+ function updateCustomButton(node, oldModel, newModel) {
146+ var signal = newModel.signal;
147+ node.elm_up.elm_signal = signal;
148+ node.elm_hover.elm_signal = signal;
149+ node.elm_down.elm_signal = signal;
150+
151+ var value = newModel.value;
152+ node.elm_up.elm_value = value;
153+ node.elm_hover.elm_value = value;
154+ node.elm_down.elm_value = value;
155+
156+ Render.update(node.elm_up, oldModel.up, newModel.up)
157+ Render.update(node.elm_hover, oldModel.hover, newModel.hover)
158+ Render.update(node.elm_down, oldModel.down, newModel.down)
159+ }
160+
161+ function max3(a,b,c) {
162+ var ab = a > b ? a : b;
163+ return ab > c ? ab : c;
164+ }
165+
166+ function customButton(signal, value, up, hover, down) {
167+ return A3(newElement,
168+ max3(up.props.width, hover.props.width, down.props.width),
169+ max3(up.props.height, hover.props.height, down.props.height),
170+ { ctor: 'Custom',
171+ type: 'CustomButton',
172+ render: renderCustomButton,
173+ update: updateCustomButton,
174+ model: { signal:signal, value:value, up:up, hover:hover, down:down }
175+ });
176+ }
177+
178+ function renderCheckbox(model) {
179+ var node = newNode('input');
180+ node.type = 'checkbox';
181+ node.checked = model.checked;
182+ node.style.display = 'block';
183+ node.style.pointerEvents = 'auto';
184+ node.elm_signal = model.signal;
185+ node.elm_handler = model.handler;
186+ function change() {
187+ elm.notify(node.elm_signal.id, node.elm_handler(node.checked));
188+ }
189+ node.addEventListener('change', change);
190+ return node;
191+ }
192+
193+ function updateCheckbox(node, oldModel, newModel) {
194+ node.elm_signal = newModel.signal;
195+ node.elm_handler = newModel.handler;
196+ node.checked = newModel.checked;
197+ return true;
198+ }
199+
200+ function checkbox(signal, handler, checked) {
201+ return A3(newElement, 13, 13, {
202+ ctor: 'Custom',
203+ type: 'CheckBox',
204+ render: renderCheckbox,
205+ update: updateCheckbox,
206+ model: { signal:signal, handler:handler, checked:checked }
207+ });
208+ }
209+
210+ function setRange(node, start, end, dir) {
211+ if (node.parentNode) {
212+ node.setSelectionRange(start, end, dir);
213+ } else {
214+ setTimeout(function(){node.setSelectionRange(start, end, dir);}, 0);
215+ }
216+ }
217+
218+ function renderField(model) {
219+ var field = newNode('input');
220+ field.style.pointerEvents = 'auto';
221+ field.elm_signal = model.signal;
222+ field.elm_handler = model.handler;
223+
224+ field.type = model.type;
225+ field.placeholder = JS.fromString(model.placeHolder);
226+ field.value = JS.fromString(model.content.string);
227+ var selection = model.content.selection;
228+ var direction = selection.direction.ctor === 'Forward' ? 'forward' : 'backward';
229+ setRange(field, selection.start, selection.end, direction);
230+ field.style.border = 'none';
231+
232+ function update() {
233+ var direction = field.selectionDirection === 'backward' ? 'Backward' : 'Forward';
234+ elm.notify(field.elm_signal.id, field.elm_handler({
235+ _:{},
236+ string:JS.toString(field.value),
237+ selection: {
238+ start:field.selectionStart,
239+ end:field.selectionEnd,
240+ direction: { ctor:direction }
241+ },
242+ }));
243+ }
244+ function mousedown() {
245+ update();
246+ elm.node.addEventListener('mouseup', mouseup);
247+ }
248+ function mouseup() {
249+ update();
250+ elm.node.removeEventListener('mouseup', mouseup)
251+ }
252+ field.addEventListener('keyup', update);
253+ field.addEventListener('mousedown', mousedown);
254+
255+ return field;
256+ }
257+
258+ function updateField(node, oldModel, newModel) {
259+ node.elmHandler = newModel.handler;
260+ var newStr = JS.fromString(newModel.content.string);
261+ if (node.value !== newStr) node.value = newStr;
262+
263+ var selection = newModel.content.selection;
264+ var start = selection.start;
265+ var end = selection.end;
266+ var direction = selection.direction.ctor === 'Forward' ? 'forward' : 'backward';
267+ if (end < start) {
268+ start = end;
269+ end = selection.start;
270+ }
271+
272+ if (node.selectionStart !== start
273+ || node.selectionEnd !== end
274+ || node.selectionDirection !== direction) {
275+ setRange(node, start, end, direction);
276+ }
277+ }
278+
279+ function mkField(type) {
280+ function field(signal, handler, placeHolder, content) {
281+ return A3(newElement, 200, 30, {
282+ ctor: 'Custom',
283+ type: type + 'Input',
284+ render: renderField,
285+ update: updateField,
286+ model: {
287+ signal:signal,
288+ handler:handler,
289+ placeHolder:placeHolder,
290+ content:content,
291+ type:type
292+ }
293+ });
294+ }
295+ return F4(field);
296+ }
297+
298+
299+ function hoverable(signal, handler, elem) {
300+ function onHover(bool) {
301+ elm.notify(signal.id, handler(bool));
302+ }
303+ var props = Utils.replace([['hover',onHover]], elem.props);
304+ return { props:props, element:elem.element };
305+ }
306+
307+ function clickable(signal, value, elem) {
308+ function onClick(bool) {
309+ elm.notify(signal.id, value);
310+ }
311+ var props = Utils.replace([['click',onClick]], elem.props);
312+ return { props:props, element:elem.element };
313+ }
314+
315+ return elm.Native.Graphics.Input.values = {
316+ input:input,
317+ button:F3(button),
318+ customButton:F5(customButton),
319+ checkbox:F3(checkbox),
320+ dropDown:F2(dropDown),
321+ field:mkField('text'),
322+ email:mkField('email'),
323+ password:mkField('password'),
324+ hoverable:F3(hoverable),
325+ clickable:F3(clickable)
326+ };
302327
303328};
libraries/Native/Trampoline.js+4−2
@@ -5,7 +5,7 @@ Elm.Native.Trampoline.make = function(elm) {
55 if (elm.Native.Trampoline.values) return elm.Native.Trampoline.values;
66
77 // trampoline : Trampoline a -> a
8− trampoline = function(t) {
8+ function trampoline(t) {
99 var tramp = t;
1010 while(true) {
1111 switch(tramp.ctor) {
@@ -18,5 +18,7 @@ Elm.Native.Trampoline.make = function(elm) {
1818 }
1919 }
2020
21− return elm.Native.Trampoline.values = { trampoline: trampoline };
21+ return elm.Native.Trampoline.values = {
22+ trampoline:trampoline
23+ };
2224};
libraries/String.elm+1−1
@@ -233,7 +233,7 @@ any = Native.String.any
233233
234234 all isDigit "90210" == True
235235 all isDigit "R2-D2" == False
236− any isDigit "heart" == False
236+ all isDigit "heart" == False
237237-}
238238all : (Char -> Bool) -> String -> Bool
239239all = Native.String.all
runtime/Render/Element.js+36−5
@@ -28,15 +28,30 @@ function setProps(props, e) {
2828 e.appendChild(a);
2929 }
3030 if (props.hover.ctor !== '_Tuple0') {
31− var overCount = 0;
31+ e.style.pointerEvents = 'auto';
32+ e.elm_hover_handler = props.hover;
33+ e.elm_hover_count = 0;
3234 e.addEventListener('mouseover', function() {
33− if (overCount++ > 0) return;
34− props.hover(true);
35+ if (e.elm_hover_count++ > 0) return;
36+ var handler = e.elm_hover_handler;
37+ if (handler !== null) {
38+ handler(true);
39+ }
3540 });
3641 e.addEventListener('mouseout', function(evt) {
3742 if (e.contains(evt.toElement || evt.relatedTarget)) return;
38− overCount = 0;
39− props.hover(false);
43+ e.elm_hover_count = 0;
44+ var handler = e.elm_hover_handler;
45+ if (handler !== null) {
46+ handler(false);
47+ }
48+ });
49+ }
50+ if (props.click.ctor !== '_Tuple0') {
51+ e.style.pointerEvents = 'auto';
52+ e.elm_click_handler = props.click;
53+ e.addEventListener('click', function() {
54+ e.elm_click_handler(Tuple0);
4055 });
4156 }
4257 return e;
@@ -99,6 +114,8 @@ function goDown(e) { return e }
99114function goRight(e) { e.style.styleFloat = e.style.cssFloat = "left"; return e; }
100115function flowWith(f, array) {
101116 var container = newElement('div');
117+ if (f == goIn) container.style.pointerEvents = 'none';
118+
102119 for (var i = array.length; i--; ) {
103120 container.appendChild(f(render(array[i])));
104121 }
@@ -319,6 +336,20 @@ function updateProps(node, curr, next) {
319336 node.lastNode.href = props.href;
320337 }
321338 }
339+
340+ // update hover handlers
341+ if (props.hover.ctor !== '_Tuple0') {
342+ e.elm_hover_handler = props.hover;
343+ } else if (e.elm_hover_handler) {
344+ e.elm_hover_handler = null;
345+ }
346+
347+ // update click handlers
348+ if (props.click.ctor !== '_Tuple0') {
349+ e.elm_click_handler = props.click;
350+ } else if (e.elm_click_handler) {
351+ e.elm_click_handler = null;
352+ }
322353}
323354
324355return { render:render, update:update };