@nerd-bible/wordgard 0.6.2-beta1 → 0.6.2-beta10

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/collab.js CHANGED
@@ -37,6 +37,7 @@ class CollabState {
37
37
  }
38
38
  }
39
39
  const collabConfig = /*@__PURE__*/GardState.Facet.define({
40
+ name: "collabConfig",
40
41
  combine(configs) {
41
42
  let combined = GardState.Facet.combineConfig(configs, {
42
43
  startVersion: 0,
@@ -52,12 +53,14 @@ const collabConfig = /*@__PURE__*/GardState.Facet.define({
52
53
  }
53
54
  });
54
55
  const collabReceive = /*@__PURE__*/Transaction.Effect.define({
56
+ name: "collabReceive",
55
57
  map(state, changes) {
56
58
  return changes.empty ? state
57
59
  : new CollabState(state.version, state.syncedDoc, state.nextUpdate, addUpdate(state.openUpdate, changes));
58
60
  }
59
61
  });
60
62
  const collabField = /*@__PURE__*/GardState.Field.define({
63
+ name: "collab",
61
64
  create(state) {
62
65
  return new CollabState(state.facet(collabConfig).startVersion, state.doc, null, null);
63
66
  },
package/dist/command.d.ts CHANGED
@@ -518,10 +518,10 @@ declare const enter: Command.Pure;
518
518
  /**
519
519
  Delete the selection, or the unit after or before the selection.
520
520
  If that unit is the start or end of a textblock, this will try to
521
- join that textblock to the next one. Otherwise, if it is a
522
- character or leaf node, that is deleted. If none of that is
523
- possible and the cursor is in an empty textblock, this will delete
524
- the textblock.
521
+ join that textblock to the next one. Otherwise, if the cursor is
522
+ in an empty textblock, this will delete the textblock. Finally, if
523
+ there is a character or leaf node in the given direction, that is
524
+ deleted.
525
525
 
526
526
  When deleting backward at the start of a list item that has a
527
527
  sibling before it, this command will try to join those list items.
@@ -709,9 +709,17 @@ declare const redo: Command;
709
709
 
710
710
  /**
711
711
  If the cursor is in an empty textblock that can be lifted out of a
712
- parent, return a transaction that does this.
712
+ parent, return a transaction that does this. Unless
713
+ `splitListItem` is set to false, if the block is part of a
714
+ continued list item, this function will split the item above the
715
+ block before it lifts it.
713
716
  */
714
- declare function liftEmptyBlock(state: GardState): Transaction.Spec | false;
717
+ declare function liftEmptyBlock(state: GardState, splitListItem?: boolean): Transaction.Spec | false;
718
+ /**
719
+ Lift the textblock that the cursor is at the start of out of its
720
+ innermost parent, if possible.
721
+ */
722
+ declare function liftTextblock(state: GardState): Transaction.Spec | false;
715
723
  /**
716
724
  If the selection is in a {@link Plot.Spec.preserveWhitespace
717
725
  `preserveWhitespace`} textblock, replace the selected content with
@@ -823,4 +831,4 @@ updated to perform those joins.
823
831
  */
824
832
  declare function autoJoinBlocks(state: GardState, tr: Transaction.Spec): Transaction.Spec;
825
833
 
826
- export { Command, Menu, autoJoinBlocks, canAddMarkInRange, clearNonFitting, deleteBackward, deleteEmptyPlot, deleteForward, deleteLine, deleteSelection, deleteToLineEnd, deleteUnit, deleteWord, doUnwrapBlock, enter, enterInCode, findUnwrappable, findWrappable, insertLineBreak, insertText, joinBackward, joinBlocks, joinForward, joinListItems, killToLineEnd, 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, yankKilled };
834
+ export { Command, Menu, autoJoinBlocks, canAddMarkInRange, clearNonFitting, deleteBackward, deleteEmptyPlot, deleteForward, deleteLine, deleteSelection, deleteToLineEnd, deleteUnit, deleteWord, doUnwrapBlock, enter, enterInCode, findUnwrappable, findWrappable, insertLineBreak, insertText, joinBackward, joinBlocks, joinForward, joinListItems, killToLineEnd, liftEmptyBlock, liftTextblock, 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, yankKilled };
package/dist/command.js CHANGED
@@ -5,6 +5,7 @@ import { findClusterBreak } from '@marijn/find-cluster-break';
5
5
  import { Alignment, Direction, Emphasis, Strong, Underline } from 'wordgard/types';
6
6
 
7
7
  const commandHandler = /*@__PURE__*/GardState.Facet.define({
8
+ name: "commandHandler",
8
9
  combine(handlers) {
9
10
  let map = new Map();
10
11
  for (let [cmd, handler] of handlers) {
@@ -45,12 +46,27 @@ const Command = /*@__PURE__*/(function (Command) {
45
46
  Command.dispatch = dispatch;
46
47
  ;return Command})({});
47
48
 
48
- function liftEmptyBlock(state) {
49
+ function liftEmptyBlock(state, splitListItem = true) {
49
50
  if (!state.selection.isCursor)
50
51
  return false;
51
52
  let sel = state.sel, block = sel.head.textblockParent;
52
53
  if (!block || !sel.head.isAtStart(block) || !sel.head.isAtEnd(block))
53
54
  return false;
55
+ if (splitListItem && block.parent?.parent && block.index && block.parent.parent.node.type.hasRole(Node.Role.List))
56
+ return {
57
+ changes: { from: block.before, insert: [Plot.End, block.parent.node.tag] },
58
+ scrollIntoView: true,
59
+ userEvent: "split.list"
60
+ };
61
+ return liftBlock(state, block);
62
+ }
63
+ function liftTextblock(state) {
64
+ if (!state.selection.isCursor)
65
+ return false;
66
+ let { head } = state.sel, block = head.textblockParent;
67
+ return !block || !head.isAtStart(block) ? false : liftBlock(state, block);
68
+ }
69
+ function liftBlock(state, block) {
54
70
  let start = block.before, end = block.after, before = [], after = [];
55
71
  for (let level = block.parent, index = block.index, atStart = true, atEnd = true, first = true; level; first = false, index = level.index, level = level.parent) {
56
72
  if (!first && state.schema.canContain(level.node.type, block.node.type))
@@ -60,7 +76,7 @@ function liftEmptyBlock(state) {
60
76
  { from: block.after, to: end, insert: after }
61
77
  ],
62
78
  scrollIntoView: true,
63
- userEvent: "unwrap.empty"
79
+ userEvent: "unwrap"
64
80
  };
65
81
  if (level.node.isInline || level.node.type.isolating)
66
82
  break;
@@ -740,7 +756,7 @@ const insertText = ({ state }, { from, to, insert, userEvent }) => {
740
756
  let marks = (from == selection.from && to == selection.to && state.sel.activeMarks) ||
741
757
  state.doc.resolve(from).marks(state.doc.resolve(to));
742
758
  return {
743
- changes: { from, to, insert: [Leaf.Text.of(insert, marks)], fit: true },
759
+ changes: { from, to, insert: insert ? [Leaf.text(insert, marks)] : undefined, fit: true },
744
760
  scrollIntoView: true,
745
761
  selection: (cx, changes) => GardSelection.near(cx, changes.mapPos(to, 1), -1),
746
762
  userEvent
@@ -792,8 +808,8 @@ const deleteUnit = ({ state }, dir) => {
792
808
  if (state.readOnly)
793
809
  return false;
794
810
  return deleteSelection(state) || (dir == "forward"
795
- ? joinForward(state) || deleteForward(state) || deleteEmptyPlot(state, 1)
796
- : joinListItems(state) || joinBackward(state) || deleteBackward(state) || deleteEmptyPlot(state, -1));
811
+ ? joinForward(state) || deleteEmptyPlot(state, 1) || deleteForward(state)
812
+ : joinListItems(state) || liftTextblock(state) || joinBackward(state) || deleteEmptyPlot(state, -1) || deleteBackward(state));
797
813
  };
798
814
  const deleteWord = ({ state }, dir) => {
799
815
  if (state.readOnly)
@@ -819,8 +835,9 @@ const deleteToLineEnd = (wg, dir) => {
819
835
  userEvent: "delete." + dir
820
836
  };
821
837
  };
822
- const addToKillBuffer = /*@__PURE__*/Transaction.Effect.define();
838
+ const addToKillBuffer = /*@__PURE__*/Transaction.Effect.define({ name: "addToKillBuffer" });
823
839
  const killBuffer = /*@__PURE__*/GardState.Field.define({
840
+ name: "killBuffer",
824
841
  create() { return { content: [], active: false }; },
825
842
  update(value, tr) {
826
843
  let add = tr.effects.find(e => e.is(addToKillBuffer));
@@ -1303,9 +1320,10 @@ const Menu = /*@__PURE__*/(function (Menu) {
1303
1320
  let src = Menu.Item.source.of(this);
1304
1321
  this.extension = this.parent ? [src, this.parent] : src;
1305
1322
  }
1323
+ toString() { return "Menu.Item"; }
1306
1324
  }
1307
1325
  Item.Base = Base;
1308
- Item.source = GardState.Facet.define();
1326
+ Item.source = GardState.Facet.define({ name: "menuItem" });
1309
1327
  })(Item = Menu.Item || (Menu.Item = {}));
1310
1328
  class Button extends Item.Base {
1311
1329
  spec;
@@ -1380,6 +1398,7 @@ const Menu = /*@__PURE__*/(function (Menu) {
1380
1398
  template(...content) {
1381
1399
  return Template.new(this, content.length ? content : ["..."]);
1382
1400
  }
1401
+ toString() { return "Menu.Group"; }
1383
1402
  }
1384
1403
  Menu.Group = Group;
1385
1404
  (function (Group) {
@@ -1541,4 +1560,4 @@ const Menu = /*@__PURE__*/(function (Menu) {
1541
1560
  Menu.resolve = resolve;
1542
1561
  ;return Menu})({});
1543
1562
 
1544
- export { Command, Menu, autoJoinBlocks, canAddMarkInRange, clearNonFitting, deleteBackward, deleteEmptyPlot, deleteForward, deleteLine, deleteSelection, deleteToLineEnd, deleteUnit, deleteWord, doUnwrapBlock, enter, enterInCode, findUnwrappable, findWrappable, insertLineBreak, insertText, joinBackward, joinBlocks, joinForward, joinListItems, killToLineEnd, 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, yankKilled };
1563
+ export { Command, Menu, autoJoinBlocks, canAddMarkInRange, clearNonFitting, deleteBackward, deleteEmptyPlot, deleteForward, deleteLine, deleteSelection, deleteToLineEnd, deleteUnit, deleteWord, doUnwrapBlock, enter, enterInCode, findUnwrappable, findWrappable, insertLineBreak, insertText, joinBackward, joinBlocks, joinForward, joinListItems, killToLineEnd, liftEmptyBlock, liftTextblock, 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, yankKilled };
package/dist/doc.d.ts CHANGED
@@ -1310,12 +1310,19 @@ declare namespace Node {
1310
1310
  implies membership of its parent groups.
1311
1311
  */
1312
1312
  readonly parent: Group | undefined;
1313
+ /**
1314
+ This group's debug name.
1315
+ */
1316
+ readonly name: string | null;
1313
1317
  private tag;
1314
1318
  private constructor();
1315
1319
  /**
1316
1320
  Define a custom node group.
1317
1321
  */
1318
- static define(parent?: Group): Group;
1322
+ static define(spec?: {
1323
+ parent?: Group;
1324
+ name?: string;
1325
+ }): Group;
1319
1326
  /**
1320
1327
  A group that contains every node type.
1321
1328
  */
@@ -1374,11 +1381,15 @@ declare namespace Node {
1374
1381
  whether a given node has the role attached.
1375
1382
  */
1376
1383
  class Role {
1384
+ /**
1385
+ This role's name.
1386
+ */
1387
+ readonly name: string | null;
1377
1388
  private constructor();
1378
1389
  /**
1379
1390
  Define a new role.
1380
1391
  */
1381
- static define(): Role;
1392
+ static define(name?: string): Role;
1382
1393
  /**
1383
1394
  This role indicates that a plot contains code, and makes some
1384
1395
  commands behave differently inside such a plot.
package/dist/doc.js CHANGED
@@ -412,7 +412,7 @@ const Attributes = /*@__PURE__*/(function (Attributes) {
412
412
  score--;
413
413
  iA += 2;
414
414
  }
415
- else if (iB < b.length && iA == a.length) {
415
+ else if (iB < b.length) {
416
416
  score--;
417
417
  iB += 2;
418
418
  }
@@ -867,8 +867,8 @@ class Pos {
867
867
  let d = this.depth;
868
868
  if (depth > d)
869
869
  throw new RangeError("Asking for parent deeper than position depth");
870
- for (let d = this.depth, p = this.parent;; p = p.parent)
871
- if (d == depth)
870
+ for (let p = this.parent;; p = p.parent)
871
+ if (d-- == depth)
872
872
  return p;
873
873
  }
874
874
  isAtStart(parent) {
@@ -1093,6 +1093,7 @@ class BaseType {
1093
1093
  get isBlock() { return (this.flags & 1) == 0; }
1094
1094
  get isAtom() { return (this.flags & 4) > 0; }
1095
1095
  get isSelectable() { return (this.flags & 32) > 0; }
1096
+ toString() { return this.name; }
1096
1097
  }
1097
1098
  class BaseTag {
1098
1099
  param;
@@ -1131,29 +1132,48 @@ const Node = /*@__PURE__*/(function (Node) {
1131
1132
  })(Node.Type || (Node.Type = {}));
1132
1133
  class Group {
1133
1134
  parent;
1135
+ name;
1134
1136
  constructor(
1135
- parent) {
1137
+ parent,
1138
+ name) {
1136
1139
  this.parent = parent;
1140
+ this.name = name;
1137
1141
  }
1138
- static define(parent) { return new Group(parent); }
1139
- static All = Group.define();
1140
- static Inline = Group.define();
1141
- static Block = Group.define();
1142
- static Leaf = Group.define();
1143
- static Plot = Group.define();
1144
- static Textblock = Group.define();
1145
- static Content = Group.define();
1146
- static TableCell = Group.define();
1147
- static ListItem = Group.define();
1142
+ static define(spec = {}) {
1143
+ return new Group(spec.parent, spec.name || null);
1144
+ }
1145
+ static All = Group.define({ name: "All" });
1146
+ static Inline = Group.define({ name: "Inline" });
1147
+ static Block = Group.define({ name: "Block" });
1148
+ static Leaf = Group.define({ name: "Leaf" });
1149
+ static Plot = Group.define({ name: "Plot" });
1150
+ static Textblock = Group.define({ name: "Textblock" });
1151
+ static Content = Group.define({ name: "Content" });
1152
+ static TableCell = Group.define({ name: "TableCell" });
1153
+ static ListItem = Group.define({ name: "ListItem" });
1148
1154
  static builtin = [Group.All, Group.Inline, Group.Block, Group.Leaf, Group.Plot, Group.Textblock];
1155
+ toString() { return "Node.Group" + (this.name ? ":" + this.name : ""); }
1149
1156
  }
1150
1157
  Node.Group = Group;
1158
+ function queryToString(q) {
1159
+ if ("and" in q)
1160
+ return `and(${q.and.map(queryToString)})`;
1161
+ if (Array.isArray(q))
1162
+ return `or(q.map(queryToString))`;
1163
+ return String(q);
1164
+ }
1165
+ Node.queryToString = queryToString;
1151
1166
  class Role {
1152
- constructor() { }
1153
- static define() { return new Role; }
1154
- static Code = Role.define();
1155
- static List = Role.define();
1156
- static LineBreak = Role.define();
1167
+ name;
1168
+ constructor(
1169
+ name) {
1170
+ this.name = name;
1171
+ }
1172
+ static define(name) { return new Role(name || null); }
1173
+ static Code = Role.define("Code");
1174
+ static List = Role.define("List");
1175
+ static LineBreak = Role.define("LineBreak");
1176
+ toString() { return "Node.Role" + (this.name ? ":" + this.name : ""); }
1157
1177
  }
1158
1178
  Node.Role = Role;
1159
1179
  ;return Node})({});
@@ -1207,6 +1227,8 @@ class Leaf extends BaseTag {
1207
1227
  return Leaf.Text.of(this.param.slice(Math.max(from, 0), Math.max(0, to)), this.marks);
1208
1228
  }
1209
1229
  static text(text, marks = Mark.none) {
1230
+ if (!text)
1231
+ throw new Error("Cannot create empty text nodes");
1210
1232
  return Leaf.Text.of(text, marks);
1211
1233
  }
1212
1234
  toString() {
@@ -1390,6 +1412,8 @@ class Plot {
1390
1412
  constructor(type, param, marks) {
1391
1413
  super(param, marks);
1392
1414
  this.type = type;
1415
+ if (this.type.isDoc && marks.length)
1416
+ throw new Error("Document nodes cannot have marks");
1393
1417
  }
1394
1418
  static new(type, param, marks) {
1395
1419
  return new Tag(type, param, marks);
@@ -1995,11 +2019,9 @@ class Builder {
1995
2019
  return this.schema.doc(this.stack.children);
1996
2020
  }
1997
2021
  }
1998
- function isAdd(m) { return !!m.add; }
1999
- function isRemove(m) { return !!m.remove; }
2000
2022
  function applyModifications(modifications, marks, type) {
2001
2023
  for (const m of modifications) {
2002
- if (isAdd(m)) {
2024
+ if ("add" in m) {
2003
2025
  marks = m.add.addToSet(marks);
2004
2026
  }
2005
2027
  else {
@@ -2009,7 +2031,7 @@ function applyModifications(modifications, marks, type) {
2009
2031
  return marks;
2010
2032
  }
2011
2033
  function modificationToJSON(m) {
2012
- return isAdd(m) ? { add: m.add.name, value: m.add.value } : { remove: m.remove.name, value: m.remove.value };
2034
+ return "add" in m ? { add: m.add.name, value: m.add.value } : { remove: m.remove.name, value: m.remove.value };
2013
2035
  }
2014
2036
  function modificationFromJSON(schema, json) {
2015
2037
  let { add, remove } = json;
@@ -2034,7 +2056,7 @@ function compareModifications(a, b) {
2034
2056
  return true;
2035
2057
  }
2036
2058
  function compareModification(a, b) {
2037
- return isAdd(a) ? isAdd(b) && a.add.eq(b.add) : isRemove(b) && a.remove.eq(b.remove);
2059
+ return "add" in a ? "add" in b && a.add.eq(b.add) : "remove" in b && a.remove.eq(b.remove);
2038
2060
  }
2039
2061
  function isNatNum(value) {
2040
2062
  return typeof value == "number" && Math.floor(value) == value && value >= 0;
@@ -2371,7 +2393,7 @@ class ChangeSet {
2371
2393
  }
2372
2394
  else if (data) {
2373
2395
  text += `[${data.map(mod => {
2374
- return `${isAdd(mod) ? "+" + mod.add : "-" + mod.remove}`;
2396
+ return `${"add" in mod ? "+" + mod.add : "-" + mod.remove}`;
2375
2397
  })}]`;
2376
2398
  }
2377
2399
  if (text)
@@ -2397,6 +2419,36 @@ class ChangeSetBuilder {
2397
2419
  sections = [];
2398
2420
  data = [];
2399
2421
  pos = 0;
2422
+ addSection(from, to, ins, value) {
2423
+ if (from >= this.pos) {
2424
+ addSection(this.sections, this.data, from - this.pos, -1, null);
2425
+ addSection(this.sections, this.data, to - from, ins, value);
2426
+ this.pos = to;
2427
+ return true;
2428
+ }
2429
+ if (this.sections.length && this.sections[0] > to && this.sections[1] == -1) {
2430
+ this.spliceSection(0, from, to, ins, value);
2431
+ return true;
2432
+ }
2433
+ for (let pos = this.pos, i = this.sections.length; i > 2 && pos > to;) {
2434
+ let nextIns = this.sections[--i], nextLen = this.sections[--i];
2435
+ let start = pos - nextLen;
2436
+ if (nextIns == -1 && start < from) {
2437
+ this.spliceSection(i, from - start, to - start, ins, value);
2438
+ return true;
2439
+ }
2440
+ pos = start;
2441
+ }
2442
+ return false;
2443
+ }
2444
+ spliceSection(i, from, to, ins, value) {
2445
+ let sections = [], data = [];
2446
+ addSection(sections, data, from, -1, null);
2447
+ addSection(sections, data, to - from, ins, value);
2448
+ addSection(sections, data, this.sections[i] - to, -1, null);
2449
+ this.sections.splice(i, 2, ...sections);
2450
+ this.data.splice(i >> 1, 1, ...data);
2451
+ }
2400
2452
  }
2401
2453
  function sliceLen(tokens) {
2402
2454
  return tokens.reduce((l, e) => l + (e.tokenType == Token.Type.Node ? e.length : 1), 0);
@@ -2417,14 +2469,11 @@ function createChangeSet(doc, spec, mayCorrect = true) {
2417
2469
  accum = accum ? accum.compose(transform(set, accum, doc, false, false).set) : set;
2418
2470
  };
2419
2471
  let section = (from, to, ins, value) => {
2420
- if (!cur || from < cur.pos) {
2472
+ if (!cur?.addSection(from, to, ins, value)) {
2421
2473
  flush();
2422
2474
  cur = new ChangeSetBuilder(doc.length);
2475
+ cur.addSection(from, to, ins, value);
2423
2476
  }
2424
- if (from > cur.pos)
2425
- addSection(cur.sections, cur.data, from - cur.pos, -1, null);
2426
- addSection(cur.sections, cur.data, to - from, ins, value);
2427
- cur.pos = to;
2428
2477
  };
2429
2478
  let build = (spec) => {
2430
2479
  if (Array.isArray(spec)) {
@@ -2474,17 +2523,20 @@ function createChangeSet(doc, spec, mayCorrect = true) {
2474
2523
  if (remove) {
2475
2524
  let mods = [{ remove }];
2476
2525
  markableSections(doc, from, to, remove.type.spanning, (node, from, to) => {
2477
- const has = remove.isInSet(node.tag.marks);
2478
- if (!has || !doc.schema.markAllowed(remove.type, node.type))
2479
- return false;
2480
- let modsHere = mods;
2481
2526
  if (remove.type.set) {
2482
- let left = subtractSet(remove.value, has.value, remove.type.set);
2527
+ let has = remove.type.isInSet(node.tag.marks);
2528
+ if (!has)
2529
+ return false;
2530
+ let left = remove.value.filter(a => has.value.some(b => remove.type.set(a, b) == 0));
2483
2531
  if (!left.length)
2484
2532
  return false;
2485
- modsHere = [{ remove: remove.type.of(left) }];
2533
+ section(from, to, -2, left.length == remove.value.length ? mods : [{ remove: remove.type.of(left) }]);
2534
+ }
2535
+ else {
2536
+ if (!remove.isInSet(node.tag.marks))
2537
+ return false;
2538
+ section(from, to, -2, mods);
2486
2539
  }
2487
- section(from, to, -2, modsHere);
2488
2540
  return true;
2489
2541
  });
2490
2542
  }
@@ -2631,16 +2683,16 @@ function filterMods(mods, against) {
2631
2683
  return mods.filter(m => !against.some(a => modCancels(a, m)));
2632
2684
  }
2633
2685
  function modCancels(mod, other) {
2634
- if (isAdd(other)) {
2635
- return isAdd(mod) ? mod.add.type == other.add.type && !mod.add.type.set : mod.remove.eq(other.add);
2686
+ if ("add" in other) {
2687
+ return "add" in mod ? mod.add.type == other.add.type && !mod.add.type.set : mod.remove.eq(other.add);
2636
2688
  }
2637
2689
  else {
2638
- return isAdd(mod) && mod.add.eq(isAdd(other) ? other.add : other.remove);
2690
+ return "add" in mod && mod.add.eq(other.remove);
2639
2691
  }
2640
2692
  }
2641
2693
  function invertMods(mods, target) {
2642
2694
  return mods.map(mod => {
2643
- if (isRemove(mod))
2695
+ if ("remove" in mod)
2644
2696
  return { add: mod.remove };
2645
2697
  if (!mod.add.type.set) {
2646
2698
  let existed = mod.add.type.isInSet(target.marks);
package/dist/editor.d.ts CHANGED
@@ -8,6 +8,7 @@ import { PointSet, Set, RangeSet } from 'wordgard/set';
8
8
 
9
9
  type MakeSelectionStyle = (wg: Wordgard, event: MouseEvent) => Wordgard.MouseSelectionStyle | null;
10
10
 
11
+ import Mark_ = Mark;
11
12
  /**
12
13
  A widget describes a piece of DOM content that can be used to
13
14
  render a node, a part of a node, or an extra element added via a
@@ -193,8 +194,8 @@ declare namespace Decoration {
193
194
  /**
194
195
  Override the shape used for a given mark inside the editor.
195
196
  */
196
- function shape<V>(type: Mark.Type<V>, shape: Shape.Element<V> | Shape.Attribute<V> | Shape.Attributes<V>): GardState.Extension;
197
- function shape(mark: Mark<null>, shape: Shape.Element<null> | Shape.Attribute<null> | Shape.Attributes<null>): GardState.Extension;
197
+ function shape<V>(type: Mark_.Type<V>, shape: Shape.Element<V> | Shape.Attribute<V> | Shape.Attributes<V>): GardState.Extension;
198
+ function shape(mark: Mark_<null>, shape: Shape.Element<null> | Shape.Attribute<null> | Shape.Attributes<null>): GardState.Extension;
198
199
  }
199
200
  /**
200
201
  A point decoration is a decoration that targets a given position
@@ -375,10 +376,11 @@ declare const enum TileFlag {
375
376
  Composition = 128,
376
377
  Synced = 256,// Node has been synced. DOM content matches child list / text content, child array becomes read-only
377
378
  Atom = 512,// Composite tile whose length isn't determined by child length
378
- HasContent = 1024,// EltTile whose elt has a content hole
379
- AfterContent = 2048,// Tiles that sit after their parent's content position
380
- ContentNotLast = 4096,// EltTile that has children with AfterContent flag
381
- Dirty = 8192
379
+ Uneditable = 1024,// Tile's DOM is uneditable
380
+ HasContent = 2048,// EltTile whose elt has a content hole
381
+ AfterContent = 4096,// Tiles that sit after their parent's content position
382
+ ContentNotLast = 8192,// EltTile that has children with AfterContent flag
383
+ Dirty = 16384
382
384
  }
383
385
  declare const enum Orientation {
384
386
  Row = 0,
@@ -420,14 +422,15 @@ declare abstract class Tile {
420
422
  get boundary(): 0 | 1;
421
423
  get firstChild(): Tile | null;
422
424
  get lastChild(): Tile | null;
423
- get nodeParent(): Tile;
424
425
  ignoreEvent(event: Event): boolean;
425
426
  get ignoreMutations(): boolean;
426
427
  toString(): string;
427
428
  abstract sync(): void;
428
429
  connect(): void;
429
430
  disconnect(reused?: Map<Tile, Reused>): void;
430
- nearestNode(): Tile;
431
+ nearestNode(): Tile & {
432
+ tag: Node$1.Tag;
433
+ };
431
434
  markDirty(): void;
432
435
  posAtCoords(state: GardState, x: number, y: number): CoordPos;
433
436
  abstract posAtCoordsInner(start: number, state: GardState, x: number, y: number, textblock: TextblockMap | null, orientation: Orientation): CoordPos;
@@ -437,6 +440,7 @@ declare class CompositeTile extends Tile {
437
440
  children: Tile[];
438
441
  dom: Element;
439
442
  addChild(child: Tile): void;
443
+ putChild(child: Tile, index: number): void;
440
444
  sync(): void;
441
445
  syncChildren(): void;
442
446
  posAtCoordsInner(start: number, state: GardState, x: number, y: number, textblock: TextblockMap | null, orientation: Orientation): CoordPos;
@@ -727,7 +731,13 @@ declare class Wordgard {
727
731
  Facet that allows you to register handlers to override paste
728
732
  behavior.
729
733
  */
730
- static pasteHandler: GardState.Facet<(wg: Wordgard, event: InputEvent, slice: wordgard_doc.Slice) => boolean, readonly ((wg: Wordgard, event: InputEvent, slice: wordgard_doc.Slice) => boolean)[]>;
734
+ static pasteHandler: GardState.Facet<(wg: Wordgard, event: InputEvent | null, slice: wordgard_doc.Slice, html: string, text: string) => boolean, readonly ((wg: Wordgard, event: InputEvent | null, slice: wordgard_doc.Slice, html: string, text: string) => boolean)[]>;
735
+ /**
736
+ Perform a paste with the given content. This will run {@link
737
+ Wordgard.pasteHandler paste handlers} and, if none handles the
738
+ paste, fall back to the default handling.
739
+ */
740
+ paste(html: string, text?: string): void;
731
741
  /**
732
742
  Facet for custom drop handlers. When the drop is done inside the
733
743
  editor and should move an existing range, the `move` parameter
@@ -853,7 +863,7 @@ declare class Wordgard {
853
863
  the editor's {@link Wordgard.dom wrapper element} (to which the
854
864
  scope class will be added) need to be explicitly differentiated
855
865
  by adding an `&` to the selector for that element—for example
856
- `&:has(wg-content:focus)`.
866
+ `&.wg-focused`.
857
867
  */
858
868
  static theme(spec: Record<string, StyleSpec>): GardState.Extension;
859
869
  /**
@@ -922,6 +932,11 @@ declare class Wordgard {
922
932
  part of the editor (for example a gutter).
923
933
  */
924
934
  static coveredMargins: GardState.Facet<(wg: Wordgard) => Partial<DOMRect> | null, readonly ((wg: Wordgard) => Partial<DOMRect> | null)[]>;
935
+ /**
936
+ Given a DOM element that may be a Wordgard editor, retrieve its
937
+ editor instance.
938
+ */
939
+ static findFromDOM(elt: Element): Wordgard | null;
925
940
  }
926
941
  declare namespace Wordgard {
927
942
  /**