diff --git a/docs/Api_Methods.md b/docs/Api_Methods.md
index 084afc4d3..8b2a99e9e 100644
--- a/docs/Api_Methods.md
+++ b/docs/Api_Methods.md
@@ -252,6 +252,10 @@ mathField.typedText('x=-b\\pm \\sqrt b^2 -4ac');
Specify an [ARIA label][`aria-label`] for this field, for screen readers. The actual [`aria-label`] includes this label followed by the math content of the field as speech. Default: `'Math Input'`
+## .setTabbable(tabbable)
+
+Specify whether this field should be in the tab order.
+
## .getAriaLabel()
Returns the [ARIA label][`aria-label`] for this field, for screen readers. If no ARIA label has been specified, `'Math Input'` is returned.
diff --git a/docs/Config.md b/docs/Config.md
index e338a805f..ecbb5b4e5 100644
--- a/docs/Config.md
+++ b/docs/Config.md
@@ -14,9 +14,8 @@ The configuration options object is of the following form:
autoCommands: 'pi theta sqrt sum',
autoOperatorNames: 'sin cos',
maxDepth: 10,
- substituteTextarea: function(tabbable) {
+ substituteTextarea: function() {
const textarea = document.createElement('textarea');
- textarea.setAttribute('tabindex', tabbable ? '0' : '-1');
return textarea;
},
handlers: {
@@ -125,8 +124,6 @@ You can also specify a speech-friendly representation of the operator name by su
`substituteTextarea` is a function that creates a focusable DOM element that is called when setting up a math field. Overwriting this may be useful for hacks like suppressing built-in virtual keyboards. It defaults to ``.
For example, [Desmos](https://www.desmos.com/calculator) substitutes `` to suppress the native virtual keyboard in favor of a custom math keypad that calls the MathQuill API. On old iOS versions that don't support `inputmode=none`, it uses `` to suppress the native virtual keyboard, at the cost of bluetooth keyboards not working.
-The `substituteTextarea` takes one argument, a boolean `tabbable` that is true for editable math fields and for static math fields configured with `{tabbable: true}`. The textarea is permanently mounted to the page, so it should have `tabindex=-1` if `tabbable` is false.
-
## tabbable
For static and editable math fields, when `tabbable` is false, the math field is not part of the page's tab order. Despite that, the math field can still be focused when selected by a mouse.
diff --git a/src/mathquill.d.ts b/src/mathquill.d.ts
index 0db653079..4972bf6b2 100644
--- a/src/mathquill.d.ts
+++ b/src/mathquill.d.ts
@@ -40,6 +40,7 @@ declare namespace MathQuill {
latex(latex: string): this;
latex(): string;
setAriaLabel(str: string): this;
+ setTabbable(tabbable: boolean): this;
blur(): this;
focus(): this;
select(): this;
diff --git a/src/publicapi.ts b/src/publicapi.ts
index 787e1aa4e..963fc508e 100644
--- a/src/publicapi.ts
+++ b/src/publicapi.ts
@@ -89,7 +89,7 @@ class Options {
constructor(public version: 1 | 2 | 3) {}
ignoreNextMousedown: (_el: MouseEvent) => boolean;
- substituteTextarea: (tabbable?: boolean) => HTMLElement;
+ substituteTextarea: () => HTMLElement;
/** Only used in interface versions 1 and 2. */
substituteKeyboardEvents: SubstituteKeyboardEvents;
@@ -304,12 +304,17 @@ function getInterface(v: number): MathQuill.v3.API | MathQuill.v1.API {
}
setAriaLabel(ariaLabel: string) {
+ if (ariaLabel === this.__controller.getAriaLabel()) return this;
this.__controller.setAriaLabel(ariaLabel);
return this;
}
getAriaLabel() {
return this.__controller.getAriaLabel();
}
+ setTabbable(tabbable: boolean) {
+ this.__controller.setTabbable(tabbable);
+ return this;
+ }
config(opts: ConfigOptions) {
config(this.__options, opts);
return this;
diff --git a/src/services/textarea.ts b/src/services/textarea.ts
index 702f20141..c3de3b315 100644
--- a/src/services/textarea.ts
+++ b/src/services/textarea.ts
@@ -2,14 +2,13 @@
* Manage the MathQuill instance's textarea
* (as owned by the Controller)
********************************************/
-Options.prototype.substituteTextarea = function (tabbable?: boolean) {
+Options.prototype.substituteTextarea = function () {
return h('textarea', {
autocapitalize: 'off',
autocomplete: 'off',
autocorrect: 'off',
spellcheck: false,
'x-palm-disable-ste-all': true,
- tabindex: tabbable ? undefined : '-1'
});
};
@@ -30,22 +29,16 @@ Options.prototype.substituteKeyboardEvents = defaultSubstituteKeyboardEvents;
class Controller extends Controller_scrollHoriz {
selectFn: (text: string) => void = noop;
+ wasTabbable: boolean | undefined;
+
createTextarea() {
this.textareaSpan = h('span', { class: 'mq-textarea' });
- const tabbable =
- this.options.tabbable !== undefined
- ? this.options.tabbable
- : this.KIND_OF_MQ !== 'StaticMath';
-
- const textarea = this.options.substituteTextarea(tabbable);
+ const textarea = this.options.substituteTextarea();
if (!textarea.nodeType) {
throw 'substituteTextarea() must return a DOM element, got ' + textarea;
}
- if (!this.options.tabbable && this.KIND_OF_MQ === 'StaticMath') {
- // aria-hide noninteractive textarea element for static math
- textarea.setAttribute('aria-hidden', 'true');
- }
+
this.textarea = domFrag(textarea)
.appendTo(this.textareaSpan)
.oneElement() as HTMLTextAreaElement;
@@ -61,14 +54,27 @@ class Controller extends Controller_scrollHoriz {
if (this.mathspeakId) {
textarea?.setAttribute('aria-labelledby', this.mathspeakId);
}
- if (tabbable && this.mathspeakSpan) {
- this.mathspeakSpan.setAttribute('aria-hidden', 'true');
- }
var ctrlr = this;
ctrlr.cursor.selectionChanged = function () {
ctrlr.selectionChanged();
};
+
+ const tabbable =
+ this.options.tabbable !== undefined
+ ? this.options.tabbable
+ : this.KIND_OF_MQ !== 'StaticMath';
+
+ this.setTabbable(tabbable);
+ }
+
+ setTabbable(tabbable: boolean) {
+ if (tabbable === this.wasTabbable) return;
+ this.wasTabbable = tabbable;
+
+ this.textarea?.setAttribute('tabindex', tabbable ? '0' : '-1');
+ this.textarea?.setAttribute('aria-hidden', !this.options.tabbable && this.KIND_OF_MQ === 'StaticMath' ? 'true' : 'false');
+ this.mathspeakSpan?.setAttribute('aria-hidden', tabbable ? 'true' : 'false');
}
selectionChanged() {