diff --git a/src/commands/math.ts b/src/commands/math.ts index 836870990..a7abddee3 100644 --- a/src/commands/math.ts +++ b/src/commands/math.ts @@ -89,6 +89,7 @@ class DOMView { * Descendant commands are organized into blocks. */ class MathCommand extends MathElement { + parent: MQNode; replacedFragment: Fragment | undefined; protected domView: DOMView; protected ends: Ends; @@ -465,7 +466,7 @@ function bindBinaryOperator( * symbols and operators that descend (in the Math DOM tree) from * ancestor operators. */ -class MathBlock extends MathElement { +class MathBlockOrRootBlock extends MathElement { controller?: Controller; join(methodName: JoinMethod) { @@ -547,32 +548,6 @@ class MathBlock extends MathElement { return super.keystroke(key, e, ctrlr); } - // editability methods: called by the cursor for editing, cursor movements, - // and selection of the MathQuill tree, these all take in a direction and - // the cursor - moveOutOf(dir: Direction, cursor: Cursor, updown?: 'up' | 'down') { - var updownInto: NodeRef | undefined; - if (updown === 'up') { - updownInto = this.parent.upInto; - } else if (updown === 'down') { - updownInto = this.parent.downInto; - } - - if (!updownInto && this[dir]) { - const otherDir = -dir as Direction; - cursor.insAtDirEnd(otherDir, this[dir] as MQNode); - cursor.controller.aria.queueDirEndOf(otherDir).queue(cursor.parent, true); - } else { - cursor.insDirOf(dir, this.parent); - cursor.controller.aria.queueDirOf(dir).queue(this.parent); - } - } - selectOutOf(dir: Direction, cursor: Cursor) { - cursor.insDirOf(dir, this.parent); - } - deleteOutOf(_dir: Direction, cursor: Cursor) { - cursor.unwrapGramp(); - } seek(clientX: number, cursor: Cursor) { var node = this.getEnd(R); if (!node) return cursor.insAtRightEnd(this); @@ -674,6 +649,38 @@ class MathBlock extends MathElement { } } +class MathBlock extends MathBlockOrRootBlock { + parent: MQNode; + + // editability methods: called by the cursor for editing, cursor movements, + // and selection of the MathQuill tree, these all take in a direction and + // the cursor + moveOutOf(dir: Direction, cursor: Cursor, updown?: 'up' | 'down') { + var updownInto: NodeRef | undefined; + if (updown === 'up') { + updownInto = this.parent.upInto; + } else if (updown === 'down') { + updownInto = this.parent.downInto; + } + + if (!updownInto && this[dir]) { + const otherDir = -dir as Direction; + cursor.insAtDirEnd(otherDir, this[dir] as MQNode); + cursor.controller.aria.queueDirEndOf(otherDir).queue(cursor.parent, true); + } else { + cursor.insDirOf(dir, this.parent); + cursor.controller.aria.queueDirOf(dir).queue(this.parent); + } + } + selectOutOf(dir: Direction, cursor: Cursor) { + cursor.insDirOf(dir, this.parent); + } + deleteOutOf(_dir: Direction, cursor: Cursor) { + pray('cursor is inside this block', cursor.parent === this); + cursor.unwrapGramp(this.parent); + } +} + Options.prototype.mouseEvents = true; API.StaticMath = function (APIClasses: APIClasses) { return class StaticMath extends APIClasses.AbstractMathQuill { diff --git a/src/commands/math/LatexCommandInput.ts b/src/commands/math/LatexCommandInput.ts index f82775e6d..69c7b7b04 100644 --- a/src/commands/math/LatexCommandInput.ts +++ b/src/commands/math/LatexCommandInput.ts @@ -26,14 +26,16 @@ CharCmds['\\'] = class LatexCommandInput extends MathCommand { const endsL = this.getEnd(L); endsL.focus = function () { - this.parent.domFrag().addClass('mq-hasCursor'); - if (this.isEmpty()) this.parent.domFrag().removeClass('mq-empty'); + (this.parent as MQNode).domFrag().addClass('mq-hasCursor'); + if (this.isEmpty()) + (this.parent as MQNode).domFrag().removeClass('mq-empty'); return this; }; endsL.blur = function () { - this.parent.domFrag().removeClass('mq-hasCursor'); - if (this.isEmpty()) this.parent.domFrag().addClass('mq-empty'); + (this.parent as MQNode).domFrag().removeClass('mq-hasCursor'); + if (this.isEmpty()) + (this.parent as MQNode).domFrag().addClass('mq-empty'); return this; }; diff --git a/src/commands/math/basicSymbols.ts b/src/commands/math/basicSymbols.ts index 0128c4a8e..041d68166 100644 --- a/src/commands/math/basicSymbols.ts +++ b/src/commands/math/basicSymbols.ts @@ -193,7 +193,9 @@ class Digit extends DigitGroupingChar { ) { new SubscriptCommand().createLeftOf(cursor); super.createLeftOf(cursor); - cursor.insRightOf(cursor.parent.parent); + var gramp = cursor.parent.parent as MQNode; + pray('gramp exists', gramp); + cursor.insRightOf(gramp); } else super.createLeftOf(cursor); } mathspeak(opts: MathspeakOptions) { diff --git a/src/commands/math/commands.ts b/src/commands/math/commands.ts index e2fc7ecf9..893df10c3 100644 --- a/src/commands/math/commands.ts +++ b/src/commands/math/commands.ts @@ -540,13 +540,14 @@ class SupSub extends MathCommand { } } -function insLeftOfMeUnlessAtEnd(this: MQNode, cursor: Cursor) { +function insLeftOfMeUnlessAtEnd(this: MathCommand | MathBlock, cursor: Cursor) { // cursor.insLeftOf(cmd), unless cursor at the end of block, and every // ancestor cmd is at the end of every ancestor block var cmd = this.parent; var ancestorCmd: MQNode | Anticursor | Cursor = cursor; do { if (ancestorCmd[R]) return cursor.insLeftOf(cmd); + if (!ancestorCmd.parent || !ancestorCmd.parent.parent) break; ancestorCmd = ancestorCmd.parent.parent; } while (ancestorCmd !== cmd); cursor.insRightOf(cmd); diff --git a/src/commands/text.ts b/src/commands/text.ts index f0bed3d43..062dda60e 100644 --- a/src/commands/text.ts +++ b/src/commands/text.ts @@ -302,6 +302,7 @@ function TextBlockFuseChildren(self: TextBlock) { * Text contents must always be nonempty. */ class TextPiece extends MQNode { + parent: MQNode; textStr: string; constructor(text: string) { diff --git a/src/cursor.ts b/src/cursor.ts index aafa2dfb3..a95e26cd3 100644 --- a/src/cursor.ts +++ b/src/cursor.ts @@ -112,6 +112,7 @@ class Cursor extends Point { /** Place the cursor before or after `el`, according the side specified by `dir`. */ insDirOf(dir: Direction, el: MQNode) { prayDirection(dir); + if (!el.parent) return this.hide(); this.domFrag().insDirOf(dir, el.domFrag()); this.withDirInsertAt(dir, el.parent, el[dir], el); this.parent.domFrag().addClass('mq-hasCursor'); @@ -181,9 +182,9 @@ class Cursor extends Point { right, }; } - unwrapGramp() { - var gramp = this.parent.parent; - var greatgramp = gramp.parent; + unwrapGramp(gramp: MQNode) { + var greatgramp = gramp.parent as MQNode; + pray('greatgramp exists', greatgramp); var rightward = gramp[R]; var cursor = this; @@ -365,7 +366,7 @@ class Cursor extends Point { return seln; } depth() { - var node: MQNode | Point = this; + var node: NodeRef | Point = this; var depth = 0; while ((node = node.parent)) { depth += node instanceof MathBlock ? 1 : 0; diff --git a/src/services/keystroke.ts b/src/services/keystroke.ts index ef64f678f..964ff8c68 100644 --- a/src/services/keystroke.ts +++ b/src/services/keystroke.ts @@ -351,11 +351,11 @@ class Controller_keystroke extends Controller_focusBlur { deleteDir(dir: Direction) { prayDirection(dir); var cursor = this.cursor; - var cursorEl = cursor[dir] as MQNode; + var cursorEl = cursor[dir]; var cursorElParent = cursor.parent.parent; var ctrlr = cursor.controller; - if (cursorEl && cursorEl instanceof MQNode) { + if (cursorEl && cursorEl.parent && cursorEl instanceof MQNode) { if (cursorEl.sides) { ctrlr.aria.queue( cursorEl.parent @@ -368,7 +368,11 @@ class Controller_keystroke extends Controller_focusBlur { } else if (!cursorEl.blocks && cursorEl.parent.ctrlSeq !== '\\text') { ctrlr.aria.queue(cursorEl); } - } else if (cursorElParent && cursorElParent instanceof MQNode) { + } else if ( + cursorElParent && + cursorElParent.parent && + cursorElParent instanceof MQNode + ) { if (cursorElParent.sides) { ctrlr.aria.queue( cursorElParent.parent diff --git a/src/tree.ts b/src/tree.ts index 3ee3b13e8..2052b7c92 100644 --- a/src/tree.ts +++ b/src/tree.ts @@ -116,8 +116,7 @@ class NodeBase { [L]: NodeRef = 0; [R]: NodeRef = 0; - // TODO - can this ever actually stay 0? if so we need to add null checks - parent: MQNode = 0 as any as MQNode; + parent: NodeRef = 0; /** * The (doubly-linked) list of this node's children. @@ -351,7 +350,11 @@ class NodeBase { write(_cursor: Cursor, _ch: string) {} } -function prayWellFormed(parent: MQNode, leftward: NodeRef, rightward: NodeRef) { +function prayWellFormed( + parent: NodeRef, + leftward: NodeRef, + rightward: NodeRef +): asserts parent { pray('a parent is always present', parent); pray( 'leftward is properly set up',