on master help sign in
Change the type of `FieldState` slightly. Now uses `selectionStart` and `selectionEnd` instead of the more ambiguous `start` and `end`.
94bb5e5evancz
compiler/Types/Alias.hs+1−1
@@ -12,7 +12,7 @@ import Types.Types
12builtins :: [(String,([X],Type))]
12builtins :: [(String,([X],Type))]
13builtins =
13builtins =
14 let touch = ("t0", time) : map (flip (,) int) ["x","y","x0","y0","id"]
14 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)]
16 line = [("color", tipe "Color"), ("width", float),
16 line = [("color", tipe "Color"), ("width", float),
17 ("cap", tipe "LineCap"), ("join", tipe "LineJoin"),
17 ("cap", tipe "LineCap"), ("join", tipe "LineJoin"),
18 ("miterLimit", float), ("dashing", listOf int),
18 ("miterLimit", float), ("dashing", listOf int),
libraries/Graphics/Input.elm+69−16
@@ -6,7 +6,16 @@ import Native.Graphics.Input as N
6
6
7id x = x
7id x = x
8
8
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 “Submit” or “Cancel”.
16+-- The `a` value is sent to `events` whenever the button is pressed.
17+buttons : a -> { events : Signal a,
9−buttons : a -> { button : a -> String -> Element, events : Signal a }
18+ button : a -> String -> Element }
10
19
11-- Create a button with a given label. The result is an `Element` and
20-- Create a button with a given label. The result is an `Element` and
12-- a signal of units. This signal triggers whenever the button is pressed.
21-- a signal of units. This signal triggers whenever the button is pressed.
@@ -15,8 +24,17 @@ button txt =
15 let pool = N.buttons ()
24 let pool = N.buttons ()
16 in (pool.button () txt, pool.events)
25 in (pool.button () txt, pool.events)
17
26
18−customButtons : a -> { button : a -> Element -> Element -> Element -> Element,
27+-- Create a group of custom buttons.
19− events : Signal a }
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 }
20
38
21-- Create a button with custom states for up, hovering, and down
39-- Create a button with custom states for up, hovering, and down
22-- (given in that order). The result is an `Element` and
40-- (given in that order). The result is an `Element` and
@@ -26,41 +44,76 @@ customButton up hover down =
26 let pool = N.customButtons ()
44 let pool = N.customButtons ()
27 in (pool.button () up hover down, pool.events)
45 in (pool.button () up hover down, pool.events)
28
46
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,
29−checkboxes : a -> { box : (Bool -> a) -> Bool -> Element, events : Signal a }
58+ checkbox : (Bool -> a) -> Bool -> Element }
30
59
31−-- Create a checkbox with a given start state. Notice that the result is a *signal*
60+-- Create a checkbox with a given start state. Unlike `button`, this result
32−-- of elements. That is because a checkbox has state that updates based on user input.
61+-- is a *signal* of elements. That is because a checkbox has state that
62+-- updates based on user input.
33-- The boolean signal represents the current state of the checkbox.
63-- The boolean signal represents the current state of the checkbox.
34checkbox : Bool -> (Signal Element, Signal Bool)
64checkbox : Bool -> (Signal Element, Signal Bool)
35checkbox b =
65checkbox b =
36 let cbs = N.checkboxes b
66 let cbs = N.checkboxes b
37 in (lift (cbs.box id) cbs.events, cbs.events)
67 in (lift (cbs.box id) cbs.events, cbs.events)
38
68
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.
39−type FieldState = { string:String, start:Int, end:Int }
77+type FieldState = { string:String, selectionStart:Int, selectionEnd:Int }
40
78
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,
41−fields : a -> { field : (FieldState -> a) -> String -> FieldState -> Element,
91+ field : (FieldState -> a) -> String -> FieldState -> Element }
42− events : Signal a }
43
92
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 }
45
98
46-- Create a field with the given default text. The output is an element that
99-- Create a field with the given default text. The output is an element that
47-- updates to match the user input and a signal of strings representing the
100-- updates to match the user input and a signal of strings representing the
48-- content of the field.
101-- content of the field.
49field : String -> (Signal Element, Signal String)
102field : String -> (Signal Element, Signal String)
50field placeHolder =
103field placeHolder =
51− let tfs = N.fields empty
104+ let tfs = N.fields emptyFieldState
52 in (lift (tfs.field id placeHolder) tfs.events, lift .string tfs.events)
105 in (lift (tfs.field id placeHolder) tfs.events, lift .string tfs.events)
53
106
54-- Same as `field` but the UI element blocks out each characters.
107-- Same as `field` but the UI element blocks out each characters.
55password : String -> (Signal Element, Signal String)
108password : String -> (Signal Element, Signal String)
56password placeHolder =
109password placeHolder =
57− let tfs = N.passwords empty
110+ let tfs = N.passwords emptyFieldState
58 in (lift (tfs.field id placeHolder) tfs.events, lift .string tfs.events)
111 in (lift (tfs.field id placeHolder) tfs.events, lift .string tfs.events)
59
112
60−-- Same as `field` but it adds an annotation that this field is for email addresses.
113+-- Same as `field` but it adds an annotation that this field is for email
61−-- This is helpful for auto-complete and for mobile users who may get a custom keyboard
114+-- addresses. This is helpful for auto-complete and for mobile users who may
62−-- with an `@` and `.com` button.
115+-- get a custom keyboard with an `@` and `.com` button.
63email : String -> (Signal Element, Signal String)
116email : String -> (Signal Element, Signal String)
64email placeHolder =
117email placeHolder =
65− let tfs = N.emails empty
118+ let tfs = N.emails emptyFieldState
66 in (lift (tfs.field id placeHolder) tfs.events, lift .string tfs.events)
119 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) {
143 function mkTextPool(type) { return function fields(defaultValue) {
143 function mkTextPool(type) { return function fields(defaultValue) {
144 var events = Signal.constant(defaultValue);
144 var events = Signal.constant(defaultValue);
145
145
146+ var state = null;
147+
146 function render(model) {
148 function render(model) {
147 var field = newNode('input');
149 var field = newNode('input');
148 field.elmHandler = model.handler;
150 field.elmHandler = model.handler;
@@ -151,8 +153,9 @@ Elm.Native.Graphics.Input = function(elm) {
151 field.type = type;
153 field.type = type;
152 field.placeholder = fromString(model.placeHolder);
154 field.placeholder = fromString(model.placeHolder);
153 field.value = fromString(model.state.string);
155 field.value = fromString(model.state.string);
154− field.setSelectionRange(model.state.start, model.state.end);
156+ field.setSelectionRange(model.state.selectionStart, model.state.selectionEnd);
155 field.style.border = 'none';
157 field.style.border = 'none';
158+ state = model.state;
156
159
157 function update() {
160 function update() {
158 var start = field.selectionStart,
161 var start = field.selectionStart,
@@ -161,10 +164,10 @@ Elm.Native.Graphics.Input = function(elm) {
161 start = end;
164 start = end;
162 end = field.selectionStart;
165 end = field.selectionStart;
163 }
166 }
164− var state = { _:{},
167+ state = { _:{},
165− string:toString(field.value),
168+ string:toString(field.value),
166− start:start,
169+ selectionStart:start,
167− end:end };
170+ selectionEnd:end };
168 elm.notify(events.id, field.elmHandler(state));
171 elm.notify(events.id, field.elmHandler(state));
169 }
172 }
170 function mousedown() {
173 function mousedown() {
@@ -183,13 +186,24 @@ Elm.Native.Graphics.Input = function(elm) {
183
186
184 function update(node, oldModel, newModel) {
187 function update(node, oldModel, newModel) {
185 node.elmHandler = newModel.handler;
188 node.elmHandler = newModel.handler;
189+ if (state === newModel.state) return;
186− node.value = fromString(newModel.state.string);
190+ var newStr = fromString(newModel.state.string);
191+ if (node.value !== newStr) node.value = newStr;
192+
187− if (newModel.state.start <= newModel.state.end) {
193+ var start = newModel.state.selectionStart;
188− node.setSelectionRange(newModel.state.start, newModel.state.end);
194+ var end = newModel.state.selectionEnd;
189− } else {
195+ var direction = 'forward';
190− node.setSelectionRange(newModel.state.end,
196+ if (end < start) {
197+ start = end;
191− newModel.state.start, 'backward');
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);
192− }
206+ }
193 }
207 }
194
208
195 function field(handler, placeHolder, state) {
209 function field(handler, placeHolder, state) {