on master help sign in
Change the type of `FieldState` slightly. Now uses `selectionStart` and `selectionEnd` instead of the more ambiguous `start` and `end`.

Also add tons of documentation to `Graphics.Input`. Also add the `emptyFieldState` value.

commit94bb5e5a3122a285d2a8703d0fe70180c62a11f4 authorevancz <info@elm-lang.org> dateMon 20 May 2013, 15:35 (13 years ago) parent9b1fa96
3 files changed+96−29
compiler/Types/Alias.hs+1−1 libraries/Graphics/Input.elm+69−16 libraries/Native/Graphics/Input.js+26−12
compiler/Types/Alias.hs+1−1
@@ -12,7 +12,7 @@ import Types.Types
1212builtins :: [(String,([X],Type))]
1313builtins =
1414 let touch = ("t0", time) : map (flip (,) int) ["x","y","x0","y0","id"]
15− state = [("string", string), ("start", int), ("end", int)]
15+ state = [("string", string), ("selectionStart", int), ("selectionEnd", int)]
1616 line = [("color", tipe "Color"), ("width", float),
1717 ("cap", tipe "LineCap"), ("join", tipe "LineJoin"),
1818 ("miterLimit", float), ("dashing", listOf int),
libraries/Graphics/Input.elm+69−16
@@ -6,7 +6,16 @@ import Native.Graphics.Input as N
66
77id x = x
88
9−buttons : a -> { button : a -> String -> Element, events : Signal a }
9+-- Create a group of buttons.
10+--
11+-- * The first argument is the default value of the `events` signal.
12+-- * The `events` signal represents all of the activity in this group
13+-- of buttons.
14+-- * The `button` function creates a button
15+-- with the given name, like &ldquo;Submit&rdquo; or &ldquo;Cancel&rdquo;.
16+-- The `a` value is sent to `events` whenever the button is pressed.
17+buttons : a -> { events : Signal a,
18+ button : a -> String -> Element }
1019
1120-- Create a button with a given label. The result is an `Element` and
1221-- a signal of units. This signal triggers whenever the button is pressed.
@@ -15,8 +24,17 @@ button txt =
1524 let pool = N.buttons ()
1625 in (pool.button () txt, pool.events)
1726
18−customButtons : a -> { button : a -> Element -> Element -> Element -> Element,
19− events : Signal a }
27+-- Create a group of custom buttons.
28+--
29+-- * The first argument is the default value of the `events` signal.
30+-- * The `events` signal represents all of the activity in this group
31+-- of custom buttons.
32+-- * The `customButton` function creates a button with three different visual
33+-- states, one for up, hovering, and down. The resulting button has dimensions
34+-- large enough to fit all three possible `Elements`.
35+-- The `a` value is sent to `events` whenever the button is pressed.
36+customButtons : a -> { events : Signal a,
37+ customButton : a -> Element -> Element -> Element -> Element }
2038
2139-- Create a button with custom states for up, hovering, and down
2240-- (given in that order). The result is an `Element` and
@@ -26,41 +44,76 @@ customButton up hover down =
2644 let pool = N.customButtons ()
2745 in (pool.button () up hover down, pool.events)
2846
29−checkboxes : a -> { box : (Bool -> a) -> Bool -> Element, events : Signal a }
47+-- Create a group of checkboxes.
48+--
49+-- * The first argument is the default value of the `events` signal.
50+-- * The `events` signal represents all of the activity in this group
51+-- of checkboxes.
52+-- * The `checkbox` function creates a
53+-- checkbox with a given state. The `(Bool -> a)` function is used
54+-- when the checkbox is modified. It takes the new state and turns
55+-- it into a value that can be sent to `events`. For example, this
56+-- lets you add an ID to distinguish between checkboxes.
57+checkboxes : a -> { events : Signal a,
58+ checkbox : (Bool -> a) -> Bool -> Element }
3059
31−-- Create a checkbox with a given start state. Notice that the result is a *signal*
32−-- of elements. That is because a checkbox has state that updates based on user input.
60+-- Create a checkbox with a given start state. Unlike `button`, this result
61+-- is a *signal* of elements. That is because a checkbox has state that
62+-- updates based on user input.
3363-- The boolean signal represents the current state of the checkbox.
3464checkbox : Bool -> (Signal Element, Signal Bool)
3565checkbox b =
3666 let cbs = N.checkboxes b
3767 in (lift (cbs.box id) cbs.events, cbs.events)
3868
39−type FieldState = { string:String, start:Int, end:Int }
69+-- Represents the current state of a text field. The `string` represents the
70+-- characters filling the text field. The `selectionStart` and `selectionEnd`
71+-- values represent what the user has selected with their mouse or keyboard.
72+-- For example:
73+--
74+-- { string="She sells sea shells", selectionStart=3, selectionEnd=0 }
75+--
76+-- This means the user highlighted the substring `"She"` backwards.
77+type FieldState = { string:String, selectionStart:Int, selectionEnd:Int }
4078
41−fields : a -> { field : (FieldState -> a) -> String -> FieldState -> Element,
42− events : Signal a }
79+-- Create a group of text input fields.
80+--
81+-- * The first argument is the default value of the `events` signal.
82+-- * The `events` signal represents all of the activity in this group
83+-- of text fields.
84+-- * The `field` function creates a
85+-- field with the given ghost text and initial field state.
86+-- When the field is modified, the `(FieldState -> a)` function
87+-- takes the new state and turns
88+-- it into a value that can be sent to `events`. For example, this
89+-- lets you add an ID to distinguish between input fields.
90+fields : a -> { events : Signal a,
91+ field : (FieldState -> a) -> String -> FieldState -> Element }
4392
44−empty = { string="", start=0, end=0 }
93+-- The empty field state:
94+--
95+-- { string="", selectionStart=0, selectionEnd=0 }
96+emptyFieldState : FieldState
97+emptyFieldState = { string="", selectionStart=0, selectionEnd=0 }
4598
4699-- Create a field with the given default text. The output is an element that
47100-- updates to match the user input and a signal of strings representing the
48101-- content of the field.
49102field : String -> (Signal Element, Signal String)
50103field placeHolder =
51− let tfs = N.fields empty
104+ let tfs = N.fields emptyFieldState
52105 in (lift (tfs.field id placeHolder) tfs.events, lift .string tfs.events)
53106
54107-- Same as `field` but the UI element blocks out each characters.
55108password : String -> (Signal Element, Signal String)
56109password placeHolder =
57− let tfs = N.passwords empty
110+ let tfs = N.passwords emptyFieldState
58111 in (lift (tfs.field id placeHolder) tfs.events, lift .string tfs.events)
59112
60−-- Same as `field` but it adds an annotation that this field is for email addresses.
61−-- This is helpful for auto-complete and for mobile users who may get a custom keyboard
62−-- with an `@` and `.com` button.
113+-- Same as `field` but it adds an annotation that this field is for email
114+-- addresses. This is helpful for auto-complete and for mobile users who may
115+-- get a custom keyboard with an `@` and `.com` button.
63116email : String -> (Signal Element, Signal String)
64117email placeHolder =
65− let tfs = N.emails empty
118+ let tfs = N.emails emptyFieldState
66119 in (lift (tfs.field id placeHolder) tfs.events, lift .string tfs.events)
libraries/Native/Graphics/Input.js+26−12
@@ -143,6 +143,8 @@ Elm.Native.Graphics.Input = function(elm) {
143143 function mkTextPool(type) { return function fields(defaultValue) {
144144 var events = Signal.constant(defaultValue);
145145
146+ var state = null;
147+
146148 function render(model) {
147149 var field = newNode('input');
148150 field.elmHandler = model.handler;
@@ -151,8 +153,9 @@ Elm.Native.Graphics.Input = function(elm) {
151153 field.type = type;
152154 field.placeholder = fromString(model.placeHolder);
153155 field.value = fromString(model.state.string);
154− field.setSelectionRange(model.state.start, model.state.end);
156+ field.setSelectionRange(model.state.selectionStart, model.state.selectionEnd);
155157 field.style.border = 'none';
158+ state = model.state;
156159
157160 function update() {
158161 var start = field.selectionStart,
@@ -161,10 +164,10 @@ Elm.Native.Graphics.Input = function(elm) {
161164 start = end;
162165 end = field.selectionStart;
163166 }
164− var state = { _:{},
165− string:toString(field.value),
166− start:start,
167− end:end };
167+ state = { _:{},
168+ string:toString(field.value),
169+ selectionStart:start,
170+ selectionEnd:end };
168171 elm.notify(events.id, field.elmHandler(state));
169172 }
170173 function mousedown() {
@@ -183,13 +186,24 @@ Elm.Native.Graphics.Input = function(elm) {
183186
184187 function update(node, oldModel, newModel) {
185188 node.elmHandler = newModel.handler;
186− node.value = fromString(newModel.state.string);
187− if (newModel.state.start <= newModel.state.end) {
188− node.setSelectionRange(newModel.state.start, newModel.state.end);
189− } else {
190− node.setSelectionRange(newModel.state.end,
191− newModel.state.start, 'backward');
192− }
189+ if (state === newModel.state) return;
190+ var newStr = fromString(newModel.state.string);
191+ if (node.value !== newStr) node.value = newStr;
192+
193+ var start = newModel.state.selectionStart;
194+ var end = newModel.state.selectionEnd;
195+ var direction = 'forward';
196+ if (end < start) {
197+ start = end;
198+ end = newModel.state.selectionStart;
199+ direction = 'backward';
200+ }
201+
202+ if (node.selectionStart !== start
203+ || node.selectionEnd !== end
204+ || node.selectionDirection !== direction) {
205+ node.setSelectionRange(start, end, direction);
206+ }
193207 }
194208
195209 function field(handler, placeHolder, state) {