@@ -1,303 +1,407 @@
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 toCss = Elm.Native.Color.make(elm).toCss;
12+ var Text = Elm.Native.Text.make(elm);
13+ var Signal = Elm.Signal.make(elm);
14+ var newElement = Elm.Graphics.Element.make(elm).newElement;
15+ var JS = Elm.Native.JavaScript.make(elm);
16+ var Utils = Elm.Native.Utils.make(elm);
17+ var Tuple2 = Utils.Tuple2;
18+
19+ function input(initialValue) {
20+ var signal = Signal.constant(initialValue);
21+ return { _:{}, signal:signal, handle:signal };
22+ }
23+
24+ function renderDropDown(signal, values) {
25+ return function(_) {
26+ var entries = JS.fromList(values);
27+
28+ var drop = newNode('select');
29+ drop.style.border = '0 solid';
30+ drop.style.pointerEvents = 'auto';
31+ for (var i = 0; i < entries.length; ++i) {
32+ var option = newNode('option');
33+ var name = JS.fromString(entries[i]._0);
34+ option.value = name;
35+ option.innerHTML = name;
36+ drop.appendChild(option);
37+ }
38+ drop.addEventListener('change', function() {
39+ elm.notify(signal.id, entries[drop.selectedIndex]._1);
40+ });
41+
42+ var t = drop.cloneNode(true);
43+ t.style.visibility = "hidden";
44+
45+ elm.node.appendChild(t);
46+ var style = window.getComputedStyle(t, null);
47+ var w = Math.ceil(style.getPropertyValue("width").slice(0,-2) - 0);
48+ var h = Math.ceil(style.getPropertyValue("height").slice(0,-2) - 0);
49+ elm.node.removeChild(t);
50+ return drop;
51+ };
52+ }
53+
54+ function updateDropDown(node, oldModel, newModel) {
55+ }
56+
57+ function dropDown(signal, values) {
58+ return A3(newElement, 100, 24, {
59+ ctor: 'Custom',
60+ type: 'DropDown',
61+ render: renderDropDown(signal,values),
62+ update: updateDropDown,
63+ model: {}
64+ });
65+ }
66+
67+ function renderButton(model) {
68+ var node = newNode('button');
69+ node.style.display = 'block';
70+ node.style.pointerEvents = 'auto';
71+ node.elm_signal = model.signal;
72+ node.elm_value = model.value;
73+ function click() {
74+ elm.notify(node.elm_signal.id, node.elm_value);
75+ }
76+ node.addEventListener('click', click);
77+ node.innerHTML = model.text;
78+ return node;
79+ }
80+
81+ function updateButton(node, oldModel, newModel) {
82+ node.elm_signal = newModel.signal;
83+ node.elm_value = newModel.value;
84+ var txt = newModel.text;
85+ if (oldModel.text !== txt) node.innerHTML = txt;
86+ }
87+
88+ function button(signal, value, text) {
89+ return A3(newElement, 100, 40, {
90+ ctor: 'Custom',
91+ type: 'Button',
92+ render: renderButton,
93+ update: updateButton,
94+ model: { signal:signal, value:value, text:JS.fromString(text) }
95+ });
96+ }
97+
98+ function renderCustomButton(model) {
99+ var btn = newNode('div');
100+ btn.style.pointerEvents = 'auto';
101+ btn.elm_signal = model.signal;
102+ btn.elm_value = model.value;
103+
104+ btn.elm_up = Render.render(model.up);
105+ btn.elm_hover = Render.render(model.hover);
106+ btn.elm_down = Render.render(model.down);
107+
108+ function replace(node) {
109+ if (node !== btn.firstChild) {
110+ btn.replaceChild(node, btn.firstChild);
111+ }
112+ }
113+ var overCount = 0;
114+ function over(e) {
115+ if (overCount++ > 0) return;
116+ replace(btn.elm_hover);
117+ }
118+ function out(e) {
119+ if (btn.contains(e.toElement || e.relatedTarget)) return;
120+ overCount = 0;
121+ replace(btn.elm_up);
122+ }
123+ function up() {
124+ replace(btn.elm_hover);
125+ elm.notify(btn.elm_signal.id, btn.elm_value);
126+ }
127+ function down() {
128+ replace(btn.elm_down);
129+ }
130+ btn.addEventListener('mouseover', over);
131+ btn.addEventListener('mouseout' , out);
132+ btn.addEventListener('mousedown', down);
133+ btn.addEventListener('mouseup' , up);
134+
135+ btn.appendChild(btn.elm_up);
136+
137+ var clicker = newNode('div');
138+ clicker.style.width = btn.elm_up.style.width;
139+ clicker.style.height = btn.elm_up.style.height;
140+ clicker.style.position = 'absolute';
141+ clicker.style.top = 0;
142+ btn.appendChild(clicker);
143+
144+ return btn;
145+ }
146+
147+ function updateCustomButton(node, oldModel, newModel) {
148+ var signal = newModel.signal;
149+ node.elm_up.elm_signal = signal;
150+ node.elm_hover.elm_signal = signal;
151+ node.elm_down.elm_signal = signal;
152+
153+ var value = newModel.value;
154+ node.elm_up.elm_value = value;
155+ node.elm_hover.elm_value = value;
156+ node.elm_down.elm_value = value;
157+
158+ Render.update(node.elm_up, oldModel.up, newModel.up)
159+ Render.update(node.elm_hover, oldModel.hover, newModel.hover)
160+ Render.update(node.elm_down, oldModel.down, newModel.down)
161+ }
162+
163+ function max3(a,b,c) {
164+ var ab = a > b ? a : b;
165+ return ab > c ? ab : c;
166+ }
167+
168+ function customButton(signal, value, up, hover, down) {
169+ return A3(newElement,
170+ max3(up.props.width, hover.props.width, down.props.width),
171+ max3(up.props.height, hover.props.height, down.props.height),
172+ { ctor: 'Custom',
173+ type: 'CustomButton',
174+ render: renderCustomButton,
175+ update: updateCustomButton,
176+ model: { signal:signal, value:value, up:up, hover:hover, down:down }
177+ });
178+ }
179+
180+ function renderCheckbox(model) {
181+ var node = newNode('input');
182+ node.type = 'checkbox';
183+ node.checked = model.checked;
184+ node.style.display = 'block';
185+ node.style.pointerEvents = 'auto';
186+ node.elm_signal = model.signal;
187+ node.elm_handler = model.handler;
188+ function change() {
189+ elm.notify(node.elm_signal.id, node.elm_handler(node.checked));
190+ }
191+ node.addEventListener('change', change);
192+ return node;
193+ }
194+
195+ function updateCheckbox(node, oldModel, newModel) {
196+ node.elm_signal = newModel.signal;
197+ node.elm_handler = newModel.handler;
198+ node.checked = newModel.checked;
199+ return true;
200+ }
201+
202+ function checkbox(signal, handler, checked) {
203+ return A3(newElement, 13, 13, {
204+ ctor: 'Custom',
205+ type: 'CheckBox',
206+ render: renderCheckbox,
207+ update: updateCheckbox,
208+ model: { signal:signal, handler:handler, checked:checked }
209+ });
210+ }
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+
220+ function updateIfNeeded(css, attribute, latestAttribute) {
221+ if (css[attribute] !== latestAttribute) {
222+ css[attribute] = latestAttribute;
223+ }
224+ }
225+ function cssDimensions(dimensions) {
226+ return dimensions.top + 'px ' +
227+ dimensions.right + 'px ' +
228+ dimensions.bottom + 'px ' +
229+ dimensions.left + 'px';
230+ }
231+ function updateFieldStyle(css, style) {
232+ updateIfNeeded(css, 'padding', cssDimensions(style.padding));
233+
234+ var outline = style.outline;
235+ updateIfNeeded(css, 'border-width', cssDimensions(outline.width));
236+ updateIfNeeded(css, 'border-color', toCss(outline.color));
237+ updateIfNeeded(css, 'border-radius', outline.radius + 'px');
238+
239+ var highlight = style.highlight;
240+ if (highlight.width === 0) {
241+ css.outline = 'none';
242+ } else {
243+ updateIfNeeded(css, 'outline-width', highlight.width + 'px');
244+ updateIfNeeded(css, 'outline-color', toCss(highlight.color));
245+ }
246+
247+ var textStyle = style.style;
248+ updateIfNeeded(css, 'color', toCss(textStyle.color));
249+ if (textStyle.typeface.ctor !== '[]') {
250+ updateIfNeeded(css, 'font-family', Text.toTypefaces(textStyle.typeface));
251+ }
252+ if (textStyle.height.ctor !== "Nothing") {
253+ updateIfNeeded(css, 'font-size', textStyle.height._0 + 'px');
254+ }
255+ updateIfNeeded(css, 'font-weight', textStyle.bold ? 'bold' : 'normal');
256+ updateIfNeeded(css, 'font-style', textStyle.italic ? 'italic' : 'normal');
257+ if (textStyle.line.ctor !== 'Nothing') {
258+ updateIfNeeded(css, 'text-decoration', Text.toLine(textStyle.line._0));
259+ }
260+ }
261+
262+ function renderField(model) {
263+ var field = newNode('input');
264+ updateFieldStyle(field.style, model.style);
265+ field.style.borderStyle = 'solid';
266+ field.style.pointerEvents = 'auto';
267+
268+ field.type = model.type;
269+ field.placeholder = JS.fromString(model.placeHolder);
270+ field.value = JS.fromString(model.content.string);
271+
272+ field.elm_signal = model.signal;
273+ field.elm_handler = model.handler;
274+ field.elm_old_value = field.value;
275+
276+ function inputUpdate(event) {
277+ var curr = field.elm_old_value;
278+ var next = field.value;
279+ if (curr === next) {
280+ return;
281+ }
282+
283+ var direction = field.selectionDirection === 'forward' ? 'Forward' : 'Backward';
284+ var start = field.selectionStart;
285+ var end = field.selectionEnd;
286+ field.value = field.elm_old_value;
287+
288+ elm.notify(field.elm_signal.id, field.elm_handler({
289+ _:{},
290+ string: JS.toString(next),
291+ selection: {
292+ _:{},
293+ start: start,
294+ end: end,
295+ direction: { ctor: direction }
296+ },
297+ }));
298+ }
299+
300+ function mouseUpdate(event) {
301+ var direction = field.selectionDirection === 'forward' ? 'Forward' : 'Backward';
302+ elm.notify(field.elm_signal.id, field.elm_handler({
303+ _:{},
304+ string: field.value,
305+ selection: {
306+ _:{},
307+ start: field.selectionStart,
308+ end: field.selectionEnd,
309+ direction: { ctor: direction }
310+ },
311+ }));
312+ }
313+ function mousedown(event) {
314+ mouseUpdate(event);
315+ elm.node.addEventListener('mouseup', mouseup);
316+ }
317+ function mouseup(event) {
318+ mouseUpdate(event);
319+ elm.node.removeEventListener('mouseup', mouseup)
320+ }
321+ field.addEventListener('input', inputUpdate);
322+ field.addEventListener('mousedown', mousedown);
323+ field.addEventListener('focus', function() {
324+ field.elm_hasFocus = true;
325+ });
326+ field.addEventListener('blur', function() {
327+ field.elm_hasFocus = false;
328+ });
329+
330+ return field;
331+ }
332+
333+ function updateField(field, oldModel, newModel) {
334+ if (oldModel.style !== newModel.style) {
335+ updateFieldStyle(field.style, newModel.style);
336+ }
337+ field.elm_signal = newModel.signal;
338+ field.elm_handler = newModel.handler;
339+
340+ field.type = newModel.type;
341+ field.placeholder = JS.fromString(newModel.placeHolder);
342+ var value = JS.fromString(newModel.content.string);
343+ field.value = value;
344+ field.elm_old_value = value;
345+ if (field.elm_hasFocus) {
346+ var selection = newModel.content.selection;
347+ var direction = selection.direction.ctor === 'Forward' ? 'forward' : 'backward';
348+ setRange(field, selection.start, selection.end, direction);
349+ }
350+ }
351+
352+ function mkField(type) {
353+ function field(style, signal, handler, placeHolder, content) {
354+ var padding = style.padding;
355+ var outline = style.outline.width;
356+ var adjustWidth = padding.left + padding.right + outline.left + outline.right;
357+ var adjustHeight = padding.top + padding.bottom + outline.top + outline.bottom;
358+ return A3(newElement, 200, 30, {
359+ ctor: 'Custom',
360+ type: type + 'Field',
361+ adjustWidth: adjustWidth,
362+ adjustHeight: adjustHeight,
363+ render: renderField,
364+ update: updateField,
365+ model: {
366+ signal:signal,
367+ handler:handler,
368+ placeHolder:placeHolder,
369+ content:content,
370+ style:style,
371+ type:type
372+ }
373+ });
374+ }
375+ return F5(field);
376+ }
377+
378+ function hoverable(signal, handler, elem) {
379+ function onHover(bool) {
380+ elm.notify(signal.id, handler(bool));
381+ }
382+ var props = Utils.replace([['hover',onHover]], elem.props);
383+ return { props:props, element:elem.element };
384+ }
385+
386+ function clickable(signal, value, elem) {
387+ function onClick(bool) {
388+ elm.notify(signal.id, value);
389+ }
390+ var props = Utils.replace([['click',onClick]], elem.props);
391+ return { props:props, element:elem.element };
392+ }
393+
394+ return elm.Native.Graphics.Input.values = {
395+ input:input,
396+ button:F3(button),
397+ customButton:F5(customButton),
398+ checkbox:F3(checkbox),
399+ dropDown:F2(dropDown),
400+ field:mkField('text'),
401+ email:mkField('email'),
402+ password:mkField('password'),
403+ hoverable:F3(hoverable),
404+ clickable:F3(clickable)
405+ };
302406
303407};