diff --git a/htdocs/js/GraphTool/circletool.js b/htdocs/js/GraphTool/circletool.js index af51d27ac..0021ed02b 100644 --- a/htdocs/js/GraphTool/circletool.js +++ b/htdocs/js/GraphTool/circletool.js @@ -16,9 +16,19 @@ fixed: true, highlight: false, strokeColor: gt.color.curve, - dash: solid ? 0 : 2 + dash: solid ? 0 : 2, + tabindex: '' }) ); + this.baseObj.setAttribute({ + aria: { + enabled: true, + label: this.constructor.ariaLabel, + roledescription: this.constructor.strId, + live: 'assertive', + atomic: true + } + }); this.definingPts.push(center, point); this.focusPoint = center; @@ -47,6 +57,14 @@ ); } + static ariaLabel(c) { + return ( + (c.getAttribute('dash') == 0 ? 'solid' : 'dashed') + + ` circle centered at ${c.center.X()}, ${c.center.Y()} and ` + + `passing through the point ${c.point2.X()}, ${c.point2.Y()}` + ); + } + static restore(string) { let pointData = gt.pointRegexp.exec(string); const points = []; @@ -116,7 +134,9 @@ highlight: false, snapSizeX: gt.snapSizeX, snapSizeY: gt.snapSizeY, - withLabel: false + withLabel: false, + tabindex: 0, + aria: gt.pointAria }); this.hlObjs.hl_point.rendNode.focus(); } @@ -129,7 +149,15 @@ fixed: true, strokeColor: gt.color.underConstruction, highlight: false, - dash: gt.drawSolid ? 0 : 2 + dash: gt.drawSolid ? 0 : 2, + tabindex: '', + aria: { + enabled: true, + label: gt.graphObjectTypes[this.object].ariaLabel, + roledescription: this.object, + live: 'assertive', + atomic: true + } }); } @@ -151,7 +179,9 @@ highlight: false, snapToGrid: true, snapSizeX: gt.snapSizeX, - snapSizeY: gt.snapSizeY + snapSizeY: gt.snapSizeY, + tabindex: '', + aria: gt.pointAria }); this.center.setAttribute({ fixed: true }); diff --git a/htdocs/js/GraphTool/cubictool.js b/htdocs/js/GraphTool/cubictool.js index 46b4e23b2..0bd133925 100644 --- a/htdocs/js/GraphTool/cubictool.js +++ b/htdocs/js/GraphTool/cubictool.js @@ -98,7 +98,19 @@ strokeWidth: 2, highlight: false, strokeColor: color ? color : gt.color.underConstruction, - dash: solid ? 0 : 2 + dash: solid ? 0 : 2, + tabindex: '', + aria: { + enabled: true, + label: (c) => + (c.getAttribute('dash') == 0 ? 'solid' : 'dashed') + + ` cubic passing through ${point1.X()}, ${point1.Y()}, and ` + + `${point2.X()}, ${point2.Y()}, and ${point3.X()}, ${point3.Y()}, ` + + `and ${point4.X()}, ${point4.Y()}`, + roledescription: 'cubic', + live: 'assertive', + atomic: true + } } ); } @@ -294,7 +306,9 @@ snapSizeX: gt.snapSizeX, snapSizeY: gt.snapSizeY, highlight: false, - withLabel: false + withLabel: false, + tabindex: '', + aria: gt.pointAria }); this.hlObjs.hl_point.rendNode.focus(); } @@ -341,7 +355,18 @@ fixed: true, strokeColor: gt.color.underConstruction, highlight: false, - dash: gt.drawSolid ? 0 : 2 + dash: gt.drawSolid ? 0 : 2, + tabindex: '', + aria: { + enabled: true, + label: (l) => + (l.getAttribute('dash') == 0 ? 'solid' : 'dashed') + + ` line through ${l.point1.X()}, ${l.point1.Y()}, ` + + `and ${l.point2.X()}, ${l.point2.Y()}`, + roledescription: 'line', + live: 'assertive', + atomic: true + } }); } diff --git a/htdocs/js/GraphTool/filltool.js b/htdocs/js/GraphTool/filltool.js index 3935edfbc..2c2331bc8 100644 --- a/htdocs/js/GraphTool/filltool.js +++ b/htdocs/js/GraphTool/filltool.js @@ -21,7 +21,15 @@ highlightStrokeOpacity: 0, fillOpacity: 0, highlightFillOpacity: 0, - fixed: gt.isStatic + fixed: gt.isStatic, + tabindex: 0, + aria: { + enabled: true, + label: (p) => `shade the region containing the point ${p.X()}, ${p.Y()}`, + roledescription: 'shading point', + live: 'assertive', + atomic: true + } }); this.definingPts.push(point); this.focusPoint = point; @@ -41,7 +49,15 @@ [() => point.X() - 12 / gt.board.unitX, () => point.Y() - 12 / gt.board.unitY], [() => 24 / gt.board.unitX, () => 24 / gt.board.unitY] ], - { withLabel: false, highlight: false, layer: 8, name: 'FillIcon', fixed: true } + { + withLabel: false, + highlight: false, + layer: 8, + name: 'FillIcon', + fixed: true, + tabindex: '', + aria: { enabled: true, hidden: true, live: 'off' } + } ); if (!gt.isStatic) { @@ -226,7 +242,21 @@ this.fillObj = gt.board.create( 'image', [dataURL, [bBox[0], bBox[3]], [bBox[2] - bBox[0], bBox[1] - bBox[3]]], - { withLabel: false, highlight: false, fixed: true, layer: 0 } + { + withLabel: false, + highlight: false, + fixed: true, + layer: 0, + tabindex: '', + aria: { + enabled: true, + label: () => + `shaded region containing the point ${this.baseObj.X()}, ${this.baseObj.Y()}`, + roledescription: 'shading', + live: 'assertive', + atomic: true + } + } ); }; @@ -343,7 +373,15 @@ withLabel: false, snapToGrid: true, snapSizeX: gt.snapSizeX, - snapSizeY: gt.snapSizeY + snapSizeY: gt.snapSizeY, + tabindex: 0, + aria: { + enabled: true, + label: (p) => `shade the region containing the point ${p.X()}, ${p.Y()}`, + roledescription: 'shading point', + live: 'assertive', + atomic: true + } }); this.hlObjs.hl_point.rendNode.classList.add('hidden-fill-point'); @@ -357,7 +395,14 @@ ], [() => 24 / gt.board.unitX, () => 24 / gt.board.unitY] ], - { withLabel: false, highlight: false, fixed: true, layer: 8 } + { + withLabel: false, + highlight: false, + fixed: true, + layer: 8, + tabindex: '', + aria: { enabled: true, hidden: true, live: 'off' } + } ); this.hlObjs.hl_point.rendNode.focus(); diff --git a/htdocs/js/GraphTool/graphtool.js b/htdocs/js/GraphTool/graphtool.js index 0991e15fe..7d7e0fb2b 100644 --- a/htdocs/js/GraphTool/graphtool.js +++ b/htdocs/js/GraphTool/graphtool.js @@ -1,4 +1,4 @@ -/* global JXG */ +/* global JXG, MathJax */ 'use strict'; @@ -14,6 +14,7 @@ window.graphTool = (containerId, options) => { setTimeout(() => window.graphTool(containerId, options), 100); return; } + gt.graphContainer.role = 'application'; // Semantic color control gt.color = { @@ -95,9 +96,11 @@ window.graphTool = (containerId, options) => { lastArrow: { size: 7 }, straightFirst: false, straightLast: false, - fixed: true + fixed: true, + tabindex: '', + aria: { enabled: true, hidden: true, live: 'off' } }, - grid: { majorStep: [gt.snapSizeX, gt.snapSizeY] }, + grid: { majorStep: [gt.snapSizeX, gt.snapSizeY], aria: { enabled: true, hidden: true, live: 'off' } }, keyboard: { enabled: true, dx: gt.snapSizeX, @@ -711,15 +714,20 @@ window.graphTool = (containerId, options) => { }, 100); }); + let currentContent = ''; + gt.setMessageText = (content) => { if (gt.confirmationActive || !gt.helpEnabled) return; const newMessage = content instanceof Array ? content.join(' ') : content; if (newMessage) { + if (currentContent === newMessage) return; + currentContent = newMessage; const par = document.createElement('p'); par.textContent = newMessage; gt.setMessageContent(par); } else { + currentContent = ''; gt.setMessageContent(); } }; @@ -777,6 +785,14 @@ window.graphTool = (containerId, options) => { gt.pointRegexp = /\( *(-?[0-9]*(?:\.[0-9]*)?), *(-?[0-9]*(?:\.[0-9]*)?) *\)/g; + gt.pointAria = { + enabled: true, + label: (p) => `point at ${p.X()}, ${p.Y()}`, + roledescription: 'point', + live: 'assertive', + atomic: true + }; + // This returns true if the points p1, p2, and p3 are colinear. // Note that p1 must be an array of two numbers, and p2 and p3 must be JSXGraph points. gt.areColinear = (p1, p2, p3) => { @@ -913,6 +929,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, + aria: gt.pointAria, ...gt.definingPointAttributes() }); point.setAttribute({ snapToGrid: true }); @@ -1514,12 +1531,8 @@ window.graphTool = (containerId, options) => { helpText() { return (gt.selectedObj && gt.selectedObj.supportsSolidDash) || (gt.activeTool && gt.activeTool.supportsSolidDash) - ? 'Use the ' + - '\\(\\rule[3px]{34px}{2px}\\) or ' + - '\\(\\rule[3px]{3px}{2px}' + - '\\hspace{4px}\\rule[3px]{4px}{2px}'.repeat(3) + - '\\hspace{4px}\\rule[3px]{3px}{2px}\\)' + - ' button or type s or d to make the selected object solid or dashed.' + ? 'Use the solid line or dashed line buttons or type s or d to ' + + 'make the selected object solid or dashed.' : ''; } } diff --git a/htdocs/js/GraphTool/intervaltools.js b/htdocs/js/GraphTool/intervaltools.js index 8fa9a720c..9946f7ae1 100644 --- a/htdocs/js/GraphTool/intervaltools.js +++ b/htdocs/js/GraphTool/intervaltools.js @@ -43,7 +43,41 @@ 0 ] ], - { fixed: true, highlight: false, strokeColor: gt.color.curve, strokeWidth: 4 } + { + fixed: true, + highlight: false, + strokeColor: gt.color.curve, + strokeWidth: 4, + tabindex: '', + aria: { + enabled: true, + label: () => { + const left = point1.X() < point2.X() ? point1 : point2; + const right = point1.X() < point2.X() ? point2 : point1; + const leftIsInf = gt.isNegInfX(left.X()); + const rightIsInf = gt.isPosInfX(right.X()); + return ( + 'interval that starts at ' + + (leftIsInf + ? '' + : left.getAttribute('fillColor') === 'transparent' + ? 'but does not include ' + : 'and includes ') + + (leftIsInf ? 'negative infinity' : left.X()) + + ' and ends at ' + + (rightIsInf + ? '' + : right.getAttribute('fillColor') === 'transparent' + ? 'but does not include ' + : 'and includes ') + + (rightIsInf ? 'infinity' : right.X()) + ); + }, + roledescription: 'interval', + live: 'assertive', + atomic: true + } + } ); // Redefine the segment's hasPoint method to return true if either of the end points has the @@ -310,7 +344,9 @@ strokeWidth: 4, strokeColor: 'transparent', highlight: false, - lastArrow: { type: 2, size: 4 } + lastArrow: { type: 2, size: 4 }, + tabindex: '', + aria: { enabled: true, hidden: true, live: 'off' } } ); this.definingPts[index].arrow.rendNodeTriangleEnd.setAttribute('fill', gt.color.curve); @@ -361,7 +397,20 @@ snapSizeX: gt.snapSizeX, snapSizeY: gt.snapSizeY, ...this.definingPointAttributes(), - ...this.maybeBracketAttributes() + ...this.maybeBracketAttributes(), + aria: { + enabled: true, + label: (p) => + gt.isNegInfX(p.X()) + ? 'end point negative infinity' + : gt.isPosInfX(p.X()) + ? 'end point infinity' + : (p.getAttribute('fillColor') === 'transparent' ? 'excluded' : 'included') + + ` end point ${p.X()}`, + roledescription: 'end point', + live: 'assertive', + atomic: true + } }); point.setAttribute({ snapToGrid: true }); @@ -407,7 +456,9 @@ display: 'internal', fixed: true, strokeColor: gt.color.focusCurve, - highlightStrokeColor: gt.color.pointHighlightDarker + highlightStrokeColor: gt.color.pointHighlightDarker, + tabindex: '', + aria: { enabled: true, hidden: true, live: 'off' } } ); @@ -507,9 +558,22 @@ snapSizeX: gt.snapSizeX, snapSizeY: gt.snapSizeY, withLabel: false, - ...gt.graphObjectTypes.interval.maybeBracketAttributes() + ...gt.graphObjectTypes.interval.maybeBracketAttributes(), + tabindex: '', + aria: { + enabled: true, + label: (p) => + gt.isNegInfX(p.X()) + ? 'end point negative infinity' + : gt.isPosInfX(p.X()) + ? 'end point infinity' + : (p.getAttribute('fillColor') === 'transparent' ? 'excluded' : 'included') + + ` end point ${p.X()}`, + roledescription: 'end point', + live: 'assertive', + atomic: true + } }); - this.point1.setAttribute({ fixed: true }); if (gt.options.useBracketEnds) { const point = this.point1; @@ -540,7 +604,9 @@ fixed: true, highlight: false, strokeColor: gt.color.underConstructionFixed, - cssStyle: 'cursor:none;font-weight:900' + cssStyle: 'cursor:none;font-weight:900', + tabindex: '', + aria: { enabled: true, hidden: true, live: 'off' } } ); } @@ -654,7 +720,22 @@ fillColor: gt.toolTypes.IncludeExcludePointTool.include ? gt.color.underConstruction : 'transparent', - ...gt.graphObjectTypes.interval.maybeBracketAttributes() + ...gt.graphObjectTypes.interval.maybeBracketAttributes(), + tabindex: 0, + aria: { + enabled: true, + label: (p) => + gt.isNegInfX(p.X()) + ? 'end point negative infinity' + : gt.isPosInfX(p.X()) + ? 'end point infinity' + : (p.getAttribute('fillColor') === 'transparent' + ? 'excluded' + : 'included') + ` end point ${p.X()}`, + roledescription: 'end point', + live: 'assertive', + atomic: true + } }); if (gt.options.useBracketEnds) { @@ -684,7 +765,9 @@ fixed: true, highlight: false, strokeColor: gt.color.underConstruction, - cssStyle: 'cursor:none;font-weight:900' + cssStyle: 'cursor:none;font-weight:900', + tabindex: '', + aria: { enabled: true, hidden: true, live: 'off' } } ); } @@ -730,7 +813,42 @@ fixed: true, strokeWidth: 5, strokeColor: gt.color.underConstruction, - highlight: false + highlight: false, + tabindex: '', + aria: { + enabled: true, + label: () => { + const left = + this.point1 && this.point1.X() < this.hlObjs.hl_point.X() + ? this.point1 + : this.hlObjs.hl_point; + const right = + !this.point1 || this.point1.X() < this.hlObjs.hl_point.X() + ? this.hlObjs.hl_point + : this.point1; + const leftIsInf = gt.isNegInfX(left.X()); + const rightIsInf = gt.isPosInfX(right.X()); + return ( + 'interval that starts at ' + + (leftIsInf + ? '' + : left.getAttribute('fillColor') === 'transparent' + ? 'but does not include ' + : 'and includes ') + + (leftIsInf ? 'negative infinity' : left.X()) + + ' and ends at ' + + (rightIsInf + ? '' + : right.getAttribute('fillColor') === 'transparent' + ? 'but does not include ' + : 'and includes ') + + (rightIsInf ? 'infinity' : right.X()) + ); + }, + roledescription: 'interval', + live: 'assertive', + atomic: true + } } ); // The default layer for lines (of which arrows are a part) is 7. @@ -778,7 +896,9 @@ strokeWidth: 5, strokeColor: 'transparent', highlight: false, - lastArrow: { type: 2, size: 4 } + lastArrow: { type: 2, size: 4 }, + tabindex: '', + aria: { enabled: true, hidden: true, live: 'off' } } ); this.hlObjs.hl_arrow.rendNodeTriangleEnd.setAttribute( diff --git a/htdocs/js/GraphTool/linetool.js b/htdocs/js/GraphTool/linetool.js index 9e97d1a3c..666039931 100644 --- a/htdocs/js/GraphTool/linetool.js +++ b/htdocs/js/GraphTool/linetool.js @@ -16,9 +16,19 @@ fixed: true, highlight: false, strokeColor: gt.color.curve, - dash: solid ? 0 : 2 + dash: solid ? 0 : 2, + tabindex: '' }) ); + this.baseObj.setAttribute({ + aria: { + enabled: true, + label: this.constructor.ariaLabel, + roledescription: this.constructor.strId, + live: 'assertive', + atomic: true + } + }); this.definingPts.push(point1, point2); this.focusPoint = point1; } @@ -38,7 +48,7 @@ return gt.sign(JXG.Math.innerProduct(point, this.baseObj.stdform)); } - hasPoint(point) { + onBoundary(point) { return ( Math.abs(JXG.Math.innerProduct(point, this.baseObj.stdform)) / Math.sqrt(this.baseObj.stdform[1] ** 2 + this.baseObj.stdform[2] ** 2) < @@ -46,6 +56,13 @@ ); } + static ariaLabel(l) { + return ( + (l.getAttribute('dash') == 0 ? 'solid' : 'dashed') + + ` line through ${l.point1.X()}, ${l.point1.Y()}, and ${l.point2.X()}, ${l.point2.Y()}` + ); + } + static restore(string) { let pointData = gt.pointRegexp.exec(string); const points = []; @@ -115,7 +132,9 @@ highlight: false, snapSizeX: gt.snapSizeX, snapSizeY: gt.snapSizeY, - withLabel: false + withLabel: false, + tabindex: 0, + aria: gt.pointAria }); this.hlObjs.hl_point.rendNode.focus(); } @@ -128,7 +147,15 @@ fixed: true, strokeColor: gt.color.underConstruction, highlight: false, - dash: gt.drawSolid ? 0 : 2 + dash: gt.drawSolid ? 0 : 2, + tabindex: '', + aria: { + enabled: true, + label: gt.graphObjectTypes[this.object].ariaLabel, + roledescription: this.object, + live: 'assertive', + atomic: true + } }); } @@ -151,7 +178,9 @@ highlight: false, snapToGrid: true, snapSizeX: gt.snapSizeX, - snapSizeY: gt.snapSizeY + snapSizeY: gt.snapSizeY, + tabindex: '', + aria: gt.pointAria }); this.point1.setAttribute({ fixed: true }); diff --git a/htdocs/js/GraphTool/parabolatool.js b/htdocs/js/GraphTool/parabolatool.js index e0d65cce5..b8a9ea6df 100644 --- a/htdocs/js/GraphTool/parabolatool.js +++ b/htdocs/js/GraphTool/parabolatool.js @@ -73,7 +73,18 @@ strokeWidth: 2, highlight: false, strokeColor: color ? color : gt.color.underConstruction, - dash: solid ? 0 : 2 + dash: solid ? 0 : 2, + tabindex: '', + aria: { + enabled: true, + label: (p) => + (p.getAttribute('dash') == 0 ? 'solid' : 'dashed') + + ` vertical parabola with vertex ${vertex.X()}, ${vertex.Y()} and ` + + `passing through the point ${point.X()}, ${point.Y()}`, + roledescription: 'vertical parabola', + live: 'assertive', + atomic: true + } } ); else @@ -91,7 +102,18 @@ strokeWidth: 2, highlight: false, strokeColor: color ? color : gt.color.underConstruction, - dash: solid ? 0 : 2 + dash: solid ? 0 : 2, + tabindex: '', + aria: { + enabled: true, + label: (p) => + (p.getAttribute('dash') == 0 ? 'solid' : 'dashed') + + ` horizontal parabola with vertex ${vertex.X()}, ${vertex.Y()} and ` + + `passing through the point ${point.X()}, ${point.Y()}`, + roledescription: 'horizontal parabola', + live: 'assertive', + atomic: true + } } ); } @@ -161,7 +183,9 @@ snapSizeX: gt.snapSizeX, snapSizeY: gt.snapSizeY, highlight: false, - withLabel: false + withLabel: false, + tabindex: 0, + aria: gt.pointAria }); this.hlObjs.hl_point.rendNode.focus(); } @@ -197,7 +221,9 @@ highlight: false, snapToGrid: true, snapSizeX: gt.snapSizeX, - snapSizeY: gt.snapSizeY + snapSizeY: gt.snapSizeY, + tabindex: '', + aria: gt.pointAria }); this.vertex.setAttribute({ fixed: true }); diff --git a/htdocs/js/GraphTool/pointtool.js b/htdocs/js/GraphTool/pointtool.js index eea92d4fd..4b76c3920 100644 --- a/htdocs/js/GraphTool/pointtool.js +++ b/htdocs/js/GraphTool/pointtool.js @@ -23,7 +23,8 @@ fixed: gt.isStatic, highlightStrokeColor: gt.color.underConstruction, highlightFillColor: gt.color.pointHighlight, - tabindex: gt.isStatic ? -1 : 0 + tabindex: gt.isStatic ? -1 : 0, + aria: gt.pointAria }) ); @@ -160,7 +161,9 @@ highlight: false, snapSizeX: gt.snapSizeX, snapSizeY: gt.snapSizeY, - withLabel: false + withLabel: false, + tabindex: 0, + aria: gt.pointAria }); this.hlObjs.hl_point.rendNode.focus(); } diff --git a/htdocs/js/GraphTool/quadratictool.js b/htdocs/js/GraphTool/quadratictool.js index bef7eb289..c25355f5c 100644 --- a/htdocs/js/GraphTool/quadratictool.js +++ b/htdocs/js/GraphTool/quadratictool.js @@ -82,7 +82,18 @@ strokeWidth: 2, highlight: false, strokeColor: color ? color : gt.color.underConstruction, - dash: solid ? 0 : 2 + dash: solid ? 0 : 2, + tabindex: '', + aria: { + enabled: true, + label: (q) => + (q.getAttribute('dash') == 0 ? 'solid' : 'dashed') + + ` quadratic passing through ${point1.X()}, ${point1.Y()}, and ` + + `${point2.X()}, ${point2.Y()}, and ${point3.X()}, ${point3.Y()}`, + roledescription: 'quadratic', + live: 'assertive', + atomic: true + } } ); } @@ -133,7 +144,9 @@ size: 2, snapSizeX: gt.snapSizeX, snapSizeY: gt.snapSizeY, - withLabel: false + withLabel: false, + tabindex: '', + aria: gt.pointAria } ); point.setAttribute({ snapToGrid: true }); @@ -311,7 +324,9 @@ snapSizeX: gt.snapSizeX, snapSizeY: gt.snapSizeY, highlight: false, - withLabel: false + withLabel: false, + tabindex: 0, + aria: gt.pointAria }); this.hlObjs.hl_point.rendNode.focus(); } @@ -343,7 +358,18 @@ fixed: true, strokeColor: gt.color.underConstruction, highlight: false, - dash: gt.drawSolid ? 0 : 2 + dash: gt.drawSolid ? 0 : 2, + tabindex: '', + aria: { + enabled: true, + label: (l) => + (l.getAttribute('dash') == 0 ? 'solid' : 'dashed') + + ` line through ${l.point1.X()}, ${l.point1.Y()}, ` + + `and ${l.point2.X()}, ${l.point2.Y()}`, + roledescription: 'line', + live: 'assertive', + atomic: true + } }); } diff --git a/htdocs/js/GraphTool/quadrilateral.js b/htdocs/js/GraphTool/quadrilateral.js index b9211b968..4ad9cf554 100644 --- a/htdocs/js/GraphTool/quadrilateral.js +++ b/htdocs/js/GraphTool/quadrilateral.js @@ -94,7 +94,7 @@ return -1; } - onBoundary(point, aVal, _from) { + onBoundary(point, aVal) { if (this.fillCmp(point) != aVal) return true; for (const border of this.baseObj.borders) { @@ -284,7 +284,9 @@ size: 2, snapSizeX: gt.snapSizeX, snapSizeY: gt.snapSizeY, - withLabel: false + withLabel: false, + tabindex: '', + aria: gt.pointAria } ); point.setAttribute({ snapToGrid: true }); @@ -344,7 +346,9 @@ highlight: false, snapToGrid: true, snapSizeX: gt.snapSizeX, - snapSizeY: gt.snapSizeY + snapSizeY: gt.snapSizeY, + tabindex: '', + aria: gt.pointAria }); this.point1.setAttribute({ fixed: true }); @@ -439,7 +443,7 @@ if (count == 0) { if (vDir != 0) ++times; count = times; - [hDir, vDir] = [!!hDir ? 0 : -vDir, !!vDir ? 0 : hDir]; + [hDir, vDir] = [hDir ? 0 : -vDir, vDir ? 0 : hDir]; } newX += hDir * gt.snapSizeX; newY += vDir * gt.snapSizeY; @@ -544,7 +548,9 @@ highlight: false, snapSizeX: gt.snapSizeX, snapSizeY: gt.snapSizeY, - withLabel: false + withLabel: false, + tabindex: 0, + aria: gt.pointAria }); this.hlObjs.hl_point.rendNode.focus(); } @@ -584,7 +590,18 @@ highlight: false, dash: gt.drawSolid ? 0 : 2, straightFirst: false, - straightLast: false + straightLast: false, + tabindex: '', + aria: { + enabled: true, + label: (l) => + (l.getAttribute('dash') == 0 ? 'solid' : 'dashed') + + ` line segment between ${l.point1.X()}, ${l.point1.Y()} ` + + `and ${l.point2.X()}, ${l.point2.Y()}`, + roledescription: 'line', + live: 'assertive', + atomic: true + } }); } diff --git a/htdocs/js/GraphTool/segments.js b/htdocs/js/GraphTool/segments.js index 48e2c2fef..ed472d646 100644 --- a/htdocs/js/GraphTool/segments.js +++ b/htdocs/js/GraphTool/segments.js @@ -1,4 +1,4 @@ -/* global graphTool */ +/* global graphTool, JXG */ 'use strict'; @@ -59,6 +59,13 @@ 0.5 / Math.sqrt(gt.board.unitX * gt.board.unitY) ); } + + static ariaLabel(l) { + return ( + (l.getAttribute('dash') == 0 ? 'solid' : 'dashed') + + ` segment between ${l.point1.X()}, ${l.point1.Y()} and ${l.point2.X()}, ${l.point2.Y()}` + ); + } }; }, @@ -97,6 +104,14 @@ super(point1, point2, solid); this.baseObj.setArrow(false, { type: 1, size: 4 }); } + + static ariaLabel(l) { + return ( + (l.getAttribute('dash') == 0 ? 'solid' : 'dashed') + + ` vector with initial point ${l.point1.X()}, ${l.point1.Y()} ` + + `and terminal point ${l.point2.X()}, ${l.point2.Y()}` + ); + } }; }, diff --git a/htdocs/js/GraphTool/sinewavetool.js b/htdocs/js/GraphTool/sinewavetool.js index a46e597a1..b7ec48d60 100644 --- a/htdocs/js/GraphTool/sinewavetool.js +++ b/htdocs/js/GraphTool/sinewavetool.js @@ -104,7 +104,25 @@ strokeWidth: 2, highlight: false, strokeColor: color ? color : gt.color.underConstruction, - dash: solid ? 0 : 2 + dash: solid ? 0 : 2, + tabindex: '', + aria: { + enabled: true, + label: (s) => + (s.getAttribute('dash') == 0 ? 'solid' : 'dashed') + + ` sine wave shifted ${Math.abs(point.X())} units to the ${ + point.X() < 0 ? 'left' : 'right' + }, shifted ${Math.abs(point.Y())} units ${ + point.X() < 0 ? 'downward' : 'upward' + }, with period ${ + (2 * Math.PI) / Math.abs(period()) + }, with amplitude ${Math.abs(amplitude())}` + + (period() < 0 ? ', sine wave horizontally reflected' : '') + + (amplitude() < 0 ? ', sine wave vertically reflected' : ''), + roledescription: 'sine wave', + live: 'assertive', + atomic: true + } } ); } @@ -199,7 +217,24 @@ size: 2, snapSizeX: periodPoint ? 1e-10 : gt.snapSizeX, snapSizeY: shiftPoint && !periodPoint ? 1e-10 : gt.snapSizeY, - withLabel: false + withLabel: false, + tabindex: '', + aria: { + enabled: true, + label: (p) => + periodPoint + ? `sine wave amplitude ${Math.abs(p.Y() - shiftPoint.Y())}` + + (p.Y() > shiftPoint.Y() ? '' : ', sine wave vertically reflected') + : shiftPoint + ? `sine wave period ${Math.abs(p.X() - shiftPoint.X())}` + + (p.X() > shiftPoint.X() ? '' : ', sine wave horizontally reflected') + : `sine wave shifted ${Math.abs(p.X())} units to the ${ + p.X() < 0 ? 'left' : 'right' + }, and shifted ${Math.abs(p.Y())} units ${p.Y() < 0 ? 'downward' : 'upward'}`, + roledescription: () => (periodPoint ? 'amplitude' : shiftPoint ? 'period' : 'shift'), + live: 'assertive', + atomic: true + } }); point.setAttribute({ snapToGrid: true }); @@ -376,7 +411,29 @@ snapSizeX: gt.snapSizeX, snapSizeY: gt.snapSizeY, highlight: false, - withLabel: false + withLabel: false, + tabindex: 0, + aria: { + enabled: true, + label: (p) => + this.periodPoint + ? `sine wave amplitude ${Math.abs(p.Y() - this.shiftPoint.Y())}` + + (p.Y() > this.shiftPoint.Y() ? '' : ', sine wave vertically reflected') + : this.shiftPoint + ? `sine wave period ${Math.abs(p.X() - this.shiftPoint.X())}` + + (p.X() > this.shiftPoint.X() + ? '' + : ', sine wave horizontally reflected') + : `sine wave shifted ${Math.abs(p.X())} units to the ${ + p.X() < 0 ? 'left' : 'right' + }, and shifted ${Math.abs(p.Y())} units ${ + p.Y() < 0 ? 'downward' : 'upward' + }`, + roledescription: () => + this.periodPoint ? 'amplitude' : this.shiftPoint ? 'period' : 'shift', + live: 'assertive', + atomic: true + } }); this.hlObjs.hl_point.rendNode.focus(); } diff --git a/htdocs/js/GraphTool/triangle.js b/htdocs/js/GraphTool/triangle.js index 757b5eb66..a8e95948b 100644 --- a/htdocs/js/GraphTool/triangle.js +++ b/htdocs/js/GraphTool/triangle.js @@ -82,7 +82,7 @@ return -1; } - onBoundary(point, aVal, _from) { + onBoundary(point, aVal) { if (this.fillCmp(point) != aVal) return true; for (const border of this.baseObj.borders) { @@ -122,7 +122,7 @@ } static createPolygon(points, solid, color) { - return gt.board.create('polygon', points, { + const polygon = gt.board.create('polygon', points, { highlight: false, fillOpacity: 0, fixed: true, @@ -132,8 +132,31 @@ fixed: true, strokeColor: color ? color : gt.color.underConstruction, dash: solid ? 0 : 2 + }, + tabindex: '', + aria: { + enabled: true, + label: (t) => + (t.borders[0].getAttribute('dash') == 0 ? 'solid ' : 'dashed ') + + (t.vertices.length === 4 + ? 'triangle' + : t.vertices.length == 5 + ? 'quadrilateral' + : 'polygon') + + ' with vertices ' + + t.vertices + .slice(0, -1) + .map((p) => `${p.X()}, ${p.Y()}`) + .join(', and '), + roledescription: 'polygon', + live: 'assertive', + atomic: true } }); + for (const border of polygon.borders) { + border.setAttribute({ tabindex: '', aria: { enabled: true, hidden: true, live: 'off' } }); + } + return polygon; } // Prevent a point from being moved off the board by a drag. If one or two other points are @@ -227,7 +250,9 @@ size: 2, snapSizeX: gt.snapSizeX, snapSizeY: gt.snapSizeY, - withLabel: false + withLabel: false, + tabindex: '', + aria: gt.pointAria } ); point.setAttribute({ snapToGrid: true }); @@ -284,7 +309,9 @@ highlight: false, snapToGrid: true, snapSizeX: gt.snapSizeX, - snapSizeY: gt.snapSizeY + snapSizeY: gt.snapSizeY, + tabindex: '', + aria: gt.pointAria }); this.point1.setAttribute({ fixed: true }); @@ -414,7 +441,9 @@ highlight: false, snapSizeX: gt.snapSizeX, snapSizeY: gt.snapSizeY, - withLabel: false + withLabel: false, + tabindex: 0, + aria: gt.pointAria }); this.hlObjs.hl_point.rendNode.focus(); } @@ -446,7 +475,18 @@ highlight: false, dash: gt.drawSolid ? 0 : 2, straightFirst: false, - straightLast: false + straightLast: false, + tabindex: '', + aria: { + enabled: true, + label: (l) => + (l.getAttribute('dash') == 0 ? 'solid' : 'dashed') + + ` line segment between ${l.point1.X()}, ${l.point1.Y()} ` + + `and ${l.point2.X()}, ${l.point2.Y()}`, + roledescription: 'line', + live: 'assertive', + atomic: true + } }); }