diff --git a/htdocs/js/GraphTool/circletool.js b/htdocs/js/GraphTool/circletool.js index 50b9c663f..af51d27ac 100644 --- a/htdocs/js/GraphTool/circletool.js +++ b/htdocs/js/GraphTool/circletool.js @@ -188,7 +188,7 @@ const center = this.center; delete this.center; - center.setAttribute(gt.definingPointAttributes); + center.setAttribute(gt.definingPointAttributes()); center.on('down', () => gt.onPointDown(center)); center.on('up', () => gt.onPointUp(center)); diff --git a/htdocs/js/GraphTool/cubictool.js b/htdocs/js/GraphTool/cubictool.js index c65983a3f..46b4e23b2 100644 --- a/htdocs/js/GraphTool/cubictool.js +++ b/htdocs/js/GraphTool/cubictool.js @@ -12,7 +12,7 @@ constructor(point1, point2, point3, point4, solid) { for (const point of [point1, point2, point3, point4]) { - point.setAttribute(gt.definingPointAttributes); + point.setAttribute(gt.definingPointAttributes()); if (!gt.isStatic) { point.on('down', () => gt.onPointDown(point)); point.on('up', () => gt.onPointUp(point)); diff --git a/htdocs/js/GraphTool/graphtool.js b/htdocs/js/GraphTool/graphtool.js index c67483774..65373a172 100644 --- a/htdocs/js/GraphTool/graphtool.js +++ b/htdocs/js/GraphTool/graphtool.js @@ -39,7 +39,7 @@ window.graphTool = (containerId, options) => { underConstructionFixed: JXG.palette.red // defined to be '#d55e00' }; - gt.definingPointAttributes = { + gt.definingPointAttributes = () => ({ size: 3, fixed: false, highlight: true, @@ -49,8 +49,9 @@ window.graphTool = (containerId, options) => { fillColor: gt.color.point, highlightStrokeWidth: 1, highlightStrokeColor: gt.color.focusCurve, - highlightFillColor: gt.color.pointHighlight - }; + highlightFillColor: gt.color.pointHighlight, + tabindex: gt.isStatic ? -1 : 0 + }); gt.options = options; gt.snapSizeX = options.snapSizeX ? options.snapSizeX : 1; @@ -262,6 +263,113 @@ window.graphTool = (containerId, options) => { gt.board.highlightInfobox = (_x, _y, el) => gt.board.highlightCustomInfobox('', el); if (!gt.isStatic) { + // This is a mess to work around an issue with JSXGraph versions 1.11.1 or later. Their keyDownListener + // calls preventDefault on the keydown event when shift-tab is pressed. That prevents keyboard focus from + // moving backward in the tab order. So this removes the JSXGraph keyboard event handlers, then overrides + // the board's keyDownListener with essentially the same code with the exception that when the tab key is + // pressed, preventDefault is not called. Then the keyboard event listeners are added back, using this + // keydownListener. + gt.board.removeKeyboardEventHandlers(); + gt.board.keyDownListener = function (evt) { + const id_node = evt.target.id; + let done = true; + + if (!this.attr.keyboard.enabled || id_node === '') return false; + + const doc = this.containerObj.shadowRoot || document; + if (doc.activeElement) { + if (doc.activeElement.tagName === 'INPUT' || doc.activeElement.tagName === 'textarea') return false; + } + + const id = id_node.replace(this.containerObj.id + '_', ''); + const el = this.select(id); + + if ( + (JXG.evaluate(this.attr.keyboard.panshift) && evt.shiftKey) || + (JXG.evaluate(this.attr.keyboard.panctrl) && evt.ctrlKey) + ) { + const doZoom = JXG.evaluate(this.attr.zoom.enabled) === true; + if (evt.keyCode === 38) this.clickUpArrow(); + else if (evt.keyCode === 40) this.clickDownArrow(); + else if (evt.keyCode === 37) this.clickLeftArrow(); + else if (evt.keyCode === 39) this.clickRightArrow(); + else if (doZoom && evt.keyCode === 171) this.zoomIn(); + else if (doZoom && evt.keyCode === 173) this.zoomOut(); + else if (doZoom && evt.keyCode === 79) this.zoom100(); + else done = false; + } else if (!evt.shiftKey && !evt.ctrlKey) { + let dx = JXG.evaluate(this.attr.keyboard.dx) / this.unitX; + let dy = JXG.evaluate(this.attr.keyboard.dy) / this.unitY; + if (JXG.exists(el.visProp)) { + if ( + JXG.exists(el.visProp.snaptogrid) && + el.visProp.snaptogrid && + el.evalVisProp('snapsizex') && + el.evalVisProp('snapsizey') + ) { + const res = el.getSnapSizes(); + dx = res[0]; + dy = res[1]; + } else if ( + JXG.exists(el.visProp.attracttogrid) && + el.visProp.attracttogrid && + el.evalVisProp('attractordistance') && + el.evalVisProp('attractorunit') + ) { + let sX = 1.1 * el.evalVisProp('attractordistance'); + let sY = sX; + if (el.evalVisProp('attractorunit') === 'screen') { + sX /= this.unitX; + sY /= this.unitX; + } + dx = Math.max(sX, dx); + dy = Math.max(sY, dy); + } + } + + let dir; + if (evt.keyCode === 38) dir = [0, dy]; + else if (evt.keyCode === 40) dir = [0, -dy]; + else if (evt.keyCode === 37) dir = [-dx, 0]; + else if (evt.keyCode === 39) dir = [dx, 0]; + else done = false; + + if ( + dir && + el.isDraggable && + el.visPropCalc.visible && + ((this.geonextCompatibilityMode && + (JXG.isPoint(el) || el.elementClass === Const.OBJECT_CLASS_TEXT)) || + !this.geonextCompatibilityMode) && + !el.evalVisProp('fixed') + ) { + this.mode = this.BOARD_MODE_DRAG; + if (JXG.exists(el.coords)) { + const actPos = el.coords.usrCoords.slice(1); + dir[0] += actPos[0]; + dir[1] += actPos[1]; + el.setPosition(JXG.COORDS_BY_USER, dir); + this.updateInfobox(el); + } else { + this.displayInfobox(false); + el.setPositionDirectly(Const.COORDS_BY_USER, dir, [0, 0]); + } + + this.triggerEventHandlers(['keymove', 'move'], [evt, this.mode]); + el.triggerEventHandlers(['keydrag', 'drag'], [evt]); + this.mode = this.BOARD_MODE_NONE; + } + } else if (evt.key === 'Tab') { + done = false; + } + + this.update(); + + if (done && JXG.exists(evt.preventDefault)) evt.preventDefault(); + return done; + }; + gt.board.addKeyboardEventHandlers(); + gt.graphContainer.tabIndex = -1; gt.board.containerObj.tabIndex = -1; @@ -805,7 +913,7 @@ window.graphTool = (containerId, options) => { const point = gt.board.create('point', [gt.snapRound(x, gt.snapSizeX), gt.snapRound(y, gt.snapSizeY)], { snapSizeX: gt.snapSizeX, snapSizeY: gt.snapSizeY, - ...gt.definingPointAttributes + ...gt.definingPointAttributes() }); point.setAttribute({ snapToGrid: true }); if (!gt.isStatic) { diff --git a/htdocs/js/GraphTool/intervaltools.js b/htdocs/js/GraphTool/intervaltools.js index 8839e82dc..8fa9a720c 100644 --- a/htdocs/js/GraphTool/intervaltools.js +++ b/htdocs/js/GraphTool/intervaltools.js @@ -454,7 +454,8 @@ highlightStrokeWidth: 3, highlightStrokeColor: gt.color.pointHighlightDarker, // highlightFillColor is gt.color.pointHighlight if not included. - highlightFillColor: gt.color.pointHighlightDarker + highlightFillColor: gt.color.pointHighlightDarker, + tabindex: gt.isStatic ? -1 : 0 }; } diff --git a/htdocs/js/GraphTool/linetool.js b/htdocs/js/GraphTool/linetool.js index acf6cb03c..9e97d1a3c 100644 --- a/htdocs/js/GraphTool/linetool.js +++ b/htdocs/js/GraphTool/linetool.js @@ -188,7 +188,7 @@ const point1 = this.point1; delete this.point1; - point1.setAttribute(gt.definingPointAttributes); + point1.setAttribute(gt.definingPointAttributes()); point1.on('down', () => gt.onPointDown(point1)); point1.on('up', () => gt.onPointUp(point1)); diff --git a/htdocs/js/GraphTool/parabolatool.js b/htdocs/js/GraphTool/parabolatool.js index c1da9bb0d..e0d65cce5 100644 --- a/htdocs/js/GraphTool/parabolatool.js +++ b/htdocs/js/GraphTool/parabolatool.js @@ -237,7 +237,7 @@ const vertex = this.vertex; delete this.vertex; - vertex.setAttribute(gt.definingPointAttributes); + vertex.setAttribute(gt.definingPointAttributes()); vertex.on('down', () => gt.onPointDown(vertex)); vertex.on('up', () => gt.onPointUp(vertex)); diff --git a/htdocs/js/GraphTool/pointtool.js b/htdocs/js/GraphTool/pointtool.js index 2420b41a2..eea92d4fd 100644 --- a/htdocs/js/GraphTool/pointtool.js +++ b/htdocs/js/GraphTool/pointtool.js @@ -22,7 +22,8 @@ strokeColor: gt.color.curve, fixed: gt.isStatic, highlightStrokeColor: gt.color.underConstruction, - highlightFillColor: gt.color.pointHighlight + highlightFillColor: gt.color.pointHighlight, + tabindex: gt.isStatic ? -1 : 0 }) ); diff --git a/htdocs/js/GraphTool/quadratictool.js b/htdocs/js/GraphTool/quadratictool.js index a5a8d9420..bef7eb289 100644 --- a/htdocs/js/GraphTool/quadratictool.js +++ b/htdocs/js/GraphTool/quadratictool.js @@ -12,7 +12,7 @@ constructor(point1, point2, point3, solid) { for (const point of [point1, point2, point3]) { - point.setAttribute(gt.definingPointAttributes); + point.setAttribute(gt.definingPointAttributes()); if (!gt.isStatic) { point.on('down', () => gt.onPointDown(point)); point.on('up', () => gt.onPointUp(point)); diff --git a/htdocs/js/GraphTool/quadrilateral.js b/htdocs/js/GraphTool/quadrilateral.js index 9ec263626..b9211b968 100644 --- a/htdocs/js/GraphTool/quadrilateral.js +++ b/htdocs/js/GraphTool/quadrilateral.js @@ -12,7 +12,7 @@ constructor(point1, point2, point3, point4, solid) { for (const point of [point1, point2, point3, point4]) { - point.setAttribute(gt.definingPointAttributes); + point.setAttribute(gt.definingPointAttributes()); if (!gt.isStatic) { point.on('down', () => gt.onPointDown(point)); point.on('up', () => gt.onPointUp(point)); diff --git a/htdocs/js/GraphTool/sinewavetool.js b/htdocs/js/GraphTool/sinewavetool.js index 057559d9f..a46e597a1 100644 --- a/htdocs/js/GraphTool/sinewavetool.js +++ b/htdocs/js/GraphTool/sinewavetool.js @@ -12,7 +12,7 @@ constructor(shiftPoint, periodPoint, amplitudePoint, solid) { for (const point of [shiftPoint, periodPoint, amplitudePoint]) { - point.setAttribute(gt.definingPointAttributes); + point.setAttribute(gt.definingPointAttributes()); if (!gt.isStatic) { point.on('down', () => gt.onPointDown(point)); point.on('up', () => gt.onPointUp(point)); diff --git a/htdocs/js/GraphTool/triangle.js b/htdocs/js/GraphTool/triangle.js index e68f15aea..757b5eb66 100644 --- a/htdocs/js/GraphTool/triangle.js +++ b/htdocs/js/GraphTool/triangle.js @@ -12,7 +12,7 @@ constructor(point1, point2, point3, solid) { for (const point of [point1, point2, point3]) { - point.setAttribute(gt.definingPointAttributes); + point.setAttribute(gt.definingPointAttributes()); if (!gt.isStatic) { point.on('down', () => gt.onPointDown(point)); point.on('up', () => gt.onPointUp(point)); diff --git a/htdocs/package-lock.json b/htdocs/package-lock.json index 8e070015f..9c40f83d1 100644 --- a/htdocs/package-lock.json +++ b/htdocs/package-lock.json @@ -8,7 +8,7 @@ "license": "GPL-2.0+", "dependencies": { "@openwebwork/mathquill": "^0.11.1", - "jsxgraph": "^1.11.1", + "jsxgraph": "^1.12.2", "jszip": "^3.10.1", "jszip-utils": "^0.1.0", "plotly.js-dist-min": "^3.1.0", @@ -1026,9 +1026,9 @@ "license": "MIT" }, "node_modules/jsxgraph": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/jsxgraph/-/jsxgraph-1.11.1.tgz", - "integrity": "sha512-0UdVqQPrKiHH29QZq0goaJvJ6eAAHln00/9urKyiTgqqFWA0xX4/akUbaz9N5cmdh8fQ6NPSwMe43TbeAWQfXA==", + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/jsxgraph/-/jsxgraph-1.12.2.tgz", + "integrity": "sha512-7kTscexFVBHirsBrxZFQg2hA6Mf/Pa2piojNgxHZ9i/rQfxDAaq7p8oD9/009clQTFQ9fNLUpmDKwV+84zA2Gg==", "license": "(MIT OR LGPL-3.0-or-later)", "dependencies": { "jsxgraph": "^1.11.0-beta2" @@ -2622,9 +2622,9 @@ "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" }, "jsxgraph": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/jsxgraph/-/jsxgraph-1.11.1.tgz", - "integrity": "sha512-0UdVqQPrKiHH29QZq0goaJvJ6eAAHln00/9urKyiTgqqFWA0xX4/akUbaz9N5cmdh8fQ6NPSwMe43TbeAWQfXA==", + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/jsxgraph/-/jsxgraph-1.12.2.tgz", + "integrity": "sha512-7kTscexFVBHirsBrxZFQg2hA6Mf/Pa2piojNgxHZ9i/rQfxDAaq7p8oD9/009clQTFQ9fNLUpmDKwV+84zA2Gg==", "requires": { "jsxgraph": "^1.11.0-beta2" } diff --git a/htdocs/package.json b/htdocs/package.json index f4cfd4ec1..6de2eed49 100644 --- a/htdocs/package.json +++ b/htdocs/package.json @@ -14,7 +14,7 @@ }, "dependencies": { "@openwebwork/mathquill": "^0.11.1", - "jsxgraph": "^1.11.1", + "jsxgraph": "^1.12.2", "jszip": "^3.10.1", "jszip-utils": "^0.1.0", "plotly.js-dist-min": "^3.1.0",