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