@nerd-bible/wordgard 0.5.3-beta7 → 0.6.2-beta1

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/state.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { ChangeSet, Node, Pos, Plot, Mark, Schema } from 'wordgard/doc';
1
+ import { Plot, Slice, ChangeSet, Node, Pos, Mark, Schema } from 'wordgard/doc';
2
2
 
3
3
  type wgNode = Node;
4
4
  /**
@@ -87,6 +87,12 @@ declare abstract class GardSelection {
87
87
  anchorSide: -1 | 1;
88
88
  };
89
89
  /**
90
+ Create a content slice and context from this selection. The
91
+ default implementation concatenates the slices for the
92
+ selection's `ranges`.
93
+ */
94
+ toSlice(doc: Plot.Doc): Slice;
95
+ /**
90
96
  The side that the selection head is associated with. -1 means it
91
97
  is after the element before its position, 1 means it is before
92
98
  the element after its position. This influences where the
@@ -144,6 +150,10 @@ declare abstract class GardSelection {
144
150
  */
145
151
  static range(anchor: number, head?: number, headSide?: -1 | 1, goalColumn?: number): GardSelection.Text;
146
152
  /**
153
+ Create a text selection.
154
+ */
155
+ static undirectionalRange(from: number, to: number): GardSelection.Text;
156
+ /**
147
157
  Create a node selection.
148
158
  */
149
159
  static node(pos: number, node: wgNode, goalColumn?: number): GardSelection.Node;
@@ -196,6 +206,13 @@ declare namespace GardSelection {
196
206
  class Text extends GardSelection {
197
207
  private _headSide;
198
208
  /**
209
+ When `false`, indicates that this selection may swap `head`
210
+ and `anchor` when extended. Used for things like selections
211
+ created by double-clicking, which don't have a clearly
212
+ defined anchor.
213
+ */
214
+ readonly directional: boolean;
215
+ /**
199
216
  A set of active marks that should be applied to content
200
217
  inserted at this selection (replacing the contextual marks).
201
218
  Used mostly for making the effect of toggling inline styles
@@ -248,6 +265,11 @@ declare namespace GardSelection {
248
265
  used for things like toggling emphasis on a cursor selection.
249
266
  */
250
267
  marks?: Mark.Set;
268
+ /**
269
+ Set to `false` to indicate that this selection isn't {@link
270
+ GardSelection.Text.directional directional}.
271
+ */
272
+ directional?: boolean;
251
273
  };
252
274
  /**
253
275
  The representation of a text selection when serialized to JSON.
@@ -256,6 +278,7 @@ declare namespace GardSelection {
256
278
  anchor: number;
257
279
  head?: number;
258
280
  side?: -1 | 1;
281
+ directional?: boolean;
259
282
  marks?: Record<string, any>;
260
283
  };
261
284
  }
@@ -771,15 +794,6 @@ declare class TextblockMap {
771
794
  Get the document position for a given string index.
772
795
  */
773
796
  fromIndex(index: number): number;
774
- /**
775
- Get the position at the start or end of the textblock. Note that
776
- in bidirectional text this may not be the actual start or end
777
- position of the node.
778
- */
779
- visualSide(start: boolean): {
780
- pos: number;
781
- side: -1 | 1;
782
- };
783
797
  }
784
798
 
785
799
  type DocSource = Plot.Doc | HTMLElement | DocumentFragment | string | Node.JSON | ((schema: Schema) => Plot.Doc);
@@ -834,12 +848,16 @@ declare class GardState {
834
848
  */
835
849
  get sel(): GardSelection.Resolved;
836
850
  /**
837
- Retrieve the value of a {@link GardState.Field state field}.
838
- Throws an error when the state doesn't have that field, unless
839
- you pass `false` as second parameter.
851
+ Retrieve the value of a {@link GardState.Field state field} or
852
+ {@link GardState.Field.Derived derived field}. Throws an error
853
+ when the state doesn't have that field, unless you pass `false`
854
+ as second parameter, in which case you'll get `undefined` for a
855
+ regular field, or the value will be eagerly computed for a
856
+ derived field.
840
857
  */
841
- field<T>(field: GardState.Field<T>): T;
858
+ field<T>(field: GardState.Field<T> | GardState.Field.Derived<T>): T;
842
859
  field<T>(field: GardState.Field<T>, require: false): T | undefined;
860
+ field<T>(field: GardState.Field.Derived<T>, require: false): T;
843
861
  /**
844
862
  Get the value of a state {@link GardState.Facet facet}.
845
863
  */
@@ -1016,6 +1034,34 @@ declare namespace GardState {
1016
1034
  */
1017
1035
  fromJSON?: (json: any, state: GardState) => Value;
1018
1036
  };
1037
+ /**
1038
+ Define a field-like state slot that is computed from other
1039
+ slots in the state. Automatically tracks dependencies and only
1040
+ recomputes when one of the inputs accessed by the given
1041
+ function changes. Use {@link GardState.field} to read the
1042
+ value of such a field.
1043
+ */
1044
+ class Derived<Value> {
1045
+ private compareF;
1046
+ private constructor();
1047
+ /**
1048
+ Define a derived field.
1049
+ */
1050
+ static define<Value>(compute: (state: GardState) => Value, spec?: {
1051
+ /**
1052
+ Optional function used to compare the value of the field,
1053
+ to prevent propagating a change when a recomputed result
1054
+ is the same as the old value. Should return `true` when
1055
+ the inputs can be considered the same. Defaults to `===`.
1056
+ */
1057
+ compare?: (a: Value, b: Value) => boolean;
1058
+ }): Derived<Value>;
1059
+ /**
1060
+ Derived fields must be included in your state configuration
1061
+ for them to be used.
1062
+ */
1063
+ get extension(): GardState.Extension;
1064
+ }
1019
1065
  }
1020
1066
  /**
1021
1067
  A facet is a labeled value that is associated with an editor
@@ -1061,7 +1107,7 @@ declare namespace GardState {
1061
1107
  */
1062
1108
  of(value: Input): GardState.Extension;
1063
1109
  /**
1064
- Create an extension that computes a value for the facet from a
1110
+ Create an extension that derives a value for the facet from a
1065
1111
  state. The given function should only depend on the state, not
1066
1112
  any external non-constant inputs. Its return value will be kept
1067
1113
  on state update, unless any of the fields or facets (including
@@ -1072,20 +1118,20 @@ declare namespace GardState {
1072
1118
  can use the {@link GardState.Facet.from `from`} method
1073
1119
  instead.
1074
1120
  */
1075
- compute(get: (state: GardState) => Input): GardState.Extension;
1121
+ derive(get: (state: GardState) => Input): GardState.Extension;
1076
1122
  /**
1077
- Create an extension that computes zero or more values for this
1123
+ Create an extension that derives zero or more values for this
1078
1124
  facet from a state.
1079
1125
  */
1080
- computeN(get: (state: GardState) => readonly Input[]): GardState.Extension;
1126
+ deriveN(get: (state: GardState) => readonly Input[]): GardState.Extension;
1081
1127
  /**
1082
1128
  Shorthand method for registering a facet source with a state
1083
1129
  field as input. If the field's type corresponds to this facet's
1084
1130
  input type, the getter function can be omitted. If given, it
1085
1131
  will be used to produce the input from the field value.
1086
1132
  */
1087
- from<T extends Input>(field: GardState.Field<T>): GardState.Extension;
1088
- from<T>(field: GardState.Field<T>, get: (value: T) => Input): GardState.Extension;
1133
+ from<T extends Input>(field: GardState.Field<T> | GardState.Field.Derived<T>): GardState.Extension;
1134
+ from<T>(field: GardState.Field<T> | GardState.Field.Derived<T>, get: (value: T) => Input): GardState.Extension;
1089
1135
  tag: Output;
1090
1136
  }
1091
1137
  namespace Facet {
@@ -1136,7 +1182,7 @@ declare namespace GardState {
1136
1182
  /**
1137
1183
  A facet reader can be used to fetch the value of a facet,
1138
1184
  through {@link GardState.facet} or as a dependency in {@link
1139
- GardState.Facet.compute `Facet.compute`}, but not to define
1185
+ GardState.Facet.derive `Facet.derive`}, but not to define
1140
1186
  new values for the facet.
1141
1187
  */
1142
1188
  type Reader<Output> = {
@@ -1314,7 +1360,7 @@ declare namespace GardState {
1314
1360
  */
1315
1361
  const visualCursorMotion: Facet<boolean, boolean>;
1316
1362
  /**
1317
- @hidden FIXME expose this?
1363
+ @hidden
1318
1364
  */
1319
1365
  const isAtom: Facet<[Plot.Type<unknown>, boolean], Map<Plot.Type<unknown>, boolean>>;
1320
1366
  }
package/dist/state.js CHANGED
@@ -1,4 +1,4 @@
1
- import { Leaf, ValidationError, Pos, ChangeSet, Plot, SchemaError, parse, Schema } from 'wordgard/doc';
1
+ import { Leaf, ValidationError, Slice, Pos, ChangeSet, Plot, SchemaError, parse, Schema } from 'wordgard/doc';
2
2
  import { findClusterBreak } from '@marijn/find-cluster-break';
3
3
 
4
4
  function dec(str) {
@@ -444,8 +444,20 @@ class TextblockMap {
444
444
  return off;
445
445
  }
446
446
  moveVisually(start, side, forward, skipped) {
447
- let startIndex = this.toIndex(start), { order, ltr } = this;
448
- let spanI = BidiSpan.find(order, startIndex, side);
447
+ if (!this.text.length)
448
+ return null;
449
+ let startIndex = this.toIndex(start), { order, ltr } = this, spanI;
450
+ if (startIndex == 0 && side < 0) { if (!forward)
451
+ return null;
452
+ startIndex = order[spanI = 0].side(false, this.ltr);
453
+ }
454
+ else if (startIndex == this.text.length && side > 0) { if (forward)
455
+ return null;
456
+ startIndex = order[spanI = order.length - 1].side(true, this.ltr);
457
+ }
458
+ else {
459
+ spanI = BidiSpan.find(order, startIndex, side);
460
+ }
449
461
  let span = order[spanI], spanEnd = span.side(forward, ltr);
450
462
  if (startIndex == spanEnd) {
451
463
  let nextI = spanI += forward ? 1 : -1;
@@ -463,8 +475,14 @@ class TextblockMap {
463
475
  if (skipped)
464
476
  skipped[0] = this.text.slice(Math.min(startIndex, nextIndex), Math.max(startIndex, nextIndex));
465
477
  let nextSpan = spanI == (forward ? order.length - 1 : 0) ? null : order[spanI + (forward ? 1 : -1)];
466
- if (nextSpan && nextIndex == spanEnd && nextSpan.level + (forward ? 0 : 1) < span.level)
467
- return { pos: this.fromIndex(nextSpan.side(!forward, ltr)), side: nextSpan.forward(forward, ltr) ? 1 : -1 };
478
+ if (nextIndex == spanEnd) {
479
+ if (!nextSpan)
480
+ return forward ? { pos: this.start + this.node.contentLength, side: 1 }
481
+ : { pos: this.start, side: -1 };
482
+ if (nextSpan.level + (forward ? 0 : 1) < span.level)
483
+ return { pos: this.fromIndex(nextSpan.side(!forward, this.ltr)),
484
+ side: nextSpan.forward(forward, this.ltr) ? 1 : -1 };
485
+ }
468
486
  return { pos: this.fromIndex(nextIndex), side: nextIndex != spanEnd ? 1 : span.forward(forward, ltr) ? -1 : 1 };
469
487
  }
470
488
  skipWord(start, side, forward, visually) {
@@ -500,18 +518,6 @@ class TextblockMap {
500
518
  return cur; let segments = [...new Intl.Segmenter(undefined, { granularity: "word" }).segment(word)];
501
519
  return history.get(segments[forward ? 0 : segments.length - 1].segment.length) || cur;
502
520
  }
503
- visualSide(start) {
504
- let pos, side;
505
- if (start) {
506
- let span = this.order[0];
507
- [pos, side] = span.ltr == this.ltr ? [span.from, 1] : [span.to, -1];
508
- }
509
- else {
510
- let span = this.order[this.order.length - 1];
511
- [pos, side] = span.ltr == this.ltr ? [span.to, -1] : [span.from, 1];
512
- }
513
- return { pos: this.fromIndex(pos), side };
514
- }
515
521
  moveLogically(start, forward) {
516
522
  let index = this.toIndex(start);
517
523
  let next = findClusterBreak(this.text, index, forward);
@@ -550,6 +556,14 @@ class GardSelection {
550
556
  get ranges() { return [this]; }
551
557
  get replacementRange() { return this; }
552
558
  get domSelection() { return this; }
559
+ toSlice(doc) {
560
+ let slice;
561
+ for (let range of this.ranges) {
562
+ let cur = Slice.get(doc, range.from, range.to);
563
+ slice = slice ? slice.concat(cur) : cur;
564
+ }
565
+ return slice;
566
+ }
553
567
  get headSide() { return this.head > this.anchor ? -1 : 1; }
554
568
  get anchorSide() { return this.anchor > this.head ? -1 : 1; }
555
569
  eqPos(other) {
@@ -580,14 +594,17 @@ class GardSelection {
580
594
  return type.fromJSON(doc, json);
581
595
  }
582
596
  static cursor(pos, side, goalColumn) {
583
- return GardSelection.Text.createInner(pos, pos, side, goalColumn);
597
+ return GardSelection.Text.createInner(pos, pos, side, true, goalColumn);
584
598
  }
585
599
  static near(cx, pos, side = 1, goalColumn) {
586
600
  let norm = findNormalAt(cx, pos, side);
587
601
  return GardSelection.cursor(norm.pos, norm.side, goalColumn);
588
602
  }
589
603
  static range(anchor, head, headSide, goalColumn) {
590
- return GardSelection.Text.createInner(anchor, head ?? anchor, headSide, goalColumn);
604
+ return GardSelection.Text.createInner(anchor, head ?? anchor, headSide, true, goalColumn);
605
+ }
606
+ static undirectionalRange(from, to) {
607
+ return GardSelection.Text.createInner(from, to, 1, false);
591
608
  }
592
609
  static node(pos, node, goalColumn) {
593
610
  return GardSelection.Node.create(pos, node, goalColumn);
@@ -604,9 +621,8 @@ class GardSelection {
604
621
  return cursorAtStart(cx, block);
605
622
  }
606
623
  static atEnd(cx, block) {
607
- let found = block
608
- ? TextblockMap.get(cx, block.start, block.node).visualSide(false)
609
- : cx.doc.inlineContent ? TextblockMap.get(cx, 0, cx.doc).visualSide(false)
624
+ let found = block ? { pos: block.end, side: 1 }
625
+ : cx.doc.inlineContent ? { pos: cx.doc.length, side: 1 }
610
626
  : findNormalAt(cx, cx.doc.length, -1);
611
627
  return GardSelection.cursor(found.pos, found.side);
612
628
  }
@@ -618,15 +634,18 @@ class GardSelection {
618
634
  GardSelection.define = define;
619
635
  class Text extends GardSelection {
620
636
  _headSide;
637
+ directional;
621
638
  marks;
622
- constructor(anchor, head, _headSide, goalColumn,
639
+ constructor(anchor, head, _headSide,
640
+ directional, goalColumn,
623
641
  marks) {
624
642
  super(anchor, head, goalColumn);
625
643
  this._headSide = _headSide;
644
+ this.directional = directional;
626
645
  this.marks = marks;
627
646
  }
628
- static createInner(anchor, head, side, goalColumn, marks) {
629
- return new Text(anchor, head, side ?? (head > anchor ? -1 : 1), goalColumn, marks);
647
+ static createInner(anchor, head, side, directional, goalColumn, marks) {
648
+ return new Text(anchor, head, side ?? (head > anchor ? -1 : 1), directional, goalColumn, marks);
630
649
  }
631
650
  get headSide() {
632
651
  return this._headSide;
@@ -636,7 +655,7 @@ class GardSelection {
636
655
  }
637
656
  static create(spec) {
638
657
  let { anchor, head = anchor } = spec;
639
- return Text.createInner(anchor, head, spec.headSide, spec.goalColumn, spec.marks);
658
+ return Text.createInner(anchor, head, spec.headSide, spec.directional !== false, spec.goalColumn, spec.marks);
640
659
  }
641
660
  map(change, cx, assoc = -1) {
642
661
  let from, to;
@@ -647,7 +666,7 @@ class GardSelection {
647
666
  from = change.mapPos(this.from, 1);
648
667
  to = Math.max(from, change.mapPos(this.to, -1));
649
668
  }
650
- return Text.createInner(from, to, this.headSide, this.goalColumn, this.marks);
669
+ return Text.createInner(from, to, this.headSide, this.directional, this.goalColumn, this.marks);
651
670
  }
652
671
  eq(other) {
653
672
  return other instanceof Text && this.eqPos(other) && this.headSide == other.headSide &&
@@ -674,7 +693,7 @@ class GardSelection {
674
693
  throw new ValidationError("Invalid JSON representation for GardSelection.Text");
675
694
  let anchor = json.anchor, head = typeof json.head == "number" ? json.head : anchor;
676
695
  let marks = json.marks ? doc.schema.marksFromJSON(json.marks) : undefined;
677
- return Text.createInner(anchor, head, json.side == 1 || json.side == -1 ? json.side : undefined, undefined, marks);
696
+ return Text.createInner(anchor, head, json.side == 1 || json.side == -1 ? json.side : undefined, json.directional !== false, undefined, marks);
678
697
  }));
679
698
  })(Text = GardSelection.Text || (GardSelection.Text = {}));
680
699
  class Node extends GardSelection {
@@ -744,8 +763,8 @@ class GardSelection {
744
763
  ;return GardSelection})(GardSelection);
745
764
  function cursorAtStart(cx, block) {
746
765
  let found = block
747
- ? TextblockMap.get(cx, block.start, block.node).visualSide(true)
748
- : cx.doc.inlineContent ? TextblockMap.get(cx, 0, cx.doc).visualSide(true)
766
+ ? { pos: block.start, side: -1 }
767
+ : cx.doc.inlineContent ? { pos: 0, side: -1 }
749
768
  : findNormalAt(cx, 0, 1);
750
769
  return GardSelection.cursor(found.pos, found.side);
751
770
  }
@@ -790,11 +809,8 @@ function scanNormalFrom(cx, from, side, forward) {
790
809
  let bottom = pos.pos, step = forward ? 1 : -1;
791
810
  for (let { parent, index } = pos, p = pos.pos;;) {
792
811
  let { node, parent: next } = parent;
793
- if (node.inlineContent) {
794
- if (cx.config.visualCursorMotion)
795
- return TextblockMap.get(cx, parent.start, parent.node).visualSide(forward);
796
- return { pos: p, side: forward ? 1 : -1 };
797
- }
812
+ if (node.inlineContent)
813
+ return { pos: p, side: forward ? -1 : 1 };
798
814
  if (index == (forward ? node.content.length : 0)) {
799
815
  let barrier = !next || isBarrier(cx, node);
800
816
  if ((bottom != from) && pastBarrier && barrier)
@@ -857,10 +873,8 @@ function findNormalAt(cx, pos, bias) {
857
873
  let nextIndex = index + (dir > 0 ? 0 : -1), next = parent.node.content[nextIndex];
858
874
  if (next.isLeaf || isBarrier(cx, next))
859
875
  break;
860
- if (!parent.node.inlineContent && next.inlineContent && cx.config.visualCursorMotion) {
861
- let startPos = curPos - (dir > 0 ? 0 : next.length) + 1;
862
- return TextblockMap.get(cx, startPos, next).visualSide(dir > 0);
863
- }
876
+ if (!parent.node.inlineContent && next.inlineContent)
877
+ return { pos: curPos + dir, side: -dir };
864
878
  parent = Pos.Plot.create(parent, next, curPos, nextIndex);
865
879
  index = dir > 0 ? 0 : next.content.length;
866
880
  curPos += dir;
@@ -886,8 +900,7 @@ function skipWord(cx, start, side, forward) {
886
900
  return next;
887
901
  if (!block.parent)
888
902
  return last;
889
- let end = visually ? map.visualSide(!forward)
890
- : forward ? { pos: block.end, side: -1 } : { pos: block.start, side: 1 };
903
+ let end = forward ? { pos: block.end, side: 1 } : { pos: block.start, side: -1 };
891
904
  if (end.pos != start)
892
905
  last = end;
893
906
  pos = forward ? block.after : block.before;
@@ -932,13 +945,14 @@ function wordAt(state, pos, bias) {
932
945
  cur = res.parent.node.content[i++];
933
946
  }
934
947
  if (!Intl.Segmenter)
935
- return GardSelection.range(start, end);
948
+ return GardSelection.undirectionalRange(start, end);
936
949
  let best = null, local = pos - start;
937
950
  for (let segment of new Intl.Segmenter(undefined, { granularity: "word" }).segment(text)) {
938
951
  if (segment.isWordLike && segment.index <= local && segment.index + segment.segment.length >= local && (!best || bias > 0))
939
952
  best = segment;
940
953
  }
941
- return best ? GardSelection.range(start + best.index, start + best.index + best.segment.length) : GardSelection.cursor(pos, bias);
954
+ return best ? GardSelection.undirectionalRange(start + best.index, start + best.index + best.segment.length)
955
+ : GardSelection.cursor(pos, bias);
942
956
  }
943
957
 
944
958
  class Transaction {
@@ -1240,7 +1254,7 @@ class GardState {
1240
1254
  if (addr == null) {
1241
1255
  if (require)
1242
1256
  throw new RangeError("Field is not present in this state");
1243
- return undefined;
1257
+ return field instanceof GardState.Field ? undefined : field.value(this);
1244
1258
  }
1245
1259
  let track = this.trackAccess;
1246
1260
  if (track) {
@@ -1413,6 +1427,65 @@ class GardState {
1413
1427
  }
1414
1428
  }
1415
1429
  GardState.Field = Field;
1430
+ (function (Field) {
1431
+ class Derived {
1432
+ value;
1433
+ compareF;
1434
+ id = nextID++;
1435
+ dependencies = [];
1436
+ constructor(
1437
+ value, compareF) {
1438
+ this.value = value;
1439
+ this.compareF = compareF;
1440
+ }
1441
+ static define(compute, spec = {}) {
1442
+ return new Derived(compute, spec.compare || ((a, b) => a === b));
1443
+ }
1444
+ get extension() { return this; }
1445
+ slot(addresses) {
1446
+ let idx = addresses[this.id] >> 1;
1447
+ let depSet = new DependencySet;
1448
+ return {
1449
+ create: state => {
1450
+ state.values[idx] = state.recordAccess(this.dependencies, this.value);
1451
+ return 1;
1452
+ },
1453
+ update: (state, tr) => {
1454
+ depSet.update(this.dependencies, addresses);
1455
+ if ((depSet.doc && tr.docChanged) || (depSet.sel && (tr.docChanged || tr.selection)) ||
1456
+ (depSet.schema && tr.startState.schema != state.schema) || ensureAll(state, depSet.addrs)) {
1457
+ let newVal = state.recordAccess(this.dependencies, this.value);
1458
+ if (!this.compareF(newVal, state.values[idx])) {
1459
+ state.values[idx] = newVal;
1460
+ return 1;
1461
+ }
1462
+ }
1463
+ return 0;
1464
+ },
1465
+ reconfigure: (state, oldState) => {
1466
+ let newVal, oldAddr = oldState.config.address[this.id];
1467
+ if (oldAddr != null) {
1468
+ let oldVal = getAddr(oldState, oldAddr);
1469
+ if (this.dependencies.every(dep => {
1470
+ return dep instanceof GardState.Facet ? oldState.facet(dep) === state.facet(dep)
1471
+ : dep instanceof GardState.Field ? oldState.field(dep, false) == state.field(dep, false)
1472
+ : true;
1473
+ }) || this.compareF(newVal = state.recordAccess(this.dependencies, this.value), oldVal)) {
1474
+ state.values[idx] = oldVal;
1475
+ return 0;
1476
+ }
1477
+ }
1478
+ else {
1479
+ newVal = state.recordAccess(this.dependencies, this.value);
1480
+ }
1481
+ state.values[idx] = newVal;
1482
+ return 1;
1483
+ }
1484
+ };
1485
+ }
1486
+ }
1487
+ Field.Derived = Derived;
1488
+ })(Field = GardState.Field || (GardState.Field = {}));
1416
1489
  class Facet {
1417
1490
  combine;
1418
1491
  compareInput;
@@ -1438,24 +1511,26 @@ class GardState {
1438
1511
  return new GardState.Facet(config.combine || ((a) => a), config.compareInput || ((a, b) => a === b), config.compare || (!config.combine ? sameArray : (a, b) => a === b), !!config.static, config.enables);
1439
1512
  }
1440
1513
  of(value) {
1441
- return new FacetProvider(none, this, 1, value);
1514
+ return new FacetProvider(this, nextID++, 1, value);
1442
1515
  }
1443
- compute(get) {
1516
+ derive(get) {
1444
1517
  if (this.isStatic)
1445
- throw new Error("Can't compute a static facet");
1446
- return new FacetProvider([], this, 4, get);
1518
+ throw new Error("Can't derive a static facet");
1519
+ let field = Field.Derived.define(get, { compare: this.compareInput });
1520
+ return [field, new FacetProvider(this, field.id, 0, null)];
1447
1521
  }
1448
- computeN(get) {
1522
+ deriveN(get) {
1449
1523
  if (this.isStatic)
1450
- throw new Error("Can't compute a static facet");
1451
- return new FacetProvider([], this, 2 | 4, get);
1524
+ throw new Error("Can't derive a static facet");
1525
+ let field = Field.Derived.define(get, { compare: (a, b) => compareArray(a, b, this.compareInput) });
1526
+ return [field, new FacetProvider(this, field.id, 2, null)];
1452
1527
  }
1453
1528
  from(field, get) {
1454
1529
  if (this.isStatic)
1455
- throw new Error("Can't compute a static facet");
1530
+ throw new Error("Can't derive a static facet");
1456
1531
  if (!get)
1457
- get = x => x;
1458
- return new FacetProvider([field], this, 0, state => get(state.field(field)));
1532
+ return new FacetProvider(this, field.id, 0, null);
1533
+ return this.derive(s => get(s.field(field)));
1459
1534
  }
1460
1535
  tag;
1461
1536
  }
@@ -1547,10 +1622,6 @@ class GardState {
1547
1622
  address[p.id] = (staticValues.length << 1) | 1;
1548
1623
  staticValues.push(p.value);
1549
1624
  }
1550
- else {
1551
- address[p.id] = dynamicSlots.length << 1;
1552
- dynamicSlots.push(a => p.dynamicSlot(a));
1553
- }
1554
1625
  }
1555
1626
  address[facet.id] = dynamicSlots.length << 1;
1556
1627
  dynamicSlots.push(a => dynamicFacetSlot(a, facet, providers));
@@ -1617,6 +1688,9 @@ class GardState {
1617
1688
  if (ext.provides)
1618
1689
  inner(ext.provides, prec);
1619
1690
  }
1691
+ else if (ext instanceof GardState.Field.Derived) {
1692
+ result[prec].push(ext);
1693
+ }
1620
1694
  else if (ext instanceof FacetProvider) {
1621
1695
  result[prec].push(ext);
1622
1696
  if (ext.facet.extensions)
@@ -1722,60 +1796,16 @@ class DependencySet {
1722
1796
  }
1723
1797
  class FacetProvider {
1724
1798
  facet;
1799
+ id;
1725
1800
  flags;
1726
1801
  value;
1727
- id = nextID++;
1728
- extension; dependencies;
1729
- constructor(dependencies, facet, flags, value) {
1802
+ extension; constructor(facet,
1803
+ id, flags,
1804
+ value) {
1730
1805
  this.facet = facet;
1806
+ this.id = id;
1731
1807
  this.flags = flags;
1732
1808
  this.value = value;
1733
- this.dependencies = dependencies; }
1734
- dynamicSlot(addresses) {
1735
- let getter = this.value;
1736
- let compare = this.facet.compareInput;
1737
- let id = this.id, idx = addresses[id] >> 1;
1738
- let multi = this.flags & 2;
1739
- let dependencies = this.dependencies;
1740
- let auto = this.flags & 4 ? dependencies : null;
1741
- let depSet = new DependencySet;
1742
- return {
1743
- create(state) {
1744
- state.values[idx] = state.recordAccess(auto, getter);
1745
- return 1;
1746
- },
1747
- update(state, tr) {
1748
- depSet.update(dependencies, addresses);
1749
- if ((depSet.doc && tr.docChanged) || (depSet.sel && (tr.docChanged || tr.selection)) ||
1750
- (depSet.schema && tr.startState.schema != state.schema) || ensureAll(state, depSet.addrs)) {
1751
- let newVal = state.recordAccess(auto, getter);
1752
- if (multi ? !compareArray(newVal, state.values[idx], compare) : !compare(newVal, state.values[idx])) {
1753
- state.values[idx] = newVal;
1754
- return 1;
1755
- }
1756
- }
1757
- return 0;
1758
- },
1759
- reconfigure(state, oldState) {
1760
- let newVal, oldAddr = oldState.config.address[id];
1761
- if (oldAddr != null) {
1762
- let oldVal = getAddr(oldState, oldAddr);
1763
- if (dependencies.every(dep => {
1764
- return dep instanceof GardState.Facet ? oldState.facet(dep) === state.facet(dep)
1765
- : dep instanceof GardState.Field ? oldState.field(dep, false) == state.field(dep, false)
1766
- : true;
1767
- }) || (multi ? compareArray(newVal = getter(state), oldVal, compare) : compare(newVal = getter(state), oldVal))) {
1768
- state.values[idx] = oldVal;
1769
- return 0;
1770
- }
1771
- }
1772
- else {
1773
- newVal = state.recordAccess(auto, getter);
1774
- }
1775
- state.values[idx] = newVal;
1776
- return 1;
1777
- }
1778
- };
1779
1809
  }
1780
1810
  }
1781
1811
  function compareArray(a, b, compare) {
package/dist/table.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import * as wordgard_state from 'wordgard/state';
2
2
  import { GardState, GardSelection, Transaction } from 'wordgard/state';
3
3
  import { Menu, Command } from 'wordgard/command';
4
- import { ChangeSet, Plot, Slice } from 'wordgard/doc';
4
+ import { Plot, Slice, ChangeSet } from 'wordgard/doc';
5
5
 
6
6
  /**
7
7
  Enable table support with the given configuration. The returned
@@ -144,6 +144,7 @@ declare class CellSelection extends GardSelection {
144
144
  readonly head: number;
145
145
  readonly headSide: -1;
146
146
  };
147
+ toSlice(doc: Plot.Doc): Slice;
147
148
  eq(other: GardSelection): boolean;
148
149
  map(changes: ChangeSet, cx: GardSelection.Context, assoc?: -1 | 1): GardSelection.Text | CellSelection;
149
150
  /**
@@ -210,6 +211,6 @@ declare const splitCell: Command.Pure;
210
211
  /**
211
212
  @hidden exported for testing
212
213
  */
213
- declare function handleTablePaste(state: GardState, slice: Slice, context: readonly Plot.Tag[], drop?: number): false | Transaction.Spec;
214
+ declare function handleTablePaste(state: GardState, slice: Slice, drop?: number): false | Transaction.Spec;
214
215
 
215
216
  export { CellSelection, addColumn, addRow, deleteColumn, deleteRow, handleTablePaste, mergeCells, splitCell, tableMenu, tables, toggleHeaderCell };