1 /*
  2     Copyright 2008-2026
  3         Matthias Ehmann,
  4         Michael Gerhaeuser,
  5         Carsten Miller,
  6         Bianca Valentin,
  7         Alfred Wassermann,
  8         Peter Wilfahrt
  9 
 10     This file is part of JSXGraph.
 11 
 12     JSXGraph is free software dual licensed under the GNU LGPL or MIT License.
 13 
 14     You can redistribute it and/or modify it under the terms of the
 15 
 16       * GNU Lesser General Public License as published by
 17         the Free Software Foundation, either version 3 of the License, or
 18         (at your option) any later version
 19       OR
 20       * MIT License: https://github.com/jsxgraph/jsxgraph/blob/master/LICENSE.MIT
 21 
 22     JSXGraph is distributed in the hope that it will be useful,
 23     but WITHOUT ANY WARRANTY; without even the implied warranty of
 24     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 25     GNU Lesser General Public License for more details.
 26 
 27     You should have received a copy of the GNU Lesser General Public License and
 28     the MIT License along with JSXGraph. If not, see <https://www.gnu.org/licenses/>
 29     and <https://opensource.org/licenses/MIT/>.
 30  */
 31 
 32 /*global JXG: true, define: true, window: true*/
 33 /*jslint nomen: true, plusplus: true*/
 34 
 35 /**
 36  * @fileoverview In this file the Text element is defined.
 37  */
 38 
 39 import JXG from "../jxg.js";
 40 import Env from "../utils/env.js";
 41 import Text from "../base/text.js";
 42 import Type from "../utils/type.js";
 43 
 44 /**
 45  * @class
 46  * @ignore
 47  */
 48 var priv = {
 49     /**
 50     * @class
 51     * @ignore
 52     */
 53 
 54     InputInputEventHandler: function (evt) {
 55         this._value = this.rendNodeInput.value;
 56         this.board.update();
 57     }
 58 };
 59 
 60 /**
 61  * @class This element is used to provide a constructor for special texts containing a
 62  * HTML form input element.
 63  * For this element, the attribute "display" has to have the value 'html' (which is the default).
 64  *
 65  * <p><b>Setting a CSS class:</b> The attribute <tt>cssClass</tt> affects the HTML div element that contains the input element. To change the CSS properties of the HTML input element a selector of the form
 66  * <tt>.myinput > input { ... }</tt> has to be used. See the analog example for buttons:
 67  * {@link Button}.
 68  *
 69  * <p><b>Access the input element with JavaScript:</b>
 70  * The underlying HTML button element can be accessed through the sub-object 'rendNodeInput', e.g. to
 71  * add event listeners.
 72  *
 73  * @pseudo
 74  * @name Input
 75  * @augments Text
 76  * @constructor
 77  * @type JXG.Text
 78  *
 79  * @param {number,function_number,function_String_String,function} x,y,value,label Parent elements for input elements.
 80  *   <p>
 81  *   x and y are the coordinates of the lower left corner of the text box. The position of the text is fixed,
 82  *   x and y are numbers. The position is variable if x or y are functions.
 83  *   <p>
 84  *   The default value of the input element must be given as string.
 85  *   <p>
 86  *   The label of the input element may be given as string or function.
 87  *
 88  * @example
 89  *  // Create an input element at position [1,4].
 90  *  var input = board.create('input', [0, 1, 'sin(x)*x', 'f(x)='], {cssStyle: 'width: 100px'});
 91  *  var f = board.jc.snippet(input.Value(), true, 'x', false);
 92  *  var graph = board.create('functiongraph',[f,
 93  *          function() {
 94  *            var c = new JXG.Coords(JXG.COORDS_BY_SCREEN,[0,0],board);
 95  *            return c.usrCoords[1];
 96  *          },
 97  *          function() {
 98  *            var c = new JXG.Coords(JXG.COORDS_BY_SCREEN,[board.canvasWidth,0],board);
 99  *            return c.usrCoords[1];
100  *          }
101  *        ]);
102  *
103  *  board.create('text', [1, 3, '<button onclick="updateGraph()">Update graph</button>']);
104  *
105  *  var updateGraph = function() {
106  *      graph.Y = board.jc.snippet(input.Value(), true, 'x', false);
107  *      graph.updateCurve();
108  *      board.update();
109  *  }
110  * </pre><div class="jxgbox" id="JXGc70f55f1-21ba-4719-a37d-a93ae2943faa" style="width: 500px; height: 300px;"></div>
111  * <script type="text/javascript">
112  *   var t1_board = JXG.JSXGraph.initBoard('JXGc70f55f1-21ba-4719-a37d-a93ae2943faa', {boundingbox: [-3, 6, 5, -3], axis: true, showcopyright: false, shownavigation: false});
113  *   var input = t1_board.create('input', [1, 4, 'sin(x)*x', 'f(x)='], {cssStyle: 'width: 100px'});
114  *   var f = t1_board.jc.snippet(input.Value(), true, 'x', false);
115  *   var graph = t1_board.create('functiongraph',[f,
116  *          function() {
117  *            var c = new JXG.Coords(JXG.COORDS_BY_SCREEN,[0,0],t1_board);
118  *            return c.usrCoords[1];
119  *          },
120  *          function() {
121  *            var c = new JXG.Coords(JXG.COORDS_BY_SCREEN,[t1_board.canvasWidth,0],t1_board);
122  *            return c.usrCoords[1];
123  *          }
124  *        ]);
125  *
126  *  t1_board.create('text', [1, 3, '<button onclick="updateGraph()">Update graph</button>']);
127  *
128  *  var updateGraph = function() {
129  *      graph.Y = t1_board.jc.snippet(input.Value(), true, 'x', false);
130  *      graph.updateCurve();
131  *      t1_board.update();
132  *  }
133  * </script><pre>
134  *
135  * @example
136  * // Add the `keyup` event to an input field
137  * var A = board.create('point', [3, -2]);
138  * var i = board.create('input', [-4, -4, "1", "x "]);
139  *
140  * i.rendNodeInput.addEventListener("keyup", ( function () {
141  *    var x = parseFloat(this.value);
142  *    if (!isNaN(x)) {
143  * 	   A.moveTo([x, 3], 100);
144  *    }
145  * }));
146  *
147  * </pre><div id="JXG81c84fa7-3f36-4874-9e0f-d4b9e93e755b" class="jxgbox" style="width: 300px; height: 300px;"></div>
148  * <script type="text/javascript">
149  *     (function() {
150  *         var board = JXG.JSXGraph.initBoard('JXG81c84fa7-3f36-4874-9e0f-d4b9e93e755b',
151  *             {boundingbox: [-5, 5, 5, -5], axis: true, showcopyright: false, shownavigation: false});
152  *     var A = board.create('point', [3, -2]);
153  *     var i = board.create('input', [-4, -4, "1", "x "]);
154  *
155  *     i.rendNodeInput.addEventListener("keyup", ( function () {
156  *        var x = parseFloat(this.value);
157  *        if (!isNaN(x)) {
158  *     	    A.moveTo([x, 3], 100);
159  *        }
160  *     }));
161  *
162  *     })();
163  *
164  * </script><pre>
165  *
166  * @example
167  * // Add the `change` event to an input field
168  * var A = board.create('point', [3, -2]);
169  * var i = board.create('input', [-4, -4, "1", "x "]);
170  *
171  * i.rendNodeInput.addEventListener("change", ( function () {
172  *    var x = parseFloat(i.Value());
173  *    A.moveTo([x, 2], 100);
174  * }));
175  *
176  * </pre><div id="JXG51c4d78b-a7ad-4c34-a983-b3ddae6192d7" class="jxgbox" style="width: 300px; height: 300px;"></div>
177  * <script type="text/javascript">
178  *     (function() {
179  *         var board = JXG.JSXGraph.initBoard('JXG51c4d78b-a7ad-4c34-a983-b3ddae6192d7',
180  *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
181  *     var A = board.create('point', [3, -2]);
182  *     var i = board.create('input', [-4, -4, "1", "x "]);
183  *
184  *     i.rendNodeInput.addEventListener("change", ( function () {
185  *        var x = parseFloat(i.Value());
186  *        A.moveTo([x, 2], 100);
187  *     }));
188  *
189  *     })();
190  *
191  * </script><pre>
192  *
193  * @example
194  * // change the width of an input field
195  *  let s = board.create('slider', [[-3, 3], [2, 3], [50, 100, 300]]);
196  *  let inp = board.create('input', [-6, 1, 'Math.sin(x)*x', 'f(x)='],{cssStyle:()=>'width:'+s.Value()+'px'});
197  *
198  * </pre><div id="JXG51c4d78b-a7ad-4c34-a983-b3ddae6192d7-1" class="jxgbox" style="width: 300px; height: 300px;"></div>
199  * <script type="text/javascript">
200  *     (function() {
201  *         var board = JXG.JSXGraph.initBoard('JXG51c4d78b-a7ad-4c34-a983-b3ddae6192d7-1',
202  *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
203  *  let s = board.create('slider', [[-3, 3], [2, 3], [50, 100, 300]]);
204  *  let inp = board.create('input', [-6, 1, 'Math.sin(x)*x', 'f(x)='],{cssStyle:()=>'width:'+s.Value()+'px'});
205  *     })();
206  *
207  * </script><pre>
208  *
209  * @example
210  *   Apply CSS classes to label and input tag
211  *     <style>
212  *         div.JXGtext_inp {
213  *             font-weight: bold;
214  *         }
215  *
216  *         // Label
217  *         div.JXGtext_inp > span > span {
218  *             padding: 3px;
219  *         }
220  *
221  *         // Input field
222  *         div.JXGtext_inp > span > input {
223  *             width: 100px;
224  *             border: solid 4px red;
225  *             border-radius: 25px;
226  *         }
227  *     </style>
228  *
229  * var inp = board.create('input', [-6, 1, 'x', 'y'], {
230  *      CssClass: 'JXGtext_inp', HighlightCssClass: 'JXGtext_inp'
231  * });
232  *
233  * </pre>
234  *         <style>
235  *             div.JXGtext_inp {
236  *                 font-weight: bold;
237  *             }
238  *
239  *             div.JXGtext_inp > span > span {
240  *                 padding: 3px;
241  *             }
242  *
243  *             div.JXGtext_inp > span > input {
244  *                 width: 100px;
245  *                 border: solid 4px red;
246  *                 border-radius: 25px;
247  *             }
248  *         </style>
249  * <div id="JXGa3642ebd-a7dc-41ac-beb2-0c9e705ab8b4" class="jxgbox" style="width: 300px; height: 300px;"></div>
250  * <script type="text/javascript">
251  *     (function() {
252  *         var board = JXG.JSXGraph.initBoard('JXGa3642ebd-a7dc-41ac-beb2-0c9e705ab8b4',
253  *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
254  *         var inp = board.create('input', [-6, 1, 'x', 'y'], {CssClass: 'JXGtext_inp', HighlightCssClass: 'JXGtext_inp'});
255  *
256  *     })();
257  * </script><pre>
258  *
259  */
260 JXG.createInput = function (board, parents, attributes) {
261     var t,
262         par,
263         setTextBackup,
264         attr = Type.copyAttributes(attributes, board.options, 'input');
265 
266     par = [
267         parents[0],
268         parents[1],
269         '<span style="display:inline; white-space:nowrap; padding:0px;">' +
270         '<label></label><input type="text" maxlength="' +
271         attr.maxlength +
272         '" style="width:100%" />' +
273         "</span>"
274     ];
275 
276     // Make sure the setText method is the original one. The JessieCode parser changes it during parsing.
277     setTextBackup = Text.prototype.setText;
278     Text.prototype.setText = Text.prototype._setText;
279 
280     // 1. Create input element with empty label
281     t = board.create("text", par, attr);
282     t.type = Type.OBJECT_TYPE_INPUT;
283 
284     // Restore whichever setText method was set before this contructor was called.
285     Text.prototype.setText = setTextBackup;
286 
287     t.rendNodeLabel = t.rendNode.childNodes[0].childNodes[0];
288     t.rendNodeInput = t.rendNode.childNodes[0].childNodes[1];
289     t.rendNodeInput.value = parents[2];
290     t.rendNodeTag = t.rendNodeInput; // Needed for unified treatment in setAttribute
291     t.rendNodeTag.disabled = !!attr.disabled;
292     t.rendNodeLabel.id = t.rendNode.id + "_label";
293     t.rendNodeInput.id = t.rendNode.id + "_input";
294     t.rendNodeInput.setAttribute("aria-labelledby", t.rendNodeLabel.id);
295 
296     // 2. Set parents[3] (string|function) as label of the input element.
297     // abstract.js selects the correct DOM element for the update
298     t.setText(parents[3]);
299 
300     t._value = parents[2];
301 
302     // 3.  capture keydown events on the input, and do not let them propagate.  The problem is that
303     // elevation controls on view3D use left and right, so editing the input triggers 3D pan.
304     t.rendNodeInput.addEventListener("keydown", (event) => {
305         // only trap left-and-right in case user wants input editing events
306         if (event.key === 'ArrowLeft' || event.key === 'ArrowRight') {
307             event.stopPropagation();
308         }
309     });
310 
311 
312 
313 
314     /**
315     * @class
316     * @ignore
317     */
318     t.update = function () {
319         if (this.needsUpdate) {
320             JXG.Text.prototype.update.call(this);
321             this._value = this.rendNodeInput.value;
322         }
323         return this;
324     };
325 
326     /**
327      * Returns the value (content) of the input element
328      * @name Value
329      * @memberOf Input.prototype
330      * @function
331      * @returns {String} content of the input field.
332      */
333     t.Value = function () {
334         return this._value;
335     };
336 
337     /**
338      * Sets value of the input element.
339      * @name set
340      * @memberOf Input.prototype
341      * @function
342      *
343      * @param {String} val
344      * @returns {JXG.GeometryElement} Reference to the element.
345      *
346      * @example
347      *         var i1 = board.create('input', [-3, 4, 'sin(x)', 'f(x)='], {cssStyle: 'width:4em', maxlength: 2});
348      *         var c1 = board.create('checkbox', [-3, 2, 'label 1'], {});
349      *         var b1 = board.create('button', [-3, -1, 'Change texts', function () {
350      *                 i1.setText('g(x)');
351      *                 i1.set('cos(x)');
352      *                 c1.setText('label 2');
353      *                 b1.setText('Texts are changed');
354      *             }],
355      *             {cssStyle: 'width:400px'});
356      *
357      * </pre><div id="JXG11cac8ff-2354-47e7-9da4-eb298e53de05" class="jxgbox" style="width: 300px; height: 300px;"></div>
358      * <script type="text/javascript">
359      *     (function() {
360      *         var board = JXG.JSXGraph.initBoard('JXG11cac8ff-2354-47e7-9da4-eb298e53de05',
361      *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
362      *             var i1 = board.create('input', [-3, 4, 'sin(x)', 'f(x)='], {cssStyle: 'width:4em', maxlength: 2});
363      *             var c1 = board.create('checkbox', [-3, 2, 'label 1'], {});
364      *             var b1 = board.create('button', [-3, -1, 'Change texts', function () {
365      *                     i1.setText('g(x)');
366      *                     i1.set('cos(x)');
367      *                     c1.setText('label 2');
368      *                     b1.setText('Texts are changed');
369      *                 }],
370      *                 {cssStyle: 'width:400px'});
371      *
372      *     })();
373      *
374      * </script><pre>
375      *
376      */
377 
378     /**
379     * @class
380     * @ignore
381     */
382 
383     t.set = function (val) {
384         this._value = val;
385         this.rendNodeInput.value = val;
386         return this;
387     };
388 
389     Env.addEvent(t.rendNodeInput, "input", priv.InputInputEventHandler, t);
390     Env.addEvent(
391         t.rendNodeInput,
392         "mousedown",
393         function (evt) {
394             if (Type.exists(evt.stopPropagation)) {
395                 evt.stopPropagation();
396             }
397         },
398         t
399     );
400     Env.addEvent(
401         t.rendNodeInput,
402         "touchstart",
403         function (evt) {
404             if (Type.exists(evt.stopPropagation)) {
405                 evt.stopPropagation();
406             }
407         },
408         t
409     );
410     Env.addEvent(
411         t.rendNodeInput,
412         "pointerdown",
413         function (evt) {
414             if (Type.exists(evt.stopPropagation)) {
415                 evt.stopPropagation();
416             }
417         },
418         t
419     );
420 
421     // This sets the font-size of the input HTML element
422     t.visPropOld.fontsize = '0px';
423     board.renderer.updateTextStyle(t, false);
424 
425     return t;
426 };
427 
428 JXG.registerElement("input", JXG.createInput);
429 
430 // export default {
431 //     createInput: JXG.createInput
432 // };
433