/*
Copyright 2008-2026
Matthias Ehmann,
Michael Gerhaeuser,
Carsten Miller,
Bianca Valentin,
Alfred Wassermann,
Peter Wilfahrt
This file is part of JSXGraph.
JSXGraph is free software dual licensed under the GNU LGPL or MIT License.
You can redistribute it and/or modify it under the terms of the
* GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version
OR
* MIT License: https://github.com/jsxgraph/jsxgraph/blob/master/LICENSE.MIT
JSXGraph is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License and
the MIT License along with JSXGraph. If not, see <https://www.gnu.org/licenses/>
and <https://opensource.org/licenses/MIT/>.
*/
/*global JXG: true, define: true, window: true*/
/*jslint nomen: true, plusplus: true*/
/**
* @fileoverview In this file the Text element is defined.
*/
import JXG from "../jxg.js";
import Env from "../utils/env.js";
import Text from "../base/text.js";
import Type from "../utils/type.js";
/**
* @class
* @ignore
*/
var priv = {
/**
* @class
* @ignore
*/
InputInputEventHandler: function (evt) {
this._value = this.rendNodeInput.value;
this.board.update();
}
};
/**
* @class This element is used to provide a constructor for special texts containing a
* HTML form input element.
* For this element, the attribute `display` has to have the value 'html' (which is the default).
*
* __Setting a CSS class:__ The attribute `cssClass` 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
* `.myinput > input { ... }` has to be used. See the analog example for buttons:
* {@link Button}.
*
* __Access the input element with JavaScript:__
* The underlying HTML button element can be accessed through the sub-object `rendNodeInput`, e.g. to add event listeners.
*
* @pseudo
* @name Input
* @elementclass control
* @augments JXG.Text
* @constructor
* @type JXG.Text
* @see Button
* @see Checkbox
*
*/
/**
* @jsxgraphsignature Input
* `x` and `y` are the coordinates of the lower left corner of the text box. The position of the text is fixed if
* `x` and `y` are numbers. The position is variable if x or y are functions.
* The default value of the input element must be given as string.
* The label of the input element may be given as string or function.
*
* @param {NumberLike} x
* @param {NumberLike} y
* @param {String} value
* @param {String|Function} label
*
* @example <caption>Create an input element at position [1,4]</caption>
* var input = board.create('input', [0, 1, 'sin(x)*x', 'f(x)='], {cssStyle: 'width: 100px'});
* var f = board.jc.snippet(input.Value(), true, 'x', false);
* var graph = board.create('functiongraph',[f,
* function() {
* var c = new JXG.Coords(JXG.COORDS_BY_SCREEN,[0,0],board);
* return c.usrCoords[1];
* },
* function() {
* var c = new JXG.Coords(JXG.COORDS_BY_SCREEN,[board.canvasWidth,0],board);
* return c.usrCoords[1];
* }
* ]);
*
* board.create('text', [1, 3, '<button onclick="window.updateGraph()">Update graph</button>']);
*
* window.updateGraph = function() {
* graph.Y = board.jc.snippet(input.Value(), true, 'x', false);
* graph.updateCurve();
* board.update();
* }
* </pre><div class="jxgbox" id="JXGc70f55f1-21ba-4719-a37d-a93ae2943faa" style="width: 500px; height: 300px;"></div>
* <script type="text/javascript">
* (function() {
* var board = JXG.JSXGraph.initBoard('JXGc70f55f1-21ba-4719-a37d-a93ae2943faa', {boundingbox: [-3, 6, 5, -3], axis: true, showcopyright: false, shownavigation: false});
* var input = board.create('input', [1, 4, 'sin(x)*x', 'f(x)='], {cssStyle: 'width: 100px'});
* var f = board.jc.snippet(input.Value(), true, 'x', false);
* var graph = board.create('functiongraph',[f,
* function() {
* var c = new JXG.Coords(JXG.COORDS_BY_SCREEN,[0,0],board);
* return c.usrCoords[1];
* },
* function() {
* var c = new JXG.Coords(JXG.COORDS_BY_SCREEN,[board.canvasWidth,0],board);
* return c.usrCoords[1];
* }
* ]);
*
* board.create('text', [1, 3, '<button onclick="window.updateGraph()">Update graph</button>']);
*
* window.updateGraph = function() {
* graph.Y = board.jc.snippet(input.Value(), true, 'x', false);
* graph.updateCurve();
* board.update();
* }
* })();
* </script><pre>
*
* @example <caption>Add the 'keyup' event to an input field</caption>
* // An alternative would be the event 'input'
* var A = board.create('point', [3, -2]);
* var i = board.create('input', [-4, -4, "1", "x "]);
*
* i.rendNodeInput.addEventListener("keyup", ( function () {
* var x = parseFloat(this.value);
* if (!isNaN(x)) {
* A.moveTo([x, 3], 100);
* }
* }));
*
* </pre><div id="JXG81c84fa7-3f36-4874-9e0f-d4b9e93e755b" class="jxgbox" style="width: 300px; height: 300px;"></div>
* <script type="text/javascript">
* (function() {
* var board = JXG.JSXGraph.initBoard('JXG81c84fa7-3f36-4874-9e0f-d4b9e93e755b',
* {boundingbox: [-5, 5, 5, -5], axis: true, showcopyright: false, shownavigation: false});
* var A = board.create('point', [3, -2]);
* var i = board.create('input', [-4, -4, "1", "x "]);
*
* i.rendNodeInput.addEventListener("keyup", ( function () {
* var x = parseFloat(this.value);
* if (!isNaN(x)) {
* A.moveTo([x, 3], 100);
* }
* }));
*
* })();
*
* </script><pre>
*
* @example <caption>Add the 'change' event to an input field</caption>
* // `change` is fired when the input element loses focus
* var A = board.create('point', [3, -2]);
* var i = board.create('input', [-4, -4, "1", "x "]);
*
* i.rendNodeInput.addEventListener("change", ( function () {
* var x = parseFloat(i.Value());
* A.moveTo([x, 2], 100);
* }));
*
* </pre><div id="JXG51c4d78b-a7ad-4c34-a983-b3ddae6192d7" class="jxgbox" style="width: 300px; height: 300px;"></div>
* <script type="text/javascript">
* (function() {
* var board = JXG.JSXGraph.initBoard('JXG51c4d78b-a7ad-4c34-a983-b3ddae6192d7',
* {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
* var A = board.create('point', [3, -2]);
* var i = board.create('input', [-4, -4, "1", "x "]);
*
* i.rendNodeInput.addEventListener("change", ( function () {
* var x = parseFloat(i.Value());
* console.log('change')
* A.moveTo([x, 2], 100);
* }));
*
* })();
*
* </script><pre>
*
* @example <caption>Change the width of an input field</caption>
* let s = board.create('slider', [[-3, 3], [2, 3], [50, 100, 300]]);
* let inp = board.create('input', [-6, 1, 'Math.sin(x)*x', 'f(x)='],{cssStyle:()=>'width:'+s.Value()+'px'});
*
* </pre><div id="JXG51c4d78b-a7ad-4c34-a983-b3ddae6192d7-1" class="jxgbox" style="width: 300px; height: 300px;"></div>
* <script type="text/javascript">
* (function() {
* var board = JXG.JSXGraph.initBoard('JXG51c4d78b-a7ad-4c34-a983-b3ddae6192d7-1',
* {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
* let s = board.create('slider', [[-3, 3], [2, 3], [50, 100, 300]]);
* let inp = board.create('input', [-6, 1, 'Math.sin(x)*x', 'f(x)='],{cssStyle:()=>'width:'+s.Value()+'px'});
* })();
*
* </script><pre>
*
* @example <caption>Apply CSS classes to label and input tag</caption>
* <style>
* div.JXGtext_inp {
* font-weight: bold;
* }
*
* // Label
* div.JXGtext_inp > span > span {
* padding: 3px;
* }
*
* // Input field
* div.JXGtext_inp > span > input {
* width: 100px;
* border: solid 4px red;
* border-radius: 25px;
* }
* </style>
*
* var inp = board.create('input', [-6, 1, 'x', 'y'], {
* CssClass: 'JXGtext_inp', HighlightCssClass: 'JXGtext_inp'
* });
*
* </pre><div id="JXGa3642ebd-a7dc-41ac-beb2-0c9e705ab8b4" class="jxgbox" style="width: 300px; height: 300px;"></div>
* <style>
* div.JXGtext_inp {
* font-weight: bold;
* }
*
* div.JXGtext_inp > span > span {
* padding: 3px;
* }
*
* div.JXGtext_inp > span > input {
* width: 100px;
* border: solid 4px red;
* border-radius: 25px;
* }
* </style>
* <script type="text/javascript">
* (function() {
* var board = JXG.JSXGraph.initBoard('JXGa3642ebd-a7dc-41ac-beb2-0c9e705ab8b4',
* {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
* var inp = board.create('input', [-6, 1, 'x', 'y'], {CssClass: 'JXGtext_inp', HighlightCssClass: 'JXGtext_inp'});
*
* })();
* </script><pre>
*
*/
JXG.createInput = function (board, parents, attributes) {
var t,
par,
setTextBackup,
attr = Type.copyAttributes(attributes, board.options, 'input');
par = [
parents[0],
parents[1],
'<span style="display:inline; white-space:nowrap; padding:0px;">' +
'<label></label><input type="text" maxlength="' +
attr.maxlength +
'" style="width:100%" />' +
"</span>"
];
// Make sure the setText method is the original one. The JessieCode parser changes it during parsing.
setTextBackup = Text.prototype.setText;
Text.prototype.setText = Text.prototype._setText;
// 1. Create input element with empty label
t = board.create("text", par, attr);
t.type = Type.OBJECT_TYPE_INPUT;
// Restore whichever setText method was set before this contructor was called.
Text.prototype.setText = setTextBackup;
t.rendNodeLabel = t.rendNode.childNodes[0].childNodes[0];
t.rendNodeInput = t.rendNode.childNodes[0].childNodes[1];
t.rendNodeInput.value = parents[2];
t.rendNodeTag = t.rendNodeInput; // Needed for unified treatment in setAttribute
t.rendNodeTag.disabled = !!attr.disabled;
t.rendNodeLabel.id = t.rendNode.id + "_label";
t.rendNodeInput.id = t.rendNode.id + "_input";
t.rendNodeInput.setAttribute("aria-labelledby", t.rendNodeLabel.id);
// 2. Set parents[3] (string|function) as label of the input element.
// abstract.js selects the correct DOM element for the update
t.setText(parents[3]);
t._value = parents[2];
// 3. capture keydown events on the input, and do not let them propagate. The problem is that
// elevation controls on view3D use left and right, so editing the input triggers 3D pan.
t.rendNodeInput.addEventListener("keydown", (event) => {
// only trap left-and-right in case user wants input editing events
if (event.key === 'ArrowLeft' || event.key === 'ArrowRight') {
event.stopPropagation();
}
});
/**
* @class
* @ignore
*/
t.update = function () {
if (this.needsUpdate) {
JXG.Text.prototype.update.call(this);
this._value = this.rendNodeInput.value;
}
return this;
};
/**
* Returns the value (content) of the input element
* @name Value
* @memberOf Input.prototype
* @function
* @returns {String} content of the input field.
*/
t.Value = function () {
return this._value;
};
/**
* Sets value of the input element.
* @name set
* @memberOf Input.prototype
* @function
*
* @param {String} val
* @returns {JXG.GeometryElement} Reference to the element.
*
* @example
* var i1 = board.create('input', [-3, 4, 'sin(x)', 'f(x)='], {cssStyle: 'width:4em', maxlength: 2});
* var c1 = board.create('checkbox', [-3, 2, 'label 1'], {});
* var b1 = board.create('button', [-3, -1, 'Change texts', function () {
* i1.setText('g(x)');
* i1.set('cos(x)');
* c1.setText('label 2');
* b1.setText('Texts are changed');
* }],
* {cssStyle: 'width:400px'});
*
* </pre><div id="JXG11cac8ff-2354-47e7-9da4-eb298e53de05" class="jxgbox" style="width: 300px; height: 300px;"></div>
* <script type="text/javascript">
* (function() {
* var board = JXG.JSXGraph.initBoard('JXG11cac8ff-2354-47e7-9da4-eb298e53de05',
* {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
* var i1 = board.create('input', [-3, 4, 'sin(x)', 'f(x)='], {cssStyle: 'width:4em', maxlength: 2});
* var c1 = board.create('checkbox', [-3, 2, 'label 1'], {});
* var b1 = board.create('button', [-3, -1, 'Change texts', function () {
* i1.setText('g(x)');
* i1.set('cos(x)');
* c1.setText('label 2');
* b1.setText('Texts are changed');
* }],
* {cssStyle: 'width:400px'});
*
* })();
*
* </script><pre>
*
*/
/**
* @class
* @ignore
*/
t.set = function (val) {
this._value = val;
this.rendNodeInput.value = val;
return this;
};
Env.addEvent(t.rendNodeInput, "input", priv.InputInputEventHandler, t);
Env.addEvent(
t.rendNodeInput,
"mousedown",
function (evt) {
if (Type.exists(evt.stopPropagation)) {
evt.stopPropagation();
}
},
t
);
Env.addEvent(
t.rendNodeInput,
"touchstart",
function (evt) {
if (Type.exists(evt.stopPropagation)) {
evt.stopPropagation();
}
},
t
);
Env.addEvent(
t.rendNodeInput,
"pointerdown",
function (evt) {
if (Type.exists(evt.stopPropagation)) {
evt.stopPropagation();
}
},
t
);
// This sets the font-size of the input HTML element
t.visPropOld.fontsize = '0px';
board.renderer.updateTextStyle(t, false);
return t;
};
JXG.registerElement("input", JXG.createInput);
// export default {
// createInput: JXG.createInput
// };