@nerd-bible/wordgard 0.3.5 → 0.3.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/command.d.ts CHANGED
@@ -511,7 +511,7 @@ declare const insertLineBreak: Command.Pure;
511
511
  The command that handles enter presses. The default handler will,
512
512
  if the selection is not in an inline context, insert an empty
513
513
  default textblock in its position. Otherwise it first tries
514
- `liftEmptyTextblock`, then `splitTextblock`.
514
+ `liftEmptyBlock`, then `splitTextblock`.
515
515
  */
516
516
  declare const enter: Command.Pure;
517
517
  /**
@@ -705,11 +705,11 @@ selection is empty.
705
705
  */
706
706
  declare function deleteSelection(state: GardState): Transaction.Spec | false;
707
707
  /**
708
- If the cursor is inside an empty textblock, return a transaction
709
- that deletes the entire block. `dir` determines which way the
710
- cursor moves after the deletion.
708
+ If the cursor is inside an empty plot, return a transaction that
709
+ deletes the entire plot. `dir` determines which way the cursor
710
+ moves after the deletion.
711
711
  */
712
- declare function deleteEmptyTextblock(state: GardState, dir?: -1 | 1): Transaction.Spec | false;
712
+ declare function deleteEmptyPlot(state: GardState, dir?: -1 | 1): Transaction.Spec | false;
713
713
  /**
714
714
  If the cursor is at the start of a textblock that can be joined to
715
715
  a textblock before it, return a transaction to performs this join.
@@ -793,4 +793,4 @@ updated to perform those joins.
793
793
  */
794
794
  declare function autoJoinBlocks(state: GardState, tr: Transaction.Spec): Transaction.Spec;
795
795
 
796
- export { Command, Menu, autoJoinBlocks, canAddMarkInRange, clearNonFitting, deleteBackward, deleteEmptyTextblock, deleteForward, deleteLine, deleteSelection, deleteToLineEnd, deleteUnit, deleteWord, doUnwrapBlock, enter, findUnwrappable, findWrappable, insertLineBreak, insertText, joinBackward, joinBlocks, joinForward, joinListItems, liftEmptyBlock, listIsActive, moveByLine, moveByPage, moveByUnit, moveByWord, moveToDocSide, moveToLineSide, moveToTextblockSide, redo, selectAll, selectedTextblocks, setAlignment, setDirection, setTextblockType, splitTextblock, toggleBlock, toggleEmphasis, toggleList, toggleMark, toggleStrong, toggleUnderline, transposeChars, undo, unwrapBlock, wrapBlock, wrapBlockRange };
796
+ export { Command, Menu, autoJoinBlocks, canAddMarkInRange, clearNonFitting, deleteBackward, deleteEmptyPlot, deleteForward, deleteLine, deleteSelection, deleteToLineEnd, deleteUnit, deleteWord, doUnwrapBlock, enter, findUnwrappable, findWrappable, insertLineBreak, insertText, joinBackward, joinBlocks, joinForward, joinListItems, liftEmptyBlock, listIsActive, moveByLine, moveByPage, moveByUnit, moveByWord, moveToDocSide, moveToLineSide, moveToTextblockSide, redo, selectAll, selectedTextblocks, setAlignment, setDirection, setTextblockType, splitTextblock, toggleBlock, toggleEmphasis, toggleList, toggleMark, toggleStrong, toggleUnderline, transposeChars, undo, unwrapBlock, wrapBlock, wrapBlockRange };
package/dist/command.js CHANGED
@@ -150,14 +150,14 @@ function deleteSelection(state) {
150
150
  userEvent: "delete.selection"
151
151
  });
152
152
  }
153
- function deleteEmptyTextblock(state, dir = -1) {
153
+ function deleteEmptyPlot(state, dir = -1) {
154
154
  if (!state.selection.isCursor)
155
155
  return false;
156
- let block = state.sel.head.textblockParent;
157
- if (!block || block.start < block.end || block.before == 0 && block.after == state.doc.length)
156
+ let plot = state.sel.head.parent;
157
+ if (!plot.parent || plot.start < plot.end)
158
158
  return false;
159
159
  return {
160
- changes: { from: block.before, to: block.after, fit: true },
160
+ changes: { from: plot.before, to: plot.after, fit: true },
161
161
  selection: (cx, changes) => GardSelection.near(cx, changes.mapPos(state.selection.head), dir),
162
162
  scrollIntoView: true,
163
163
  userEvent: dir < 0 ? "delete.backward" : "delete.forward"
@@ -289,12 +289,9 @@ function deleteBackward(state, word = false) {
289
289
  for (;;) {
290
290
  if (next.isPlot && next.type.isolating)
291
291
  return false;
292
- if (next.isLeaf || state.isAtom(next.type))
292
+ if (next.isLeaf || state.isAtom(next.type) || !next.content.length)
293
293
  break;
294
- let last = next.content.length - 1;
295
- if (last < 0)
296
- return false;
297
- next = next.content[last];
294
+ next = next.content[next.content.length - 1];
298
295
  pos--;
299
296
  }
300
297
  if (next.is(Leaf.Text)) {
@@ -336,13 +333,14 @@ function deleteBackward(state, word = false) {
336
333
  }
337
334
  let from = pos - next.length, to = pos;
338
335
  let parent = state.doc.resolve(pos).parent;
339
- while (parent && parent.node.type.isBlock && parent.node.content.length == 1) {
340
- if (!parent.parent)
341
- return false;
342
- parent = parent.parent;
343
- from--;
344
- to++;
345
- }
336
+ if (next.type.isBlock)
337
+ while (parent && parent.node.type.isBlock && parent.node.content.length == 1) {
338
+ if (!parent.parent)
339
+ return false;
340
+ parent = parent.parent;
341
+ from--;
342
+ to++;
343
+ }
346
344
  return {
347
345
  changes: { from, to },
348
346
  scrollIntoView: true,
@@ -366,10 +364,8 @@ function deleteForward(state, word = false) {
366
364
  for (;;) {
367
365
  if (next.isPlot && next.type.isolating)
368
366
  return false;
369
- if (next.isLeaf || state.isAtom(next.type))
367
+ if (next.isLeaf || state.isAtom(next.type) || !next.content.length)
370
368
  break;
371
- if (!next.content.length)
372
- return false;
373
369
  next = next.content[0];
374
370
  pos++;
375
371
  }
@@ -412,13 +408,14 @@ function deleteForward(state, word = false) {
412
408
  }
413
409
  let from = pos, to = pos + next.length;
414
410
  let parent = state.doc.resolve(pos).parent;
415
- while (parent && parent.node.type.isBlock && parent.node.content.length == 1) {
416
- if (!parent.parent)
417
- return false;
418
- parent = parent.parent;
419
- from--;
420
- to++;
421
- }
411
+ if (next.type.isBlock)
412
+ while (parent && parent.node.type.isBlock && parent.node.content.length == 1) {
413
+ if (!parent.parent)
414
+ return false;
415
+ parent = parent.parent;
416
+ from--;
417
+ to++;
418
+ }
422
419
  return {
423
420
  changes: { from, to },
424
421
  scrollIntoView: true,
@@ -766,15 +763,15 @@ const deleteUnit = ({ state }, dir) => {
766
763
  if (state.readOnly)
767
764
  return false;
768
765
  return deleteSelection(state) || (dir == "forward"
769
- ? joinForward(state) || deleteForward(state) || deleteEmptyTextblock(state, 1)
770
- : joinListItems(state) || joinBackward(state) || deleteBackward(state) || deleteEmptyTextblock(state, -1));
766
+ ? joinForward(state) || deleteForward(state) || deleteEmptyPlot(state, 1)
767
+ : joinListItems(state) || joinBackward(state) || deleteBackward(state) || deleteEmptyPlot(state, -1));
771
768
  };
772
769
  const deleteWord = ({ state }, dir) => {
773
770
  if (state.readOnly)
774
771
  return false;
775
772
  return deleteSelection(state) || (dir == "forward"
776
- ? joinForward(state) || deleteForward(state, true) || deleteEmptyTextblock(state, 1)
777
- : joinListItems(state) || joinBackward(state) || deleteBackward(state, true) || deleteEmptyTextblock(state, -1));
773
+ ? joinForward(state) || deleteForward(state, true) || deleteEmptyPlot(state, 1)
774
+ : joinListItems(state) || joinBackward(state) || deleteBackward(state, true) || deleteEmptyPlot(state, -1));
778
775
  };
779
776
  const deleteToLineEnd = (wg, dir) => {
780
777
  if (wg.state.readOnly)
@@ -1101,8 +1098,7 @@ function extendSel(base, head) {
1101
1098
  const moveByUnit = ({ state }, { dir, extend }) => {
1102
1099
  let forward = isForward(dir, state), selection = asTextSel(state.selection, forward);
1103
1100
  if (!selection.empty && !extend) {
1104
- let next = selection.normalCursorAtBound(state, forward);
1105
- return next ? setSelection(next) : false;
1101
+ return setSelection(GardSelection.near(state, forward ? selection.to : selection.from, forward ? -1 : 1));
1106
1102
  }
1107
1103
  else {
1108
1104
  let next = selection.nextNormalCursor(state, forward);
@@ -1133,10 +1129,10 @@ function nextVertical(wg, sel, forward, distance, allowNode) {
1133
1129
  }
1134
1130
  const moveByLine = (wg, { dir, extend }) => {
1135
1131
  let { state } = wg, { selection } = state, forward = dir == "down";
1136
- if (state.selection instanceof GardSelection.Node) {
1137
- let next = !extend && state.selection.normalCursorAtBound(state, forward);
1132
+ if (selection instanceof GardSelection.Node) {
1133
+ let next = !extend && GardSelection.near(state, forward ? selection.to : selection.from, forward ? -1 : 1);
1138
1134
  if (next && !state.doc.resolve(next.head).parent.node.inlineContent)
1139
- return setSelection(GardSelection.cursor(next.head, next.headSide, state.selection.goalColumn));
1135
+ return setSelection(GardSelection.cursor(next.head, next.headSide, selection.goalColumn));
1140
1136
  selection = GardSelection.cursor(forward ? selection.to : selection.from, undefined, selection.goalColumn);
1141
1137
  }
1142
1138
  else {
@@ -1448,4 +1444,4 @@ const Menu = /*@__PURE__*/(function (Menu) {
1448
1444
  Menu.resolve = resolve;
1449
1445
  ;return Menu})({});
1450
1446
 
1451
- export { Command, Menu, autoJoinBlocks, canAddMarkInRange, clearNonFitting, deleteBackward, deleteEmptyTextblock, deleteForward, deleteLine, deleteSelection, deleteToLineEnd, deleteUnit, deleteWord, doUnwrapBlock, enter, findUnwrappable, findWrappable, insertLineBreak, insertText, joinBackward, joinBlocks, joinForward, joinListItems, liftEmptyBlock, listIsActive, moveByLine, moveByPage, moveByUnit, moveByWord, moveToDocSide, moveToLineSide, moveToTextblockSide, redo, selectAll, selectedTextblocks, setAlignment, setDirection, setTextblockType, splitTextblock, toggleBlock, toggleEmphasis, toggleList, toggleMark, toggleStrong, toggleUnderline, transposeChars, undo, unwrapBlock, wrapBlock, wrapBlockRange };
1447
+ export { Command, Menu, autoJoinBlocks, canAddMarkInRange, clearNonFitting, deleteBackward, deleteEmptyPlot, deleteForward, deleteLine, deleteSelection, deleteToLineEnd, deleteUnit, deleteWord, doUnwrapBlock, enter, findUnwrappable, findWrappable, insertLineBreak, insertText, joinBackward, joinBlocks, joinForward, joinListItems, liftEmptyBlock, listIsActive, moveByLine, moveByPage, moveByUnit, moveByWord, moveToDocSide, moveToLineSide, moveToTextblockSide, redo, selectAll, selectedTextblocks, setAlignment, setDirection, setTextblockType, splitTextblock, toggleBlock, toggleEmphasis, toggleList, toggleMark, toggleStrong, toggleUnderline, transposeChars, undo, unwrapBlock, wrapBlock, wrapBlockRange };
package/dist/doc.d.ts CHANGED
@@ -250,7 +250,7 @@ declare class Elt<T = string> {
250
250
  /**
251
251
  Convert an element (with only string content) to a DOM tree.
252
252
  */
253
- toDOM(this: Elt<string>, doc?: Document): Element | Text;
253
+ toDOM(this: Elt<string>, doc?: Document): Element;
254
254
  }
255
255
  declare namespace Elt {
256
256
  /**
@@ -1685,6 +1685,11 @@ declare namespace Plot {
1685
1685
  */
1686
1686
  readonly orientation: "row" | "column";
1687
1687
  /**
1688
+ True if this is an inline plot with {@link
1689
+ Plot.Spec.cursorInsideBounds `cursorInsideBounds`} set.
1690
+ */
1691
+ readonly cursorInsideBounds: boolean;
1692
+ /**
1688
1693
  The spec used to define this plot type.
1689
1694
  */
1690
1695
  readonly spec: Plot.Spec<any>;
package/dist/doc.js CHANGED
@@ -1429,6 +1429,7 @@ class Plot {
1429
1429
  neutral;
1430
1430
  preserveWhitespace;
1431
1431
  orientation;
1432
+ cursorInsideBounds;
1432
1433
  spec;
1433
1434
  constructor(name, flags, spec) {
1434
1435
  super(name, flags, spec, NodeShape.from(name, false, spec.shape));
@@ -1440,6 +1441,7 @@ class Plot {
1440
1441
  this.neutral = spec.neutral ?? !this.defining;
1441
1442
  this.preserveWhitespace = spec.preserveWhitespace ?? !!this.hasRole(Node.Role.Code);
1442
1443
  this.orientation = flags & 2 ? "row" : spec.orientation || "column";
1444
+ this.cursorInsideBounds = !!((flags & 1) && spec.cursorInsideBounds);
1443
1445
  this.default = "defaultParam" in spec ? Plot.Tag.new(this, spec.defaultParam, none) :
1444
1446
  (flags & 16) ? Plot.Tag.new(this, null, none) : null;
1445
1447
  if (!this.shape.atom && this.isInline && !this.inlineContent)
@@ -3069,6 +3071,9 @@ function splatContext(top, cx) {
3069
3071
  for (let ch of cx.children)
3070
3072
  top.push(ch);
3071
3073
  }
3074
+ function isFitBarrier(plot) {
3075
+ return plot.isolating || plot.cursorInsideBounds;
3076
+ }
3072
3077
  function fitReplacement(doc, from, to, slice, context) {
3073
3078
  if (!slice.length)
3074
3079
  return fitDeletion(doc, from, to);
@@ -3099,10 +3104,10 @@ function fitReplacement(doc, from, to, slice, context) {
3099
3104
  let found, foundCost = 1e8;
3100
3105
  let neutral = true, toEnd = true;
3101
3106
  scan: for (let cxFrom = from.parent, cxTo = to.parent, fromDepth = from.depth, toDepth = to.depth, start = from.pos, end = to.pos; cxFrom.parent; cxFrom = cxFrom.parent, start--, fromDepth--) {
3102
- if (cxFrom.start != start || cxFrom.node.type.isolating)
3107
+ if (cxFrom.start != start || isFitBarrier(cxFrom.node.type))
3103
3108
  break;
3104
3109
  while (toDepth > fromDepth) {
3105
- if (cxTo.node.type.isolating)
3110
+ if (isFitBarrier(cxTo.node.type))
3106
3111
  break scan;
3107
3112
  cxTo = cxTo.parent;
3108
3113
  toDepth--;
@@ -3136,17 +3141,22 @@ function fitReplacement(doc, from, to, slice, context) {
3136
3141
  if (found)
3137
3142
  return found;
3138
3143
  if (from.pos == to.pos && !from.inText) {
3139
- let cx = from.parent, before = from.pos, after = from.pos;
3140
- for (; cx.parent && !cx.node.type.isolating && (before == cx.start || after == cx.end); cx = cx.parent, before--, after++) {
3144
+ let cx = from.parent;
3145
+ for (let before = from.index ? -1 : from.pos, after = from.pos == from.parent.end ? from.pos : -1; before > -1 || after > -1;) {
3141
3146
  for (let i = -1; i < context.length; i++) {
3142
3147
  let type = i >= 0 ? context[i].type : firstType;
3143
3148
  if (!type)
3144
3149
  continue;
3145
- if (doc.schema.canContain(cx.parent.node.type, type)) {
3146
- let pos = before == cx.start ? cx.before : cx.after;
3150
+ if (doc.schema.canContain(cx.node.type, type)) {
3151
+ let pos = before > -1 ? before : after;
3147
3152
  return { from: pos, to: pos, slice: i >= 0 ? closeSlice(doc.schema, slice, context, i + 1, true) : slice };
3148
3153
  }
3149
3154
  }
3155
+ if (isFitBarrier(cx.node.type) || !cx.parent)
3156
+ break;
3157
+ before = before == cx.start ? before - 1 : -1;
3158
+ after = after == cx.end ? after + 1 : -1;
3159
+ cx = cx.parent;
3150
3160
  }
3151
3161
  }
3152
3162
  for (let i = 0; i < context.length; i++) {
@@ -3161,7 +3171,7 @@ function fitDeletion(doc, from, to) {
3161
3171
  let toDepth = to.depth;
3162
3172
  let covered;
3163
3173
  for (let cx = from.parent, cxTo = to.parent, depth = from.depth, start = from.pos, end = to.pos; cx.parent; start--, cx = cx.parent, depth--) {
3164
- if (cx.start != start || cx.node.type.isolating)
3174
+ if (cx.start != start || isFitBarrier(cx.node.type))
3165
3175
  break;
3166
3176
  while (toDepth > depth) {
3167
3177
  cxTo = cxTo.parent;
package/dist/editor.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { GardState, GardSelection, TextblockMap, BidiSpan, Transaction } from 'wordgard/state';
2
- import { Attributes, Elt, Node, Leaf, ChangeSet, parse, Slice, Plot, serialize, Pos, ValidationError } from 'wordgard/doc';
2
+ import { Attributes, Elt, Node, Leaf, ChangeSet, parse, Slice, Plot, serialize, Pos, ValidationError, Mark } from 'wordgard/doc';
3
3
  import { StyleModule } from 'style-mod';
4
4
  import { findClusterBreak } from '@marijn/find-cluster-break';
5
5
  import { enter, insertLineBreak, selectAll, undo, redo, transposeChars, Command, deleteUnit, deleteWord, deleteToLineEnd, moveByUnit, moveByLine, moveByWord, moveToLineSide, moveToDocSide, moveByPage, moveToTextblockSide, setAlignment, toggleUnderline, toggleEmphasis, toggleStrong, deleteLine, insertText, setDirection, deleteSelection, Menu, findWrappable, wrapBlockRange, autoJoinBlocks } from 'wordgard/command';
@@ -229,6 +229,15 @@ const baseTagShape = /*@__PURE__*/memo((tag) => {
229
229
  return addMarkAttributes(tag.is(Leaf.Text) ? Widget.EditableText.of(tag.param)
230
230
  : tag.type.shape.create(tag.param), tag);
231
231
  });
232
+ function renderMarks(marks, around) {
233
+ let result = addMarkAttributes(Elt.create("span", Attributes.none, [around]), Leaf.text(around, marks));
234
+ for (let i = marks.length - 1; i >= 0; i--) {
235
+ let mark = marks[i];
236
+ if (mark.type.element)
237
+ result = renderMarkWrapper(mark).fill([result]);
238
+ }
239
+ return result.toDOM();
240
+ }
232
241
  class AttributeRangeDecoration extends Decoration.Range {
233
242
  attribute;
234
243
  value;
@@ -2610,15 +2619,20 @@ class ContentUpdate {
2610
2619
  return tile;
2611
2620
  }
2612
2621
  }
2613
- ensureBR() {
2622
+ ensureHackNode() {
2614
2623
  let tile = this.new;
2615
2624
  if (!tile.isPlotContent)
2616
2625
  return;
2617
2626
  while (tile.isNodeInner)
2618
2627
  tile = tile.parent;
2619
2628
  let node = tile.node;
2620
- if (!node || !node.isPlot || !node.isTextblock)
2629
+ if (!node || !node.isPlot || !(node.isTextblock || node.type.isInline))
2630
+ return;
2631
+ if (node.type.isInline) {
2632
+ if (!this.new.children.length)
2633
+ this.new.addChild(new WidgetTile(imgHack, null, 16 | 64, imgHack.render(this.wg)));
2621
2634
  return;
2635
+ }
2622
2636
  let hasHack = -1, needsHack = true;
2623
2637
  for (let parent = this.new, i = parent.children.length;;) {
2624
2638
  if (i > 0) {
@@ -2656,7 +2670,7 @@ class ContentUpdate {
2656
2670
  }
2657
2671
  }
2658
2672
  up() {
2659
- this.ensureBR();
2673
+ this.ensureHackNode();
2660
2674
  this.new = this.new.parent;
2661
2675
  }
2662
2676
  leaveNode() {
@@ -2739,7 +2753,7 @@ class ContentUpdate {
2739
2753
  finish() {
2740
2754
  while (!(this.new instanceof DocTile))
2741
2755
  this.up();
2742
- this.ensureBR();
2756
+ this.ensureHackNode();
2743
2757
  return this.new;
2744
2758
  }
2745
2759
  }
@@ -2784,7 +2798,11 @@ const brHack = /*@__PURE__*/Widget.create({
2784
2798
  editable: true
2785
2799
  });
2786
2800
  const imgHack = /*@__PURE__*/Widget.create({
2787
- render() { return document.createElement("img"); },
2801
+ render() {
2802
+ let img = document.createElement("img");
2803
+ img.className = "wg-buffer";
2804
+ return img;
2805
+ },
2788
2806
  editable: true
2789
2807
  });
2790
2808
  function separateComposition(sections, comp) {
@@ -2817,18 +2835,26 @@ function separateComposition(sections, comp) {
2817
2835
  return result;
2818
2836
  }
2819
2837
 
2838
+ class Coords {
2839
+ ref;
2840
+ rect;
2841
+ constructor(ref, rect) {
2842
+ this.ref = ref;
2843
+ this.rect = rect;
2844
+ }
2845
+ }
2820
2846
  function coordsAtPos(wg, pos, assoc) {
2821
2847
  let { offset, tile, pos: tilePos } = wg.docTile.resolve(pos, assoc);
2822
2848
  if (tile instanceof TextTile) {
2823
2849
  let node = tile.dom, len = node.nodeValue.length;
2824
2850
  if (!len)
2825
- return singleRect(textRange(node, 0, 0), 1);
2851
+ return new Coords(tile, singleRect(textRange(node, 0, 0), 1));
2826
2852
  let from = offset, to = offset, side = assoc < 0 && from || from == len ? 1 : -1;
2827
2853
  if (side < 0)
2828
2854
  to++;
2829
2855
  else
2830
2856
  from--;
2831
- return flattenV(singleRect(textRange(node, from, to), side, true), (side < 0) == ltrAt(wg.state, pos, assoc));
2857
+ return new Coords(tile, flattenV(singleRect(textRange(node, from, to), side, true), (side < 0) == ltrAt(wg.state, pos, assoc)));
2832
2858
  }
2833
2859
  let tagTile = tile;
2834
2860
  while (!tagTile.node)
@@ -2839,10 +2865,10 @@ function coordsAtPos(wg, pos, assoc) {
2839
2865
  if (tile.widget.type.inFlow) {
2840
2866
  let rect = singleRect(tile.dom, after ? 1 : -1);
2841
2867
  if (rect.width || rect.height)
2842
- return horizontal ? flattenH(rect, !after) : flattenV(rect, ltrAt(wg.state, pos, 1) == !after);
2868
+ return new Coords(tile, horizontal ? flattenH(rect, !after) : flattenV(rect, ltrAt(wg.state, pos, 1) == !after));
2843
2869
  }
2844
2870
  if (!tile.parent)
2845
- return new DOMRect;
2871
+ return new Coords(tile, new DOMRect);
2846
2872
  offset = tile.parent.children.indexOf(tile) + (after ? 1 : 0);
2847
2873
  tile = tile.parent;
2848
2874
  assoc = after ? 1 : -1;
@@ -2854,7 +2880,7 @@ function coordsAtPos(wg, pos, assoc) {
2854
2880
  continue;
2855
2881
  let rect = singleRect(before.dom, 1);
2856
2882
  if (rect.width || rect.height)
2857
- return horizontal ? flattenH(rect, false) : flattenV(rect, !ltrAt(wg.state, pos, 1));
2883
+ return new Coords(before, horizontal ? flattenH(rect, false) : flattenV(rect, !ltrAt(wg.state, pos, 1)));
2858
2884
  }
2859
2885
  }
2860
2886
  else { for (let i = offset; i < tile.children.length; i++) {
@@ -2863,12 +2889,12 @@ function coordsAtPos(wg, pos, assoc) {
2863
2889
  continue;
2864
2890
  let rect = singleRect(after.dom, -1);
2865
2891
  if (rect.width || rect.height)
2866
- return horizontal ? flattenH(rect, true) : flattenV(rect, ltrAt(wg.state, pos, 1));
2892
+ return new Coords(after, horizontal ? flattenH(rect, true) : flattenV(rect, ltrAt(wg.state, pos, 1)));
2867
2893
  }
2868
2894
  }
2869
2895
  }
2870
2896
  let rect = singleRect(tile.dom, -assoc);
2871
- return horizontal ? flattenH(rect, assoc < 0) : flattenV(rect, assoc < 0);
2897
+ return new Coords(tile, horizontal ? flattenH(rect, assoc < 0) : flattenV(rect, assoc < 0));
2872
2898
  }
2873
2899
  function flattenV(rect, left) {
2874
2900
  return rect.width ? new DOMRect(left ? rect.left : rect.right, rect.top, 0, rect.height) : rect;
@@ -3141,6 +3167,13 @@ const baseStyles = /*@__PURE__*/buildTheme("." + styleID, {
3141
3167
  borderLeft: "1.8px solid currentColor",
3142
3168
  marginLeft: "-0.9px",
3143
3169
  },
3170
+ ".wg-cursor-v.wg-cursor-bold": {
3171
+ borderLeft: "2.4px solid currentColor",
3172
+ marginLeft: "-1.2px",
3173
+ },
3174
+ ".wg-cursor-v.wg-cursor-italic": {
3175
+ transform: "rotate(10deg)"
3176
+ },
3144
3177
  ".wg-cursor-h": {
3145
3178
  borderTop: "1.8px solid currentColor",
3146
3179
  marginTop: "-0.9px",
@@ -3151,6 +3184,10 @@ const baseStyles = /*@__PURE__*/buildTheme("." + styleID, {
3151
3184
  backgroundColor: "transparent"
3152
3185
  }
3153
3186
  },
3187
+ ".wg-buffer": {
3188
+ verticalAlign: "bottom",
3189
+ height: "1em",
3190
+ },
3154
3191
  "wg-placeholder": {
3155
3192
  opacity: "0.6",
3156
3193
  display: "inline-block",
@@ -3963,7 +4000,7 @@ class InputState {
3963
4000
  let inText = node.nodeType == 3;
3964
4001
  let ref = this.wg.docTile.posFromDOM(node, inText ? 0 : offset);
3965
4002
  let dir = -1;
3966
- let textBefore = textNodeBefore(node.parentNode, domIndex(node));
4003
+ let textBefore = node.parentNode && textNodeBefore(node.parentNode, domIndex(node));
3967
4004
  let prev = textBefore && Tile.get(textBefore);
3968
4005
  if (prev instanceof TextTile && prev.length < prev.dom.nodeValue.length)
3969
4006
  dir = 1;
@@ -3986,8 +4023,8 @@ class InputState {
3986
4023
  let type = event.inputType, range;
3987
4024
  let { wg } = this, sel = wg.state.selection;
3988
4025
  if (data.domRange) {
3989
- range = { from: this.domMapping.mapPos(data.domRange.from),
3990
- to: this.domMapping.mapPos(data.domRange.to) };
4026
+ range = { from: snapToSel(wg.state, this.domMapping.mapPos(data.domRange.from)),
4027
+ to: snapToSel(wg.state, this.domMapping.mapPos(data.domRange.to)) };
3991
4028
  if (!this.domMapping.empty && type == "insertText" && !this.composing && range.from == range.to) {
3992
4029
  let fromMax = this.domMapping.mapPos(data.domRange.from, 1);
3993
4030
  if (range.from <= sel.from && fromMax >= sel.to)
@@ -3996,7 +4033,7 @@ class InputState {
3996
4033
  }
3997
4034
  let command = inputTypeCommands[type];
3998
4035
  if ((type == "deleteContentBackward" || type == "deleteContentForward") && range &&
3999
- (sel.empty
4036
+ range.from != range.to && (sel.empty
4000
4037
  ? !isSingleChar(this.domDoc, data.domRange.from, data.domRange.to) ||
4001
4038
  sel.head != (type == "deleteContentBackward" ? range.to : range.from)
4002
4039
  : sel.from != range.from || sel.to != range.to)) {
@@ -4089,6 +4126,39 @@ class InputState {
4089
4126
  this.mouseSelection.disconnect();
4090
4127
  }
4091
4128
  }
4129
+ function snapToSel(state, pos) {
4130
+ let { head, anchor } = state.selection;
4131
+ if (pos == head || pos == anchor)
4132
+ return pos;
4133
+ if (onlyInlineNodeBoundsBetween(state.doc, head, pos))
4134
+ return head;
4135
+ if (head != anchor && onlyInlineNodeBoundsBetween(state.doc, head, pos))
4136
+ return anchor;
4137
+ return pos;
4138
+ }
4139
+ function onlyInlineNodeBoundsBetween(doc, a, b) {
4140
+ if (a > b)
4141
+ [a, b] = [b, a];
4142
+ let { parent, index, inText } = doc.resolve(a);
4143
+ if (inText)
4144
+ return false;
4145
+ for (; a < b; a++) {
4146
+ if (index == parent.node.content.length) {
4147
+ if (!parent.node.type.isInline)
4148
+ return false;
4149
+ index = parent.index + 1;
4150
+ parent = parent.parent;
4151
+ }
4152
+ else {
4153
+ let next = parent.node.content[index];
4154
+ if (!next.isPlot || !next.type.isInline)
4155
+ return false;
4156
+ parent = Pos.Plot.create(parent, next, a, index);
4157
+ index = 0;
4158
+ }
4159
+ }
4160
+ return true;
4161
+ }
4092
4162
  function isSingleChar(doc, from, to) {
4093
4163
  if (to > from + 10)
4094
4164
  return false;
@@ -4712,9 +4782,34 @@ class ViewState {
4712
4782
  const cursorBlinkRate = /*@__PURE__*/GardState.Facet.define({
4713
4783
  combine: inputs => inputs.length ? Math.min(...inputs) : 1200
4714
4784
  });
4715
- class cursorLayer {
4785
+ class CursorStyle {
4786
+ height;
4787
+ align;
4788
+ color;
4789
+ bold;
4790
+ italic;
4791
+ constructor(height, align, color, bold, italic) {
4792
+ this.height = height;
4793
+ this.align = align;
4794
+ this.color = color;
4795
+ this.bold = bold;
4796
+ this.italic = italic;
4797
+ }
4798
+ static read(node, height) {
4799
+ let win = node.ownerDocument.defaultView || window;
4800
+ let elt = node.nodeType == 1 ? node : node.parentNode;
4801
+ let style = win.getComputedStyle(elt);
4802
+ return new CursorStyle(height, style.verticalAlign, style.color, +style.fontWeight > 400, style.fontStyle == "italic");
4803
+ }
4804
+ eq(other) {
4805
+ return other && this.height == other.height && this.align == other.align &&
4806
+ this.color == other.color && this.bold == other.bold && this.italic == other.italic;
4807
+ }
4808
+ }
4809
+ class CursorLayer {
4716
4810
  layer;
4717
- pos = null;
4811
+ info = null;
4812
+ cached = null;
4718
4813
  constructor(wg) {
4719
4814
  this.layer = wg.scrollDOM.appendChild(document.createElement("wg-cursor-layer"));
4720
4815
  this.positionCursor = this.positionCursor.bind(this);
@@ -4737,48 +4832,131 @@ class cursorLayer {
4737
4832
  this.layer.remove();
4738
4833
  }
4739
4834
  positionCursor(wg) {
4740
- let pos = cursorPos(wg), cur = this.pos;
4741
- if (!pos ? cur : !cur || cur.left != pos.left || cur.top != pos.top || cur.size != pos.size) {
4742
- this.pos = pos;
4743
- wg.scheduleDOMWrite(() => {
4744
- let cursor = this.layer.firstChild;
4745
- if (!pos) {
4746
- if (cursor)
4747
- cursor.remove();
4748
- }
4749
- else {
4750
- if (!cursor)
4751
- cursor = this.layer.appendChild(document.createElement("wg-cursor"));
4752
- cursor.className = "wg-cursor-" + (pos.horiz ? "h" : "v");
4753
- cursor.style.top = pos.top + "px";
4754
- cursor.style.left = pos.left + "px";
4755
- cursor.style.width = pos.horiz ? pos.size + "px" : "";
4756
- cursor.style.height = pos.horiz ? "" : pos.size + "px";
4757
- }
4758
- });
4759
- }
4835
+ getCursorInfo(wg, this, info => {
4836
+ let cur = this.info;
4837
+ if (!info ? cur : !cur || cur.left != info.left || cur.top != info.top || cur.size != info.size ||
4838
+ (cur.style ? !cur.style.eq(info.style) : info.style)) {
4839
+ this.info = info;
4840
+ wg.scheduleDOMWrite(() => {
4841
+ let cursor = this.layer.firstChild;
4842
+ if (!info) {
4843
+ if (cursor)
4844
+ cursor.remove();
4845
+ }
4846
+ else {
4847
+ if (!cursor)
4848
+ cursor = this.layer.appendChild(document.createElement("wg-cursor"));
4849
+ cursor.className = "wg-cursor-" + (info.horiz ? "h" : "v");
4850
+ cursor.style.top = info.top + "px";
4851
+ cursor.style.left = info.left + "px";
4852
+ cursor.style.width = info.horiz ? info.size + "px" : "";
4853
+ cursor.style.height = info.horiz ? "" : info.size + "px";
4854
+ cursor.style.borderLeftColor = info.style ? info.style.color : "";
4855
+ cursor.classList.toggle("wg-cursor-bold", info.style?.bold ?? false);
4856
+ cursor.classList.toggle("wg-cursor-italic", info.style?.italic ?? false);
4857
+ }
4858
+ });
4859
+ }
4860
+ });
4760
4861
  }
4761
4862
  }
4863
+ function alignOffset(align, height) {
4864
+ if (align == "super")
4865
+ return height * 0.36;
4866
+ if (align == "sub")
4867
+ return height * -0.17;
4868
+ if (align.endsWith("px"))
4869
+ return +align.slice(0, align.length - 2);
4870
+ if (align.endsWith("%"))
4871
+ return height * (+align.slice(0, align.length - 1)) * 100;
4872
+ return 0;
4873
+ }
4762
4874
  const VertWidth = 30, VertGap = 5;
4763
- function cursorPos(wg) {
4875
+ function getCursorInfo(wg, plugin, cont) {
4764
4876
  let { state } = wg;
4765
4877
  if (!state.selection.isCursor)
4766
- return null;
4767
- let { head, headSide } = state.selection;
4768
- let { left, right, top, bottom } = wg.coordsAtPos(head, headSide);
4769
- let horiz = top == bottom, size = horiz ? right - left : bottom - top;
4770
- if (horiz && size > VertWidth) {
4771
- size = VertWidth;
4772
- if (!wg.state.textLTR)
4773
- left = right - size;
4878
+ return cont(null);
4879
+ let { head, headSide } = state.selection, { sel } = wg.state;
4880
+ let { ref, rect } = coordsAtPos(wg, head, headSide);
4881
+ let doc = wg.contentDOM.getBoundingClientRect();
4882
+ if (!sel.head.parent.node.inlineContent) {
4883
+ let width = Math.max(VertWidth, rect.width), top = rect.top;
4774
4884
  let other = wg.coordsAtPos(head, headSide > 0 ? -1 : 1);
4775
4885
  if (other.top == other.bottom && other.top != top) {
4776
4886
  let move = Math.min(VertGap, Math.abs(other.top - top) / 2);
4777
- top = bottom = top + move * (other.top < top ? -1 : 1);
4887
+ top = top + move * (other.top < top ? -1 : 1);
4778
4888
  }
4889
+ return cont({
4890
+ left: (wg.state.textLTR ? rect.left : rect.right - width) - doc.left,
4891
+ top: top - doc.top,
4892
+ size: width,
4893
+ horiz: true, style: null
4894
+ });
4779
4895
  }
4780
- let doc = wg.contentDOM.getBoundingClientRect();
4781
- return { left: left - doc.left, top: top - doc.top, size, horiz };
4896
+ let finish = (style, vertRect) => {
4897
+ if (style && (!plugin.cached || plugin.cached.style != style))
4898
+ plugin.cached = { style, marks, parent: sel.head.parent.node.tag };
4899
+ let height = style ? style.height : rect.height;
4900
+ let bot = rect.bottom;
4901
+ if (vertRect && (vertRect.top < rect.bottom && vertRect.bottom > rect.top)) {
4902
+ bot = vertRect.bottom;
4903
+ }
4904
+ else {
4905
+ let win = ref.dom.ownerDocument.defaultView || window;
4906
+ let refAlign = win.getComputedStyle((ref.dom.nodeType == 1 ? ref.dom : ref.dom.parentNode)).verticalAlign;
4907
+ if (style && refAlign != style.align) {
4908
+ bot += alignOffset(refAlign, rect.height) - alignOffset(style.align, style.height);
4909
+ }
4910
+ }
4911
+ cont({
4912
+ left: rect.left - doc.left,
4913
+ top: bot - height - doc.top,
4914
+ size: height,
4915
+ horiz: false, style
4916
+ });
4917
+ };
4918
+ let marks = sel.activeMarks;
4919
+ if (ref instanceof TextTile) {
4920
+ let node = ref.posBefore < head ? state.sel.head.nodeBefore : state.sel.head.nodeAfter;
4921
+ if (node && node.isText && Mark.sameSet(node.marks, marks))
4922
+ return finish(CursorStyle.read(ref.dom, rect.height), rect);
4923
+ }
4924
+ let pos = sel.head.parent.start, foundRect, foundNode;
4925
+ for (let sibling of sel.head.parent.node.content) {
4926
+ if (sibling.isText && Mark.sameSet(sibling.marks, marks)) {
4927
+ let { tile } = wg.docTile.resolve(pos, 1);
4928
+ if (tile instanceof TextTile) {
4929
+ let rects = textRange(tile.dom, 0, tile.length).getClientRects();
4930
+ foundNode = tile.dom;
4931
+ for (let i = 0; i < rects.length; i++) {
4932
+ foundRect = rects[i];
4933
+ if (foundRect.top < rect.bottom && foundRect.bottom > rect.top)
4934
+ break;
4935
+ }
4936
+ }
4937
+ }
4938
+ pos += sibling.length;
4939
+ }
4940
+ if (foundNode)
4941
+ return finish(CursorStyle.read(foundNode, foundRect.height), foundRect);
4942
+ if (plugin.cached && Mark.sameSet(plugin.cached.marks, marks) && plugin.cached.parent.eq(sel.head.parent.node.tag))
4943
+ return finish(plugin.cached.style);
4944
+ if (!marks.length)
4945
+ return finish(null);
4946
+ let target = sel.head.parent.node.isDoc ? wg.contentDOM : wg.nodeDOM(sel.head.parent.before);
4947
+ wg.scheduleDOMWrite(() => {
4948
+ let temp = renderMarks(marks, "M");
4949
+ temp.style.position = "absolute";
4950
+ wg.observer.ignore(() => target.insertBefore(temp, target.firstChild));
4951
+ wg.scheduleDOMRead(() => {
4952
+ wg.scheduleDOMWrite(() => wg.observer.ignore(() => temp.remove()));
4953
+ let inner = temp;
4954
+ while (inner.firstChild)
4955
+ inner = inner.firstChild;
4956
+ let r = textRange(inner, 0, 1).getClientRects()[0];
4957
+ finish(CursorStyle.read(inner, r.height), r);
4958
+ });
4959
+ });
4782
4960
  }
4783
4961
  function setBlinkRate(state, dom) {
4784
4962
  dom.style.animationDuration = state.facet(cursorBlinkRate) + "ms";
@@ -5132,7 +5310,7 @@ class Wordgard {
5132
5310
  }
5133
5311
  coordsAtPos(pos, assoc = -1) {
5134
5312
  this.ensureFlushed();
5135
- return coordsAtPos(this, pos, assoc);
5313
+ return coordsAtPos(this, pos, assoc).rect;
5136
5314
  }
5137
5315
  coordsForElement(pos) {
5138
5316
  this.ensureFlushed();
@@ -5381,7 +5559,7 @@ function attrsFromFacet(wg, facet, base) {
5381
5559
  Wordgard.Update = Update;
5382
5560
  ;return Wordgard})(Wordgard);
5383
5561
  const editorPlugin = /*@__PURE__*/GardState.Facet.define();
5384
- const cursorPlugin = /*@__PURE__*/Wordgard.Plugin.fromClass(cursorLayer);
5562
+ const cursorPlugin = /*@__PURE__*/Wordgard.Plugin.fromClass(CursorLayer);
5385
5563
  class PluginInstance {
5386
5564
  spec;
5387
5565
  mustUpdate = null;
package/dist/state.d.ts CHANGED
@@ -136,6 +136,10 @@ declare abstract class GardSelection {
136
136
  */
137
137
  static cursor(pos: number, side?: -1 | 1, goalColumn?: number): GardSelection.Text;
138
138
  /**
139
+ Find a normal cursor near the given position.
140
+ */
141
+ static near(cx: GardSelection.Context, pos: number, side?: -1 | 1, goalColumn?: number): GardSelection.Text;
142
+ /**
139
143
  Create a text selection.
140
144
  */
141
145
  static range(anchor: number, head?: number, headSide?: -1 | 1, goalColumn?: number): GardSelection.Text;
@@ -161,18 +165,10 @@ declare abstract class GardSelection {
161
165
  */
162
166
  nextNormalCursor(cx: GardSelection.Context, forward?: boolean): GardSelection.Text | null;
163
167
  /**
164
- Get a normal cursor at the start or end of this selection.
165
- */
166
- normalCursorAtBound(cx: GardSelection.Context, forward?: boolean): GardSelection.Text | null;
167
- /**
168
168
  Move across one word starting from this selection's head.
169
169
  */
170
170
  skipWord(cx: GardSelection.Context, forward?: boolean): GardSelection.Text | null;
171
171
  /**
172
- Find a normal selection near the given position.
173
- */
174
- static near(cx: GardSelection.Context, pos: number, bias?: -1 | 1): GardSelection.Text;
175
- /**
176
172
  Find a normal selection at the start of the document or the
177
173
  given textblock.
178
174
  */
package/dist/state.js CHANGED
@@ -384,7 +384,7 @@ class TextblockMap {
384
384
  sectionPos = pos + ch.length;
385
385
  }
386
386
  }
387
- else if (ch.type.spec.cursorInsideBounds) {
387
+ else if (ch.type.cursorInsideBounds) {
388
388
  text += " ";
389
389
  scan(ch, pos + 1);
390
390
  text += " ";
@@ -590,6 +590,10 @@ class GardSelection {
590
590
  static cursor(pos, side, goalColumn) {
591
591
  return GardSelection.Text.createInner(pos, pos, side, goalColumn);
592
592
  }
593
+ static near(cx, pos, side = 1, goalColumn) {
594
+ let norm = findNormalAt(cx, pos, side);
595
+ return GardSelection.cursor(norm.pos, norm.side, goalColumn);
596
+ }
593
597
  static range(anchor, head, headSide, goalColumn) {
594
598
  return GardSelection.Text.createInner(anchor, head ?? anchor, headSide, goalColumn);
595
599
  }
@@ -597,23 +601,13 @@ class GardSelection {
597
601
  return GardSelection.Node.create(pos, node, goalColumn);
598
602
  }
599
603
  nextNormalCursor(cx, forward = true) {
600
- let found = scanNormalFrom(cx, this.head, this.headSide, forward, true);
601
- return found && GardSelection.cursor(found.pos, found.side);
602
- }
603
- normalCursorAtBound(cx, forward = true) {
604
- let found = scanNormalFrom(cx, forward ? this.to : this.from, forward ? -1 : 1, forward, false);
604
+ let found = scanNormalFrom(cx, this.head, this.headSide, forward);
605
605
  return found && GardSelection.cursor(found.pos, found.side);
606
606
  }
607
607
  skipWord(cx, forward = true) {
608
608
  let found = skipWord(cx, this.head, this.headSide, forward);
609
609
  return found && GardSelection.cursor(found.pos, found.side);
610
610
  }
611
- static near(cx, pos, bias = 1) {
612
- let norm = scanNormalFrom(cx, pos, bias, bias > 0, false) ??
613
- scanNormalFrom(cx, pos, -bias, bias < 0, false) ??
614
- { pos: pos, side: -1 };
615
- return GardSelection.cursor(norm.pos, norm.side);
616
- }
617
611
  static atStart(cx, block) {
618
612
  return cursorAtStart(cx, block);
619
613
  }
@@ -621,7 +615,7 @@ class GardSelection {
621
615
  let found = block
622
616
  ? TextblockMap.get(cx, block.start, block.node).visualSide(false)
623
617
  : cx.doc.inlineContent ? TextblockMap.get(cx, 0, cx.doc).visualSide(false)
624
- : scanNormalFrom(cx, cx.doc.length, -1, false, false) ?? { pos: cx.doc.length, side: -1 };
618
+ : findNormalAt(cx, cx.doc.length, -1);
625
619
  return GardSelection.cursor(found.pos, found.side);
626
620
  }
627
621
  }
@@ -760,7 +754,7 @@ function cursorAtStart(cx, block) {
760
754
  let found = block
761
755
  ? TextblockMap.get(cx, block.start, block.node).visualSide(true)
762
756
  : cx.doc.inlineContent ? TextblockMap.get(cx, 0, cx.doc).visualSide(true)
763
- : scanNormalFrom(cx, 0, 1, true, false) ?? { pos: 0, side: 1 };
757
+ : findNormalAt(cx, 0, 1);
764
758
  return GardSelection.cursor(found.pos, found.side);
765
759
  }
766
760
  function isBarrier(cx, node) {
@@ -769,13 +763,11 @@ function isBarrier(cx, node) {
769
763
  let override = node.type.spec.cursorBarrier;
770
764
  if (override != null)
771
765
  return override;
772
- return node.type.isolating || node.type.preserveWhitespace || node.type.isBlock && cx.config.isAtom(node.type);
766
+ return node.type.isBlock && (node.type.isolating || node.type.preserveWhitespace || cx.config.isAtom(node.type));
773
767
  }
774
- function scanNormalFrom(cx, from, side, forward, mustMove) {
768
+ function scanNormalFrom(cx, from, side, forward) {
775
769
  let pos = cx.doc.resolve(from), pastBarrier = false;
776
770
  if (pos.parent.node.inlineContent) {
777
- if (!mustMove)
778
- return { pos: pos.pos, side };
779
771
  let block = pos.textblockParent;
780
772
  let map = TextblockMap.get(cx, block.start, block.node);
781
773
  let next = cx.config.visualCursorMotion ? map.moveVisually(pos.pos, side, forward) : map.moveLogically(pos.pos, forward);
@@ -813,7 +805,7 @@ function scanNormalFrom(cx, from, side, forward, mustMove) {
813
805
  }
814
806
  if (index == (forward ? node.content.length : 0)) {
815
807
  let barrier = !next || isBarrier(cx, node);
816
- if ((bottom != from || !mustMove) && pastBarrier && barrier)
808
+ if ((bottom != from) && pastBarrier && barrier)
817
809
  return { pos: bottom, side: forward ? -1 : 1 };
818
810
  if (!next)
819
811
  return null;
@@ -827,7 +819,7 @@ function scanNormalFrom(cx, from, side, forward, mustMove) {
827
819
  else {
828
820
  let nextNode = node.content[index - (forward ? 0 : 1)];
829
821
  let barrier = isBarrier(cx, nextNode);
830
- if (pastBarrier && (bottom != from || !mustMove) && barrier)
822
+ if (pastBarrier && (bottom != from) && barrier)
831
823
  return { pos: bottom, side: forward ? -1 : 1 };
832
824
  if (nextNode.isLeaf || cx.config.isAtom(nextNode.type)) {
833
825
  index += step;
@@ -847,12 +839,52 @@ function scanNormalFrom(cx, from, side, forward, mustMove) {
847
839
  }
848
840
  }
849
841
  }
842
+ function findNormalAt(cx, pos, bias) {
843
+ let res = cx.doc.resolve(pos), lowest = pos;
844
+ if (res.inText) {
845
+ let text = res.parent.node.content[res.index].tag.param;
846
+ let off = bias > 0 ? findClusterBreak(text, res.inText - 1) : findClusterBreak(text, res.inText + 1, false);
847
+ pos += off - res.inText;
848
+ if (off > 0 && off < text.length)
849
+ return { pos, side: bias };
850
+ res = Pos.create(res.parent, pos, res.index + (off ? 1 : 0), 0);
851
+ }
852
+ for (let pass = 0; pass < 2; pass++) {
853
+ let dir = !pass ? bias : -bias, { parent, index } = res, curPos = pos;
854
+ for (;;) {
855
+ if (parent.node.inlineContent &&
856
+ (parent.node.type.isBlock || curPos > parent.start && curPos < parent.end || parent.node.type.cursorInsideBounds))
857
+ return { pos: curPos, side: bias };
858
+ if (index == (dir > 0 ? parent.node.content.length : 0)) {
859
+ if ((parent.node.type.isInline ? parent.node.type.cursorInsideBounds : isBarrier(cx, parent.node)) ||
860
+ !parent.parent)
861
+ break;
862
+ lowest = curPos = curPos + dir;
863
+ index = parent.index + (dir > 0 ? 1 : 0);
864
+ parent = parent.parent;
865
+ }
866
+ else {
867
+ let next = parent.node.content[index + (dir > 0 ? 0 : -1)];
868
+ if (next.isLeaf || isBarrier(cx, next))
869
+ break;
870
+ if (!parent.node.inlineContent && next.inlineContent && cx.config.visualCursorMotion) {
871
+ let startPos = curPos - (dir > 0 ? 0 : next.length) + 1;
872
+ return TextblockMap.get(cx, startPos, next).visualSide(dir > 0);
873
+ }
874
+ parent = Pos.Plot.create(parent, next, curPos, index);
875
+ index = dir > 0 ? 0 : next.content.length;
876
+ curPos += dir;
877
+ }
878
+ }
879
+ }
880
+ return { pos: lowest, side: bias };
881
+ }
850
882
  function skipWord(cx, start, side, forward) {
851
883
  let last = null;
852
884
  for (let pos = start, visually = cx.config.visualCursorMotion;;) {
853
885
  let block = cx.doc.resolve(pos).textblockParent;
854
886
  if (!block) {
855
- let next = scanNormalFrom(cx, pos, side, forward, true);
887
+ let next = scanNormalFrom(cx, pos, side, forward);
856
888
  if (!next)
857
889
  return last;
858
890
  ({ pos, side } = next);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nerd-bible/wordgard",
3
- "version": "0.3.5",
3
+ "version": "0.3.6",
4
4
  "description": "Semantic rich text editor system",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",