@nerd-bible/wordgard 0.3.3 → 0.3.5

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/doc.d.ts CHANGED
@@ -1536,6 +1536,15 @@ declare class Plot implements Node.Shared {
1536
1536
  node that overlaps the given range, outer nodes before inner
1537
1537
  nodes. When the function returns `false` for a node, descendents
1538
1538
  of that node are not iterated.
1539
+
1540
+ When `from` is greater than `to`, iteration happens in inverted
1541
+ order, yielding later siblings before earlier ones.
1542
+
1543
+ Note that positions passed to the callback are relative to the
1544
+ start of the node this method is called on. That will usually be
1545
+ the document, in which case they are normal document positions,
1546
+ but you can also call it on an arbitrary plot, in which case
1547
+ they are local positions.
1539
1548
  */
1540
1549
  iterate(from: number, to: number, f: (node: Node, pos: number, parent: Plot | null, index: number) => boolean | void): void;
1541
1550
  iterate(f: (node: Node, pos: number, parent: Plot | null, index: number) => boolean | void): void;
package/dist/doc.js CHANGED
@@ -1301,8 +1301,12 @@ class Plot {
1301
1301
  }
1302
1302
  iterate(a, b, c) {
1303
1303
  let [from, to, f] = typeof a == "number" ? [a, b, c] : [0, this.length, a];
1304
- if (this.isDoc || f(this, 0, null, 0) !== false)
1305
- this.iterInner(0, from, to, f);
1304
+ if (this.isDoc || f(this, 0, null, 0) !== false) {
1305
+ if (to < from)
1306
+ this.iterBack(this.contentLength, to, from, f);
1307
+ else
1308
+ this.iterInner(0, from, to, f);
1309
+ }
1306
1310
  }
1307
1311
  nodeAt(pos) {
1308
1312
  for (let node of this.content) {
@@ -1333,12 +1337,20 @@ class Plot {
1333
1337
  break;
1334
1338
  let node = this.content[i], start = pos;
1335
1339
  pos += node.length;
1336
- if (pos <= from)
1337
- continue;
1338
- if (f(node, start, this, i) !== false && node.isPlot)
1340
+ if (pos > from && f(node, start, this, i) !== false && node.isPlot)
1339
1341
  node.iterInner(start + 1, from, to, f);
1340
1342
  }
1341
1343
  }
1344
+ iterBack(contentEnd, from, to, f) {
1345
+ for (let pos = contentEnd, i = this.content.length - 1; i >= 0; i--) {
1346
+ if (pos <= from)
1347
+ break;
1348
+ let node = this.content[i], end = pos;
1349
+ pos -= node.length;
1350
+ if (pos < to && f(node, pos, this, i) !== false && node.isPlot)
1351
+ node.iterBack(end - 1, from, to, f);
1352
+ }
1353
+ }
1342
1354
  toString() {
1343
1355
  return this.name + markString(this.tag.marks) + "(" + this.content.join() + ")";
1344
1356
  }
@@ -2945,15 +2957,17 @@ class SectionIter {
2945
2957
  }
2946
2958
  next() {
2947
2959
  let { sections } = this;
2960
+ this.off = 0;
2948
2961
  if (this.i < sections.length) {
2949
2962
  this.len = sections[this.i++];
2950
2963
  this.ins = sections[this.i++];
2964
+ if (this.len == 0 && this.ins < 0)
2965
+ this.next();
2951
2966
  }
2952
2967
  else {
2953
2968
  this.len = 0;
2954
2969
  this.ins = -3;
2955
2970
  }
2956
- this.off = 0;
2957
2971
  }
2958
2972
  get keep() { return this.ins == -1 || this.ins == -2; }
2959
2973
  get done() { return this.ins == -3; }
package/dist/editor.d.ts CHANGED
@@ -46,7 +46,7 @@ declare namespace Widget {
46
46
  /**
47
47
  How to render the widget as DOM content.
48
48
  */
49
- render: (value: Param) => Element | Text;
49
+ render: (value: Param, wg: Wordgard) => Element | Text;
50
50
  /**
51
51
  Compare the widget value for equality. Will default to `===`.
52
52
  */
@@ -64,16 +64,22 @@ declare namespace Widget {
64
64
  */
65
65
  disconnect?: (value: Param, dom: Element | Text) => void;
66
66
  /**
67
- Called before the editor handles a DOM event that comes from
68
- inside the widget. May return true to indicate that no further
69
- handling of the event should happen.
67
+ Used to determine whether events originating from the widget's
68
+ DOM should ignored by the editor. `false` or a function that
69
+ returns `false` for the event will prevent the editor's
70
+ regular event handling for the event.
70
71
  */
71
- handleEvent?: (event: Event, wg: Wordgard) => boolean;
72
+ propagateEvent?: boolean | ((event: Event) => boolean);
72
73
  /**
73
74
  Set this to false for widgets that either aren't visible or
74
75
  are positioned outside of the regular document flow.
75
76
  */
76
77
  inFlow?: boolean;
78
+ /**
79
+ By default, widgets are set to be ineditable. Set this to
80
+ `true` to suppress that.
81
+ */
82
+ editable?: boolean;
77
83
  };
78
84
  /**
79
85
  Each widget has an associated type that describes how it
@@ -506,7 +512,7 @@ declare abstract class Tile {
506
512
  get boundary(): 0 | 1;
507
513
  get firstChild(): Tile | null;
508
514
  get lastChild(): Tile | null;
509
- handleEvent(event: Event, wg: Wordgard): boolean;
515
+ ignoreEvent(event: Event): boolean;
510
516
  get ignoreMutations(): boolean;
511
517
  toString(): string;
512
518
  sync(): void;
@@ -606,6 +612,10 @@ declare class Wordgard {
606
612
  private editorAttrs;
607
613
  private contentAttrs;
608
614
  private styleModules;
615
+ /**
616
+ True when the editor is connected to a DOM document.
617
+ */
618
+ connected: boolean;
609
619
  private flushing;
610
620
  private willFlush;
611
621
  private flushFunc;
package/dist/editor.js CHANGED
@@ -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
  }
@@ -262,7 +270,7 @@ class ShapeDecoration extends Decoration.Point {
262
270
  return this == other || other instanceof ShapeDecoration && other.shape.eq(this.shape);
263
271
  }
264
272
  get trackMode() { return "after"; }
265
- get side() { return 1e9; }
273
+ get side() { return 1000000000; }
266
274
  }
267
275
  class WidgetDecoration extends Decoration.Point {
268
276
  widget;
@@ -273,7 +281,7 @@ class WidgetDecoration extends Decoration.Point {
273
281
  this.widget = widget;
274
282
  this.side = side;
275
283
  this.trackMode = trackMode;
276
- if (side >= 1e9)
284
+ if (side >= 1000000000)
277
285
  throw new Error("Invalid widget side");
278
286
  }
279
287
  eq(other) {
@@ -297,7 +305,7 @@ class AttributeDecoration extends Decoration.Point {
297
305
  selectorEq(other.selector, this.selector);
298
306
  }
299
307
  get trackMode() { return "after"; }
300
- get side() { return 1e9; }
308
+ get side() { return 1000000000; }
301
309
  }
302
310
  class WrapperDecoration extends Decoration.Point {
303
311
  elt;
@@ -312,7 +320,7 @@ class WrapperDecoration extends Decoration.Point {
312
320
  selectorEq(other.selector, this.selector);
313
321
  }
314
322
  get trackMode() { return "after"; }
315
- get side() { return 1e9; }
323
+ get side() { return 1000000000; }
316
324
  }
317
325
  const nodeSelectionDeco = /*@__PURE__*/Decoration.Point.attributes({ class: "wg-selected-node" });
318
326
  function nodeSelection(state) {
@@ -494,9 +502,14 @@ class PointIterator {
494
502
  get side() {
495
503
  return this.done ? 1 : this.value.side;
496
504
  }
497
- goto(pos) {
505
+ goto(pos, inclusive) {
498
506
  this.done = false;
499
- this.fill(findAbove(this.set.positions, 0, pos - 1));
507
+ let i = findAbove(this.set.positions, 0, pos - 1);
508
+ if (!inclusive) {
509
+ while (i < this.set.values.length && this.set.values[i].side < 1000000000)
510
+ i++;
511
+ }
512
+ this.fill(i);
500
513
  }
501
514
  }
502
515
  function addDel(deleted, i) {
@@ -585,8 +598,8 @@ class RangeSet {
585
598
  compareRange(fromA, b, fromB, len, change) {
586
599
  let a = this, toB = fromB + len;
587
600
  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;
601
+ let iA = findAbove(a.to, 0, fromA - 1), lA = a.from.length;
602
+ let iB = findAbove(b.to, 0, fromB - 1), lB = b.from.length;
590
603
  let off = fromB - fromA;
591
604
  let sameVals = a.values == b.values;
592
605
  for (;;) {
@@ -972,11 +985,14 @@ class DecoIterator {
972
985
  pos;
973
986
  rangeIter = [];
974
987
  pointIter = [];
988
+ endWidgets;
975
989
  constructor(state, decoSet) {
976
990
  this.state = state;
977
991
  this.decoSet = decoSet;
978
992
  this.tagShapes = state.facet(tagShape);
979
993
  this.globalWidgets = state.facet(tagWidget);
994
+ this.endWidgets = this.globalWidgets
995
+ .some(w => (w.place == 1 || w.place == 3) && typeof w.widget == "function");
980
996
  this.globalWrappers = state.facet(tagWrapper);
981
997
  this.globalAttrs = state.facet(tagAttribute);
982
998
  this.pos = state.doc.resolve(0);
@@ -995,17 +1011,22 @@ class DecoIterator {
995
1011
  widgets(tag, place, walker) {
996
1012
  for (let src of this.globalWidgets) {
997
1013
  if (src.place == place && tag.type == src.type) {
998
- let widget = src.widget(tag);
1014
+ let widget = typeof src.widget == "function" ? src.widget(tag) : src.widget;
999
1015
  if (widget)
1000
1016
  walker.widget(widget, place == 0 || place == 3 ? 1 : -1);
1001
1017
  }
1002
1018
  }
1003
1019
  }
1020
+ hasEndWidget(type) {
1021
+ return this.globalWidgets.some(tw => tw.type == type &&
1022
+ (tw.place == 3 || tw.place == 1) &&
1023
+ typeof tw.widget == "function");
1024
+ }
1004
1025
  walk(from, inclusiveStart, to, walker) {
1005
1026
  for (let i of this.rangeIter)
1006
1027
  i.goto(from);
1007
1028
  for (let i of this.pointIter)
1008
- i.goto(inclusiveStart ? from : from + 1);
1029
+ i.goto(from, inclusiveStart);
1009
1030
  let iter = new HeapIterator(this.rangeIter.filter(i => !i.done), this.pointIter.filter(i => !i.done), from, to);
1010
1031
  let pos = this.pos.advance(from - this.pos.pos), started = inclusiveStart;
1011
1032
  let atomParent;
@@ -1567,7 +1588,7 @@ class Tile {
1567
1588
  let last = this.children.length - 1;
1568
1589
  return last < 0 ? null : this.children[last];
1569
1590
  }
1570
- handleEvent(event, wg) { return false; }
1591
+ ignoreEvent(event) { return false; }
1571
1592
  get ignoreMutations() { return false; }
1572
1593
  toString() { return this.dom.nodeName + (this.children.length ? `(${this.children})` : ""); }
1573
1594
  sync() { }
@@ -1754,18 +1775,18 @@ class DocTile extends CompositeTile {
1754
1775
  this.cursorWrapper = cursorWrapper;
1755
1776
  this.decoSet = decoSet;
1756
1777
  }
1757
- static create(state, dom) {
1778
+ static create(state, dom, wg) {
1758
1779
  return new DocTile(state, dom, null, { points: new Map, ranges: new Map })
1759
- .updateRanges(state, getDecoSet(state), [0, state.doc.length], false);
1780
+ .updateRanges(state, getDecoSet(state), [0, state.doc.length], wg);
1760
1781
  }
1761
1782
  get isDoc() { return true; }
1762
1783
  get node() { return this.state.doc; }
1763
- update(state, changes, connected = false, composition) {
1784
+ update(state, changes, wg, composition) {
1764
1785
  let decoSet = getDecoSet(state);
1765
1786
  let changed = findChangedRanges(this.state, this.decoSet, state, decoSet, changes);
1766
- return this.updateRanges(state, decoSet, changed, connected, composition);
1787
+ return this.updateRanges(state, decoSet, changed, wg, composition);
1767
1788
  }
1768
- updateRanges(state, decoSet, sections, connected, composition) {
1789
+ updateRanges(state, decoSet, sections, wg, composition) {
1769
1790
  let wrapper = composition?.wrapCursor || null;
1770
1791
  if ((!sections.length || sections.length == 2 && sections[1] == -1) && eqArray(wrapper, this.cursorWrapper))
1771
1792
  return this;
@@ -1776,7 +1797,7 @@ class DocTile extends CompositeTile {
1776
1797
  else
1777
1798
  sections = separated;
1778
1799
  }
1779
- let builder = new ContentUpdate(state, this, new DecoIterator(state, decoSet), wrapper);
1800
+ let builder = new ContentUpdate(state, this, wg, new DecoIterator(state, decoSet), wrapper);
1780
1801
  for (let i = 0, posB = 0, startCovered = false; i < sections.length;) {
1781
1802
  let len = sections[i++], ins = sections[i++];
1782
1803
  if (composition && posB == composition.fromB && ins >= 0) {
@@ -1802,7 +1823,7 @@ class DocTile extends CompositeTile {
1802
1823
  }
1803
1824
  let result = builder.finish();
1804
1825
  result.sync();
1805
- if (connected) {
1826
+ if (wg.connected) {
1806
1827
  for (let ch of this.children)
1807
1828
  ch.disconnect(builder.reused);
1808
1829
  for (let tile of builder.toConnect)
@@ -2031,17 +2052,19 @@ class EltTile extends CompositeTile {
2031
2052
  class WidgetTile extends Tile {
2032
2053
  widget;
2033
2054
  _node;
2034
- constructor(widget, _node, flags, length = 0, dom) {
2035
- super(dom || widget.type.render(widget.value), flags);
2055
+ constructor(widget, _node, flags, dom, length = 0) {
2056
+ super(dom, flags);
2036
2057
  this.widget = widget;
2037
2058
  this._node = _node;
2038
2059
  this.length = length;
2060
+ if (dom.nodeType == 1 && !widget.type.editable && dom.contentEditable == "inherit")
2061
+ dom.contentEditable = "false";
2039
2062
  }
2040
2063
  get isNodeOuter() { return !!this._node; }
2041
2064
  get isAtom() { return true; }
2042
2065
  get node() { return this._node; }
2043
2066
  get children() { return noChildren; }
2044
- handleEvent(event, wg) { return this.widget.type.handleEvent(event, wg); }
2067
+ ignoreEvent(event) { return !this.widget.type.propagateEvent(event); }
2045
2068
  connect() {
2046
2069
  this.widget.type.connect?.(this.widget.value, this.dom);
2047
2070
  }
@@ -2232,6 +2255,7 @@ class TilePointer {
2232
2255
  }
2233
2256
  class ContentUpdate {
2234
2257
  state;
2258
+ wg;
2235
2259
  deco;
2236
2260
  old;
2237
2261
  new;
@@ -2240,8 +2264,9 @@ class ContentUpdate {
2240
2264
  keepWalker;
2241
2265
  toConnect = [];
2242
2266
  partialNode = null;
2243
- constructor(state, old, deco, cursorWrapper) {
2267
+ constructor(state, old, wg, deco, cursorWrapper) {
2244
2268
  this.state = state;
2269
+ this.wg = wg;
2245
2270
  this.deco = deco;
2246
2271
  this.old = new TilePointer(old, 0, null);
2247
2272
  this.new = new DocTile(state, old.dom, cursorWrapper, deco.decoSet);
@@ -2321,6 +2346,54 @@ class ContentUpdate {
2321
2346
  };
2322
2347
  }
2323
2348
  keep(len, includeStart, includeEnd) {
2349
+ let cut = [], end = this.posB + len;
2350
+ if (this.deco.endWidgets)
2351
+ for (let nw = this.new, { tile, index, parent } = this.old, pos = this.posB; pos < end;) {
2352
+ if (tile instanceof TextTile) {
2353
+ pos += tile.length - index;
2354
+ ({ tile, index, parent } = parent);
2355
+ index++;
2356
+ }
2357
+ else if (index < tile.children.length) {
2358
+ pos += tile.children[index].length;
2359
+ index++;
2360
+ }
2361
+ else {
2362
+ if (tile.node) {
2363
+ while (!nw.node && nw.parent)
2364
+ nw = nw.parent;
2365
+ if (!nw.parent)
2366
+ break;
2367
+ if (!nw.node.tag.eq(tile.node.tag) &&
2368
+ (this.deco.hasEndWidget(tile.node.type) || this.deco.hasEndWidget(nw.node.type)))
2369
+ cut.push(pos);
2370
+ nw = nw.parent;
2371
+ if (!nw)
2372
+ break;
2373
+ pos++;
2374
+ }
2375
+ if (!parent)
2376
+ break;
2377
+ ({ tile, index, parent } = parent);
2378
+ index++;
2379
+ }
2380
+ }
2381
+ for (let i = cut.length; i;) {
2382
+ let from = cut[--i], to = Math.min(from + 1, includeEnd ? end : end - 1);
2383
+ while (i && cut[i - 1] == from - 1) {
2384
+ from--;
2385
+ i--;
2386
+ }
2387
+ if (from > this.posB)
2388
+ this.keepInner(from - this.posB, includeStart, false);
2389
+ this.update(to - from, true);
2390
+ includeStart = false;
2391
+ len = end - to;
2392
+ }
2393
+ if (len)
2394
+ this.keepInner(len, includeStart, includeEnd);
2395
+ }
2396
+ keepInner(len, includeStart, includeEnd) {
2324
2397
  if (!includeStart) {
2325
2398
  this.old = this.old.walk(0, 1);
2326
2399
  this.openOldWrappers();
@@ -2343,7 +2416,7 @@ class ContentUpdate {
2343
2416
  if (mark.type.element) {
2344
2417
  this.openWrapper(renderMarkWrapper(mark), mark.spanning, false);
2345
2418
  }
2346
- this.new.addChild(new WidgetTile(imgHack, null, 16 | 32));
2419
+ this.new.addChild(new WidgetTile(imgHack, null, 16 | 32, imgHack.render(this.wg)));
2347
2420
  return;
2348
2421
  }
2349
2422
  let found = [];
@@ -2460,7 +2533,7 @@ class ContentUpdate {
2460
2533
  : endOld && this.posB == end ? endOld.matchingWidget(widget, sideFlag, this.reused)
2461
2534
  : null;
2462
2535
  if (!tile) {
2463
- tile = new WidgetTile(widget, null, 16 | sideFlag, 0);
2536
+ tile = new WidgetTile(widget, null, 16 | sideFlag, widget.render(this.wg));
2464
2537
  if (widget.type.connect)
2465
2538
  this.toConnect.push(tile);
2466
2539
  }
@@ -2497,6 +2570,9 @@ class ContentUpdate {
2497
2570
  }
2498
2571
  buildNodeShape(node, shape, reuse, afterContent = 0) {
2499
2572
  if (shape instanceof Elt) {
2573
+ if (node && !shape.hasContent && Attributes.get(shape.attrs, "contenteditable") == null &&
2574
+ !/^(br|hr|img|input|wbr)$/i.test(shape.tagName))
2575
+ shape = Elt.create(shape.tagName, Attributes.merge(shape.attrs, ["contenteditable", "false"]), shape.children);
2500
2576
  let reusable, dom, strict = true;
2501
2577
  if (reusable = this.findReusableTile(shape, reuse, strict) || this.findReusableTile(shape, reuse, strict = false)) {
2502
2578
  this.reused.set(reusable, 2);
@@ -2528,7 +2604,7 @@ class ContentUpdate {
2528
2604
  dom = reusable.dom;
2529
2605
  }
2530
2606
  let flags = (node ? 512 : 16 | 1) | afterContent;
2531
- let tile = new WidgetTile(shape, node, flags, node ? node.length : 0, dom);
2607
+ let tile = new WidgetTile(shape, node, flags, dom || shape.render(this.wg), node ? node.length : 0);
2532
2608
  if (shape.type.connect)
2533
2609
  this.toConnect.push(tile);
2534
2610
  return tile;
@@ -2576,7 +2652,7 @@ class ContentUpdate {
2576
2652
  this.new.children.splice(hasHack, 1);
2577
2653
  }
2578
2654
  else if (needsHack) {
2579
- this.new.addChild(new WidgetTile(brHack, null, 16 | 64, 0));
2655
+ this.new.addChild(new WidgetTile(brHack, null, 16 | 64, brHack.render(this.wg)));
2580
2656
  }
2581
2657
  }
2582
2658
  up() {
@@ -2704,10 +2780,12 @@ function updateAttributes(dom, a, b) {
2704
2780
  return changed;
2705
2781
  }
2706
2782
  const brHack = /*@__PURE__*/Widget.create({
2707
- render() { return document.createElement("br"); }
2783
+ render() { return document.createElement("br"); },
2784
+ editable: true
2708
2785
  });
2709
2786
  const imgHack = /*@__PURE__*/Widget.create({
2710
- render() { return document.createElement("img"); }
2787
+ render() { return document.createElement("img"); },
2788
+ editable: true
2711
2789
  });
2712
2790
  function separateComposition(sections, comp) {
2713
2791
  let result = [], { fromB, toB } = comp;
@@ -4185,13 +4263,17 @@ function isInPrimarySelection(wg, event) {
4185
4263
  }
4186
4264
  return false;
4187
4265
  }
4266
+ const focusEvents = /*@__PURE__*/(() => new Set(["input", "beforeinput", "keydown", "keyup", "keypress", "copy", "cut", "paste"]))();
4188
4267
  function eventBelongsToEditor(wg, event) {
4189
- if (!event.bubbles)
4190
- return true;
4191
4268
  if (event.defaultPrevented)
4192
4269
  return false;
4270
+ if (!event.bubbles)
4271
+ return true;
4272
+ let active = wg.root.activeElement;
4193
4273
  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))
4274
+ if (!node || node.nodeType == 11 ||
4275
+ (tile = Tile.get(node)) && tile.ignoreEvent(event) ||
4276
+ node == active && focusEvents.has(event.type))
4195
4277
  return false;
4196
4278
  return true;
4197
4279
  }
@@ -4756,7 +4838,7 @@ class Wordgard {
4756
4838
  for (let plugin of this.plugins)
4757
4839
  plugin.update(this);
4758
4840
  this.inputState = new InputState(this);
4759
- this.docTile = DocTile.create(this.state, this.contentDOM);
4841
+ this.docTile = DocTile.create(this.state, this.contentDOM, this);
4760
4842
  this.updateAttrs();
4761
4843
  this.observer = new DOMObserver(this);
4762
4844
  if (spec.parent)
@@ -4899,7 +4981,7 @@ class Wordgard {
4899
4981
  this.mountStyles();
4900
4982
  this.updateAttrs();
4901
4983
  }
4902
- this.docTile = prevDocTile.update(update.state, changes, this.connected, composition);
4984
+ this.docTile = prevDocTile.update(update.state, changes, this, composition);
4903
4985
  if ((composition?.wrapCursor || !composition && (prevDocTile != this.docTile || update.selectionSet)) && this.hasFocus)
4904
4986
  setDOMSelection(this);
4905
4987
  this.observer.clear();
package/dist/schema.js CHANGED
@@ -4,7 +4,7 @@ import { Paragraph, Heading, Direction, Blockquote, HorizontalRule, Alignment, D
4
4
  import { phrases, imagePhrases, colorNames } from 'wordgard/phrases';
5
5
  import { Command, setTextblockType, Menu, setAlignment, setDirection, toggleBlock, listIsActive, toggleList, enter, toggleMark } from 'wordgard/command';
6
6
  import { history } from 'wordgard/history';
7
- import { KeyBinding, InputRule, Wordgard, Panel, Dialog, Decoration, PointSet, Tooltip } from 'wordgard/editor';
7
+ import { KeyBinding, InputRule, Wordgard, Widget, Decoration, Panel, Dialog, PointSet, Tooltip } from 'wordgard/editor';
8
8
  import cr from 'crelt';
9
9
 
10
10
  function blockDoc() {
@@ -313,6 +313,97 @@ function codeBlock() {
313
313
  };
314
314
  }));
315
315
  ;return codeBlock})(codeBlock);
316
+ function codeBlockLanguage(options = {}) {
317
+ return [
318
+ GardState.schemaElement.of(CodeBlockLanguage),
319
+ options.languages ? [
320
+ languageStyles,
321
+ languageOptions.of(options.languages),
322
+ languageSelect,
323
+ codeBlockLanguage.selectLanguageBinding
324
+ ] : []
325
+ ];
326
+ }
327
+ const languageStyles = /*@__PURE__*/Wordgard.styles({
328
+ pre: {
329
+ position: "relative",
330
+ "& select.wg-code-language": {
331
+ font: "var(--wg-dialog-font)",
332
+ fontSize: "70%",
333
+ position: "absolute",
334
+ top: "2px",
335
+ right: "4px",
336
+ border: "none",
337
+ borderRadius: "4px"
338
+ }
339
+ }
340
+ });
341
+ const languageOptions = /*@__PURE__*/GardState.Facet.define({
342
+ combine: input => input.reduce((a, b) => a.concat(b), [])
343
+ });
344
+ function option(text, selected, value) {
345
+ let node = document.createElement("option");
346
+ node.textContent = text;
347
+ node.value = value;
348
+ if (selected)
349
+ node.selected = true;
350
+ return node;
351
+ }
352
+ const languageWidget = /*@__PURE__*/Widget.define({
353
+ render({ options, value }, wg) {
354
+ let sel = document.createElement("select");
355
+ sel.className = "wg-code-language";
356
+ sel.tabIndex = -1;
357
+ sel.appendChild(option("Plain", value == null, ""));
358
+ let selected = value && options.find(o => o.toLowerCase() == value);
359
+ if (value && !selected)
360
+ sel.appendChild(option(value, true, value));
361
+ for (let opt of options)
362
+ sel.appendChild(option(opt, opt == selected, opt.toLowerCase()));
363
+ sel.onchange = () => {
364
+ let value = sel.value || undefined;
365
+ let pos = wg.posAtDOM(sel.parentNode);
366
+ let block = pos > 0 && wg.state.doc.nodeAt(pos - 1);
367
+ if (block && block.type == CodeBlock.type && block.mark(CodeBlockLanguage) != value) {
368
+ wg.dispatch({
369
+ changes: value ? { from: pos - 1, add: CodeBlockLanguage.of(value) } :
370
+ { from: pos - 1, remove: CodeBlockLanguage.isInSet(block.tag.marks) }
371
+ });
372
+ wg.focus();
373
+ }
374
+ };
375
+ return sel;
376
+ },
377
+ propagateEvent: false,
378
+ eq(a, b) {
379
+ return a.value == b.value && a.options.length == b.options.length &&
380
+ a.options.every((o, i) => o == b.options[i]);
381
+ },
382
+ inFlow: false
383
+ });
384
+ const languageSelect = /*@__PURE__*/Decoration.Tag.widget.dynamic(CodeBlock, "end", state => {
385
+ let options = state.facet(languageOptions);
386
+ return tag => {
387
+ return languageWidget.of({ options, value: tag.mark(CodeBlockLanguage) });
388
+ };
389
+ });
390
+ ;codeBlockLanguage = /*@__PURE__*/(function (codeBlockLanguage) {
391
+ codeBlockLanguage.selectLanguage = wg => {
392
+ let blk = wg.state.sel.head.textblockParent;
393
+ if (!blk || blk.node.type != CodeBlock.type)
394
+ return false;
395
+ let sel = wg.nodeDOM(blk.before)?.querySelector("select.wg-code-language");
396
+ if (!sel)
397
+ return false;
398
+ sel.focus();
399
+ sel.showPicker();
400
+ return true;
401
+ };
402
+ codeBlockLanguage.selectLanguageBinding = KeyBinding.of({
403
+ key: "Shift-Mod-l",
404
+ run: codeBlockLanguage.selectLanguage
405
+ });
406
+ ;return codeBlockLanguage})(codeBlockLanguage);
316
407
 
317
408
  function strong() {
318
409
  return [GardState.schemaElement.of(Strong), strong.button, strong.keyBinding];
@@ -1188,7 +1279,7 @@ function toggleLink(wg) {
1188
1279
  let url = form && form.elements.namedItem("url")?.value;
1189
1280
  if (url)
1190
1281
  wg.dispatch({
1191
- changes: selection.ranges.map(r => ({ from: r.from, to: r.to, add: Link.of(url) })),
1282
+ changes: wg.state.selection.ranges.map(r => ({ from: r.from, to: r.to, add: Link.of(url) })),
1192
1283
  userEvent: "mark.add"
1193
1284
  });
1194
1285
  });
package/dist/state.js CHANGED
@@ -1884,6 +1884,7 @@ function scanChanges(changes, doc, corrections) {
1884
1884
  pos = off;
1885
1885
  break;
1886
1886
  }
1887
+ off = end;
1887
1888
  }
1888
1889
  if (queried.has(pos))
1889
1890
  return;
package/dist/table.js CHANGED
@@ -159,7 +159,7 @@ function sameCell(a, b) {
159
159
  return a != 0 && a == b;
160
160
  }
161
161
  function computeMap(table) {
162
- if (table.tag != Table)
162
+ if (table.type != Table.type)
163
163
  throw new RangeError(`Not a table node: ${table.type.name}`);
164
164
  let width = table.content[0].content.reduce((w, c) => w + (c.mark(ColSpan) ?? 1), 0);
165
165
  let height = table.content.length;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nerd-bible/wordgard",
3
- "version": "0.3.3",
3
+ "version": "0.3.5",
4
4
  "description": "Semantic rich text editor system",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -17,6 +17,14 @@
17
17
  "./collab": "./dist/collab.js",
18
18
  "./phrases": "./dist/phrases.js"
19
19
  },
20
+ "scripts": {
21
+ "test": "mocha test/test-*.ts",
22
+ "test-headless": "node bin/test-headless.ts",
23
+ "prepare": "node bin/build.ts",
24
+ "watch": "node bin/build.ts --watch",
25
+ "testserver": "node bin/run-testserver.ts",
26
+ "release": "node bin/release.ts"
27
+ },
20
28
  "files": [
21
29
  "/dist"
22
30
  ],
@@ -49,4 +57,4 @@
49
57
  "repository": {
50
58
  "url": "https://github.com/nerd-bible/wordgard"
51
59
  }
52
- }
60
+ }