on master help sign in
Revert "Get rid of Selection, simplifying the field API"
cbf7a86Evan Czaplicki
libraries/Graphics/Input/Field.elm+36−6
@@ -6,6 +6,9 @@ approach as the [`Graphics.Input`](Graphics-Input) module for describing an
6# Create Fields
6# Create Fields
7@docs field, password, email
7@docs field, password, email
8
8
9+# Field Content
10+@docs Content, Selection, Direction, noContent
11+
9# Field Style
12# Field Style
10@docs Style, Outline, noOutline, Highlight, noHighlight, Dimensions, uniformly
13@docs Style, Outline, noOutline, Highlight, noHighlight, Dimensions, uniformly
11-}
14-}
@@ -72,29 +75,56 @@ defaultStyle =
72 , style = Text.defaultStyle
75 , style = Text.defaultStyle
73 }
76 }
74
77
78+{-| Represents the current content of a text field. For example:
79+
80+ Content "She sells sea shells" (Selection 0 3 Backward)
81+
82+This means the user highlighted the substring `"She"` backwards.
83+-}
84+type Content = { string:String, selection:Selection }
85+
86+{-| The selection within a text field. `start` is never greater than `end`:
87+
88+ Selection 0 0 Forward -- cursor precedes all characters
89+
90+ Selection 5 9 Backward -- highlighting characters starting after
91+ -- the 5th and ending after the 9th
92+-}
93+type Selection = { start:Int, end:Int, direction:Direction }
94+
95+{-| The direction of selection.-}
96+data Direction = Forward | Backward
97+
98+{-| A field with no content:
99+
100+ Content "" (Selection 0 0 Forward)
101+-}
102+noContent : Content
103+noContent = Content "" (Selection 0 0 Forward)
104+
75{-| Create a text field. The following example creates a time-varying element
105{-| Create a text field. The following example creates a time-varying element
76called `nameField`. As the user types their name, the field will be updated
106called `nameField`. As the user types their name, the field will be updated
77to match what they have entered.
107to match what they have entered.
78
108
79− name : Input String
109+ name : Input Content
80− name = input ""
110+ name = input noContent
81
111
82 nameField : Signal Element
112 nameField : Signal Element
83 nameField = field name.handle id defaultStyle "Name" <~ name.signal
113 nameField = field name.handle id defaultStyle "Name" <~ name.signal
84-}
114-}
85−field : Handle a -> (String -> a) -> Style -> String -> String -> Element
115+field : Handle a -> (Content -> a) -> Style -> String -> Content -> Element
86field = Native.Graphics.Input.field
116field = Native.Graphics.Input.field
87
117
88{-| Same as `field` but the UI element blocks out each characters. -}
118{-| Same as `field` but the UI element blocks out each characters. -}
89−password : Handle a -> (String -> a) -> Style -> String -> String -> Element
119+password : Handle a -> (Content -> a) -> Style -> String -> Content -> Element
90password = Native.Graphics.Input.password
120password = Native.Graphics.Input.password
91
121
92{-| Same as `field` but it adds an annotation that this field is for email
122{-| Same as `field` but it adds an annotation that this field is for email
93addresses. This is helpful for auto-complete and for mobile users who may
123addresses. This is helpful for auto-complete and for mobile users who may
94get a custom keyboard with an `@` and `.com` button.
124get a custom keyboard with an `@` and `.com` button.
95-}
125-}
96−email : Handle a -> (String -> a) -> Style -> String -> String -> Element
126+email : Handle a -> (Content -> a) -> Style -> String -> Content -> Element
97email = Native.Graphics.Input.email
127email = Native.Graphics.Input.email
98
128
99−-- area : Handle a -> (String -> a) -> Handle b -> ((Int,Int) -> b) -> (Int,Int) -> String -> String -> Element
129+-- area : Handle a -> (Content -> a) -> Handle b -> ((Int,Int) -> b) -> (Int,Int) -> String -> Content -> Element
100-- area = Native.Graphics.Input.area
130-- area = Native.Graphics.Input.area
libraries/Native/Graphics/Input.js+60−4
@@ -209,6 +209,14 @@ Elm.Native.Graphics.Input.make = function(elm) {
209 });
209 });
210 }
210 }
211
211
212+ function setRange(node, start, end, dir) {
213+ if (node.parentNode) {
214+ node.setSelectionRange(start, end, dir);
215+ } else {
216+ setTimeout(function(){node.setSelectionRange(start, end, dir);}, 0);
217+ }
218+ }
219+
212 function updateIfNeeded(css, attribute, latestAttribute) {
220 function updateIfNeeded(css, attribute, latestAttribute) {
213 if (css[attribute] !== latestAttribute) {
221 if (css[attribute] !== latestAttribute) {
214 css[attribute] = latestAttribute;
222 css[attribute] = latestAttribute;
@@ -259,7 +267,10 @@ Elm.Native.Graphics.Input.make = function(elm) {
259
267
260 field.type = model.type;
268 field.type = model.type;
261 field.placeholder = JS.fromString(model.placeHolder);
269 field.placeholder = JS.fromString(model.placeHolder);
262− field.value = JS.fromString(model.content);
270+ field.value = JS.fromString(model.content.string);
271+ var selection = model.content.selection;
272+ var direction = selection.direction.ctor === 'Forward' ? 'forward' : 'backward';
273+ setRange(field, selection.start, selection.end, direction);
263
274
264 field.elm_signal = model.signal;
275 field.elm_signal = model.signal;
265 field.elm_handler = model.handler;
276 field.elm_handler = model.handler;
@@ -271,7 +282,15 @@ Elm.Native.Graphics.Input.make = function(elm) {
271 String.fromCharCode(event.keyCode) +
282 String.fromCharCode(event.keyCode) +
272 curr.slice(field.selectionEnd));
283 curr.slice(field.selectionEnd));
273 var pos = field.selectionEnd + 1;
284 var pos = field.selectionEnd + 1;
274− elm.notify(field.elm_signal.id, field.elm_handler(JS.toString(next)));
285+ elm.notify(field.elm_signal.id, field.elm_handler({
286+ _:{},
287+ string: JS.toString(next),
288+ selection: {
289+ start: pos,
290+ end: pos,
291+ direction: { ctor:'Forward' }
292+ },
293+ }));
275 event.preventDefault();
294 event.preventDefault();
276 }
295 }
277
296
@@ -281,12 +300,46 @@ Elm.Native.Graphics.Input.make = function(elm) {
281 if (curr === next) {
300 if (curr === next) {
282 return;
301 return;
283 }
302 }
303+
304+ var direction = field.selectionDirection === 'forward' ? 'Forward' : 'Backward';
305+ var start = field.selectionStart;
306+ var end = field.selectionEnd;
284 field.value = field.elm_old_value;
307 field.value = field.elm_old_value;
308+
285− elm.notify(field.elm_signal.id, field.elm_handler(JS.toString(next)));
309+ elm.notify(field.elm_signal.id, field.elm_handler({
310+ _:{},
311+ string: JS.toString(next),
312+ selection: {
313+ start: start,
314+ end: end,
315+ direction: { ctor: direction }
316+ },
317+ }));
286 }
318 }
287
319
320+ function mouseUpdate(event) {
321+ var direction = field.selectionDirection === 'forward' ? 'Forward' : 'Backward';
322+ elm.notify(field.elm_signal.id, field.elm_handler({
323+ _:{},
324+ string: field.value,
325+ selection: {
326+ start: field.selectionStart,
327+ end: field.selectionEnd,
328+ direction: { ctor: direction }
329+ },
330+ }));
331+ }
332+ function mousedown(event) {
333+ mouseUpdate(event);
334+ elm.node.addEventListener('mouseup', mouseup);
335+ }
336+ function mouseup(event) {
337+ mouseUpdate(event);
338+ elm.node.removeEventListener('mouseup', mouseup)
339+ }
288 field.addEventListener('keypress', keyUpdate);
340 field.addEventListener('keypress', keyUpdate);
289 field.addEventListener('input', inputUpdate);
341 field.addEventListener('input', inputUpdate);
342+ field.addEventListener('mousedown', mousedown);
290
343
291 return field;
344 return field;
292 }
345 }
@@ -300,9 +353,12 @@ Elm.Native.Graphics.Input.make = function(elm) {
300
353
301 field.type = newModel.type;
354 field.type = newModel.type;
302 field.placeholder = JS.fromString(newModel.placeHolder);
355 field.placeholder = JS.fromString(newModel.placeHolder);
303− var value = JS.fromString(newModel.content);
356+ var value = JS.fromString(newModel.content.string);
304 field.value = value;
357 field.value = value;
305 field.elm_old_value = value;
358 field.elm_old_value = value;
359+ var selection = newModel.content.selection;
360+ var direction = selection.direction.ctor === 'Forward' ? 'forward' : 'backward';
361+ setRange(field, selection.start, selection.end, direction);
306 }
362 }
307
363
308 function mkField(type) {
364 function mkField(type) {