Skip to content

Commit

Permalink
Add "tabbable" option
Browse files Browse the repository at this point in the history
For StaticMath, this defaults to "false"
For MathField, this defaults to "true"

This also adds a "focus" method to StaticMath, which
automatically selects the contents
  • Loading branch information
eluberoff committed Sep 15, 2024
1 parent 107e2c1 commit 7628c5a
Show file tree
Hide file tree
Showing 10 changed files with 102 additions and 36 deletions.
6 changes: 6 additions & 0 deletions docs/Config.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,12 @@ 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 `<textarea autocorrect=off .../>`.
For example, [Desmos](https://www.desmos.com/calculator) substitutes `<span tabindex=0></span>` on iOS to suppress the built-in virtual keyboard in favor of a custom math keypad that calls the MathQuill API. Unfortunately there's no universal [check for a virtual keyboard](http://stackoverflow.com/q/2593139/362030) or [way to detect a touchscreen](http://www.stucox.com/blog/you-cant-detect-a-touchscreen/), and even if you could, a touchscreen ≠ virtual keyboard (Windows 8 and ChromeOS devices have both physical keyboards and touchscreens and iOS and Android devices can have Bluetooth keyboards). Desmos currently sniffs the user agent for iOS, so Bluetooth keyboards just don't work in Desmos on iOS. The tradeoffs are up to you.

## 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.

Static math fields default to `tabbable: false`, Editable math fields default to `tabbable:true`.

# Handlers

Handlers are called after a specified event. They are called directly on the `handlers` object passed in, preserving the `this` value, so you can do stuff like:
Expand Down
7 changes: 5 additions & 2 deletions src/mathquill.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ declare namespace MathQuill {
html: () => string;
mathspeak: () => string;
text(): string;
blur: () => void;
focus: () => void;
}

interface EditableMathQuill {
Expand Down Expand Up @@ -120,6 +122,7 @@ declare namespace MathQuill {
typingSlashWritesDivisionSymbol?: boolean;
typingPercentWritesPercentOf?: boolean;
resetCursorOnBlur?: boolean | undefined;
tabbable?: boolean;
leftRightIntoCmdGoes?: 'up' | 'down';
enableDigitGrouping?: boolean;
tripleDotsAreEllipsis?: boolean;
Expand Down Expand Up @@ -178,6 +181,8 @@ declare namespace MathQuill {
html: () => string;
mathspeak: () => string;
text(): string;
blur: () => void;
focus: () => void;
}

interface EditableMathQuill extends BaseMathQuill {
Expand All @@ -188,8 +193,6 @@ declare namespace MathQuill {
keystroke: (key: string, evt?: KeyboardEvent) => void;
typedText: (text: string) => void;
clearSelection: () => void;
blur: () => void;
focus: () => void;
getAriaPostLabel: () => string;
setAriaPostLabel: (str: string, timeout?: number) => void;
ignoreNextMousedown: (func: () => boolean) => void;
Expand Down
30 changes: 16 additions & 14 deletions src/publicapi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ class Options {
constructor(public version: 1 | 2 | 3) {}

ignoreNextMousedown: (_el: MouseEvent) => boolean;
substituteTextarea: () => HTMLElement;
substituteTextarea: (tabbable?: boolean) => HTMLElement;
/** Only used in interface versions 1 and 2. */
substituteKeyboardEvents: SubstituteKeyboardEvents;

Expand All @@ -106,6 +106,7 @@ class Options {
leftRightIntoCmdGoes?: 'up' | 'down';
enableDigitGrouping?: boolean;
tripleDotsAreEllipsis?: boolean;
tabbable?: boolean;
mouseEvents?: boolean;
maxDepth?: number;
disableCopyPaste?: boolean;
Expand Down Expand Up @@ -334,14 +335,6 @@ function getInterface(v: number): MathQuill.v3.API | MathQuill.v1.API {
selection() {
return this.__controller.exportLatexSelection();
}
select() {
this.__controller.selectAll();
return this;
}
clearSelection() {
this.__controller.cursor.clearSelection();
return this;
}
html() {
return this.__controller.root
.domFrag()
Expand All @@ -358,6 +351,15 @@ function getInterface(v: number): MathQuill.v3.API | MathQuill.v1.API {
});
return this;
}
focus() {
this.__controller.getTextareaOrThrow().focus();
if (this.__controller.editable) this.__controller.scrollHoriz();
return this;
}
blur() {
this.__controller.getTextareaOrThrow().blur();
return this;
}
}

abstract class EditableField
Expand All @@ -371,15 +373,15 @@ function getInterface(v: number): MathQuill.v3.API | MathQuill.v1.API {
this.__controller.editablesTextareaEvents();
return this;
}
focus() {
this.__controller.getTextareaOrThrow().focus();
this.__controller.scrollHoriz();
select() {
this.__controller.selectAll();
return this;
}
blur() {
this.__controller.getTextareaOrThrow().blur();
clearSelection() {
this.__controller.cursor.clearSelection();
return this;
}

write(latex: string) {
this.__controller.writeLatex(latex);
this.__controller.scrollHoriz();
Expand Down
12 changes: 4 additions & 8 deletions src/services/focusBlur.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,18 +71,14 @@ class Controller_focusBlur extends Controller_exportText {
};

private handleTextareaFocusStatic = () => {
if (!this.cursor.selection || this.cursor.selection.isCleared()) {
this.cursor.controller.selectAll();
}
this.blurred = false;
};

private handleTextareaBlurStatic = () => {
if (this.cursor.selection) {
this.cursor.selection.clear();
}
//detaching during blur explodes in WebKit
setTimeout(() => {
domFrag(this.getTextareaSpanOrThrow()).detach();
this.blurred = true;
});
this.cursor.selection?.clear();
};

private handleWindowBlur = () => {
Expand Down
6 changes: 0 additions & 6 deletions src/services/mouse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@ class Controller_mouse extends Controller_latex {
var ctrlr = root.controller,
cursor = ctrlr.cursor,
blink = cursor.blink;
var textareaSpan = ctrlr.getTextareaSpanOrThrow();
var textarea = ctrlr.getTextareaOrThrow();

e.preventDefault(); // doesn't work in IE≤8, but it's a one-line fix:
Expand Down Expand Up @@ -91,8 +90,6 @@ class Controller_mouse extends Controller_latex {
if (ctrlr.editable) {
cursor.show();
cursor.controller.aria.queue(cursor.parent).alert();
} else {
domFrag(textareaSpan).detach();
}
}

Expand All @@ -118,9 +115,6 @@ class Controller_mouse extends Controller_latex {
};

if (ctrlr.blurred) {
if (rootElement && !ctrlr.editable) {
domFrag(rootElement).prepend(domFrag(textareaSpan));
}
textarea.focus();
// focus call may bubble to clients, who may then write to
// mathquill, triggering cancelSelectionOnEdit. If that happens, we
Expand Down
14 changes: 11 additions & 3 deletions src/services/textarea.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@
* Manage the MathQuill instance's textarea
* (as owned by the Controller)
********************************************/
Options.prototype.substituteTextarea = function () {
Options.prototype.substituteTextarea = function (tabbable?: boolean) {
return h('textarea', {
autocapitalize: 'off',
autocomplete: 'off',
autocorrect: 'off',
spellcheck: false,
'x-palm-disable-ste-all': true
'x-palm-disable-ste-all': true,
tabindex: tabbable ? undefined : '-1'
});
};
function defaultSubstituteKeyboardEvents(jq: $, controller: Controller) {
Expand All @@ -21,7 +22,13 @@ class Controller extends Controller_scrollHoriz {

createTextarea() {
this.textareaSpan = h('span', { class: 'mq-textarea' });
const textarea = this.options.substituteTextarea();

const tabbable =
this.options.tabbable !== undefined
? this.options.tabbable
: this.KIND_OF_MQ !== 'StaticMath';

const textarea = this.options.substituteTextarea(tabbable);
if (!textarea.nodeType) {
throw 'substituteTextarea() must return a DOM element, got ' + textarea;
}
Expand Down Expand Up @@ -180,6 +187,7 @@ class Controller extends Controller_scrollHoriz {
setupStaticField() {
this.mathspeakSpan = h('span', { class: 'mq-mathspeak' });
domFrag(this.container).prepend(domFrag(this.mathspeakSpan));
domFrag(this.container).prepend(domFrag(this.textareaSpan));
this.updateMathspeak();
this.blurred = true;
this.cursor.hide().parent.blur(this.cursor);
Expand Down
1 change: 1 addition & 0 deletions test/basic.html
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ <h1>
});
});
var mq = MQ.MathField($('#basic')[0], {
tabbable: false,
autoSubscriptNumerals: true,
autoCommands:
'alpha beta sqrt theta phi pi tau nthroot cbrt prod int ans percent mid square',
Expand Down
12 changes: 11 additions & 1 deletion test/demo.html
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,13 @@ <h1>
>.
</p>

<p>
On the other hand, you can make static math tabbable to appear in the
tab order despite being non-editable. The entire range is selected when
tabbed into:
<span class="static-math-tabbable">1.234\times 10^{8}</span>.
</p>

<p>
Note that if you're only rendering static math,
<a href="http://mathjax.org">MathJax</a> supports more of LaTeX and
Expand All @@ -118,7 +125,7 @@ <h1>
<p>
In many applications, such as a chat client, you probably type mostly
normal text with some math interspersed, so there is also a MathQuill
textbox that let's you type math between $'s:
textbox that lets you type math between $'s:
<span class="mathquill-text-field"
>The Quadratic Equation is $x=\frac{-b\pm\sqrt{b^2-4ac}}{2a}$</span
>
Expand Down Expand Up @@ -161,6 +168,9 @@ <h1>
$('.static-math-no-mouse-events').each(function () {
MQ.StaticMath(this, { mouseEvents: false });
});
$('.static-math-tabbable').each(function () {
MQ.StaticMath(this, { tabbable: true });
});
$('.mathquill-math-field').each(function () {
MQ.MathField(this);
});
Expand Down
8 changes: 6 additions & 2 deletions test/unit/aria.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,12 @@ suite('aria', function () {
test('MathQuillMathField aria-hidden', function () {
var staticMath = MQ.StaticMath(container);
staticMath.latex('1+\\sqrt{\\MathQuillMathField{x^2+y^2}}+\\frac{1}{x}');
var textArea = $(container).find('textarea');
assert.equal(textArea.length, 1, 'One text area for inner editable field');
assert.equal(
$(container).find('textarea').length,
2,
'Two text area for inner editable field'
);
var textArea = $(container).find('textarea:eq(0)');
assert.equal(
textArea.closest('[aria-hidden]="true"').length,
0,
Expand Down
42 changes: 42 additions & 0 deletions test/unit/focusBlur.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -102,4 +102,46 @@ suite('focusBlur', function () {
done();
});
});

test('full range selected on focusing tabbable static math', function () {
var mq = MQ.StaticMath(
$('<span>1234\\times 10^{23}</span>').appendTo('#mock')[0],
{ tabbable: true }
);

mq.focus();

assertHasFocus(mq, 'math field');
assert.equal(
mq.selection().latex,
'1234\\times10^{23}',
'full textarea selected'
);

assert.equal($(document.activeElement).attr('tabindex'), 0);

mq.blur();
assertHasFocus(mq, 'math field', 'not');
});

test('full range selected on focusing un-tabbable static math', function () {
var mq = MQ.StaticMath(
$('<span>1234\\times 10^{23}</span>').appendTo('#mock')[0]
);

mq.focus();

assertHasFocus(mq, 'math field');
assert.equal(
mq.selection().latex,
'1234\\times10^{23}',
'full textarea selected'
);

console.log(document.activeElement);
assert.equal($(document.activeElement).attr('tabindex'), '-1');

mq.blur();
assertHasFocus(mq, 'math field', 'not');
});
});

0 comments on commit 7628c5a

Please sign in to comment.