@nerd-bible/wordgard 0.3.4 → 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/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';
@@ -24,32 +24,40 @@ class Widget {
24
24
  return Widget.Type.new(spec).of(null);
25
25
  }
26
26
  type;
27
+ render(wg) {
28
+ return this.type.render(this.value, wg);
29
+ }
27
30
  get hasContent() { return false; }
28
31
  }
29
32
  ;Widget = /*@__PURE__*/(function (Widget) {
30
33
  class Type {
31
34
  render;
32
35
  eq;
33
- handleEvent;
36
+ propagateEvent;
34
37
  connect;
35
38
  disconnect;
36
39
  inFlow;
40
+ editable;
37
41
  constructor(
38
42
  render,
39
43
  eq,
40
- handleEvent,
44
+ propagateEvent,
41
45
  connect,
42
46
  disconnect,
43
- inFlow) {
47
+ inFlow,
48
+ editable) {
44
49
  this.render = render;
45
50
  this.eq = eq;
46
- this.handleEvent = handleEvent;
51
+ this.propagateEvent = propagateEvent;
47
52
  this.connect = connect;
48
53
  this.disconnect = disconnect;
49
54
  this.inFlow = inFlow;
55
+ this.editable = editable;
50
56
  }
51
57
  static new(spec) {
52
- return new Type(spec.render, spec.eq || ((a, b) => a === b), spec.handleEvent || (() => false), spec.connect ?? null, spec.disconnect ?? null, spec.inFlow !== false);
58
+ let prop = spec.propagateEvent;
59
+ let propEvent = typeof prop == "function" ? prop : prop == null ? () => true : () => prop;
60
+ return new Type(spec.render, spec.eq || ((a, b) => a === b), propEvent, spec.connect ?? null, spec.disconnect ?? null, spec.inFlow !== false, spec.editable === true);
53
61
  }
54
62
  of(value) { return Widget.new(this, value); }
55
63
  }
@@ -106,7 +114,7 @@ const Decoration = /*@__PURE__*/(function (Decoration) {
106
114
  return tagWidget.of({
107
115
  type: Node.Type.get(type),
108
116
  place: getPlace(place),
109
- widget: typeof widget == "function" ? memo(widget) : (() => widget)
117
+ widget: typeof widget == "function" ? memo(widget) : widget
110
118
  });
111
119
  }
112
120
  Tag.widget = widget;
@@ -119,7 +127,7 @@ const Decoration = /*@__PURE__*/(function (Decoration) {
119
127
  return {
120
128
  type: tp,
121
129
  place: p,
122
- widget: typeof w == "function" ? memo(w) : (() => w)
130
+ widget: typeof w == "function" ? memo(w) : w
123
131
  };
124
132
  });
125
133
  }
@@ -221,6 +229,15 @@ const baseTagShape = /*@__PURE__*/memo((tag) => {
221
229
  return addMarkAttributes(tag.is(Leaf.Text) ? Widget.EditableText.of(tag.param)
222
230
  : tag.type.shape.create(tag.param), tag);
223
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
+ }
224
241
  class AttributeRangeDecoration extends Decoration.Range {
225
242
  attribute;
226
243
  value;
@@ -262,7 +279,7 @@ class ShapeDecoration extends Decoration.Point {
262
279
  return this == other || other instanceof ShapeDecoration && other.shape.eq(this.shape);
263
280
  }
264
281
  get trackMode() { return "after"; }
265
- get side() { return 1e9; }
282
+ get side() { return 1000000000; }
266
283
  }
267
284
  class WidgetDecoration extends Decoration.Point {
268
285
  widget;
@@ -273,7 +290,7 @@ class WidgetDecoration extends Decoration.Point {
273
290
  this.widget = widget;
274
291
  this.side = side;
275
292
  this.trackMode = trackMode;
276
- if (side >= 1e9)
293
+ if (side >= 1000000000)
277
294
  throw new Error("Invalid widget side");
278
295
  }
279
296
  eq(other) {
@@ -297,7 +314,7 @@ class AttributeDecoration extends Decoration.Point {
297
314
  selectorEq(other.selector, this.selector);
298
315
  }
299
316
  get trackMode() { return "after"; }
300
- get side() { return 1e9; }
317
+ get side() { return 1000000000; }
301
318
  }
302
319
  class WrapperDecoration extends Decoration.Point {
303
320
  elt;
@@ -312,7 +329,7 @@ class WrapperDecoration extends Decoration.Point {
312
329
  selectorEq(other.selector, this.selector);
313
330
  }
314
331
  get trackMode() { return "after"; }
315
- get side() { return 1e9; }
332
+ get side() { return 1000000000; }
316
333
  }
317
334
  const nodeSelectionDeco = /*@__PURE__*/Decoration.Point.attributes({ class: "wg-selected-node" });
318
335
  function nodeSelection(state) {
@@ -494,9 +511,14 @@ class PointIterator {
494
511
  get side() {
495
512
  return this.done ? 1 : this.value.side;
496
513
  }
497
- goto(pos) {
514
+ goto(pos, inclusive) {
498
515
  this.done = false;
499
- this.fill(findAbove(this.set.positions, 0, pos - 1));
516
+ let i = findAbove(this.set.positions, 0, pos - 1);
517
+ if (!inclusive) {
518
+ while (i < this.set.values.length && this.set.values[i].side < 1000000000)
519
+ i++;
520
+ }
521
+ this.fill(i);
500
522
  }
501
523
  }
502
524
  function addDel(deleted, i) {
@@ -585,8 +607,8 @@ class RangeSet {
585
607
  compareRange(fromA, b, fromB, len, change) {
586
608
  let a = this, toB = fromB + len;
587
609
  if (a != b || fromA != fromB) {
588
- let iA = findAbove(a.from, 0, fromA - 1), lA = a.from.length;
589
- let iB = findAbove(b.from, 0, fromB - 1), lB = b.from.length;
610
+ let iA = findAbove(a.to, 0, fromA - 1), lA = a.from.length;
611
+ let iB = findAbove(b.to, 0, fromB - 1), lB = b.from.length;
590
612
  let off = fromB - fromA;
591
613
  let sameVals = a.values == b.values;
592
614
  for (;;) {
@@ -972,11 +994,14 @@ class DecoIterator {
972
994
  pos;
973
995
  rangeIter = [];
974
996
  pointIter = [];
997
+ endWidgets;
975
998
  constructor(state, decoSet) {
976
999
  this.state = state;
977
1000
  this.decoSet = decoSet;
978
1001
  this.tagShapes = state.facet(tagShape);
979
1002
  this.globalWidgets = state.facet(tagWidget);
1003
+ this.endWidgets = this.globalWidgets
1004
+ .some(w => (w.place == 1 || w.place == 3) && typeof w.widget == "function");
980
1005
  this.globalWrappers = state.facet(tagWrapper);
981
1006
  this.globalAttrs = state.facet(tagAttribute);
982
1007
  this.pos = state.doc.resolve(0);
@@ -995,17 +1020,22 @@ class DecoIterator {
995
1020
  widgets(tag, place, walker) {
996
1021
  for (let src of this.globalWidgets) {
997
1022
  if (src.place == place && tag.type == src.type) {
998
- let widget = src.widget(tag);
1023
+ let widget = typeof src.widget == "function" ? src.widget(tag) : src.widget;
999
1024
  if (widget)
1000
1025
  walker.widget(widget, place == 0 || place == 3 ? 1 : -1);
1001
1026
  }
1002
1027
  }
1003
1028
  }
1029
+ hasEndWidget(type) {
1030
+ return this.globalWidgets.some(tw => tw.type == type &&
1031
+ (tw.place == 3 || tw.place == 1) &&
1032
+ typeof tw.widget == "function");
1033
+ }
1004
1034
  walk(from, inclusiveStart, to, walker) {
1005
1035
  for (let i of this.rangeIter)
1006
1036
  i.goto(from);
1007
1037
  for (let i of this.pointIter)
1008
- i.goto(inclusiveStart ? from : from + 1);
1038
+ i.goto(from, inclusiveStart);
1009
1039
  let iter = new HeapIterator(this.rangeIter.filter(i => !i.done), this.pointIter.filter(i => !i.done), from, to);
1010
1040
  let pos = this.pos.advance(from - this.pos.pos), started = inclusiveStart;
1011
1041
  let atomParent;
@@ -1567,7 +1597,7 @@ class Tile {
1567
1597
  let last = this.children.length - 1;
1568
1598
  return last < 0 ? null : this.children[last];
1569
1599
  }
1570
- handleEvent(event, wg) { return false; }
1600
+ ignoreEvent(event) { return false; }
1571
1601
  get ignoreMutations() { return false; }
1572
1602
  toString() { return this.dom.nodeName + (this.children.length ? `(${this.children})` : ""); }
1573
1603
  sync() { }
@@ -1754,18 +1784,18 @@ class DocTile extends CompositeTile {
1754
1784
  this.cursorWrapper = cursorWrapper;
1755
1785
  this.decoSet = decoSet;
1756
1786
  }
1757
- static create(state, dom) {
1787
+ static create(state, dom, wg) {
1758
1788
  return new DocTile(state, dom, null, { points: new Map, ranges: new Map })
1759
- .updateRanges(state, getDecoSet(state), [0, state.doc.length], false);
1789
+ .updateRanges(state, getDecoSet(state), [0, state.doc.length], wg);
1760
1790
  }
1761
1791
  get isDoc() { return true; }
1762
1792
  get node() { return this.state.doc; }
1763
- update(state, changes, connected = false, composition) {
1793
+ update(state, changes, wg, composition) {
1764
1794
  let decoSet = getDecoSet(state);
1765
1795
  let changed = findChangedRanges(this.state, this.decoSet, state, decoSet, changes);
1766
- return this.updateRanges(state, decoSet, changed, connected, composition);
1796
+ return this.updateRanges(state, decoSet, changed, wg, composition);
1767
1797
  }
1768
- updateRanges(state, decoSet, sections, connected, composition) {
1798
+ updateRanges(state, decoSet, sections, wg, composition) {
1769
1799
  let wrapper = composition?.wrapCursor || null;
1770
1800
  if ((!sections.length || sections.length == 2 && sections[1] == -1) && eqArray(wrapper, this.cursorWrapper))
1771
1801
  return this;
@@ -1776,7 +1806,7 @@ class DocTile extends CompositeTile {
1776
1806
  else
1777
1807
  sections = separated;
1778
1808
  }
1779
- let builder = new ContentUpdate(state, this, new DecoIterator(state, decoSet), wrapper);
1809
+ let builder = new ContentUpdate(state, this, wg, new DecoIterator(state, decoSet), wrapper);
1780
1810
  for (let i = 0, posB = 0, startCovered = false; i < sections.length;) {
1781
1811
  let len = sections[i++], ins = sections[i++];
1782
1812
  if (composition && posB == composition.fromB && ins >= 0) {
@@ -1802,7 +1832,7 @@ class DocTile extends CompositeTile {
1802
1832
  }
1803
1833
  let result = builder.finish();
1804
1834
  result.sync();
1805
- if (connected) {
1835
+ if (wg.connected) {
1806
1836
  for (let ch of this.children)
1807
1837
  ch.disconnect(builder.reused);
1808
1838
  for (let tile of builder.toConnect)
@@ -2031,17 +2061,19 @@ class EltTile extends CompositeTile {
2031
2061
  class WidgetTile extends Tile {
2032
2062
  widget;
2033
2063
  _node;
2034
- constructor(widget, _node, flags, length = 0, dom) {
2035
- super(dom || widget.type.render(widget.value), flags);
2064
+ constructor(widget, _node, flags, dom, length = 0) {
2065
+ super(dom, flags);
2036
2066
  this.widget = widget;
2037
2067
  this._node = _node;
2038
2068
  this.length = length;
2069
+ if (dom.nodeType == 1 && !widget.type.editable && dom.contentEditable == "inherit")
2070
+ dom.contentEditable = "false";
2039
2071
  }
2040
2072
  get isNodeOuter() { return !!this._node; }
2041
2073
  get isAtom() { return true; }
2042
2074
  get node() { return this._node; }
2043
2075
  get children() { return noChildren; }
2044
- handleEvent(event, wg) { return this.widget.type.handleEvent(event, wg); }
2076
+ ignoreEvent(event) { return !this.widget.type.propagateEvent(event); }
2045
2077
  connect() {
2046
2078
  this.widget.type.connect?.(this.widget.value, this.dom);
2047
2079
  }
@@ -2232,6 +2264,7 @@ class TilePointer {
2232
2264
  }
2233
2265
  class ContentUpdate {
2234
2266
  state;
2267
+ wg;
2235
2268
  deco;
2236
2269
  old;
2237
2270
  new;
@@ -2240,8 +2273,9 @@ class ContentUpdate {
2240
2273
  keepWalker;
2241
2274
  toConnect = [];
2242
2275
  partialNode = null;
2243
- constructor(state, old, deco, cursorWrapper) {
2276
+ constructor(state, old, wg, deco, cursorWrapper) {
2244
2277
  this.state = state;
2278
+ this.wg = wg;
2245
2279
  this.deco = deco;
2246
2280
  this.old = new TilePointer(old, 0, null);
2247
2281
  this.new = new DocTile(state, old.dom, cursorWrapper, deco.decoSet);
@@ -2321,6 +2355,54 @@ class ContentUpdate {
2321
2355
  };
2322
2356
  }
2323
2357
  keep(len, includeStart, includeEnd) {
2358
+ let cut = [], end = this.posB + len;
2359
+ if (this.deco.endWidgets)
2360
+ for (let nw = this.new, { tile, index, parent } = this.old, pos = this.posB; pos < end;) {
2361
+ if (tile instanceof TextTile) {
2362
+ pos += tile.length - index;
2363
+ ({ tile, index, parent } = parent);
2364
+ index++;
2365
+ }
2366
+ else if (index < tile.children.length) {
2367
+ pos += tile.children[index].length;
2368
+ index++;
2369
+ }
2370
+ else {
2371
+ if (tile.node) {
2372
+ while (!nw.node && nw.parent)
2373
+ nw = nw.parent;
2374
+ if (!nw.parent)
2375
+ break;
2376
+ if (!nw.node.tag.eq(tile.node.tag) &&
2377
+ (this.deco.hasEndWidget(tile.node.type) || this.deco.hasEndWidget(nw.node.type)))
2378
+ cut.push(pos);
2379
+ nw = nw.parent;
2380
+ if (!nw)
2381
+ break;
2382
+ pos++;
2383
+ }
2384
+ if (!parent)
2385
+ break;
2386
+ ({ tile, index, parent } = parent);
2387
+ index++;
2388
+ }
2389
+ }
2390
+ for (let i = cut.length; i;) {
2391
+ let from = cut[--i], to = Math.min(from + 1, includeEnd ? end : end - 1);
2392
+ while (i && cut[i - 1] == from - 1) {
2393
+ from--;
2394
+ i--;
2395
+ }
2396
+ if (from > this.posB)
2397
+ this.keepInner(from - this.posB, includeStart, false);
2398
+ this.update(to - from, true);
2399
+ includeStart = false;
2400
+ len = end - to;
2401
+ }
2402
+ if (len)
2403
+ this.keepInner(len, includeStart, includeEnd);
2404
+ }
2405
+ keepInner(len, includeStart, includeEnd) {
2324
2406
  if (!includeStart) {
2325
2407
  this.old = this.old.walk(0, 1);
2326
2408
  this.openOldWrappers();
@@ -2343,7 +2425,7 @@ class ContentUpdate {
2343
2425
  if (mark.type.element) {
2344
2426
  this.openWrapper(renderMarkWrapper(mark), mark.spanning, false);
2345
2427
  }
2346
- this.new.addChild(new WidgetTile(imgHack, null, 16 | 32));
2428
+ this.new.addChild(new WidgetTile(imgHack, null, 16 | 32, imgHack.render(this.wg)));
2347
2429
  return;
2348
2430
  }
2349
2431
  let found = [];
@@ -2460,7 +2542,7 @@ class ContentUpdate {
2460
2542
  : endOld && this.posB == end ? endOld.matchingWidget(widget, sideFlag, this.reused)
2461
2543
  : null;
2462
2544
  if (!tile) {
2463
- tile = new WidgetTile(widget, null, 16 | sideFlag, 0);
2545
+ tile = new WidgetTile(widget, null, 16 | sideFlag, widget.render(this.wg));
2464
2546
  if (widget.type.connect)
2465
2547
  this.toConnect.push(tile);
2466
2548
  }
@@ -2497,6 +2579,9 @@ class ContentUpdate {
2497
2579
  }
2498
2580
  buildNodeShape(node, shape, reuse, afterContent = 0) {
2499
2581
  if (shape instanceof Elt) {
2582
+ if (node && !shape.hasContent && Attributes.get(shape.attrs, "contenteditable") == null &&
2583
+ !/^(br|hr|img|input|wbr)$/i.test(shape.tagName))
2584
+ shape = Elt.create(shape.tagName, Attributes.merge(shape.attrs, ["contenteditable", "false"]), shape.children);
2500
2585
  let reusable, dom, strict = true;
2501
2586
  if (reusable = this.findReusableTile(shape, reuse, strict) || this.findReusableTile(shape, reuse, strict = false)) {
2502
2587
  this.reused.set(reusable, 2);
@@ -2528,21 +2613,26 @@ class ContentUpdate {
2528
2613
  dom = reusable.dom;
2529
2614
  }
2530
2615
  let flags = (node ? 512 : 16 | 1) | afterContent;
2531
- let tile = new WidgetTile(shape, node, flags, node ? node.length : 0, dom);
2616
+ let tile = new WidgetTile(shape, node, flags, dom || shape.render(this.wg), node ? node.length : 0);
2532
2617
  if (shape.type.connect)
2533
2618
  this.toConnect.push(tile);
2534
2619
  return tile;
2535
2620
  }
2536
2621
  }
2537
- ensureBR() {
2622
+ ensureHackNode() {
2538
2623
  let tile = this.new;
2539
2624
  if (!tile.isPlotContent)
2540
2625
  return;
2541
2626
  while (tile.isNodeInner)
2542
2627
  tile = tile.parent;
2543
2628
  let node = tile.node;
2544
- if (!node || !node.isPlot || !node.isTextblock)
2629
+ if (!node || !node.isPlot || !(node.isTextblock || node.type.isInline))
2545
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)));
2634
+ return;
2635
+ }
2546
2636
  let hasHack = -1, needsHack = true;
2547
2637
  for (let parent = this.new, i = parent.children.length;;) {
2548
2638
  if (i > 0) {
@@ -2576,11 +2666,11 @@ class ContentUpdate {
2576
2666
  this.new.children.splice(hasHack, 1);
2577
2667
  }
2578
2668
  else if (needsHack) {
2579
- this.new.addChild(new WidgetTile(brHack, null, 16 | 64, 0));
2669
+ this.new.addChild(new WidgetTile(brHack, null, 16 | 64, brHack.render(this.wg)));
2580
2670
  }
2581
2671
  }
2582
2672
  up() {
2583
- this.ensureBR();
2673
+ this.ensureHackNode();
2584
2674
  this.new = this.new.parent;
2585
2675
  }
2586
2676
  leaveNode() {
@@ -2663,7 +2753,7 @@ class ContentUpdate {
2663
2753
  finish() {
2664
2754
  while (!(this.new instanceof DocTile))
2665
2755
  this.up();
2666
- this.ensureBR();
2756
+ this.ensureHackNode();
2667
2757
  return this.new;
2668
2758
  }
2669
2759
  }
@@ -2704,10 +2794,16 @@ function updateAttributes(dom, a, b) {
2704
2794
  return changed;
2705
2795
  }
2706
2796
  const brHack = /*@__PURE__*/Widget.create({
2707
- render() { return document.createElement("br"); }
2797
+ render() { return document.createElement("br"); },
2798
+ editable: true
2708
2799
  });
2709
2800
  const imgHack = /*@__PURE__*/Widget.create({
2710
- render() { return document.createElement("img"); }
2801
+ render() {
2802
+ let img = document.createElement("img");
2803
+ img.className = "wg-buffer";
2804
+ return img;
2805
+ },
2806
+ editable: true
2711
2807
  });
2712
2808
  function separateComposition(sections, comp) {
2713
2809
  let result = [], { fromB, toB } = comp;
@@ -2739,18 +2835,26 @@ function separateComposition(sections, comp) {
2739
2835
  return result;
2740
2836
  }
2741
2837
 
2838
+ class Coords {
2839
+ ref;
2840
+ rect;
2841
+ constructor(ref, rect) {
2842
+ this.ref = ref;
2843
+ this.rect = rect;
2844
+ }
2845
+ }
2742
2846
  function coordsAtPos(wg, pos, assoc) {
2743
2847
  let { offset, tile, pos: tilePos } = wg.docTile.resolve(pos, assoc);
2744
2848
  if (tile instanceof TextTile) {
2745
2849
  let node = tile.dom, len = node.nodeValue.length;
2746
2850
  if (!len)
2747
- return singleRect(textRange(node, 0, 0), 1);
2851
+ return new Coords(tile, singleRect(textRange(node, 0, 0), 1));
2748
2852
  let from = offset, to = offset, side = assoc < 0 && from || from == len ? 1 : -1;
2749
2853
  if (side < 0)
2750
2854
  to++;
2751
2855
  else
2752
2856
  from--;
2753
- 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)));
2754
2858
  }
2755
2859
  let tagTile = tile;
2756
2860
  while (!tagTile.node)
@@ -2761,10 +2865,10 @@ function coordsAtPos(wg, pos, assoc) {
2761
2865
  if (tile.widget.type.inFlow) {
2762
2866
  let rect = singleRect(tile.dom, after ? 1 : -1);
2763
2867
  if (rect.width || rect.height)
2764
- 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));
2765
2869
  }
2766
2870
  if (!tile.parent)
2767
- return new DOMRect;
2871
+ return new Coords(tile, new DOMRect);
2768
2872
  offset = tile.parent.children.indexOf(tile) + (after ? 1 : 0);
2769
2873
  tile = tile.parent;
2770
2874
  assoc = after ? 1 : -1;
@@ -2776,7 +2880,7 @@ function coordsAtPos(wg, pos, assoc) {
2776
2880
  continue;
2777
2881
  let rect = singleRect(before.dom, 1);
2778
2882
  if (rect.width || rect.height)
2779
- 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)));
2780
2884
  }
2781
2885
  }
2782
2886
  else { for (let i = offset; i < tile.children.length; i++) {
@@ -2785,12 +2889,12 @@ function coordsAtPos(wg, pos, assoc) {
2785
2889
  continue;
2786
2890
  let rect = singleRect(after.dom, -1);
2787
2891
  if (rect.width || rect.height)
2788
- 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)));
2789
2893
  }
2790
2894
  }
2791
2895
  }
2792
2896
  let rect = singleRect(tile.dom, -assoc);
2793
- return horizontal ? flattenH(rect, assoc < 0) : flattenV(rect, assoc < 0);
2897
+ return new Coords(tile, horizontal ? flattenH(rect, assoc < 0) : flattenV(rect, assoc < 0));
2794
2898
  }
2795
2899
  function flattenV(rect, left) {
2796
2900
  return rect.width ? new DOMRect(left ? rect.left : rect.right, rect.top, 0, rect.height) : rect;
@@ -3063,6 +3167,13 @@ const baseStyles = /*@__PURE__*/buildTheme("." + styleID, {
3063
3167
  borderLeft: "1.8px solid currentColor",
3064
3168
  marginLeft: "-0.9px",
3065
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
+ },
3066
3177
  ".wg-cursor-h": {
3067
3178
  borderTop: "1.8px solid currentColor",
3068
3179
  marginTop: "-0.9px",
@@ -3073,6 +3184,10 @@ const baseStyles = /*@__PURE__*/buildTheme("." + styleID, {
3073
3184
  backgroundColor: "transparent"
3074
3185
  }
3075
3186
  },
3187
+ ".wg-buffer": {
3188
+ verticalAlign: "bottom",
3189
+ height: "1em",
3190
+ },
3076
3191
  "wg-placeholder": {
3077
3192
  opacity: "0.6",
3078
3193
  display: "inline-block",
@@ -3885,7 +4000,7 @@ class InputState {
3885
4000
  let inText = node.nodeType == 3;
3886
4001
  let ref = this.wg.docTile.posFromDOM(node, inText ? 0 : offset);
3887
4002
  let dir = -1;
3888
- let textBefore = textNodeBefore(node.parentNode, domIndex(node));
4003
+ let textBefore = node.parentNode && textNodeBefore(node.parentNode, domIndex(node));
3889
4004
  let prev = textBefore && Tile.get(textBefore);
3890
4005
  if (prev instanceof TextTile && prev.length < prev.dom.nodeValue.length)
3891
4006
  dir = 1;
@@ -3908,8 +4023,8 @@ class InputState {
3908
4023
  let type = event.inputType, range;
3909
4024
  let { wg } = this, sel = wg.state.selection;
3910
4025
  if (data.domRange) {
3911
- range = { from: this.domMapping.mapPos(data.domRange.from),
3912
- 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)) };
3913
4028
  if (!this.domMapping.empty && type == "insertText" && !this.composing && range.from == range.to) {
3914
4029
  let fromMax = this.domMapping.mapPos(data.domRange.from, 1);
3915
4030
  if (range.from <= sel.from && fromMax >= sel.to)
@@ -3918,7 +4033,7 @@ class InputState {
3918
4033
  }
3919
4034
  let command = inputTypeCommands[type];
3920
4035
  if ((type == "deleteContentBackward" || type == "deleteContentForward") && range &&
3921
- (sel.empty
4036
+ range.from != range.to && (sel.empty
3922
4037
  ? !isSingleChar(this.domDoc, data.domRange.from, data.domRange.to) ||
3923
4038
  sel.head != (type == "deleteContentBackward" ? range.to : range.from)
3924
4039
  : sel.from != range.from || sel.to != range.to)) {
@@ -4011,6 +4126,39 @@ class InputState {
4011
4126
  this.mouseSelection.disconnect();
4012
4127
  }
4013
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
+ }
4014
4162
  function isSingleChar(doc, from, to) {
4015
4163
  if (to > from + 10)
4016
4164
  return false;
@@ -4185,13 +4333,17 @@ function isInPrimarySelection(wg, event) {
4185
4333
  }
4186
4334
  return false;
4187
4335
  }
4336
+ const focusEvents = /*@__PURE__*/(() => new Set(["input", "beforeinput", "keydown", "keyup", "keypress", "copy", "cut", "paste"]))();
4188
4337
  function eventBelongsToEditor(wg, event) {
4189
- if (!event.bubbles)
4190
- return true;
4191
4338
  if (event.defaultPrevented)
4192
4339
  return false;
4340
+ if (!event.bubbles)
4341
+ return true;
4342
+ let active = wg.root.activeElement;
4193
4343
  for (let node = event.target, tile; node != wg.contentDOM; node = node.parentNode)
4194
- if (!node || node.nodeType == 11 || (tile = Tile.get(node)) && tile.handleEvent(event, wg))
4344
+ if (!node || node.nodeType == 11 ||
4345
+ (tile = Tile.get(node)) && tile.ignoreEvent(event) ||
4346
+ node == active && focusEvents.has(event.type))
4195
4347
  return false;
4196
4348
  return true;
4197
4349
  }
@@ -4630,9 +4782,34 @@ class ViewState {
4630
4782
  const cursorBlinkRate = /*@__PURE__*/GardState.Facet.define({
4631
4783
  combine: inputs => inputs.length ? Math.min(...inputs) : 1200
4632
4784
  });
4633
- 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 {
4634
4810
  layer;
4635
- pos = null;
4811
+ info = null;
4812
+ cached = null;
4636
4813
  constructor(wg) {
4637
4814
  this.layer = wg.scrollDOM.appendChild(document.createElement("wg-cursor-layer"));
4638
4815
  this.positionCursor = this.positionCursor.bind(this);
@@ -4655,48 +4832,131 @@ class cursorLayer {
4655
4832
  this.layer.remove();
4656
4833
  }
4657
4834
  positionCursor(wg) {
4658
- let pos = cursorPos(wg), cur = this.pos;
4659
- if (!pos ? cur : !cur || cur.left != pos.left || cur.top != pos.top || cur.size != pos.size) {
4660
- this.pos = pos;
4661
- wg.scheduleDOMWrite(() => {
4662
- let cursor = this.layer.firstChild;
4663
- if (!pos) {
4664
- if (cursor)
4665
- cursor.remove();
4666
- }
4667
- else {
4668
- if (!cursor)
4669
- cursor = this.layer.appendChild(document.createElement("wg-cursor"));
4670
- cursor.className = "wg-cursor-" + (pos.horiz ? "h" : "v");
4671
- cursor.style.top = pos.top + "px";
4672
- cursor.style.left = pos.left + "px";
4673
- cursor.style.width = pos.horiz ? pos.size + "px" : "";
4674
- cursor.style.height = pos.horiz ? "" : pos.size + "px";
4675
- }
4676
- });
4677
- }
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
+ });
4678
4861
  }
4679
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
+ }
4680
4874
  const VertWidth = 30, VertGap = 5;
4681
- function cursorPos(wg) {
4875
+ function getCursorInfo(wg, plugin, cont) {
4682
4876
  let { state } = wg;
4683
4877
  if (!state.selection.isCursor)
4684
- return null;
4685
- let { head, headSide } = state.selection;
4686
- let { left, right, top, bottom } = wg.coordsAtPos(head, headSide);
4687
- let horiz = top == bottom, size = horiz ? right - left : bottom - top;
4688
- if (horiz && size > VertWidth) {
4689
- size = VertWidth;
4690
- if (!wg.state.textLTR)
4691
- 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;
4692
4884
  let other = wg.coordsAtPos(head, headSide > 0 ? -1 : 1);
4693
4885
  if (other.top == other.bottom && other.top != top) {
4694
4886
  let move = Math.min(VertGap, Math.abs(other.top - top) / 2);
4695
- top = bottom = top + move * (other.top < top ? -1 : 1);
4887
+ top = top + move * (other.top < top ? -1 : 1);
4696
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
+ });
4697
4895
  }
4698
- let doc = wg.contentDOM.getBoundingClientRect();
4699
- 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
+ });
4700
4960
  }
4701
4961
  function setBlinkRate(state, dom) {
4702
4962
  dom.style.animationDuration = state.facet(cursorBlinkRate) + "ms";
@@ -4756,7 +5016,7 @@ class Wordgard {
4756
5016
  for (let plugin of this.plugins)
4757
5017
  plugin.update(this);
4758
5018
  this.inputState = new InputState(this);
4759
- this.docTile = DocTile.create(this.state, this.contentDOM);
5019
+ this.docTile = DocTile.create(this.state, this.contentDOM, this);
4760
5020
  this.updateAttrs();
4761
5021
  this.observer = new DOMObserver(this);
4762
5022
  if (spec.parent)
@@ -4899,7 +5159,7 @@ class Wordgard {
4899
5159
  this.mountStyles();
4900
5160
  this.updateAttrs();
4901
5161
  }
4902
- this.docTile = prevDocTile.update(update.state, changes, this.connected, composition);
5162
+ this.docTile = prevDocTile.update(update.state, changes, this, composition);
4903
5163
  if ((composition?.wrapCursor || !composition && (prevDocTile != this.docTile || update.selectionSet)) && this.hasFocus)
4904
5164
  setDOMSelection(this);
4905
5165
  this.observer.clear();
@@ -5050,7 +5310,7 @@ class Wordgard {
5050
5310
  }
5051
5311
  coordsAtPos(pos, assoc = -1) {
5052
5312
  this.ensureFlushed();
5053
- return coordsAtPos(this, pos, assoc);
5313
+ return coordsAtPos(this, pos, assoc).rect;
5054
5314
  }
5055
5315
  coordsForElement(pos) {
5056
5316
  this.ensureFlushed();
@@ -5299,7 +5559,7 @@ function attrsFromFacet(wg, facet, base) {
5299
5559
  Wordgard.Update = Update;
5300
5560
  ;return Wordgard})(Wordgard);
5301
5561
  const editorPlugin = /*@__PURE__*/GardState.Facet.define();
5302
- const cursorPlugin = /*@__PURE__*/Wordgard.Plugin.fromClass(cursorLayer);
5562
+ const cursorPlugin = /*@__PURE__*/Wordgard.Plugin.fromClass(CursorLayer);
5303
5563
  class PluginInstance {
5304
5564
  spec;
5305
5565
  mustUpdate = null;