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

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,10 +1,11 @@
1
1
  import { GardState, GardSelection, TextblockMap, BidiSpan, Transaction } from 'wordgard/state';
2
- import { Attributes, Elt, Node, Leaf, parse, Slice, Plot, serialize, Pos, ChangeSet, ValidationError, Mark } from 'wordgard/doc';
2
+ import { Attributes, Elt, Node, Mark, Leaf, parse, Slice, Plot, serialize, Pos, ChangeSet, ValidationError } from 'wordgard/doc';
3
3
  import { StyleModule } from 'style-mod';
4
4
  import { findClusterBreak } from '@marijn/find-cluster-break';
5
5
  import { findAbove, PointSet, RangeSet } from 'wordgard/set';
6
6
  import { enter, insertLineBreak, selectAll, undo, redo, killToLineEnd, yankKilled, 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';
7
- import { PhraseSet, phrases } from 'wordgard/phrases';
7
+ import { phrases, PhraseSet } from 'wordgard/phrases';
8
+ import cr from 'crelt';
8
9
  import { history } from 'wordgard/history';
9
10
 
10
11
  function addUpdated(sections, updated) {
@@ -211,19 +212,19 @@ const Decoration = /*@__PURE__*/(function (Decoration) {
211
212
  function shape(type, shape, config) {
212
213
  let tp = Node.Type.get(type);
213
214
  let shapeFunc = typeof shape == "function"
214
- ? tag => addMarkAttributes(shape(tag), tag)
215
- : tag => addMarkAttributes(shape, tag);
215
+ ? memo(tag => shape(tag))
216
+ : () => shape;
216
217
  let atom = typeof shape == "function" ? config?.atom : !shape.hasContent;
217
- let ext = tagShape.of({ type: tp, shape: memo(shapeFunc) });
218
+ let ext = tagShape.of({ type: tp, shape: shapeFunc });
218
219
  if (tp.isPlot && atom != null)
219
220
  ext = [ext, GardState.isAtom.of([tp, atom])];
220
221
  return ext;
221
222
  }
222
223
  Tag.shape = shape;
223
224
  (function (shape_1) {
224
- function dynamic( type, shape, config) {
225
+ function derived(type, shape, config) {
225
226
  let tp = Node.Type.get(type);
226
- let ext = tagShape.compute(state => {
227
+ let ext = tagShape.derive(state => {
227
228
  let s = shape(state);
228
229
  return { type: tp, shape: typeof s == "function" ? memo(s) : () => s };
229
230
  });
@@ -232,7 +233,7 @@ const Decoration = /*@__PURE__*/(function (Decoration) {
232
233
  ext = [ext, GardState.isAtom.of([tp, atom])];
233
234
  return ext;
234
235
  }
235
- shape_1.dynamic = dynamic;
236
+ shape_1.derived = derived;
236
237
  })(shape = Tag.shape || (Tag.shape = {}));
237
238
  function wrapper(type, wrapper, options) {
238
239
  if (!wrapper.hasContent)
@@ -256,10 +257,10 @@ const Decoration = /*@__PURE__*/(function (Decoration) {
256
257
  }
257
258
  Tag.widget = widget;
258
259
  (function (widget_1) {
259
- function dynamic(type, place, widget) {
260
+ function derived(type, place, widget) {
260
261
  let tp = Node.Type.get(type);
261
262
  let p = getPlace(place);
262
- return tagWidget.compute(state => {
263
+ return tagWidget.derive(state => {
263
264
  let w = widget(state);
264
265
  return {
265
266
  type: tp,
@@ -268,7 +269,7 @@ const Decoration = /*@__PURE__*/(function (Decoration) {
268
269
  };
269
270
  });
270
271
  }
271
- widget_1.dynamic = dynamic;
272
+ widget_1.derived = derived;
272
273
  })(widget = Tag.widget || (Tag.widget = {}));
273
274
  function attribute(type, attr, value, options) {
274
275
  let tp = Node.Type.get(type);
@@ -277,6 +278,16 @@ const Decoration = /*@__PURE__*/(function (Decoration) {
277
278
  }
278
279
  Tag.attribute = attribute;
279
280
  })(Decoration.Tag || (Decoration.Tag = {}));
281
+ (function (Mark$1) {
282
+ function shape(type, shape) {
283
+ if (type instanceof Mark)
284
+ type = type.type;
285
+ if (type.spanning && !("element" in shape))
286
+ throw new Error("Spanning marks must have an element shape");
287
+ return markShape.of({ type, modified: Mark.Type.define(type.name, { ...type.spec, shape }) });
288
+ }
289
+ Mark$1.shape = shape;
290
+ })(Decoration.Mark || (Decoration.Mark = {}));
280
291
  class Point {
281
292
  constructor() { }
282
293
  static widget(widget, options) {
@@ -324,10 +335,15 @@ const Decoration = /*@__PURE__*/(function (Decoration) {
324
335
  }
325
336
  Decoration.Range = Range;
326
337
  ;return Decoration})({});
327
- const tagShape = /*@__PURE__*/GardState.Facet.define();
338
+ const tagShape = /*@__PURE__*/GardState.Facet.define({
339
+ combine: shapes => new Map(shapes.map(s => [s.type, s.shape]).reverse())
340
+ });
328
341
  const tagWrapper = /*@__PURE__*/GardState.Facet.define();
329
342
  const tagWidget = /*@__PURE__*/GardState.Facet.define();
330
343
  const tagAttribute = /*@__PURE__*/GardState.Facet.define();
344
+ const markShape = /*@__PURE__*/GardState.Facet.define({
345
+ combine: shapes => new Map(shapes.map(s => [s.type, s.modified]).reverse())
346
+ });
331
347
  function memo(f) {
332
348
  let map = new WeakMap();
333
349
  return (arg) => {
@@ -337,11 +353,12 @@ function memo(f) {
337
353
  return found;
338
354
  };
339
355
  }
340
- function addMarkAttributes(shape, tag) {
356
+ function addMarkAttributes(overrides, shape, tag) {
341
357
  let attrs;
342
358
  for (let mark of tag.marks) {
343
- if (mark.type.attribute && (mark.spanning || !tag.isText)) {
344
- let { get, target } = mark.type.attribute;
359
+ let type = overrides.get(mark.type) || mark.type;
360
+ if (type.attribute && (mark.spanning || !tag.isText)) {
361
+ let { get, target } = type.attribute;
345
362
  let markAttrs = get(mark.value);
346
363
  if (markAttrs.length) {
347
364
  if (target && shape instanceof Elt)
@@ -367,13 +384,15 @@ function applyDeco(shape, deco, tag) {
367
384
  return shape;
368
385
  }
369
386
  const baseTagShape = /*@__PURE__*/memo((tag) => {
370
- return addMarkAttributes(tag.is(Leaf.Text) ? Widget.editableText.of(tag.param)
371
- : tag.type.shape.create(tag.param), tag);
387
+ return tag.is(Leaf.Text) ? Widget.editableText.of(tag.param) : tag.type.shape.create(tag.param);
372
388
  });
373
- function renderMarks(marks, around) {
374
- let result = addMarkAttributes(Elt.create("span", Attributes.none, [around]), Leaf.text(around, marks));
389
+ function renderMarks(state, marks, around) {
390
+ let markShapes = state.facet(markShape);
391
+ let result = addMarkAttributes(markShapes, Elt.create("span", Attributes.none, [around]), Leaf.text(around, marks));
375
392
  for (let i = marks.length - 1; i >= 0; i--) {
376
- let mark = marks[i];
393
+ let mark = marks[i], shape = markShapes.get(mark.type);
394
+ if (shape)
395
+ mark = shape.of(mark.value);
377
396
  if (mark.type.element)
378
397
  result = renderMarkWrapper(mark).fill([result]);
379
398
  }
@@ -494,14 +513,10 @@ function compareDecoSet(setA, setB, cmp) {
494
513
  if (!setA.has(srcB))
495
514
  cmp(null, valB);
496
515
  }
497
- function compareGlobal(stateA, stateB, facet) {
498
- return stateA.facet(facet) != stateB.facet(facet);
499
- }
516
+ const globalDecoFacets = [tagShape, tagWidget, tagWrapper, tagAttribute, markShape];
500
517
  function findChangedRanges(prevState, prevDeco, state, deco, sections) {
501
518
  let result = [];
502
- let globalChange = compareGlobal(prevState, state, tagShape) || compareGlobal(prevState, state, tagWidget) ||
503
- compareGlobal(prevState, state, tagWrapper) || compareGlobal(prevState, state, tagAttribute);
504
- let shapeChanges = [];
519
+ let globalChange = globalDecoFacets.some(facet => prevState.facet(facet) !== state.facet(facet));
505
520
  for (let i = 0, posA = 0, posB = 0; i < sections.length;) {
506
521
  let len = sections[i++], ins = sections[i++];
507
522
  if (ins == -1 && globalChange) {
@@ -524,11 +539,6 @@ function findChangedRanges(prevState, prevDeco, state, deco, sections) {
524
539
  compareDecoSet(prevDeco.points, deco.points, (a, b) => {
525
540
  (a || PointSet.empty).compareRange(posA, b || PointSet.empty, posB, len, (pos, val) => {
526
541
  add(pos, Math.min(pos + (val instanceof WidgetDecoration ? 0 : 1), endB));
527
- if (val instanceof ShapeDecoration && !globalChange) {
528
- let idx = findAbove(shapeChanges, 0, pos - 1);
529
- if (idx == shapeChanges.length || shapeChanges[idx] != pos)
530
- shapeChanges.splice(idx, 0, pos);
531
- }
532
542
  });
533
543
  });
534
544
  let joined = joinRanges(ranges), pos = posB, end = pos + len, j = 0;
@@ -557,34 +567,8 @@ function findChangedRanges(prevState, prevDeco, state, deco, sections) {
557
567
  addSection(result, len, ins);
558
568
  }
559
569
  }
560
- if (shapeChanges.length)
561
- return addAtomicityChanges(result, prevState, shapeChanges);
562
570
  return result;
563
571
  }
564
- function addAtomicityChanges(changes, prev, nodes) {
565
- let added = [];
566
- let scan = prev.doc.resolve(0), sectionPos = 0, sectionI = 0, off = 0;
567
- for (let posB of nodes) {
568
- while (posB >= sectionPos) {
569
- let len = changes[sectionI++], ins = changes[sectionI++];
570
- if (ins < 0) {
571
- sectionPos += len;
572
- }
573
- else {
574
- sectionPos += ins;
575
- off += len - ins;
576
- }
577
- }
578
- let posA = posB - off;
579
- if (scan.pos < posA)
580
- scan = scan.advance(posA - scan.pos);
581
- let node = scan.nodeAfter;
582
- if (!node)
583
- continue;
584
- added.push(posA, posA + node.length);
585
- }
586
- return added.length ? addUpdated(changes, added) : changes;
587
- }
588
572
  class SpanIterator {
589
573
  ranges;
590
574
  points;
@@ -652,24 +636,6 @@ class SpanIterator {
652
636
  return this;
653
637
  }
654
638
  }
655
- function nodeWrappers(schema, tag, active, atom) {
656
- let wrappers;
657
- for (let mark of tag.marks)
658
- if (mark.type.element)
659
- (wrappers || (wrappers = [])).push(mark);
660
- if (active.length) {
661
- for (let val of active) {
662
- if (val instanceof WrapperRangeDecoration && (tagScope(tag, atom) & val.scope) &&
663
- (!val.query || schema.matchNode(tag.type, val.query)))
664
- (wrappers || (wrappers = [])).push(val);
665
- }
666
- }
667
- if (!wrappers)
668
- return none;
669
- if (wrappers.length > 1)
670
- wrappers.sort((a, b) => (a.spanning == b.spanning ? 0 : a.spanning ? -1 : 1) || a.rank - b.rank);
671
- return wrappers;
672
- }
673
639
  function tagScope(tag, atom) {
674
640
  return 4 |
675
641
  (atom ? 1 | (tag.type.isInline ? 2 : 0) : 0);
@@ -690,6 +656,7 @@ class DecoIterator {
690
656
  globalWidgets;
691
657
  globalWrappers;
692
658
  globalAttrs;
659
+ markShapes;
693
660
  schema;
694
661
  pos;
695
662
  rangeCursor;
@@ -705,6 +672,7 @@ class DecoIterator {
705
672
  .some(w => (w.place == 1 || w.place == 3) && typeof w.widget == "function");
706
673
  this.globalWrappers = state.facet(tagWrapper);
707
674
  this.globalAttrs = state.facet(tagAttribute);
675
+ this.markShapes = state.facet(markShape);
708
676
  this.pos = state.doc.resolve(0);
709
677
  this.schema = state.schema;
710
678
  this.rangeCursor = RangeSet.cursor(state.facet(Decoration.Range.source).map(s => s(state)));
@@ -729,6 +697,26 @@ class DecoIterator {
729
697
  (tw.place == 3 || tw.place == 1) &&
730
698
  typeof tw.widget == "function");
731
699
  }
700
+ nodeWrappers(tag, active, atom) {
701
+ let wrappers;
702
+ for (let mark of tag.marks) {
703
+ let shape = this.markShapes.get(mark.type) || mark.type;
704
+ if (shape.element)
705
+ (wrappers || (wrappers = [])).push(shape == mark.type ? mark : shape.of(mark.value));
706
+ }
707
+ if (active.length) {
708
+ for (let val of active) {
709
+ if (val instanceof WrapperRangeDecoration && (tagScope(tag, atom) & val.scope) &&
710
+ (!val.query || this.schema.matchNode(tag.type, val.query)))
711
+ (wrappers || (wrappers = [])).push(val);
712
+ }
713
+ }
714
+ if (!wrappers)
715
+ return none;
716
+ if (wrappers.length > 1)
717
+ wrappers.sort((a, b) => (a.spanning == b.spanning ? 0 : a.spanning ? -1 : 1) || a.rank - b.rank);
718
+ return wrappers;
719
+ }
732
720
  walk(from, inclusiveStart, to, walker) {
733
721
  this.rangeCursor.goto(from);
734
722
  this.pointCursor.goto(from, inclusiveStart ? -1e9 : 1000000000);
@@ -736,7 +724,7 @@ class DecoIterator {
736
724
  let pos = this.pos.advance(from - this.pos.pos), started = inclusiveStart;
737
725
  let atomParent;
738
726
  for (let p = pos.parent; p; p = p.parent)
739
- if (this.state.isAtom(p.node.type))
727
+ if (!p.node.isDoc && (this.state.isAtom(p.node.type) || this.hasAtomShape(p.before)))
740
728
  atomParent = p;
741
729
  let pendingDeco = [], pendingPos = -1;
742
730
  let pendingShape = null, pendingShapeSet = null;
@@ -753,7 +741,7 @@ class DecoIterator {
753
741
  shape = applyDeco(shape, deco, node.tag);
754
742
  if (shape.hasContent)
755
743
  throw new Error("Leaf nodes shapes shouldn't have a content hole");
756
- walker.node(node, shape, nodeWrappers(this.schema, node.tag, iter.active, true), undefined);
744
+ walker.node(node, shape, this.nodeWrappers(node.tag, iter.active, true), undefined);
757
745
  this.widgets(node.tag, 1, walker);
758
746
  },
759
747
  enterPlot: (node, pos) => {
@@ -766,7 +754,7 @@ class DecoIterator {
766
754
  if (pendingPos == pos)
767
755
  for (let deco of pendingDeco)
768
756
  shape = applyDeco(shape, deco, node.tag);
769
- let wrappers = nodeWrappers(this.schema, node.tag, iter.active, !shape.hasContent);
757
+ let wrappers = this.nodeWrappers(node.tag, iter.active, !shape.hasContent);
770
758
  let atom = !shape.hasContent;
771
759
  if (atom)
772
760
  walker.node(node, shape, wrappers, pos + node.length > to ? to - pos : undefined);
@@ -842,15 +830,10 @@ class DecoIterator {
842
830
  this.pos = pos;
843
831
  }
844
832
  tagShape(tag, active) {
845
- let shape;
846
- if (!tag.is(Leaf.Text))
847
- for (let src of this.tagShapes)
848
- if (src.type == tag.type) {
849
- shape = src.shape(tag);
850
- break;
851
- }
852
- if (!shape)
853
- shape = baseTagShape(tag);
833
+ let override = this.tagShapes.get(tag.type);
834
+ let shape = override ? override(tag) : baseTagShape(tag);
835
+ if (tag.marks.length)
836
+ shape = addMarkAttributes(this.markShapes, shape, tag);
854
837
  let add;
855
838
  for (let src of this.globalAttrs)
856
839
  if (tag.type == src.type) {
@@ -877,6 +860,15 @@ class DecoIterator {
877
860
  }
878
861
  return shape;
879
862
  }
863
+ atomCache = new Map();
864
+ hasAtomShape(pos) {
865
+ let has = this.atomCache.get(pos);
866
+ if (has == null) {
867
+ has = this.pointSets.some(s => s.at(pos, d => d instanceof ShapeDecoration && !d.shape.hasContent));
868
+ this.atomCache.set(pos, has);
869
+ }
870
+ return has;
871
+ }
880
872
  }
881
873
  function compareSetPrec(setA, setB, array) {
882
874
  if (setA != setB)
@@ -1423,6 +1415,13 @@ class CompositeTile extends Tile {
1423
1415
  return null;
1424
1416
  let { closest, rect } = result;
1425
1417
  let pos = this.posBeforeChild(closest, start);
1418
+ if (textblock && textblock.start == start && (x < rect.left || x > rect.right)) {
1419
+ let start = (x < rect.left) == textblock.ltr;
1420
+ let span = start ? textblock.order[0] : textblock.order[textblock.order.length - 1];
1421
+ if (span.ltr != textblock.ltr && pos >= span.from + textblock.start && pos <= span.to + textblock.start)
1422
+ return start ? CoordPos.create(textblock.start, -1)
1423
+ : CoordPos.create(textblock.start + textblock.node.contentLength, 1);
1424
+ }
1426
1425
  if (closest.dom.nodeName == "BR")
1427
1426
  return CoordPos.create(pos, 1);
1428
1427
  if (closest.tag && closest.tag.isPlot && closest.tag.type.isInline) {
@@ -1541,8 +1540,7 @@ class DocTile extends CompositeTile {
1541
1540
  builder.update(0, true);
1542
1541
  }
1543
1542
  else if (ins == -1) {
1544
- builder.keep(len, !startCovered, i == changes.length);
1545
- startCovered = false;
1543
+ startCovered = builder.keep(len, !startCovered, i == changes.length);
1546
1544
  }
1547
1545
  else if (ins == -2) {
1548
1546
  builder.update(len, !startCovered);
@@ -1949,7 +1947,7 @@ class TilePointer {
1949
1947
  }
1950
1948
  tileAfter() {
1951
1949
  let { tile, index } = this;
1952
- if (tile.isText)
1950
+ if (tile.isAtom)
1953
1951
  return tile;
1954
1952
  return index < tile.children.length ? tile.children[index] : null;
1955
1953
  }
@@ -1975,25 +1973,26 @@ class TilePointer {
1975
1973
  }
1976
1974
  matchingWidget(widget, sideFlag, reused) {
1977
1975
  let { index, tile, parent } = this;
1978
- for (;;) {
1979
- if (!index) {
1980
- if (!parent || (tile instanceof EltTile ? tile.tag : !(tile instanceof TextTile)))
1981
- break;
1982
- ({ index, tile, parent } = parent);
1983
- }
1984
- else {
1985
- if (tile instanceof TextTile)
1986
- break;
1987
- let before = tile.children[--index];
1988
- if (!before.isPoint)
1989
- break;
1990
- if (!reused.has(before) && before instanceof WidgetTile && before.widget.eq(widget) &&
1991
- (before.flags & 96) == sideFlag && !(before.flags & 8192)) {
1992
- reused.set(before, 1);
1993
- return before;
1976
+ if (!tile.isAtom)
1977
+ for (;;) {
1978
+ if (!index) {
1979
+ if (!parent || (tile instanceof EltTile ? tile.tag : !(tile instanceof TextTile)))
1980
+ break;
1981
+ ({ index, tile, parent } = parent);
1982
+ }
1983
+ else {
1984
+ if (tile instanceof TextTile)
1985
+ break;
1986
+ let before = tile.children[--index];
1987
+ if (!before.isPoint)
1988
+ break;
1989
+ if (!reused.has(before) && before instanceof WidgetTile && before.widget.eq(widget) &&
1990
+ (before.flags & 96) == sideFlag && !(before.flags & 8192)) {
1991
+ reused.set(before, 1);
1992
+ return before;
1993
+ }
1994
1994
  }
1995
1995
  }
1996
- }
1997
1996
  return null;
1998
1997
  }
1999
1998
  }
@@ -2020,11 +2019,9 @@ class ContentUpdate {
2020
2019
  if (span) {
2021
2020
  this.new = span;
2022
2021
  }
2023
- else {
2024
- this.reused.set(tile, 2);
2025
- let inner = EltTile.of(tile.elt, tile.tag, tile.flags, tile.boundary * 2, tile.dom);
2026
- this.new.addChild(inner);
2027
- this.new = inner;
2022
+ else if (!tile.isNodeInner) { let copy = this.copyEltStructure(tile);
2023
+ this.new.addChild(copy);
2024
+ this.new = copy.contentTile;
2028
2025
  }
2029
2026
  },
2030
2027
  leave: tile => {
@@ -2048,7 +2045,8 @@ class ContentUpdate {
2048
2045
  }
2049
2046
  },
2050
2047
  skip: (tile, from, to) => {
2051
- if (!(tile instanceof TextTile)) {
2048
+ if (tile.isNodeInner) ;
2049
+ else if (!(tile instanceof TextTile)) {
2052
2050
  if (!from && to == tile.length) {
2053
2051
  this.reused.set(tile, 1);
2054
2052
  this.new.addChild(tile);
@@ -2090,10 +2088,24 @@ class ContentUpdate {
2090
2088
  };
2091
2089
  }
2092
2090
  keep(len, includeStart, includeEnd) {
2091
+ if (!!this.partialNode != (this.old.tile.isAtom && !this.old.tile.isText)) {
2092
+ let dist = Math.min(len, this.partialNode
2093
+ ? this.state.doc.nodeAt(this.posB - this.partialNode.length).length - this.partialNode.length
2094
+ : this.old.tile.length - this.old.index);
2095
+ this.replace(dist, dist, includeStart);
2096
+ if (dist == len)
2097
+ return true;
2098
+ includeStart = false;
2099
+ len -= dist;
2100
+ }
2101
+ this.keepCheckEnd(len, includeStart, includeEnd);
2102
+ return includeEnd;
2103
+ }
2104
+ keepCheckEnd(len, includeStart, includeEnd) {
2093
2105
  let cut = [], end = this.posB + len;
2094
2106
  if (this.deco.endWidgets)
2095
2107
  for (let nw = this.new, { tile, index, parent } = this.old, pos = this.posB; pos < end;) {
2096
- if (tile instanceof TextTile) {
2108
+ if (tile.isAtom) {
2097
2109
  pos += tile.length - index;
2098
2110
  ({ tile, index, parent } = parent);
2099
2111
  index++;
@@ -2364,6 +2376,16 @@ class ContentUpdate {
2364
2376
  return tile;
2365
2377
  }
2366
2378
  }
2379
+ copyEltStructure(tile) {
2380
+ this.reused.set(tile, 2);
2381
+ let copy = EltTile.of(tile.elt, tile.tag, tile.flags, tile.boundary * 2, tile.dom);
2382
+ if (tile.isNodeOuter)
2383
+ for (let child of tile.children)
2384
+ if (child.isNodeInner) {
2385
+ copy.addChild((child.flags & 1024) ? this.copyEltStructure(child) : child);
2386
+ }
2387
+ return copy;
2388
+ }
2367
2389
  ensureHackNode() {
2368
2390
  let tile = this.new;
2369
2391
  if (!tile.isPlotContent)
@@ -2543,6 +2565,16 @@ class Coords {
2543
2565
  }
2544
2566
  }
2545
2567
  function coordsAtPos(wg, pos, assoc) {
2568
+ let textblock = wg.state.doc.resolve(pos).textblockParent;
2569
+ let map = textblock ? wg.state.textblockMap(textblock) : null;
2570
+ if (textblock && textblock.node.contentLength &&
2571
+ (pos == textblock.start && assoc < 0 || pos == textblock.end && assoc > 0)) {
2572
+ let span = map.order[pos == textblock.start ? 0 : map.order.length - 1];
2573
+ if (span.ltr != map.ltr)
2574
+ pos = textblock.start + (pos == textblock.start ? span.to : span.from);
2575
+ else
2576
+ assoc = -assoc;
2577
+ }
2546
2578
  let { offset, tile, pos: tilePos } = wg.docTile.resolve(pos, assoc);
2547
2579
  if (tile instanceof TextTile) {
2548
2580
  let from = offset, to = offset;
@@ -2551,7 +2583,7 @@ function coordsAtPos(wg, pos, assoc) {
2551
2583
  to++;
2552
2584
  else
2553
2585
  from--;
2554
- return new Coords(tile, flattenV(singleRect(textRange(tile.dom, from, to), side, true), (side < 0) == ltrAt(wg.state, pos, assoc)));
2586
+ return new Coords(tile, flattenV(singleRect(textRange(tile.dom, from, to), side, true), (side < 0) == ltrAt(wg.state, pos, assoc, map)));
2555
2587
  }
2556
2588
  let tagTile = tile;
2557
2589
  while (!tagTile.tag)
@@ -2562,7 +2594,7 @@ function coordsAtPos(wg, pos, assoc) {
2562
2594
  if (tile.widget.type.inFlow) {
2563
2595
  let rect = singleRect(tile.dom, after ? 1 : -1);
2564
2596
  if (rect.width || rect.height)
2565
- return new Coords(tile, horizontal ? flattenH(rect, !after) : flattenV(rect, ltrAt(wg.state, pos, 1) == !after));
2597
+ return new Coords(tile, horizontal ? flattenH(rect, !after) : flattenV(rect, ltrAt(wg.state, pos, 1, map) == !after));
2566
2598
  }
2567
2599
  if (!tile.parent)
2568
2600
  return new Coords(tile, new DOMRect);
@@ -2579,7 +2611,7 @@ function coordsAtPos(wg, pos, assoc) {
2579
2611
  break;
2580
2612
  let rect = singleRect(before.dom, 1);
2581
2613
  if (rect.width || rect.height)
2582
- return new Coords(before, horizontal ? flattenH(rect, false) : flattenV(rect, !ltrAt(wg.state, pos, 1)));
2614
+ return new Coords(before, horizontal ? flattenH(rect, false) : flattenV(rect, !ltrAt(wg.state, pos, 1, map)));
2583
2615
  }
2584
2616
  }
2585
2617
  else { for (let i = offset; i < tile.children.length; i++) {
@@ -2588,7 +2620,7 @@ function coordsAtPos(wg, pos, assoc) {
2588
2620
  continue;
2589
2621
  let rect = singleRect(after.dom, -1);
2590
2622
  if (rect.width || rect.height)
2591
- return new Coords(after, horizontal ? flattenH(rect, true) : flattenV(rect, ltrAt(wg.state, pos, 1)));
2623
+ return new Coords(after, horizontal ? flattenH(rect, true) : flattenV(rect, ltrAt(wg.state, pos, 1, map)));
2592
2624
  }
2593
2625
  }
2594
2626
  }
@@ -2632,19 +2664,18 @@ const clipboardInputFilter = /*@__PURE__*/GardState.Facet.define();
2632
2664
  const clipboardInputHTMLFilter = /*@__PURE__*/GardState.Facet.define();
2633
2665
  const clipboardTextParser = /*@__PURE__*/GardState.Facet.define();
2634
2666
  const clipboardInputTextFilter = /*@__PURE__*/GardState.Facet.define();
2635
- function writeClipboard(state, slice, context, data) {
2667
+ function writeClipboard(state, slice, data) {
2636
2668
  for (let filter of state.facet(clipboardOutputFilter))
2637
2669
  slice = filter(slice, state);
2638
2670
  let includeContext = 0;
2639
- for (let i = 0; i < context.length; i++) {
2640
- let next = context[i];
2641
- if (next.type.defining && (!includeContext || next.type != context[includeContext - 1].type))
2671
+ for (let i = 0; i < slice.context.length; i++) {
2672
+ let next = slice.context[i];
2673
+ if (next.type.defining && (!includeContext || next.type != slice.context[includeContext - 1].type))
2642
2674
  includeContext = i + 1;
2643
2675
  else if (next.type.defining || !next.isTextblock)
2644
2676
  break;
2645
2677
  }
2646
2678
  let doc = detachedDoc(), dom = serialize.slice(slice, {
2647
- context,
2648
2679
  includeContext,
2649
2680
  openAttr: "wg-open"
2650
2681
  }).toDOM();
@@ -2668,7 +2699,7 @@ function writeClipboard(state, slice, context, data) {
2668
2699
  data.setData("text/html", html);
2669
2700
  let text;
2670
2701
  for (let serialize of state.facet(clipboardTextSerializer)) {
2671
- if ((text = serialize(slice, context, state)) != null)
2702
+ if ((text = serialize(slice, state)) != null)
2672
2703
  break;
2673
2704
  }
2674
2705
  if (text == null)
@@ -2683,7 +2714,7 @@ function isOpen(elt) {
2683
2714
  function readClipboard(state, data, targetContext, plain) {
2684
2715
  let html = data.getData("text/html");
2685
2716
  let text = data.getData("text/plain") || data.getData("Text") || data.getData("text/uri-list").replace(/\r?\n/g, " ");
2686
- let slice, context = [];
2717
+ let slice;
2687
2718
  if (text && (targetContext.parent.node.type.hasRole(Node.Role.Code) || !html || plain)) {
2688
2719
  for (let filter of state.facet(clipboardInputTextFilter))
2689
2720
  text = filter(text, state);
@@ -2699,14 +2730,14 @@ function readClipboard(state, data, targetContext, plain) {
2699
2730
  if (browser.webkit)
2700
2731
  restoreReplacedSpaces(dom);
2701
2732
  let fromWordgard = dom.querySelector("[wg-content=true]");
2702
- ({ slice, context } = parse.slice(state.schema, dom, {
2733
+ slice = parse.slice(state.schema, dom, {
2703
2734
  collapseWhiteSpace: !fromWordgard,
2704
2735
  isOpen: fromWordgard ? isOpen : undefined
2705
- }));
2736
+ });
2706
2737
  }
2707
2738
  for (let filter of state.facet(clipboardInputFilter))
2708
2739
  slice = filter(slice, state);
2709
- return { slice, context };
2740
+ return slice;
2710
2741
  }
2711
2742
  function readClipboardText(state, text, context, plain) {
2712
2743
  if (!plain)
@@ -2839,7 +2870,6 @@ const baseStyles = /*@__PURE__*/buildTheme("." + styleID, {
2839
2870
  whiteSpace: "pre-wrap",
2840
2871
  overflowWrap: "anywhere",
2841
2872
  wordBreak: "break-word", boxSizing: "border-box",
2842
- minHeight: "100%",
2843
2873
  padding: "4px 12px",
2844
2874
  outline: "none",
2845
2875
  caretColor: "transparent",
@@ -2924,14 +2954,26 @@ const baseStyles = /*@__PURE__*/buildTheme("." + styleID, {
2924
2954
  },
2925
2955
  ".wg-panels-top": { top: "0" },
2926
2956
  ".wg-panels-bottom": { bottom: "0" },
2927
- "wg-dialog": {
2928
- display: "block",
2929
- padding: "5px 19px 5px 6px",
2930
- position: "relative",
2931
- "& label, & .wg-label": {
2932
- fontSize: "90%"
2933
- },
2934
- borderBottom: "1px solid var(--wg-border-color)"
2957
+ dialog: {
2958
+ font: "var(--wg-dialog-font)",
2959
+ padding: "7px 22px 7px 8px",
2960
+ minWidth: "120px",
2961
+ border: "1px solid var(--wg-border-color)",
2962
+ borderRadius: "5px",
2963
+ boxShadow: "0 0 8px 0 rgba(128, 128, 128, 0.2)",
2964
+ inset: "unset",
2965
+ "&.wg-free-dialog": {
2966
+ position: "fixed"
2967
+ }
2968
+ },
2969
+ "h3.wg-dialog-title": {
2970
+ fontSize: "90%",
2971
+ fontWeight: "bold",
2972
+ textAlign: "center",
2973
+ margin: "0 0 0.8em"
2974
+ },
2975
+ "dialog label, dialog .wg-label": {
2976
+ fontSize: "90%"
2935
2977
  },
2936
2978
  ".wg-dialog-close": {
2937
2979
  position: "absolute",
@@ -2961,6 +3003,24 @@ const baseStyles = /*@__PURE__*/buildTheme("." + styleID, {
2961
3003
  backgroundColor: "#222"
2962
3004
  }
2963
3005
  },
3006
+ ".wg-key-help": {
3007
+ padding: "15px 4px 4px",
3008
+ maxWidth: "350px"
3009
+ },
3010
+ ".wg-key-list": {
3011
+ padding: "11px",
3012
+ outline: "none",
3013
+ maxHeight: "calc(min(70vh, 300px))",
3014
+ overflowY: "auto",
3015
+ display: "grid",
3016
+ gridTemplateColumns: "max-content auto",
3017
+ gap: "8px 15px",
3018
+ "& h4": {
3019
+ gridColumn: "span 2",
3020
+ margin: "0.5em 0",
3021
+ fontSize: "110%"
3022
+ }
3023
+ }
2964
3024
  }, lightDarkIDs);
2965
3025
 
2966
3026
  function setDOMSelection(wg) {
@@ -3013,7 +3073,7 @@ function rangeForClick(wg, pos, type) {
3013
3073
  }
3014
3074
  else { let cx = wg.state.doc.resolve(pos.pos), block = cx.textblockParent;
3015
3075
  if (block)
3016
- return GardSelection.range(block.start, block.end);
3076
+ return GardSelection.undirectionalRange(block.start, block.end);
3017
3077
  else
3018
3078
  return GardSelection.near(wg.state, pos.pos, pos.side || -1);
3019
3079
  }
@@ -3126,12 +3186,23 @@ function moveToLineBoundary(wg, start, forward) {
3126
3186
  let startCoords = wg.coordsAtPos(start.head, start.headSide);
3127
3187
  let ltr = wg.state.textblockLTR(block.node);
3128
3188
  let y = (startCoords.top + startCoords.bottom) / 2, left = forward != ltr;
3129
- let { pos } = wg.posAtCoords({ x: left ? -1e7 : 1e7, y });
3189
+ let { pos, side } = wg.posAtCoords({ x: left ? -1e7 : 1e7, y });
3130
3190
  if (pos < block.start || pos > block.end) {
3131
3191
  let blockRect = wg.docTile.nodeTile(block.before).dom.getBoundingClientRect();
3132
- pos = wg.posAtCoords({ x: left ? blockRect.left : blockRect.right, y }).pos;
3192
+ let result = wg.posAtCoords({ x: left ? blockRect.left + 0.2 : blockRect.right - 0.2, y });
3193
+ if (result.pos < block.start) {
3194
+ pos = block.start;
3195
+ side = -1;
3196
+ }
3197
+ else if (result.pos > block.end) {
3198
+ pos = block.end;
3199
+ side = 1;
3200
+ }
3201
+ else {
3202
+ ({ pos, side } = result);
3203
+ }
3133
3204
  }
3134
- return GardSelection.cursor(pos, forward ? -1 : 1);
3205
+ return GardSelection.cursor(pos, side);
3135
3206
  }
3136
3207
 
3137
3208
  const observeOptions = {
@@ -3269,7 +3340,7 @@ class DOMObserver {
3269
3340
  if (selRange)
3270
3341
  range = buildSelectionRangeFromRange(wg, selRange);
3271
3342
  }
3272
- if (!range || this.selectionRange.eq(range))
3343
+ if (!range || !range.focusNode || this.selectionRange.eq(range))
3273
3344
  return false;
3274
3345
  let context = range.anchorNode && wg.docTile.nearest(range.anchorNode);
3275
3346
  if (context instanceof WidgetTile)
@@ -3281,10 +3352,6 @@ class DOMObserver {
3281
3352
  this.selectionRange.set(anchor.dom, anchor.offset, head.dom, head.offset);
3282
3353
  this.selectionChanged = false;
3283
3354
  }
3284
- clearSelectionRange() {
3285
- this.selectionRange.set(null, 0, null, 0);
3286
- this.selectionChanged = false;
3287
- }
3288
3355
  ignore(f) {
3289
3356
  let result = f();
3290
3357
  this.clear();
@@ -3322,7 +3389,14 @@ class DOMObserver {
3322
3389
  return null;
3323
3390
  }
3324
3391
  else if (tile.dom == record.target) {
3325
- return [tile.posBefore, tile.posAfter];
3392
+ if (!tile.isNodeInner)
3393
+ return [tile.posBefore, tile.posAfter];
3394
+ while (tile.parent && tile.isNodeInner)
3395
+ tile = tile.parent;
3396
+ if (tile.isDoc)
3397
+ return null;
3398
+ let nodePos = tile.posBefore;
3399
+ return [nodePos, nodePos + 1];
3326
3400
  }
3327
3401
  else {
3328
3402
  return childRange(tile, record);
@@ -3378,18 +3452,46 @@ class KeyBinding {
3378
3452
  static of(spec) { return new KeyBinding(spec); }
3379
3453
  }
3380
3454
  ;KeyBinding = /*@__PURE__*/(function (KeyBinding) {
3381
- function runScopeHandlers(wg, event, scope) {
3382
- let map = getKeymap(wg.state.facet(KeyBinding.source), wg.state.facet(KeyBinding.useDefaultKeymap));
3383
- return runHandlers(map, event, wg, scope);
3384
- }
3385
- KeyBinding.runScopeHandlers = runScopeHandlers;
3386
- KeyBinding.source = GardState.Facet.define();
3455
+ function handle(wg, event) {
3456
+ let map = getKeymap(wg.state);
3457
+ return runHandlers(map, event, wg);
3458
+ }
3459
+ KeyBinding.handle = handle;
3460
+ KeyBinding.keymap = GardState.Field.Derived.define(s => {
3461
+ let bindings = s.facet(KeyBinding.source);
3462
+ return s.facet(KeyBinding.useDefaultKeymap) ? bindings.concat(KeyBinding.defaultKeymap) : bindings;
3463
+ });
3464
+ KeyBinding.source = GardState.Facet.define({
3465
+ enables: KeyBinding.keymap
3466
+ });
3387
3467
  KeyBinding.useDefaultKeymap = GardState.Facet.define({
3388
3468
  combine: input => input.length ? input[0] : true
3389
3469
  });
3470
+ class Group {
3471
+ description;
3472
+ rank;
3473
+ constructor(
3474
+ description,
3475
+ rank) {
3476
+ this.description = description;
3477
+ this.rank = rank;
3478
+ }
3479
+ static define(spec) {
3480
+ return new Group(spec.description, spec.rank || 1000);
3481
+ }
3482
+ static Inline = new Group(phrases.ref("group_inline"), 20);
3483
+ static Block = new Group(phrases.ref("group_block"), 30);
3484
+ static History = new Group(phrases.ref("group_history"), 70);
3485
+ }
3486
+ KeyBinding.Group = Group;
3487
+ function lookupByDescription(state, desc) {
3488
+ return getDescMap(state).get(desc) || null;
3489
+ }
3490
+ KeyBinding.lookupByDescription = lookupByDescription;
3390
3491
  KeyBinding.defaultKeymap = [
3391
3492
  { key: "Enter", run: enter },
3392
- { key: "Shift-Enter", run: insertLineBreak },
3493
+ { key: "Shift-Enter", run: insertLineBreak,
3494
+ description: phrases.ref("insert_break"), group: Group.Inline },
3393
3495
  { key: "Backspace", run: Command.bind(deleteUnit, "backward") },
3394
3496
  { key: "Delete", run: Command.bind(deleteUnit, "forward") },
3395
3497
  { key: "Ctrl-Backspace", mac: "Alt-Backspace", run: Command.bind(deleteWord, "backward") },
@@ -3433,9 +3535,9 @@ class KeyBinding {
3433
3535
  { key: "Mod-End", run: Command.bind(moveToDocSide, { side: "end" }),
3434
3536
  shift: Command.bind(moveToDocSide, { side: "end", extend: true }) },
3435
3537
  { key: "Mod-a", run: selectAll },
3436
- { key: "Mod-z", run: undo },
3437
- { key: "Mod-y", mac: "Mod-Shift-z", run: redo },
3438
- { linux: "Ctrl-Shift-z", run: redo },
3538
+ { key: "Mod-z", run: undo, description: phrases.ref("undo"), group: Group.History },
3539
+ { key: "Mod-y", mac: "Mod-Shift-z", run: redo, description: phrases.ref("redo"), group: Group.History },
3540
+ { linux: "Ctrl-Shift-z", run: redo, description: phrases.ref("redo"), group: Group.History },
3439
3541
  { mac: "Ctrl-b", run: Command.bind(moveByUnit, { dir: "backward" }),
3440
3542
  shift: Command.bind(moveByUnit, { dir: "backward", extend: true }) },
3441
3543
  { mac: "Ctrl-f", run: Command.bind(moveByUnit, { dir: "forward" }),
@@ -3469,15 +3571,15 @@ function normalizeKeyName(name, platform) {
3469
3571
  let alt, ctrl, shift, meta;
3470
3572
  for (let i = 0; i < parts.length - 1; ++i) {
3471
3573
  const mod = parts[i];
3472
- if (/^(cmd|meta|m)$/i.test(mod))
3574
+ if (mod == "Cmd" || mod == "Meta")
3473
3575
  meta = true;
3474
- else if (/^a(lt)?$/i.test(mod))
3576
+ else if (mod == "Alt")
3475
3577
  alt = true;
3476
- else if (/^(c|ctrl|control)$/i.test(mod))
3578
+ else if (mod == "Ctrl")
3477
3579
  ctrl = true;
3478
- else if (/^s(hift)?$/i.test(mod))
3580
+ else if (mod == "Shift")
3479
3581
  shift = true;
3480
- else if (/^mod$/i.test(mod)) {
3582
+ else if (mod == "Mod") {
3481
3583
  if (platform == "mac")
3482
3584
  meta = true;
3483
3585
  else
@@ -3491,18 +3593,18 @@ function normalizeKeyName(name, platform) {
3491
3593
  if (ctrl)
3492
3594
  result = "Ctrl-" + result;
3493
3595
  if (meta)
3494
- result = "Meta-" + result;
3596
+ result = (platform == "mac" ? "Cmd-" : "Meta-") + result;
3495
3597
  if (shift)
3496
3598
  result = "Shift-" + result;
3497
3599
  return result;
3498
3600
  }
3499
- function modifiers(name, event) {
3601
+ function modifiers(name, event, platform) {
3500
3602
  if (event.altKey)
3501
3603
  name = "Alt-" + name;
3502
3604
  if (event.ctrlKey)
3503
3605
  name = "Ctrl-" + name;
3504
3606
  if (event.metaKey)
3505
- name = "Meta-" + name;
3607
+ name = (platform == "mac" ? "Cmd-" : "Meta-") + name;
3506
3608
  if (event.shiftKey)
3507
3609
  name = "Shift-" + name;
3508
3610
  return name;
@@ -3518,56 +3620,65 @@ class NormalizedBinding {
3518
3620
  }
3519
3621
  }
3520
3622
  const keymapCache = /*@__PURE__*/(() => new WeakMap())();
3521
- function getKeymap(bindings, addDefault) {
3623
+ function getKeymap(state) {
3624
+ let bindings = state.field(KeyBinding.keymap);
3522
3625
  let found = keymapCache.get(bindings);
3523
- if (!found || found.deflt != addDefault) {
3524
- found = {
3525
- map: buildKeymap(addDefault ? bindings.concat(KeyBinding.defaultKeymap) : bindings, currentPlatform),
3526
- deflt: addDefault
3527
- };
3528
- keymapCache.set(bindings, found);
3626
+ if (!found)
3627
+ keymapCache.set(bindings, found = buildKeymap(bindings, currentPlatform));
3628
+ return found;
3629
+ }
3630
+ const lookupCache = /*@__PURE__*/(() => new WeakMap())();
3631
+ function getDescMap(state) {
3632
+ let bindings = state.field(KeyBinding.keymap);
3633
+ let map = lookupCache.get(bindings);
3634
+ if (!map) {
3635
+ lookupCache.set(bindings, map = new Map);
3636
+ for (let binding of bindings) {
3637
+ let { description, shiftDescription } = binding.spec;
3638
+ if (description || shiftDescription) {
3639
+ let name = binding.spec[currentPlatform] || binding.spec.key;
3640
+ if (name && description)
3641
+ map.set(description, normalizeKeyName(name, currentPlatform));
3642
+ if (name && shiftDescription)
3643
+ map.set(shiftDescription, "Shift-" + normalizeKeyName(name, currentPlatform));
3644
+ }
3645
+ }
3529
3646
  }
3530
- return found.map;
3647
+ return map;
3531
3648
  }
3532
3649
  function bind(run) {
3533
3650
  return (wg) => Command.dispatch(wg, run);
3534
3651
  }
3535
3652
  function buildKeymap(bindings, platform) {
3536
- let scopes = Object.create(null);
3653
+ let map = [];
3537
3654
  for (let { spec: b } of bindings) {
3538
3655
  let baseFlags = (b.allowDefault ? 8 : 0);
3539
- for (let scope of b.scope ? b.scope.split(" ") : ["editor"]) {
3540
- let array = scopes[scope] || (scopes[scope] = []);
3541
- let key = b[platform] || b.key;
3542
- if (b.char) {
3543
- if (key)
3544
- throw new Error("A key binding may not provide both a char and a key field");
3545
- if (b.shift)
3546
- throw new Error("Shift-modified bindings are not supported for char bindings");
3547
- array.push(new NormalizedBinding(baseFlags | 1, b.char, bind(b.run)));
3548
- }
3656
+ let key = b[platform] || b.key;
3657
+ if (b.char) {
3549
3658
  if (key)
3550
- array.push(new NormalizedBinding(baseFlags | 2, normalizeKeyName(key, platform), bind(b.run)));
3551
- if (key && b.shift)
3552
- array.push(new NormalizedBinding(baseFlags | 2, normalizeKeyName("Shift-" + key, platform), bind(b.shift)));
3553
- if (b.any)
3554
- array.push(new NormalizedBinding(4, "", b.any));
3659
+ throw new Error("A key binding may not provide both a char and a key field");
3660
+ if (b.shift)
3661
+ throw new Error("Shift-modified bindings are not supported for char bindings");
3662
+ map.push(new NormalizedBinding(baseFlags | 1, b.char, bind(b.run)));
3555
3663
  }
3664
+ if (key)
3665
+ map.push(new NormalizedBinding(baseFlags | 2, normalizeKeyName(key, platform), bind(b.run)));
3666
+ if (key && b.shift)
3667
+ map.push(new NormalizedBinding(baseFlags | 2, normalizeKeyName("Shift-" + key, platform), bind(b.shift)));
3668
+ if (b.any)
3669
+ map.push(new NormalizedBinding(4, "", b.any));
3556
3670
  }
3557
- return scopes;
3671
+ return map;
3558
3672
  }
3559
- function runHandlers(map, event, wg, scope) {
3560
- let handlers = map[scope];
3561
- if (!handlers)
3562
- return false;
3673
+ function runHandlers(map, event, wg) {
3563
3674
  let key = event.key, charCode = key.codePointAt(0);
3564
3675
  let altGr = event.getModifierState("AltGraph"), fromCode = charKeyCodes[event.keyCode];
3565
3676
  let isChar = codePointSize(charCode) == key.length;
3566
3677
  let char = isChar ? String.fromCodePoint(charCode) : null;
3567
- let base = modifiers(key, event);
3568
- let fallback = isChar && !altGr && fromCode && fromCode != base ? modifiers(fromCode, event) : null;
3678
+ let base = modifiers(key, event, currentPlatform);
3679
+ let fallback = isChar && !altGr && fromCode && fromCode != base ? modifiers(fromCode, event, currentPlatform) : null;
3569
3680
  let handled = false, didMatch = false, allowDefault = false;
3570
- for (let binding of handlers) {
3681
+ for (let binding of map) {
3571
3682
  let matched = ((binding.flags & 1) && (altGr || !event.ctrlKey && !event.metaKey) && binding.name == char) ||
3572
3683
  ((binding.flags & 2) && (binding.name == base || binding.name == fallback)) ||
3573
3684
  (binding.flags & 4);
@@ -3614,6 +3725,11 @@ const eventObserver = /*@__PURE__*/GardState.Facet.define({
3614
3725
  return result;
3615
3726
  }
3616
3727
  });
3728
+ const compositionInputTypes = /*@__PURE__*/(() => new Set([
3729
+ "insertCompositionText",
3730
+ "deleteCompositionText",
3731
+ "insertFromComposition"
3732
+ ]))();
3617
3733
  class InputState {
3618
3734
  wg;
3619
3735
  shiftKey = false;
@@ -3772,11 +3888,12 @@ class InputState {
3772
3888
  }
3773
3889
  }
3774
3890
  let command = inputTypeCommands[type];
3775
- if ((type == "deleteContentBackward" || type == "deleteContentForward") && range &&
3776
- range.from != range.to && !this.unflushedSelection && (sel.empty
3777
- ? !isSingleChar(this.domDoc, data.domRange.from, data.domRange.to) ||
3778
- sel.head != (type == "deleteContentBackward" ? range.to : range.from)
3779
- : sel.from != range.from || sel.to != range.to)) {
3891
+ if (isDeletionInputEvent(type) && range &&
3892
+ (!command ||
3893
+ (range.from != range.to && !this.unflushedSelection && (sel.empty
3894
+ ? !isSingleChar(this.domDoc, data.domRange.from, data.domRange.to) ||
3895
+ sel.head != (type == "deleteContentBackward" ? range.to : range.from)
3896
+ : sel.from != range.from || sel.to != range.to)))) {
3780
3897
  wg.dispatch({ changes: { from: range.from, to: range.to, fit: true }, userEvent: "delete" });
3781
3898
  }
3782
3899
  else if (command) {
@@ -3787,13 +3904,28 @@ class InputState {
3787
3904
  let { from, to } = this.unflushedSelection ? wg.state.selection : range;
3788
3905
  Command.dispatch(wg, insertText, { from, to, insert, userEvent: "input.type" });
3789
3906
  }
3907
+ else if (type == "insertFromPaste") {
3908
+ let { from, to } = this.unflushedSelection ? wg.state.selection.replacementRange : range;
3909
+ let slice = readClipboard(wg.state, event.dataTransfer, wg.state.doc.resolve(from), wg.inputState.shiftKey);
3910
+ let isSel = from == wg.state.selection.replacementRange.from && to == wg.state.selection.replacementRange.to;
3911
+ if (isSel && wg.state.facet(pasteHandler).some(h => h(wg, event, slice || Slice.empty)))
3912
+ return;
3913
+ if (slice) {
3914
+ wg.dispatch({
3915
+ changes: { from, to, insert: slice, fit: true },
3916
+ selection: isSel ? (cx, changes) => GardSelection.near(cx, changes.mapPos(to, 1), -1) : undefined,
3917
+ userEvent: "input.paste",
3918
+ scrollIntoView: isSel
3919
+ });
3920
+ }
3921
+ }
3790
3922
  else if ((type == "insertReplacementText" || type == "insertFromYank")) {
3791
3923
  let read = readClipboard(wg.state, event.dataTransfer, wg.state.sel.head, true);
3792
3924
  let { from, to } = this.unflushedSelection ? wg.state.selection : range;
3793
3925
  let sel = wg.state.selection, touchesSel = from <= sel.to && to >= sel.from;
3794
3926
  if (read)
3795
3927
  wg.dispatch({
3796
- changes: { from, to, insert: read.slice, fit: read.context },
3928
+ changes: { from, to, insert: read, fit: true },
3797
3929
  selection: touchesSel ? (cx, changes) => {
3798
3930
  return GardSelection.near(cx, changes.mapPos(to, 1), -1);
3799
3931
  } : undefined,
@@ -3801,7 +3933,7 @@ class InputState {
3801
3933
  userEvent: "insert.replacementText"
3802
3934
  });
3803
3935
  }
3804
- else if (type == "insertCompositionText") {
3936
+ else if (compositionInputTypes.has(type)) {
3805
3937
  let compositionStart = true;
3806
3938
  if (wg.inputState.composing) {
3807
3939
  compositionStart = !wg.inputState.composing.changes;
@@ -3811,7 +3943,7 @@ class InputState {
3811
3943
  if (!sel.focusNode)
3812
3944
  return false;
3813
3945
  let userEvent = "input.type.compose" + (compositionStart ? ".start" : "");
3814
- Command.dispatch(wg, insertText, { from: range.from, to: range.to, insert: event.data, userEvent });
3946
+ Command.dispatch(wg, insertText, { from: range.from, to: range.to, insert: event.data || "", userEvent });
3815
3947
  }
3816
3948
  else if (type == "formatSetBlockTextDirection") {
3817
3949
  if (event.data == "ltr" || event.data == "rtl")
@@ -3890,6 +4022,7 @@ class InputState {
3890
4022
  clearComposition() {
3891
4023
  let comp = this.composing;
3892
4024
  this.composing = null;
4025
+ this.compositionPendingKey = true;
3893
4026
  this.compositionEndedAt = Date.now();
3894
4027
  if (!comp.target)
3895
4028
  return false;
@@ -4131,32 +4264,38 @@ function basicMouseSelection(wg, event) {
4131
4264
  get(event, extend) {
4132
4265
  let cur = queryPos(wg, event), range = rangeForClick(wg, cur, type), { from, to } = range;
4133
4266
  if (extend) {
4267
+ let startAnchor = !(startSel instanceof GardSelection.Text) || startSel.directional ? startSel.anchor
4268
+ : from < startSel.from ? startSel.to : startSel.from;
4134
4269
  if (from < startSel.anchor)
4135
- return GardSelection.range(startSel.anchor, from, from < to ? 1 : cur.side);
4270
+ return GardSelection.range(startAnchor, from, from < to ? 1 : cur.side);
4136
4271
  else
4137
- return GardSelection.range(startSel.anchor, to, from < to ? -1 : cur.side);
4272
+ return GardSelection.range(startAnchor, to, from < to ? -1 : cur.side);
4138
4273
  }
4274
+ let { anchor, head } = range;
4139
4275
  if (start.pos != cur.pos) {
4140
4276
  let startRange = rangeForClick(wg, start, type);
4141
- from = Math.min(startRange.from, from);
4142
- to = Math.max(startRange.to, to);
4277
+ let from = Math.min(range.from, startRange.from), to = Math.max(range.to, startRange.to);
4278
+ if (startRange.from < range.from) {
4279
+ anchor = from;
4280
+ head = to;
4281
+ }
4282
+ else {
4283
+ anchor = to;
4284
+ head = from;
4285
+ }
4143
4286
  }
4144
- return from == range.from && to == range.to ? range : GardSelection.range(from, to, cur.side);
4287
+ return anchor == range.anchor && head == range.head ? range
4288
+ : GardSelection.range(anchor, head, head == cur.pos ? cur.side : undefined);
4145
4289
  }
4146
4290
  };
4147
4291
  }
4148
4292
  const dropHandler = /*@__PURE__*/GardState.Facet.define();
4149
4293
  const pasteHandler = /*@__PURE__*/GardState.Facet.define();
4150
- function selectionSlice(state) {
4151
- return {
4152
- slice: state.doc.slice(state.selection.from, state.selection.to),
4153
- context: state.doc.contextAt(state.selection.from)
4154
- };
4155
- }
4156
4294
  function copy(wg, event) {
4157
4295
  let { state } = wg;
4158
- if (!state.selection.empty && event.clipboardData) { let { slice, context } = selectionSlice(state);
4159
- writeClipboard(state, slice, context, event.clipboardData);
4296
+ if (!state.selection.empty && event.clipboardData) {
4297
+ let slice = state.selection.toSlice(state.doc);
4298
+ writeClipboard(state, slice, event.clipboardData);
4160
4299
  if (event.type == "cut" && !state.readOnly)
4161
4300
  wg.dispatch({
4162
4301
  changes: state.selection.ranges.map(r => ({ from: r.from, to: r.to, fit: true })),
@@ -4216,7 +4355,7 @@ function inEditableDOM(wg, node) {
4216
4355
  let tile = wg.docTile.nearest(node);
4217
4356
  return tile ? !(tile.isPoint || tile instanceof WidgetTile) : false;
4218
4357
  }
4219
- function isDeletionInputEvent(type) { return /^delete(Content|Word)/.test(type); }
4358
+ function isDeletionInputEvent(type) { return /^delete(Content|Word|ByComposition)/.test(type); }
4220
4359
  const inputTypeCommands = /*@__PURE__*/(() => ({
4221
4360
  historyUndo: undo,
4222
4361
  historyRedo: redo,
@@ -4249,7 +4388,7 @@ const baseHandlers = {
4249
4388
  keydown(wg, event) {
4250
4389
  if ((browser.ios || browser.android) && (event.key == "Backspace" || event.key == "Enter"))
4251
4390
  return false;
4252
- return KeyBinding.runScopeHandlers(wg, event, "editor");
4391
+ return KeyBinding.handle(wg, event);
4253
4392
  },
4254
4393
  mousedown(wg, event) {
4255
4394
  wg.inputState.shiftKey = event.shiftKey;
@@ -4288,8 +4427,8 @@ const baseHandlers = {
4288
4427
  inputState.mouseSelection.dragging = true;
4289
4428
  inputState.draggedContent = selection;
4290
4429
  if (event.dataTransfer) {
4291
- let { slice, context } = selectionSlice(wg.state);
4292
- writeClipboard(wg.state, slice, context, event.dataTransfer);
4430
+ let slice = wg.state.selection.toSlice(wg.state.doc);
4431
+ writeClipboard(wg.state, slice, event.dataTransfer);
4293
4432
  event.dataTransfer.effectAllowed = "copyMove";
4294
4433
  }
4295
4434
  return false;
@@ -4303,52 +4442,31 @@ const baseHandlers = {
4303
4442
  drop(wg, event) {
4304
4443
  if (!event.dataTransfer || wg.state.readOnly)
4305
4444
  return true;
4306
- let content = readClipboard(wg.state, event.dataTransfer, wg.state.sel.head, false);
4307
- if (!content)
4445
+ let slice = readClipboard(wg.state, event.dataTransfer, wg.state.sel.head, false);
4446
+ if (!slice)
4308
4447
  return false;
4309
4448
  let dropPos = wg.posAtCoords({ x: event.clientX, y: event.clientY }).pos;
4310
4449
  let { draggedContent } = wg.inputState;
4311
4450
  let del = draggedContent && dragMovesSelection(wg, event)
4312
4451
  ? { from: draggedContent.from, to: draggedContent.to } : null;
4313
- if (wg.state.facet(dropHandler).some(f => f(wg, event, dropPos, del, content.slice, content.context)))
4452
+ if (wg.state.facet(dropHandler).some(f => f(wg, event, dropPos, del, slice)))
4314
4453
  return true;
4315
- let ins = { from: dropPos, insert: content.slice, fit: content.context };
4454
+ let ins = { from: dropPos, insert: slice, fit: true };
4316
4455
  let changes = ChangeSet.create(wg.state.doc, del ? [del, ins] : ins);
4317
4456
  wg.focus();
4318
4457
  wg.dispatch({
4319
4458
  changes,
4320
- selection: GardSelection.range(changes.mapPos(dropPos, -1), changes.mapPos(dropPos, 1)),
4459
+ selection: GardSelection.undirectionalRange(changes.mapPos(dropPos, -1), changes.mapPos(dropPos, 1)),
4321
4460
  userEvent: del ? "move.drop" : "input.drop"
4322
4461
  });
4323
4462
  wg.inputState.draggedContent = null;
4324
4463
  return true;
4325
4464
  },
4326
- paste(wg, event) {
4327
- if (wg.state.readOnly || !event.clipboardData)
4328
- return true;
4329
- let { state } = wg;
4330
- let content = readClipboard(state, event.clipboardData, state.sel.head, wg.inputState.shiftKey);
4331
- if (wg.state.facet(pasteHandler).some(h => h(wg, event, content ? content.slice : Slice.empty, content ? content.context : [])))
4332
- return true;
4333
- if (content) { wg.dispatch({
4334
- changes: {
4335
- from: state.selection.from,
4336
- to: state.selection.to,
4337
- insert: content.slice,
4338
- fit: content.context
4339
- },
4340
- selection: (cx, changes) => GardSelection.near(cx, changes.mapPos(state.selection.to, 1), -1),
4341
- userEvent: "input.paste",
4342
- scrollIntoView: true
4343
- });
4344
- }
4345
- return true;
4346
- },
4347
4465
  beforeinput(wg, event) {
4348
4466
  let type = event.inputType;
4349
4467
  if (browser.safari && type == "insertText" && wg.inputState.composing)
4350
4468
  compositionEnd(wg);
4351
- if (type == "insertCompositionText" && !wg.inputState.composing)
4469
+ if (compositionInputTypes.has(type) && !wg.inputState.composing)
4352
4470
  wg.inputState.composing = { changes: 0, target: null };
4353
4471
  let data = {
4354
4472
  inputType: type,
@@ -4364,7 +4482,7 @@ const baseHandlers = {
4364
4482
  }
4365
4483
  wg.inputState.beforeInput(event, wg.inputState.pendingInputEvent = data);
4366
4484
  wg.scheduleFlush();
4367
- let allow = type == "insertCompositionText" ||
4485
+ let allow = compositionInputTypes.has(type) ||
4368
4486
  editable && (type == "insertText" || isDeletionInputEvent(type) &&
4369
4487
  data.domRange && inlineContext(wg.inputState.domDoc, data.domRange));
4370
4488
  return !allow;
@@ -4375,8 +4493,8 @@ const baseHandlers = {
4375
4493
  return false;
4376
4494
  wg.inputState.pendingInputEvent = null;
4377
4495
  let change;
4378
- if (event.inputType == "insertCompositionText" || event.inputType == "insertText") {
4379
- change = { from: pending.domRange.from, to: pending.domRange.to, insert: [Leaf.text(pending.data)] };
4496
+ if (compositionInputTypes.has(event.inputType) || event.inputType == "insertText") {
4497
+ change = { from: pending.domRange.from, to: pending.domRange.to, insert: [Leaf.text(pending.data || "")] };
4380
4498
  }
4381
4499
  else if (isDeletionInputEvent(event.inputType)) {
4382
4500
  change = { from: pending.domRange.from, to: pending.domRange.to };
@@ -4403,7 +4521,6 @@ const baseObservers = {
4403
4521
  updateForFocusChange(wg);
4404
4522
  },
4405
4523
  blur(wg) {
4406
- wg.observer.clearSelectionRange();
4407
4524
  updateForFocusChange(wg);
4408
4525
  },
4409
4526
  compositionstart: compositionUpdate,
@@ -4487,15 +4604,18 @@ class ViewState {
4487
4604
  measure(wg) {
4488
4605
  let dom = wg.contentDOM, style = window.getComputedStyle(dom);
4489
4606
  this.styleLTR = style.direction == "ltr";
4490
- let domRect = dom.getBoundingClientRect();
4491
- this.contentDOMHeight = domRect.height;
4492
4607
  let result = 0;
4608
+ let domRect = dom.getBoundingClientRect();
4609
+ if (Math.abs(this.contentDOMHeight - domRect.height) > 0.5) {
4610
+ result |= 2;
4611
+ this.contentDOMHeight = domRect.height;
4612
+ }
4493
4613
  if (this.editorWidth != wg.scrollDOM.clientWidth) {
4494
4614
  this.editorWidth = wg.scrollDOM.clientWidth;
4495
4615
  result |= 2;
4496
4616
  }
4497
4617
  let contentWidth = domRect.width;
4498
- if (this.contentDOMWidth != contentWidth || this.editorHeight != wg.scrollDOM.clientHeight ||
4618
+ if (Math.abs(this.contentDOMWidth - contentWidth) > 0.5 || this.editorHeight != wg.scrollDOM.clientHeight ||
4499
4619
  this.editorOffset != wg.scrollDOM.offsetTop) {
4500
4620
  this.contentDOMWidth = domRect.width;
4501
4621
  this.editorHeight = wg.scrollDOM.clientHeight;
@@ -4684,7 +4804,7 @@ function getCursorInfo(wg, plugin, cont) {
4684
4804
  return finish(null);
4685
4805
  let target = sel.head.parent.node.isDoc ? wg.contentDOM : wg.nodeDOM(sel.head.parent.before);
4686
4806
  wg.scheduleDOMWrite(() => {
4687
- let temp = renderMarks(marks, "M");
4807
+ let temp = renderMarks(wg.state, marks, "M");
4688
4808
  temp.style.position = "absolute";
4689
4809
  wg.observer.ignore(() => target.insertBefore(temp, target.firstChild));
4690
4810
  wg.scheduleDOMRead(() => {
@@ -5398,844 +5518,719 @@ class PluginInstance {
5398
5518
  }
5399
5519
  }
5400
5520
 
5401
- function offset(add, offset) {
5402
- return (v, from, to) => add(v, from + offset, to == null ? undefined : to + offset);
5403
- }
5404
- function init(type, create, source, doc) {
5405
- return create(add => {
5406
- doc.iterate((node, pos) => {
5407
- if (node.isPlot && doc.schema.matchNode(node.type, type))
5408
- source(node, pos + 1, add);
5409
- });
5410
- });
5411
- }
5412
- function refreshByPred(type, source, doc, deco, pred) {
5413
- let recreate = [];
5414
- doc.iterate((node, pos) => {
5415
- if (doc.schema.matchNode(node.type, type) && pred(node))
5416
- recreate.push({
5417
- from: pos, to: pos + node.length,
5418
- add: add => source(node, pos + 1, add)
5419
- });
5420
- });
5421
- return recreate.length ? deco.modify({ replace: recreate }) : deco;
5422
- }
5423
- function update(config, create, source, deco, tr) {
5424
- let doc = tr.newDoc;
5425
- let refresh = config.refresh && config.refresh(tr);
5426
- if (refresh === true)
5427
- return init(config.type, create, source, doc);
5428
- if (refresh)
5429
- deco = refreshByPred(config.type, source, tr.startState.doc, deco, refresh);
5430
- if (!tr.docChanged && !refresh)
5431
- return deco;
5432
- let recreate = [];
5433
- let clear = [];
5434
- tr.changes.iterChangedRanges((fromA, toA, fromB, toB) => {
5435
- let covered = false;
5436
- doc.iterate(fromB, toB, (node, pos) => {
5437
- if (doc.schema.matchNode(node.type, config.type)) {
5438
- recreate.push({
5439
- from: pos, to: pos + node.length,
5440
- add: add => source(node, pos + 1, add)
5441
- });
5442
- if (pos < toB && pos + node.length > toB)
5443
- covered = true;
5444
- return false;
5521
+ const Outside = "-10000px";
5522
+ class TooltipViewManager {
5523
+ facet;
5524
+ createTooltipView;
5525
+ removeTooltipView;
5526
+ input;
5527
+ tooltips;
5528
+ tooltipViews;
5529
+ constructor(wg, facet, createTooltipView, removeTooltipView) {
5530
+ this.facet = facet;
5531
+ this.createTooltipView = createTooltipView;
5532
+ this.removeTooltipView = removeTooltipView;
5533
+ this.input = wg.state.facet(facet);
5534
+ this.tooltips = this.input.filter(t => t);
5535
+ let prev = null;
5536
+ this.tooltipViews = this.tooltips.map(t => prev = createTooltipView(t, prev));
5537
+ }
5538
+ update(update, above) {
5539
+ let input = update.state.facet(this.facet);
5540
+ let tooltips = input.filter(x => x);
5541
+ if (input === this.input) {
5542
+ for (let t of this.tooltipViews)
5543
+ if (t.update)
5544
+ t.update(update);
5545
+ return false;
5546
+ }
5547
+ let tooltipViews = [], newAbove = above ? [] : null;
5548
+ for (let i = 0; i < tooltips.length; i++) {
5549
+ let tip = tooltips[i], known = -1;
5550
+ if (!tip)
5551
+ continue;
5552
+ for (let i = 0; i < this.tooltips.length; i++) {
5553
+ let other = this.tooltips[i];
5554
+ if (other && other.create == tip.create)
5555
+ known = i;
5556
+ }
5557
+ if (known < 0) {
5558
+ tooltipViews[i] = this.createTooltipView(tip, i ? tooltipViews[i - 1] : null);
5559
+ if (newAbove)
5560
+ newAbove[i] = !!tip.above;
5561
+ }
5562
+ else {
5563
+ let tooltipView = tooltipViews[i] = this.tooltipViews[known];
5564
+ if (newAbove)
5565
+ newAbove[i] = above[known];
5566
+ if (tooltipView.update)
5567
+ tooltipView.update(update);
5445
5568
  }
5446
- });
5447
- if (!covered) {
5448
- let end = tr.startState.doc.resolve(toA);
5449
- let open = end.matchingParent(p => doc.schema.matchNode(p.type, config.type));
5450
- if (open && open.before >= fromA)
5451
- clear.push({ from: open.before, to: open.after });
5452
5569
  }
5453
- });
5454
- if (clear.length)
5455
- deco = deco.modify({ replace: clear });
5456
- deco = deco.map(tr.changes, recreate);
5457
- return deco;
5458
- }
5459
- function decoratePlots(config) {
5460
- let result = [];
5461
- let { type, points, ranges } = config;
5462
- if (points) {
5463
- let source = (plot, start, add) => points(plot, offset(add, start));
5464
- let field = GardState.Field.define({
5465
- create(state) { return init(type, PointSet.create, source, state.doc); },
5466
- update(value, tr) { return update(config, PointSet.create, source, value, tr); }
5467
- });
5468
- result.push(field, Decoration.Point.source.of(s => s.field(field)));
5469
- }
5470
- if (ranges) {
5471
- let source = (plot, start, add) => ranges(plot, offset(add, start));
5472
- let field = GardState.Field.define({
5473
- create(state) { return init(type, RangeSet.create, source, state.doc); },
5474
- update(value, tr) { return update(config, RangeSet.create, source, value, tr); }
5475
- });
5476
- result.push(field, Decoration.Range.source.of(s => s.field(field)));
5477
- }
5478
- return result;
5479
- }
5480
-
5481
- const panelConfig = /*@__PURE__*/GardState.Facet.define({
5482
- combine(configs) {
5483
- let topContainer, bottomContainer;
5484
- for (let c of configs) {
5485
- topContainer ||= c.topContainer;
5486
- bottomContainer ||= c.bottomContainer;
5570
+ for (let t of this.tooltipViews)
5571
+ if (tooltipViews.indexOf(t) < 0) {
5572
+ this.removeTooltipView(t);
5573
+ if (update.editor.connected)
5574
+ t.disconnect?.(update.editor);
5575
+ t.remove?.(update.editor);
5576
+ }
5577
+ if (above) {
5578
+ newAbove.forEach((val, i) => above[i] = val);
5579
+ above.length = newAbove.length;
5487
5580
  }
5488
- return { topContainer, bottomContainer };
5581
+ this.input = input;
5582
+ this.tooltips = tooltips;
5583
+ this.tooltipViews = tooltipViews;
5584
+ return true;
5489
5585
  }
5490
- });
5491
- const panelPlugin = /*@__PURE__*/Wordgard.Plugin.fromClass(class {
5492
- input;
5493
- specs;
5494
- panels;
5495
- top;
5496
- bottom;
5586
+ }
5587
+ const tooltipConfig = /*@__PURE__*/GardState.Facet.define({
5588
+ combine: values => ({
5589
+ position: browser.ios ? "absolute" : values.find(conf => conf.position)?.position || "fixed",
5590
+ parent: values.find(conf => conf.parent)?.parent || null,
5591
+ tooltipSpace: values.find(conf => conf.tooltipSpace)?.tooltipSpace || (wg => windowRect(wg.win)),
5592
+ })
5593
+ });
5594
+ const knownHeight = /*@__PURE__*/(() => new WeakMap())();
5595
+ const tooltipPlugin = /*@__PURE__*/Wordgard.Plugin.fromClass(class {
5596
+ wg;
5597
+ manager;
5598
+ above = [];
5599
+ inView = true;
5600
+ position;
5601
+ madeAbsolute = false;
5602
+ parent;
5603
+ classes;
5604
+ intersectionObserver;
5605
+ resizeObserver;
5606
+ lastTransaction = 0;
5607
+ measureTimeout = -1;
5497
5608
  constructor(wg) {
5498
- this.input = wg.state.facet(Panel.show);
5499
- this.specs = this.input.filter(s => s);
5500
- this.panels = this.specs.map(spec => spec(wg));
5501
- for (let p of this.panels)
5502
- p.dom.classList.add("wg-panel");
5503
- let conf = wg.state.facet(panelConfig);
5504
- this.top = new PanelGroup(wg, true, conf.topContainer);
5505
- this.bottom = new PanelGroup(wg, false, conf.bottomContainer);
5506
- this.top.sync(this.panels.filter(p => p.top), wg);
5507
- this.bottom.sync(this.panels.filter(p => !p.top), wg);
5609
+ this.wg = wg;
5610
+ let config = wg.state.facet(tooltipConfig);
5611
+ this.position = config.position;
5612
+ this.parent = config.parent;
5613
+ this.classes = wg.themeClasses;
5614
+ this.createContainer();
5615
+ this.measure = this.measure.bind(this);
5616
+ this.resizeObserver = typeof ResizeObserver == "function" ? new ResizeObserver(() => this.measureSoon()) : null;
5617
+ this.manager = new TooltipViewManager(wg, Tooltip.show, (t, p) => this.createTooltip(t, p), t => {
5618
+ if (this.resizeObserver)
5619
+ this.resizeObserver.unobserve(t.dom);
5620
+ t.dom.remove();
5621
+ });
5622
+ this.above = this.manager.tooltips.map(t => !!t.above);
5623
+ this.intersectionObserver = typeof IntersectionObserver == "function" ? new IntersectionObserver(entries => {
5624
+ if (Date.now() > this.lastTransaction - 50 &&
5625
+ entries.length > 0 && entries[entries.length - 1].intersectionRatio < 1)
5626
+ this.measureSoon();
5627
+ }, { threshold: [1] }) : null;
5628
+ this.observeIntersection();
5629
+ this.maybeMeasure();
5630
+ }
5631
+ createContainer() {
5632
+ if (this.parent) {
5633
+ this.container = document.createElement("wg-tooltip-root");
5634
+ this.container.style.position = "relative";
5635
+ this.container.className = this.wg.themeClasses;
5636
+ this.parent.appendChild(this.container);
5637
+ }
5638
+ else {
5639
+ this.container = this.wg.dom;
5640
+ }
5641
+ }
5642
+ observeIntersection() {
5643
+ if (this.intersectionObserver && this.wg.connected) {
5644
+ this.intersectionObserver.disconnect();
5645
+ for (let tooltip of this.manager.tooltipViews)
5646
+ this.intersectionObserver.observe(tooltip.dom);
5647
+ }
5648
+ }
5649
+ measureSoon() {
5650
+ if (this.measureTimeout < 0)
5651
+ this.measureTimeout = setTimeout(() => {
5652
+ this.measureTimeout = -1;
5653
+ this.maybeMeasure();
5654
+ }, 50);
5508
5655
  }
5509
5656
  update(update) {
5510
- let conf = update.state.facet(panelConfig);
5511
- if (this.top.container != conf.topContainer) {
5512
- this.top.sync([], update.editor);
5513
- this.top = new PanelGroup(update.editor, true, conf.topContainer);
5657
+ if (update.transactions.length)
5658
+ this.lastTransaction = Date.now();
5659
+ let updated = this.manager.update(update, this.above);
5660
+ if (updated)
5661
+ this.observeIntersection();
5662
+ let shouldMeasure = updated || update.geometryChanged;
5663
+ let newConfig = update.state.facet(tooltipConfig);
5664
+ if (newConfig.position != this.position && !this.madeAbsolute) {
5665
+ this.position = newConfig.position;
5666
+ for (let t of this.manager.tooltipViews)
5667
+ t.dom.style.position = this.position;
5668
+ shouldMeasure = true;
5514
5669
  }
5515
- if (this.bottom.container != conf.bottomContainer) {
5516
- this.bottom.sync([], update.editor);
5517
- this.bottom = new PanelGroup(update.editor, false, conf.bottomContainer);
5670
+ if (newConfig.parent != this.parent) {
5671
+ if (this.parent)
5672
+ this.container.remove();
5673
+ this.parent = newConfig.parent;
5674
+ this.createContainer();
5675
+ for (let t of this.manager.tooltipViews)
5676
+ this.container.appendChild(t.dom);
5677
+ shouldMeasure = true;
5518
5678
  }
5519
- this.top.syncClasses();
5520
- this.bottom.syncClasses();
5521
- let input = update.state.facet(Panel.show);
5522
- if (input != this.input) {
5523
- let specs = input.filter(x => x);
5524
- let panels = [], top = [], bottom = [], mount = [];
5525
- for (let spec of specs) {
5526
- let known = this.specs.indexOf(spec), panel;
5527
- if (known < 0) {
5528
- panel = spec(update.editor);
5529
- mount.push(panel);
5530
- }
5531
- else {
5532
- panel = this.panels[known];
5533
- if (panel.update)
5534
- panel.update(update);
5535
- }
5536
- panels.push(panel);
5537
- (panel.top ? top : bottom).push(panel);
5538
- }
5539
- this.specs = specs;
5540
- this.panels = panels;
5541
- this.top.sync(top, update.editor);
5542
- this.bottom.sync(bottom, update.editor);
5543
- for (let p of mount) {
5544
- p.dom.classList.add("wg-panel");
5545
- if (p.connect && update.editor.connected)
5546
- p.connect(update.editor);
5547
- }
5679
+ else if (this.parent && this.wg.themeClasses != this.classes) {
5680
+ this.classes = this.container.className = this.wg.themeClasses;
5548
5681
  }
5549
- else {
5550
- for (let p of this.panels)
5551
- if (p.update)
5552
- p.update(update);
5682
+ if (shouldMeasure)
5683
+ this.maybeMeasure();
5684
+ }
5685
+ createTooltip(tooltip, prev) {
5686
+ let tooltipView = tooltip.create(this.wg);
5687
+ let before = prev ? prev.dom : null;
5688
+ tooltipView.dom.classList.add("wg-tooltip");
5689
+ if (tooltip.arrow && !tooltipView.dom.querySelector(".wg-tooltip > wg-tooltip-arrow")) {
5690
+ let arrow = document.createElement("wg-tooltip-arrow");
5691
+ tooltipView.dom.appendChild(arrow);
5553
5692
  }
5693
+ tooltipView.dom.style.position = this.position;
5694
+ tooltipView.dom.style.top = Outside;
5695
+ tooltipView.dom.style.left = "0px";
5696
+ this.container.insertBefore(tooltipView.dom, before);
5697
+ if (this.wg.connected)
5698
+ tooltipView.connect?.(this.wg);
5699
+ if (this.resizeObserver && this.wg.connected)
5700
+ this.resizeObserver.observe(tooltipView.dom);
5701
+ return tooltipView;
5554
5702
  }
5555
5703
  connect(wg) {
5556
- for (let p of this.panels)
5557
- p.connect?.(wg);
5704
+ wg.win.addEventListener("resize", this.measureSoon = this.measureSoon.bind(this));
5705
+ for (let t of this.manager.tooltipViews) {
5706
+ t.connect?.(wg);
5707
+ if (this.resizeObserver)
5708
+ this.resizeObserver.observe(t.dom);
5709
+ }
5710
+ this.observeIntersection();
5558
5711
  }
5559
5712
  disconnect(wg) {
5560
- for (let p of this.panels)
5561
- p.disconnect?.(wg);
5562
- }
5563
- remove(wg) {
5564
- this.top.sync([], wg);
5565
- this.bottom.sync([], wg);
5566
- }
5567
- }, plugin => Wordgard.coveredMargins.of(wg => {
5568
- let value = wg.plugin(plugin);
5569
- return value && { top: value.top.scrollMargin(), bottom: value.bottom.scrollMargin() };
5570
- }));
5571
- const Panel = /*@__PURE__*/(function (Panel) {
5572
- Panel.show = GardState.Facet.define({
5573
- enables: panelPlugin
5574
- });
5575
- function get(wg, constructor) {
5576
- let plugin = wg.plugin(panelPlugin);
5577
- let index = plugin ? plugin.specs.indexOf(constructor) : -1;
5578
- return index > -1 ? plugin.panels[index] : null;
5713
+ this.wg.win.removeEventListener("resize", this.measureSoon);
5714
+ for (let t of this.manager.tooltipViews) {
5715
+ t.disconnect?.(wg);
5716
+ if (this.resizeObserver)
5717
+ this.resizeObserver.unobserve(t.dom);
5718
+ }
5719
+ if (this.intersectionObserver)
5720
+ this.intersectionObserver.disconnect();
5579
5721
  }
5580
- Panel.get = get;
5581
- function configure(config) {
5582
- return config ? [panelConfig.of(config)] : [];
5722
+ remove() {
5723
+ for (let tooltipView of this.manager.tooltipViews) {
5724
+ tooltipView.dom.remove();
5725
+ if (this.wg.connected)
5726
+ tooltipView.disconnect?.(this.wg);
5727
+ tooltipView.remove?.(this.wg);
5728
+ }
5729
+ if (this.parent)
5730
+ this.container.remove();
5731
+ clearTimeout(this.measureTimeout);
5583
5732
  }
5584
- Panel.configure = configure;
5585
- ;return Panel})({});
5586
- class PanelGroup {
5587
- wg;
5588
- top;
5589
- container;
5590
- dom = undefined;
5591
- classes = "";
5592
- panels = [];
5593
- constructor(wg, top, container) {
5594
- this.wg = wg;
5595
- this.top = top;
5596
- this.container = container;
5597
- this.syncClasses();
5733
+ measure() {
5734
+ let measure = this.readMeasure();
5735
+ this.wg.scheduleDOMWrite(() => this.writeMeasure(measure));
5598
5736
  }
5599
- sync(panels, wg) {
5600
- for (let p of this.panels)
5601
- if (!panels.includes(p)) {
5602
- if (wg.connected)
5603
- p.disconnect?.(wg);
5604
- p.remove?.(wg);
5737
+ readMeasure() {
5738
+ let scaleX = 1, scaleY = 1, makeAbsolute = false;
5739
+ if (this.position == "fixed" && this.manager.tooltipViews.length) {
5740
+ let { dom } = this.manager.tooltipViews[0];
5741
+ if (browser.safari) {
5742
+ let rect = dom.getBoundingClientRect();
5743
+ makeAbsolute = Math.abs(rect.top + 10000) > 1 || Math.abs(rect.left) > 1;
5605
5744
  }
5606
- this.panels = panels;
5607
- this.syncDOM();
5608
- }
5609
- syncDOM() {
5610
- if (this.panels.length == 0) {
5611
- if (this.dom) {
5612
- this.dom.remove();
5613
- this.dom = undefined;
5745
+ else {
5746
+ makeAbsolute = !!dom.offsetParent && dom.offsetParent != this.container.ownerDocument.body;
5614
5747
  }
5615
- return;
5616
5748
  }
5617
- if (!this.dom) {
5618
- this.dom = document.createElement("wg-panels");
5619
- this.dom.className = this.top ? "wg-panels-top" : "wg-panels-bottom";
5620
- let parent = this.container || this.wg.dom;
5621
- parent.insertBefore(this.dom, this.top ? parent.firstChild : null);
5622
- }
5623
- let curDOM = this.dom.firstChild;
5624
- for (let panel of this.panels) {
5625
- if (panel.dom.parentNode == this.dom) {
5626
- while (curDOM != panel.dom)
5627
- curDOM = rmDOM(curDOM);
5628
- curDOM = curDOM.nextSibling;
5629
- }
5630
- else {
5631
- this.dom.insertBefore(panel.dom, curDOM);
5749
+ if (makeAbsolute || this.position == "absolute") {
5750
+ let measure = this.parent || this.container, rect = measure.getBoundingClientRect();
5751
+ if (rect.width && rect.height) {
5752
+ scaleX = rect.width / measure.offsetWidth;
5753
+ scaleY = rect.height / measure.offsetHeight;
5632
5754
  }
5633
5755
  }
5634
- while (curDOM)
5635
- curDOM = rmDOM(curDOM);
5636
- }
5637
- scrollMargin() {
5638
- return !this.dom || this.container ? 0
5639
- : Math.max(0, this.top ?
5640
- this.dom.getBoundingClientRect().bottom - Math.max(0, this.wg.scrollDOM.getBoundingClientRect().top) :
5641
- Math.min(innerHeight, this.wg.scrollDOM.getBoundingClientRect().bottom) - this.dom.getBoundingClientRect().top);
5642
- }
5643
- syncClasses() {
5644
- if (!this.container || this.classes == this.wg.themeClasses)
5645
- return;
5646
- for (let cls of this.classes.split(" "))
5647
- if (cls)
5648
- this.container.classList.remove(cls);
5649
- for (let cls of (this.classes = this.wg.themeClasses).split(" "))
5650
- if (cls)
5651
- this.container.classList.add(cls);
5652
- }
5653
- }
5654
-
5655
- let nextID = 0;
5656
- function id(prefix) {
5657
- return prefix + "-" + (nextID++ % 0xffffff).toString(16);
5658
- }
5659
- const SVG = "http://www.w3.org/2000/svg";
5660
- function labelButton(wg, button, label) {
5661
- button.textContent = "";
5662
- if (!label) ;
5663
- else if (typeof label == "function" || typeof label == "string") {
5664
- let span = button.appendChild(document.createElement("span"));
5665
- span.className = "wg-button-label";
5666
- span.textContent = typeof label == "string" ? label : label(wg.state);
5667
- }
5668
- else if ("icon" in label) {
5669
- let svg = button.appendChild(document.createElementNS(SVG, "svg"));
5670
- svg.classList.add("wg-icon");
5671
- svg.setAttribute("viewBox", "0 0 100 100");
5672
- let path = svg.appendChild(document.createElementNS(SVG, "path"));
5673
- path.setAttribute("d", label.icon);
5674
- if (label.directional && !wg.state.textLTR)
5675
- svg.setAttribute("transform", "scale(-1, 1)");
5756
+ let visible = this.wg.scrollDOM.getBoundingClientRect(), margins = this.wg.getScrollMargins();
5757
+ let visLeft = visible.left + margins.left, visTop = visible.top + margins.top;
5758
+ return {
5759
+ visible: new DOMRect(visLeft, visTop, visible.right - margins.right - visLeft, visible.bottom - margins.bottom - visTop),
5760
+ parent: this.parent ? this.container.getBoundingClientRect() : this.wg.dom.getBoundingClientRect(),
5761
+ pos: this.manager.tooltips.map((t, i) => {
5762
+ let tv = this.manager.tooltipViews[i];
5763
+ return tv.getCoords ? tv.getCoords(t.pos) : this.wg.coordsAtPos(t.pos);
5764
+ }),
5765
+ size: this.manager.tooltipViews.map(({ dom }) => dom.getBoundingClientRect()),
5766
+ space: this.wg.state.facet(tooltipConfig).tooltipSpace(this.wg),
5767
+ scaleX, scaleY, makeAbsolute
5768
+ };
5676
5769
  }
5677
- }
5678
- class BarButton {
5679
- item;
5680
- dom;
5681
- flags = 0;
5682
- index = 0;
5683
- constructor(item, wg) {
5684
- this.item = item;
5685
- this.dom = document.createElement("button");
5686
- this.dom.className = "wg-menu-button";
5687
- this.dom.type = "button";
5688
- this.dom.tabIndex = -1;
5689
- labelButton(wg, this.dom, item.label);
5690
- if (item.description) {
5691
- let desc = typeof item.description == "function" ? item.description(wg.state) : item.description;
5692
- this.dom.title = desc;
5693
- this.dom.setAttribute("aria-label", desc);
5770
+ writeMeasure(measured) {
5771
+ if (measured.makeAbsolute) {
5772
+ this.madeAbsolute = true;
5773
+ this.position = "absolute";
5774
+ for (let t of this.manager.tooltipViews)
5775
+ t.dom.style.position = "absolute";
5694
5776
  }
5695
- }
5696
- get focusDOM() { return this.dom; }
5697
- update(flags, wg, update) {
5698
- if (flags != this.flags) {
5699
- if ((flags & 32) != (this.flags & 32))
5700
- this.dom.style.display = flags & 32 ? "none" : "";
5701
- if ((flags & 8) != (this.flags & 8)) {
5702
- if (flags & 8)
5703
- this.dom.setAttribute("aria-disabled", "true");
5704
- else
5705
- this.dom.removeAttribute("aria-disabled");
5777
+ let { visible, space, scaleX, scaleY } = measured;
5778
+ let others = [];
5779
+ for (let i = 0; i < this.manager.tooltips.length; i++) {
5780
+ let tooltip = this.manager.tooltips[i], tView = this.manager.tooltipViews[i], { dom } = tView;
5781
+ let pos = measured.pos[i], size = measured.size[i];
5782
+ if (!pos || tooltip.clip !== false && (pos.bottom <= Math.max(visible.top, space.top) ||
5783
+ pos.top >= Math.min(visible.bottom, space.bottom) ||
5784
+ pos.right < Math.max(visible.left, space.left) - .1 ||
5785
+ pos.left > Math.min(visible.right, space.right) + .1)) {
5786
+ dom.style.top = Outside;
5787
+ continue;
5706
5788
  }
5707
- if ((flags & 4) != (this.flags & 4)) {
5708
- if (flags & 4)
5709
- this.dom.setAttribute("aria-selected", "true");
5710
- else
5711
- this.dom.removeAttribute("aria-selected");
5712
- this.dom.tabIndex = flags & 4 ? 0 : -1;
5789
+ let arrow = tooltip.arrow ? tView.dom.querySelector("wg-tooltip-arrow") : null;
5790
+ let arrowHeight = arrow ? 7 : 0;
5791
+ let width = size.right - size.left, height = knownHeight.get(tView) ?? size.bottom - size.top;
5792
+ let offset = tView.offset || noOffset, ltr = this.wg.state.textLTR;
5793
+ let left = size.width > space.right - space.left
5794
+ ? (ltr ? space.left : space.right - size.width)
5795
+ : ltr ? Math.max(space.left, Math.min(pos.left - (arrow ? 14 : 0) + offset.x, space.right - width))
5796
+ : Math.min(Math.max(space.left, pos.left - width + (arrow ? 14 : 0) - offset.x), space.right - width);
5797
+ let above = this.above[i];
5798
+ if (!tooltip.strictSide && (above
5799
+ ? pos.top - height - arrowHeight - offset.y < space.top
5800
+ : pos.bottom + height + arrowHeight + offset.y > space.bottom) &&
5801
+ above == (space.bottom - pos.bottom > pos.top - space.top))
5802
+ above = this.above[i] = !above;
5803
+ let spaceVert = (above ? pos.top - space.top : space.bottom - pos.bottom) - arrowHeight;
5804
+ if (spaceVert < height && tView.resize !== false) {
5805
+ if (spaceVert < 15) {
5806
+ dom.style.top = Outside;
5807
+ continue;
5808
+ }
5809
+ knownHeight.set(tView, height);
5810
+ dom.style.height = (height = spaceVert) / scaleY + "px";
5713
5811
  }
5714
- if ((flags & 16) != (this.flags & 16)) {
5715
- if (flags & 16)
5716
- this.dom.setAttribute("aria-pressed", "true");
5717
- else
5718
- this.dom.removeAttribute("aria-pressed");
5812
+ else if (dom.style.height) {
5813
+ dom.style.height = "";
5719
5814
  }
5720
- this.flags = flags;
5815
+ let top = above ? pos.top - height - arrowHeight - offset.y : pos.bottom + arrowHeight + offset.y;
5816
+ let right = left + width;
5817
+ if (tView.overlap !== true)
5818
+ for (let r of others)
5819
+ if (r.left < right && r.right > left && r.top < top + height && r.bottom > top)
5820
+ top = above ? r.top - height - 2 - arrowHeight : r.bottom + arrowHeight + 2;
5821
+ if (this.position == "absolute") {
5822
+ dom.style.top = (top - measured.parent.top) / scaleY + "px";
5823
+ setLeftStyle(dom, (left - measured.parent.left) / scaleX);
5824
+ }
5825
+ else {
5826
+ dom.style.top = top / scaleY + "px";
5827
+ setLeftStyle(dom, left / scaleX);
5828
+ }
5829
+ if (arrow) {
5830
+ let arrowLeft = pos.left + (ltr ? offset.x : -offset.x) - (left + 14 - 7);
5831
+ arrow.style.left = arrowLeft / scaleX + "px";
5832
+ }
5833
+ if (tView.overlap !== true)
5834
+ others.push({ left, top, right, bottom: top + height });
5835
+ dom.classList.toggle("wg-tooltip-above", above);
5836
+ dom.classList.toggle("wg-tooltip-below", !above);
5837
+ if (tView.positioned)
5838
+ tView.positioned(measured.space);
5721
5839
  }
5722
5840
  }
5723
- get children() { return null; }
5724
- run(wg) {
5725
- Command.dispatch(wg, this.item.run);
5841
+ maybeMeasure() {
5842
+ if (this.manager.tooltips.length)
5843
+ this.wg.scheduleDOMRead(this.measure);
5726
5844
  }
5845
+ }, plugin => plugin.eventObserver("scroll", (event, wg, value) => value.maybeMeasure()));
5846
+ function setLeftStyle(elt, value) {
5847
+ let current = parseInt(elt.style.left, 10);
5848
+ if (isNaN(current) || Math.abs(value - current) > 1)
5849
+ elt.style.left = value + "px";
5727
5850
  }
5728
- class BarControl {
5729
- item;
5730
- dom;
5731
- focusDOM;
5732
- flags = 0;
5733
- index = 0;
5734
- constructor(item, wg, done) {
5735
- this.item = item;
5736
- let { dom, focus } = item.render(wg, done);
5737
- this.dom = dom;
5738
- this.focusDOM = focus || dom;
5739
- this.focusDOM.tabIndex = -1;
5740
- }
5741
- update(flags, wg, update) {
5742
- if (flags != this.flags) {
5743
- if ((flags & 32) != (this.flags & 32))
5744
- this.dom.style.display = flags & 32 ? "none" : "";
5745
- if ((flags & 8) != (this.flags & 8) && this.item.setEnabled)
5746
- this.item.setEnabled(this.dom, !(flags & 8));
5747
- if ((flags & 4) != (this.flags & 4)) {
5748
- if (flags & 4)
5749
- this.focusDOM.setAttribute("aria-selected", "true");
5750
- else
5751
- this.focusDOM.removeAttribute("aria-selected");
5752
- this.focusDOM.tabIndex = flags & 4 ? 0 : -1;
5851
+ const styles = /*@__PURE__*/Wordgard.styles({
5852
+ ".wg-tooltip": {
5853
+ zIndex: 500,
5854
+ boxSizing: "border-box",
5855
+ backgroundColor: "var(--wg-panel-color)",
5856
+ boxShadow: "0 0 8px 0 rgba(128, 128, 128, 0.2)",
5857
+ font: "var(--wg-dialog-font)",
5858
+ },
5859
+ ".wg-tooltip-section:not(:first-child)": {
5860
+ borderTop: "1px solid var(--wg-border-color)",
5861
+ },
5862
+ "wg-tooltip-arrow": {
5863
+ display: "block",
5864
+ height: `${7}px`,
5865
+ width: `${7 * 2}px`,
5866
+ position: "absolute",
5867
+ zIndex: -1,
5868
+ overflow: "hidden",
5869
+ "&:before, &:after": {
5870
+ content: "''",
5871
+ position: "absolute",
5872
+ width: 0,
5873
+ height: 0,
5874
+ borderLeft: `${7}px solid transparent`,
5875
+ borderRight: `${7}px solid transparent`,
5876
+ },
5877
+ ".wg-tooltip-above &": {
5878
+ bottom: `-${7}px`,
5879
+ "&:before": {
5880
+ borderTop: `${7}px solid var(--wg-border-color)`,
5881
+ },
5882
+ "&:after": {
5883
+ borderTop: `${7}px solid var(--wg-panel-color)`,
5884
+ bottom: "1px"
5753
5885
  }
5754
- this.flags = flags;
5755
- }
5756
- }
5757
- get children() { return null; }
5758
- get run() { return null; }
5759
- }
5760
- class BarSubmenu {
5761
- item;
5762
- dom;
5763
- button;
5764
- list;
5765
- flags = 0;
5766
- activeChild = -3;
5767
- index = 0;
5768
- children;
5769
- constructor(item, children, wg) {
5770
- this.item = item;
5771
- this.dom = document.createElement("wg-submenu");
5772
- this.button = this.dom.appendChild(document.createElement("button"));
5773
- this.button.type = "button";
5774
- this.button.tabIndex = -1;
5775
- this.button.className = "wg-menu-button";
5776
- this.button.setAttribute("aria-haspopup", "true");
5777
- this.button.setAttribute("aria-expanded", "false");
5778
- if (item.description) {
5779
- let desc = typeof item.description == "function" ? item.description(wg.state) : item.description;
5780
- this.dom.title = desc;
5781
- this.dom.setAttribute("aria-label", desc);
5782
- }
5783
- if (item.label) {
5784
- labelButton(wg, this.button, item.label);
5785
- this.activeChild = -2;
5786
- }
5787
- if (item.width != null)
5788
- this.dom.style.setProperty("--wg-submenu-width", item.width + "ch");
5789
- if (item.arrow)
5790
- this.button.classList.add("wg-submenu-arrow");
5791
- this.list = this.dom.appendChild(document.createElement("wg-menu-list"));
5792
- this.list.style.display = "none";
5793
- this.list.role = "menu";
5794
- this.list.id = id("wg-popup");
5795
- this.list.setAttribute("aria-label", this.button.title);
5796
- this.button.setAttribute("aria-controls", this.list.id);
5797
- this.children = children.filter((ch) => !(ch instanceof BarSpacer));
5798
- for (let child of children) {
5799
- this.list.appendChild(child.dom);
5800
- if (!(child instanceof BarSpacer))
5801
- child.focusDOM.role = "menuitem";
5802
- }
5803
- }
5804
- get focusDOM() { return this.button; }
5805
- update(flags, wg) {
5806
- if (flags != this.flags) {
5807
- if ((flags & 32) != (this.flags & 32))
5808
- this.dom.style.display = flags & 32 ? "none" : "";
5809
- if ((flags & 8) != (this.flags & 8)) {
5810
- if (flags & 8)
5811
- this.button.setAttribute("aria-disabled", "true");
5812
- else
5813
- this.button.removeAttribute("aria-disabled");
5814
- }
5815
- if ((flags & 4) != (this.flags & 4)) {
5816
- if (flags & 4)
5817
- this.button.setAttribute("aria-selected", "true");
5818
- else
5819
- this.button.removeAttribute("aria-selected");
5820
- this.button.tabIndex = flags & 4 ? 0 : -1;
5821
- }
5822
- if ((flags & 2) != (this.flags & 2))
5823
- this.list.style.display = flags & 2 ? "" : "none";
5824
- this.flags = flags;
5825
- }
5826
- if (this.activeChild != -2) {
5827
- let activeChild = this.children.findIndex(ch => ch.flags & 16);
5828
- if (this.activeChild != activeChild) {
5829
- this.activeChild = activeChild;
5830
- let label = activeChild < 0 ? this.item.defaultLabel : this.children[activeChild].item.label;
5831
- labelButton(wg, this.button, label);
5886
+ },
5887
+ ".wg-tooltip-below &": {
5888
+ top: `-${7}px`,
5889
+ "&:before": {
5890
+ borderBottom: `${7}px solid var(--wg-border-color)`,
5891
+ },
5892
+ "&:after": {
5893
+ borderBottom: `${7}px solid var(--wg-panel-color)`,
5894
+ top: "1px"
5832
5895
  }
5833
- }
5896
+ },
5897
+ },
5898
+ });
5899
+ const closeHoverTooltipEffect = /*@__PURE__*/Transaction.Effect.define();
5900
+ const Tooltip = /*@__PURE__*/(function (Tooltip) {
5901
+ function configure(config = {}) {
5902
+ return tooltipConfig.of(config);
5834
5903
  }
5835
- get run() { return null; }
5836
- }
5837
- class BarSpacer {
5838
- dom;
5839
- constructor() {
5840
- this.dom = document.createElement("wg-menu-spacer");
5904
+ Tooltip.configure = configure;
5905
+ Tooltip.show = GardState.Facet.define({
5906
+ enables: [tooltipPlugin, styles]
5907
+ });
5908
+ function get(wg, tooltip) {
5909
+ let plugin = wg.plugin(tooltipPlugin);
5910
+ if (!plugin)
5911
+ return null;
5912
+ let found = plugin.manager.tooltips.findIndex(typeof tooltip == "function" ? p => p.create == tooltip : p => p == tooltip);
5913
+ return found < 0 ? null : plugin.manager.tooltipViews[found];
5841
5914
  }
5842
- }
5843
- function instantiate(item, bar, flat) {
5844
- let elt;
5845
- if (item instanceof Menu.Submenu.Resolved)
5846
- elt = new BarSubmenu(item.item, item.content.map(i => instantiate(i, bar, flat)), bar.wg);
5847
- else if (item === "|")
5848
- return new BarSpacer();
5849
- else if (item instanceof Menu.Button)
5850
- elt = new BarButton(item, bar.wg);
5851
- else
5852
- elt = new BarControl(item, bar.wg, () => bar.up());
5853
- elt.index = flat.length;
5854
- flat.push(elt);
5855
- return elt;
5856
- }
5857
- class MenuBar {
5915
+ Tooltip.get = get;
5916
+ function reposition(wg) {
5917
+ let plugin = wg.plugin(tooltipPlugin);
5918
+ if (plugin)
5919
+ plugin.maybeMeasure();
5920
+ }
5921
+ Tooltip.reposition = reposition;
5922
+ function hover(source, options = {}) {
5923
+ let setHover = Transaction.Effect.define();
5924
+ let hoverState = GardState.Field.define({
5925
+ create() { return []; },
5926
+ update(value, tr) {
5927
+ if (value.length) {
5928
+ if (options.hideOnChange && (tr.docChanged || tr.selection))
5929
+ value = [];
5930
+ else if (options.hideOn)
5931
+ value = value.filter(v => !options.hideOn(tr, v));
5932
+ if (tr.docChanged) {
5933
+ let mapped = [];
5934
+ for (let tooltip of value) {
5935
+ let newPos = tr.changes.mapPos(tooltip.pos, -1, "around");
5936
+ if (newPos != null) {
5937
+ let copy = Object.assign(Object.create(null), tooltip);
5938
+ copy.pos = newPos;
5939
+ if (copy.end != null)
5940
+ copy.end = tr.changes.mapPos(copy.end);
5941
+ mapped.push(copy);
5942
+ }
5943
+ }
5944
+ value = mapped;
5945
+ }
5946
+ }
5947
+ for (let effect of tr.effects) {
5948
+ if (effect.is(setHover))
5949
+ value = effect.value;
5950
+ if (effect.is(closeHoverTooltipEffect))
5951
+ value = [];
5952
+ }
5953
+ return value;
5954
+ },
5955
+ provide: f => showHoverTooltip.from(f)
5956
+ });
5957
+ return {
5958
+ active: hoverState,
5959
+ extension: [
5960
+ hoverState,
5961
+ Wordgard.Plugin.define(wg => new HoverPlugin(wg, source, hoverState, setHover, options.hoverTime || 300)),
5962
+ showHoverTooltipHost
5963
+ ]
5964
+ };
5965
+ }
5966
+ Tooltip.hover = hover;
5967
+ (function (hover) {
5968
+ function has(state) {
5969
+ return state.facet(showHoverTooltip).some(x => x);
5970
+ }
5971
+ hover.has = has;
5972
+ hover.closeAll = closeHoverTooltipEffect.of(null);
5973
+ })(hover = Tooltip.hover || (Tooltip.hover = {}));
5974
+ ;return Tooltip})({});
5975
+ const noOffset = { x: 0, y: 0 };
5976
+ const showHoverTooltip = /*@__PURE__*/GardState.Facet.define({
5977
+ combine: inputs => inputs.reduce((a, i) => a.concat(i), [])
5978
+ });
5979
+ class HoverTooltipHost {
5858
5980
  wg;
5981
+ manager;
5859
5982
  dom;
5860
- focusTimeout = -1;
5861
- items;
5983
+ connected = false;
5984
+ static create(wg) {
5985
+ return new HoverTooltipHost(wg);
5986
+ }
5862
5987
  constructor(wg) {
5863
5988
  this.wg = wg;
5864
- this.dom = document.createElement("wg-menubar");
5865
- this.dom.role = "toolbar";
5866
- this.dom.addEventListener("keydown", this.key.bind(this));
5867
- this.dom.addEventListener("mousedown", this.click.bind(this));
5868
- this.dom.addEventListener("focusout", this.focusout.bind(this));
5869
- this.items = wg.state.facet(Menu.Item.source);
5870
- this.init();
5871
- this.globalClick = this.globalClick.bind(this);
5989
+ this.dom = document.createElement("wg-tooltip-hover");
5990
+ this.manager = new TooltipViewManager(wg, showHoverTooltip, (t, p) => this.createHostedView(t, p), t => t.dom.remove());
5872
5991
  }
5873
- static create(wg) { return new MenuBar(wg); }
5874
- init() {
5875
- let elts = [];
5876
- this.elts = elts;
5877
- let children = Menu.resolve(this.items, this.wg.state.facet(barTemplate)).map(i => instantiate(i, this, elts));
5878
- this.children = children.filter((ch) => !(ch instanceof BarSpacer));
5879
- for (let elt of children)
5880
- this.dom.appendChild(elt.dom);
5881
- this.selection = this.children.length ? [this.children[0]] : [];
5882
- this.updateElts(true, this.selection);
5992
+ createHostedView(tooltip, prev) {
5993
+ let hostedView = tooltip.create(this.wg);
5994
+ hostedView.dom.classList.add("wg-tooltip-section");
5995
+ this.dom.insertBefore(hostedView.dom, prev ? prev.dom.nextSibling : this.dom.firstChild);
5996
+ if (this.connected && hostedView.connect)
5997
+ hostedView.connect(this.wg);
5998
+ return hostedView;
5883
5999
  }
5884
- update(update) {
5885
- let items = update.state.facet(Menu.Item.source);
5886
- if (items != this.items ||
5887
- update.startState.facet(barTemplate) != update.state.facet(barTemplate) ||
5888
- update.startState.textLTR != update.state.textLTR ||
5889
- PhraseSet.didChange(update.startState, update.state)) {
5890
- this.items = items;
5891
- this.dom.textContent = "";
5892
- this.init();
5893
- }
5894
- else {
5895
- this.updateElts(update, this.selection);
6000
+ connect(wg) {
6001
+ for (let t of this.manager.tooltipViews)
6002
+ t.connect?.(wg);
6003
+ this.connected = true;
6004
+ }
6005
+ disconnect(wg) {
6006
+ for (let t of this.manager.tooltipViews)
6007
+ t.disconnect?.(wg);
6008
+ this.connected = false;
6009
+ }
6010
+ positioned(space) {
6011
+ for (let hostedView of this.manager.tooltipViews) {
6012
+ if (hostedView.positioned)
6013
+ hostedView.positioned(space);
5896
6014
  }
5897
6015
  }
5898
- connect() {
5899
- this.dom.setAttribute("aria-controls", this.wg.contentDOM.id);
6016
+ update(update) {
6017
+ this.manager.update(update);
5900
6018
  }
5901
- updateElts(update, selection) {
5902
- let { state } = this.wg;
5903
- let changed = typeof update == "boolean" ? update : update.docChanged || update.selectionSet;
5904
- let updateObj = typeof update == "boolean" ? null : update;
5905
- for (let i = 0; i < this.elts.length; i++) {
5906
- let elt = this.elts[i], flags;
5907
- if (update && (update === true || (elt.item.updateFor ? update.transactions.some(tr => elt.item.updateFor(tr)) : changed))) {
5908
- flags = ((elt.item.select ? elt.item.select(state) : true) ? 0 : 32) |
5909
- ((elt.item.enable ? elt.item.enable(state) : true) ? 0 : 8) |
5910
- ((elt.item instanceof Menu.Button && elt.item.active ? elt.item.active(state) : false) ? 16 : 0);
5911
- }
5912
- else {
5913
- flags = elt.flags & (32 | 8 | 16);
5914
- }
5915
- if (selection.length) {
5916
- let selected = selection.indexOf(elt);
5917
- if (selected == selection.length - 1)
5918
- flags |= 4;
5919
- else if (selected > -1)
5920
- flags |= 2;
6019
+ remove(wg) {
6020
+ for (let t of this.manager.tooltipViews)
6021
+ t.remove?.(wg);
6022
+ }
6023
+ passProp(name) {
6024
+ let value = undefined;
6025
+ for (let view of this.manager.tooltipViews) {
6026
+ let given = view[name];
6027
+ if (given !== undefined) {
6028
+ if (value === undefined)
6029
+ value = given;
6030
+ else if (value !== given)
6031
+ return undefined;
5921
6032
  }
5922
- elt.update(flags, this.wg, updateObj);
5923
6033
  }
5924
- if (update && selection.some(e => e.flags & 32)) {
5925
- let reset = selection[0].flags & 32 ? findChild(this.children, true) : selection[0];
5926
- this.setSelection(reset ? [reset] : [], this.dom.contains(this.wg.root.activeElement));
6034
+ return value;
6035
+ }
6036
+ get offset() { return this.passProp("offset"); }
6037
+ get getCoords() { return this.passProp("getCoords"); }
6038
+ get overlap() { return this.passProp("overlap"); }
6039
+ get resize() { return this.passProp("resize"); }
6040
+ }
6041
+ const showHoverTooltipHost = /*@__PURE__*/Tooltip.show.derive(state => {
6042
+ let tooltips = state.facet(showHoverTooltip);
6043
+ if (tooltips.length === 0)
6044
+ return null;
6045
+ return {
6046
+ pos: Math.min(...tooltips.map(t => t.pos)),
6047
+ end: Math.max(...tooltips.map(t => t.end ?? t.pos)),
6048
+ create: HoverTooltipHost.create,
6049
+ above: tooltips[0].above,
6050
+ arrow: tooltips.some(t => t.arrow),
6051
+ };
6052
+ });
6053
+ class HoverPlugin {
6054
+ wg;
6055
+ source;
6056
+ field;
6057
+ setHover;
6058
+ hoverTime;
6059
+ lastMove;
6060
+ hoverTimeout = -1;
6061
+ restartTimeout = -1;
6062
+ pending = null;
6063
+ constructor(wg, source, field, setHover, hoverTime) {
6064
+ this.wg = wg;
6065
+ this.source = source;
6066
+ this.field = field;
6067
+ this.setHover = setHover;
6068
+ this.hoverTime = hoverTime;
6069
+ this.lastMove = { x: 0, y: 0, target: wg.dom, time: 0 };
6070
+ this.checkHover = this.checkHover.bind(this);
6071
+ wg.dom.addEventListener("mouseleave", this.mouseleave = this.mouseleave.bind(this));
6072
+ wg.dom.addEventListener("mousemove", this.mousemove = this.mousemove.bind(this));
6073
+ }
6074
+ update() {
6075
+ if (this.pending) {
6076
+ this.pending = null;
6077
+ clearTimeout(this.restartTimeout);
6078
+ this.restartTimeout = setTimeout(() => this.startHover(), 20);
5927
6079
  }
5928
6080
  }
5929
- setSelection(selection, focus = true) {
5930
- this.updateElts(false, selection);
5931
- if (selection.length > 1 && this.selection.length <= 1)
5932
- this.dom.ownerDocument.addEventListener("mousedown", this.globalClick);
5933
- this.selection = selection;
5934
- if (focus && selection.length)
5935
- selection[selection.length - 1].focusDOM.focus();
6081
+ get active() {
6082
+ return this.wg.state.field(this.field);
5936
6083
  }
5937
- key(event) {
5938
- if (event.ctrlKey || event.altKey || event.metaKey || event.defaultPrevented)
6084
+ checkHover() {
6085
+ this.hoverTimeout = -1;
6086
+ if (this.active.length)
5939
6087
  return;
5940
- let sLen = this.selection.length;
5941
- if (event.key == "ArrowLeft" || event.key == "ArrowRight") {
5942
- if (sLen) {
5943
- let forward = (event.key == "ArrowRight") == (getComputedStyle(this.dom).direction == "ltr");
5944
- let next = findNextChild(this.children, this.selection[0], forward ? 1 : -1);
5945
- this.setSelection(next ? [next] : []);
5946
- }
5947
- }
5948
- else if (event.key == "ArrowDown" || event.key == "ArrowUp") {
5949
- if (sLen > 1) {
5950
- let parent = this.selection[sLen - 2];
5951
- let next = findNextChild(parent.children, this.selection[sLen - 1], event.key == "ArrowUp" ? -1 : 1);
5952
- if (next)
5953
- this.setSelection(this.selection.slice(0, sLen - 1).concat(next));
5954
- }
5955
- else if (sLen == 1 && this.selection[0].children) {
5956
- let inner = defaultChild(this.selection[0].children);
5957
- if (inner)
5958
- this.setSelection([this.selection[0], inner]);
5959
- }
5960
- }
5961
- else if (event.key == "Home" || event.key == "End") {
5962
- let child = findChild(sLen > 1 ? this.selection[sLen - 2].children : this.children, event.key == "Home");
5963
- if (child)
5964
- this.setSelection(this.selection.slice(0, sLen - 1).concat(child));
5965
- }
5966
- else if (event.key == " " || event.key == "Enter") {
5967
- if (sLen) {
5968
- let child = this.selection[sLen - 1];
5969
- if (child.flags & 8) ;
5970
- else if (child.children) {
5971
- let inner = defaultChild(child.children);
5972
- if (inner)
5973
- this.setSelection(this.selection.concat(inner));
5974
- }
5975
- else {
5976
- if (child.run)
5977
- child.run(this.wg);
5978
- this.setSelection([this.selection[0]]);
6088
+ let hovered = Date.now() - this.lastMove.time;
6089
+ if (hovered < this.hoverTime)
6090
+ this.hoverTimeout = setTimeout(this.checkHover, this.hoverTime - hovered);
6091
+ else
6092
+ this.startHover();
6093
+ }
6094
+ startHover() {
6095
+ clearTimeout(this.restartTimeout);
6096
+ let { wg, lastMove } = this;
6097
+ let { pos, side } = wg.posAtCoords(lastMove);
6098
+ let open = this.source(wg, pos, side || -1);
6099
+ if (open?.then) {
6100
+ let pending = this.pending = { pos };
6101
+ open.then(result => {
6102
+ if (this.pending == pending) {
6103
+ this.pending = null;
6104
+ if (result && !(Array.isArray(result) && !result.length))
6105
+ wg.dispatch({ effects: this.setHover.of(Array.isArray(result) ? result : [result]) });
5979
6106
  }
5980
- }
5981
- }
5982
- else if (event.key == "Escape" && sLen > 1) {
5983
- this.setSelection(this.selection.slice(0, sLen - 1));
6107
+ }, e => logException(wg.state, e, "hover tooltip"));
5984
6108
  }
5985
- else {
5986
- return;
6109
+ else if (open && !(Array.isArray(open) && !open.length)) {
6110
+ wg.dispatch({ effects: this.setHover.of(Array.isArray(open) ? open : [open]) });
5987
6111
  }
5988
- event.preventDefault();
5989
6112
  }
5990
- click(event) {
5991
- if (event.defaultPrevented)
5992
- return;
5993
- let target, idx;
5994
- for (let node = event.target;; node = node.parentNode) {
5995
- if (!node || node == this.dom)
5996
- return;
5997
- target = this.elts.find(e => e.dom == node);
5998
- if (target)
5999
- break;
6000
- }
6001
- if (target.flags & 8) ;
6002
- else if (target.children) {
6003
- if ((idx = this.selection.indexOf(target)) > -1 && idx < this.selection.length - 1) {
6004
- this.setSelection(this.selection.slice(0, idx + 1), false);
6005
- }
6006
- else {
6007
- for (let i = 0; i < this.selection.length; i++) {
6008
- let { children } = i ? this.selection[i - 1] : this;
6009
- if (children.includes(target)) {
6010
- let next = defaultChild(target.children);
6011
- if (next)
6012
- this.setSelection(this.selection.slice(0, i).concat([target, next]), false);
6013
- }
6014
- }
6113
+ get tooltip() {
6114
+ let plugin = this.wg.plugin(tooltipPlugin);
6115
+ let index = plugin ? plugin.manager.tooltips.findIndex(t => t.create == HoverTooltipHost.create) : -1;
6116
+ return index > -1 ? plugin.manager.tooltipViews[index] : null;
6117
+ }
6118
+ mousemove(event) {
6119
+ this.lastMove = { x: event.clientX, y: event.clientY, target: event.target, time: Date.now() };
6120
+ if (this.hoverTimeout < 0)
6121
+ this.hoverTimeout = setTimeout(this.checkHover, this.hoverTime);
6122
+ let { active, tooltip } = this;
6123
+ if (active.length && tooltip && !isInTooltip(tooltip.dom, event) || this.pending) {
6124
+ let { pos } = active[0] || this.pending, end = active[0]?.end ?? pos;
6125
+ if ((pos == end ? this.wg.posAtCoords(this.lastMove).pos != pos
6126
+ : !isOverRange(this.wg, pos, end, event.clientX, event.clientY))) {
6127
+ this.wg.dispatch({ effects: this.setHover.of([]) });
6128
+ this.pending = null;
6015
6129
  }
6016
6130
  }
6017
- else {
6018
- if (target.run)
6019
- target.run(this.wg);
6020
- this.setSelection(this.children.includes(target) ? [target] : this.selection.length ? [this.selection[0]] : [], false);
6021
- }
6022
- event.preventDefault();
6023
6131
  }
6024
- globalClick(event) {
6025
- if (!event.composedPath().includes(this.dom)) {
6026
- this.dom.ownerDocument.removeEventListener("mousedown", this.globalClick);
6027
- if (this.selection.length > 1)
6028
- this.setSelection([this.selection[0]], false);
6132
+ mouseleave(event) {
6133
+ clearTimeout(this.hoverTimeout);
6134
+ this.hoverTimeout = -1;
6135
+ let { active } = this;
6136
+ if (active.length) {
6137
+ let { tooltip } = this;
6138
+ let inTooltip = tooltip && tooltip.dom.contains(event.relatedTarget);
6139
+ if (!inTooltip)
6140
+ this.wg.dispatch({ effects: this.setHover.of([]) });
6141
+ else
6142
+ this.watchTooltipLeave(tooltip.dom);
6029
6143
  }
6030
6144
  }
6031
- focusout() {
6032
- clearTimeout(this.focusTimeout);
6033
- if (this.selection.length > 1) {
6034
- this.focusTimeout = setTimeout(() => {
6035
- let active = this.wg.root.activeElement;
6036
- for (let i = 0; i < this.selection.length - 1; i++) {
6037
- if (!active || !this.selection[i].dom.contains(active)) {
6038
- this.setSelection([this.selection[0]], false);
6039
- break;
6040
- }
6041
- }
6042
- }, 20);
6043
- }
6145
+ watchTooltipLeave(tooltip) {
6146
+ let watch = (event) => {
6147
+ tooltip.removeEventListener("mouseleave", watch);
6148
+ if (this.active.length && !this.wg.dom.contains(event.relatedTarget))
6149
+ this.wg.dispatch({ effects: this.setHover.of([]) });
6150
+ };
6151
+ tooltip.addEventListener("mouseleave", watch);
6044
6152
  }
6045
- up() {
6046
- if (this.selection.length > 1)
6047
- this.setSelection([this.selection[0]], false);
6153
+ remove() {
6154
+ clearTimeout(this.hoverTimeout);
6155
+ this.wg.dom.removeEventListener("mouseleave", this.mouseleave);
6156
+ this.wg.dom.removeEventListener("mousemove", this.mousemove);
6048
6157
  }
6049
- get top() { return true; }
6050
6158
  }
6051
- const menuBarPanel = /*@__PURE__*/(() => Panel.show.of(MenuBar.create))();
6052
- function findChild(children, start) {
6053
- for (let i = start ? 0 : children.length - 1; start ? i < children.length : i >= 0; start ? i++ : i--) {
6054
- let child = children[i];
6055
- if (!(child.flags & 8))
6056
- return child;
6159
+ const tooltipMargin = 4;
6160
+ function isInTooltip(tooltip, event) {
6161
+ let { left, right, top, bottom } = tooltip.getBoundingClientRect(), arrow;
6162
+ if (arrow = tooltip.querySelector(".wg-tooltip-arrow")) {
6163
+ let arrowRect = arrow.getBoundingClientRect();
6164
+ top = Math.min(arrowRect.top, top);
6165
+ bottom = Math.max(arrowRect.bottom, bottom);
6057
6166
  }
6058
- return null;
6059
- }
6060
- function findNextChild(children, child, dir) {
6061
- let index = children.indexOf(child);
6062
- if (index < 0)
6063
- return null;
6064
- for (let i = index + dir;; i += dir) {
6065
- if (i < 0)
6066
- i = children.length - 1;
6067
- else if (i >= children.length)
6068
- i = 0;
6069
- if (i == index)
6070
- return null;
6071
- let child = children[i];
6072
- if (!(child.flags & 32))
6073
- return child;
6074
- }
6075
- }
6076
- function defaultChild(children) {
6077
- return children.length ? children.find(ch => ch.flags & 16) || children[0] : null;
6167
+ return event.clientX >= left - tooltipMargin && event.clientX <= right + tooltipMargin &&
6168
+ event.clientY >= top - tooltipMargin && event.clientY <= bottom + tooltipMargin;
6078
6169
  }
6079
- const theme = /*@__PURE__*/Wordgard.styles({
6080
- "&": {
6081
- "--wg-menu-item-size": "20px"
6082
- },
6083
- "&light": {
6084
- "--wg-menu-color": "#555"
6085
- },
6086
- "&dark": {
6087
- "--wg-menu-color": "#ccc"
6088
- },
6089
- "wg-menubar": {
6090
- display: "flex",
6091
- flexWrap: "wrap",
6092
- gap: "5px",
6093
- padding: "3px",
6094
- color: "var(--wg-menu-color)",
6095
- borderBottom: "1px solid var(--wg-border-color)",
6096
- },
6097
- ".wg-menu-button:focus": {
6098
- outline: "1.5px solid var(--wg-highlight-color)"
6099
- },
6100
- ".wg-menu-button": {
6101
- border: 0,
6102
- padding: "3px",
6103
- borderRadius: "4px",
6104
- boxSizing: "content-box",
6105
- backgroundColor: "transparent",
6106
- font: "inherit",
6107
- color: "inherit",
6108
- height: "var(--wg-menu-item-size)",
6109
- textAlign: "left",
6110
- "&[aria-disabled]": {
6111
- opacity: "0.3"
6112
- },
6113
- "&[aria-pressed]": {
6114
- color: "var(--wg-highlight-color)"
6115
- },
6116
- "&:hover": {
6117
- backgroundColor: "#88888820",
6118
- },
6119
- },
6120
- ".wg-button-label": {
6121
- display: "inline-block",
6122
- minWidth: "var(--wg-submenu-width)",
6123
- padding: "0 3px",
6124
- },
6125
- "svg.wg-icon": {
6126
- fill: "currentColor",
6127
- width: "var(--wg-menu-item-size)",
6128
- height: "var(--wg-menu-item-size)",
6129
- },
6130
- "wg-menu-spacer": {
6131
- display: "block",
6132
- width: "7px"
6133
- },
6134
- "wg-submenu": {
6135
- display: "block",
6136
- position: "relative",
6137
- lineHeight: ".6",
6138
- whiteSpace: "nowrap"
6139
- },
6140
- "wg-menu-list": {
6141
- display: "flex",
6142
- flexDirection: "column",
6143
- alignItems: "stretch",
6144
- gap: "5px",
6145
- padding: "5px",
6146
- borderRadius: "4px",
6147
- backgroundColor: "var(--wg-panel-color)",
6148
- position: "absolute",
6149
- zIndex: "10",
6150
- top: "100%",
6151
- boxShadow: "0 2px 8px 0 rgba(128, 128, 128, 0.2)",
6152
- "& wg-menu-list": {
6153
- left: "100%",
6154
- top: 0
6155
- }
6156
- },
6157
- ".wg-submenu-arrow:after": {
6158
- padding: "0 2px",
6159
- fontSize: "80%",
6160
- verticalAlign: "10%",
6161
- opacity: "0.4",
6162
- content: "'▾'"
6163
- },
6164
- ".wg-submenu-arrow.wg-submenu-arrow-open:after": {
6165
- content: "'▴'"
6166
- },
6167
- });
6168
- const barTemplate = /*@__PURE__*/GardState.Facet.define({
6169
- combine: inputs => inputs.length ? inputs[0] : [Menu.Group.top.template()]
6170
- });
6171
- function menuBar(config = {}) {
6172
- let extensions = [menuBarPanel, theme];
6173
- if (config.template)
6174
- extensions.push(barTemplate.of(Array.isArray(config.template) ? config.template : [config.template]));
6175
- return extensions;
6170
+ function isOverRange(wg, from, to, x, y, margin) {
6171
+ let rect = wg.contentDOM.getBoundingClientRect();
6172
+ if (rect.left > x || rect.right < x || rect.top > y || rect.bottom < y)
6173
+ return false;
6174
+ let pos = wg.posAtCoords({ x, y }).pos;
6175
+ return pos >= from && pos <= to;
6176
6176
  }
6177
6177
 
6178
6178
  const Dialog = /*@__PURE__*/(function (Dialog) {
6179
6179
  function show(wg, config) {
6180
6180
  let resolve;
6181
- let promise = new Promise(r => resolve = r);
6182
- let panelCtor = (wg) => createDialog(wg, config, resolve);
6183
- if (wg.state.field(dialogField, false)) {
6184
- wg.dispatch({ effects: openDialogEffect.of(panelCtor) });
6181
+ let result = new Promise(r => resolve = r);
6182
+ let hint = config.positioningHint === true ? PositioningHint.get() : config.positioningHint;
6183
+ let tooltip = hint || !config.tooltip ? null : {
6184
+ ...config.tooltip,
6185
+ create: (wg) => createDialog(wg, dialog),
6186
+ }, offset = null;
6187
+ if (hint) {
6188
+ let editorRect = wg.dom.getBoundingClientRect();
6189
+ offset = {
6190
+ x: wg.state.textLTR ? hint.left - editorRect.left : editorRect.right - hint.right,
6191
+ y: hint.bottom - editorRect.top
6192
+ };
6185
6193
  }
6186
- else {
6187
- wg.dispatch({ effects: GardState.appendConfig.of(dialogField.init(() => [panelCtor])) });
6188
- }
6189
- let close = closeDialogEffect.of(panelCtor);
6190
- return { close, result: promise.then(form => {
6191
- let queue = wg.win.queueMicrotask || ((f) => wg.win.setTimeout(f, 10));
6192
- queue(() => {
6193
- if (wg.state.field(dialogField).indexOf(panelCtor) > -1)
6194
- wg.dispatch({ effects: close });
6195
- });
6196
- return form;
6197
- }) };
6194
+ let dialog = new OpenDialog(config, resolve, tooltip, offset);
6195
+ let effects = [openDialog.of(dialog)];
6196
+ if (!wg.state.field(dialogs, false))
6197
+ effects.push(GardState.appendConfig.of([dialogs, renderDialogs]));
6198
+ wg.dispatch({ effects });
6199
+ wg.scheduleDOMRead(() => {
6200
+ if (!wg.state.field(dialogs).includes(dialog))
6201
+ dialog.resolve(null);
6202
+ });
6203
+ return {
6204
+ close: () => doCloseDialog(wg, dialog),
6205
+ result
6206
+ };
6198
6207
  }
6199
6208
  Dialog.show = show;
6200
- function get(wg, className) {
6201
- let dialogs = wg.state.field(dialogField, false) || [];
6202
- for (let open of dialogs) {
6203
- let panel = Panel.get(wg, open);
6204
- if (panel && panel.dom.classList.contains(className))
6205
- return panel;
6206
- }
6207
- return null;
6209
+ function get(wg, key) {
6210
+ let active = wg.state.field(dialogs, false);
6211
+ let found = active && active.find(d => d.spec.key === key);
6212
+ if (!found)
6213
+ return null;
6214
+ return { dom: dialogDOM(wg, found), close: () => doCloseDialog(wg, found) };
6208
6215
  }
6209
6216
  Dialog.get = get;
6210
- function close(wg, className) {
6211
- let dialogs = wg.state.field(dialogField, false) || [];
6212
- for (let open of dialogs) {
6213
- let panel = Panel.get(wg, open);
6214
- if (panel && panel.dom.classList.contains(className)) {
6215
- wg.dispatch({ effects: closeDialogEffect.of(open) });
6216
- return true;
6217
- }
6218
- }
6219
- return false;
6220
- }
6221
- Dialog.close = close;
6222
6217
  ;return Dialog})({});
6223
- const dialogField = /*@__PURE__*/GardState.Field.define({
6224
- create() { return []; },
6225
- update(dialogs, tr) {
6226
- for (let e of tr.effects) {
6227
- if (e.is(openDialogEffect))
6228
- dialogs = [e.value].concat(dialogs);
6229
- else if (e.is(closeDialogEffect))
6230
- dialogs = dialogs.filter(d => d != e.value);
6231
- }
6232
- return dialogs;
6233
- },
6234
- provide: f => Panel.show.computeN(state => state.field(f))
6235
- });
6236
- const openDialogEffect = /*@__PURE__*/Transaction.Effect.define();
6237
- const closeDialogEffect = /*@__PURE__*/Transaction.Effect.define();
6238
- function createDialog(wg, config, result) {
6218
+ function dialogDOM(wg, dialog) {
6219
+ let plugin = wg.plugin(renderDialogs);
6220
+ let index = plugin ? plugin.active.indexOf(dialog) : -1;
6221
+ return index > -1 ? plugin.elements[index] : null;
6222
+ }
6223
+ function doCloseDialog(wg, dialog) {
6224
+ let active = wg.state.field(dialogs, false);
6225
+ if (active && active.includes(dialog)) {
6226
+ let dom = dialogDOM(wg, dialog);
6227
+ if (dom && dom.contains(wg.win.document.activeElement))
6228
+ wg.focus();
6229
+ wg.dispatch({ effects: closeDialog.of(dialog) });
6230
+ }
6231
+ }
6232
+ function createDialog(wg, dialog) {
6233
+ let config = dialog.spec;
6239
6234
  let content = config.content ? config.content(wg, () => done(null)) : null;
6240
6235
  if (!content) {
6241
6236
  content = document.createElement("form");
@@ -6270,10 +6265,7 @@ function createDialog(wg, config, result) {
6270
6265
  for (let i = 0; i < forms.length; i++) {
6271
6266
  let form = forms[i];
6272
6267
  form.addEventListener("keydown", (event) => {
6273
- if (event.keyCode == 27) { event.preventDefault();
6274
- done(null);
6275
- }
6276
- else if (event.keyCode == 13) { event.preventDefault();
6268
+ if (event.keyCode == 13) { event.preventDefault();
6277
6269
  done(form);
6278
6270
  }
6279
6271
  });
@@ -6288,691 +6280,1057 @@ function createDialog(wg, config, result) {
6288
6280
  close.className = "wg-dialog-close";
6289
6281
  close.type = "button";
6290
6282
  close.append("×");
6291
- let panel = document.createElement("wg-dialog");
6292
- panel.append(content, close);
6283
+ let dom = document.createElement("dialog");
6284
+ dom.open = true;
6285
+ dom.append(content, close);
6286
+ dom.addEventListener("keydown", event => {
6287
+ if (event.keyCode == 27) { event.preventDefault();
6288
+ done(null);
6289
+ }
6290
+ });
6293
6291
  if (config.class)
6294
- panel.className = config.class;
6292
+ dom.className = config.class;
6295
6293
  function done(form) {
6296
- if (panel.contains(panel.ownerDocument.activeElement))
6297
- wg.focus();
6298
- result(form);
6294
+ dialog.resolve(form);
6295
+ doCloseDialog(wg, dialog);
6296
+ }
6297
+ let focus = null;
6298
+ if (config.focus) {
6299
+ if (typeof config.focus == "string")
6300
+ focus = dom.querySelector(config.focus);
6301
+ else
6302
+ focus = content.querySelector("input") || content.querySelector("button");
6299
6303
  }
6300
- let mustFocus = config.focus;
6301
6304
  return {
6302
- dom: panel,
6303
- top: config.top !== false,
6305
+ dom,
6304
6306
  connect: () => {
6305
- if (mustFocus) {
6306
- mustFocus = false;
6307
- let focus;
6308
- if (typeof config.focus == "string")
6309
- focus = content.querySelector(config.focus);
6310
- else
6311
- focus = content.querySelector("input") || content.querySelector("button");
6312
- if (focus && "select" in focus)
6307
+ if (focus) {
6308
+ if ("select" in focus)
6313
6309
  focus.select();
6314
- else if (focus && "focus" in focus)
6310
+ else if ("focus" in focus)
6315
6311
  focus.focus();
6312
+ focus = null;
6316
6313
  }
6317
6314
  }
6318
6315
  };
6319
6316
  }
6320
-
6321
- const Outside = "-10000px";
6322
- class TooltipViewManager {
6323
- facet;
6324
- createTooltipView;
6325
- removeTooltipView;
6326
- input;
6327
- tooltips;
6328
- tooltipViews;
6329
- constructor(wg, facet, createTooltipView, removeTooltipView) {
6330
- this.facet = facet;
6331
- this.createTooltipView = createTooltipView;
6332
- this.removeTooltipView = removeTooltipView;
6333
- this.input = wg.state.facet(facet);
6334
- this.tooltips = this.input.filter(t => t);
6335
- let prev = null;
6336
- this.tooltipViews = this.tooltips.map(t => prev = createTooltipView(t, prev));
6317
+ class OpenDialog {
6318
+ spec;
6319
+ resolve;
6320
+ tooltip;
6321
+ offset;
6322
+ constructor(spec, resolve, tooltip, offset) {
6323
+ this.spec = spec;
6324
+ this.resolve = resolve;
6325
+ this.tooltip = tooltip;
6326
+ this.offset = offset;
6337
6327
  }
6338
- update(update, above) {
6339
- let input = update.state.facet(this.facet);
6340
- let tooltips = input.filter(x => x);
6341
- if (input === this.input) {
6342
- for (let t of this.tooltipViews)
6343
- if (t.update)
6344
- t.update(update);
6345
- return false;
6346
- }
6347
- let tooltipViews = [], newAbove = above ? [] : null;
6348
- for (let i = 0; i < tooltips.length; i++) {
6349
- let tip = tooltips[i], known = -1;
6350
- if (!tip)
6351
- continue;
6352
- for (let i = 0; i < this.tooltips.length; i++) {
6353
- let other = this.tooltips[i];
6354
- if (other && other.create == tip.create)
6355
- known = i;
6328
+ }
6329
+ const openDialog = /*@__PURE__*/Transaction.Effect.define();
6330
+ const closeDialog = /*@__PURE__*/Transaction.Effect.define();
6331
+ const dialogs = /*@__PURE__*/GardState.Field.define({
6332
+ create: () => [],
6333
+ update: (value, tr) => {
6334
+ for (let e of tr.effects) {
6335
+ if (e.is(openDialog))
6336
+ value = value.concat(e.value);
6337
+ else if (e.is(closeDialog))
6338
+ value = value.filter(d => d != e.value);
6339
+ }
6340
+ for (let i = 0; i < value.length; i++) {
6341
+ let { closedBy } = value[i].spec;
6342
+ if (closedBy && closedBy(tr)) {
6343
+ value = value.slice(0, i).concat(value.slice(i + 1));
6344
+ i--;
6356
6345
  }
6357
- if (known < 0) {
6358
- tooltipViews[i] = this.createTooltipView(tip, i ? tooltipViews[i - 1] : null);
6359
- if (newAbove)
6360
- newAbove[i] = !!tip.above;
6346
+ }
6347
+ return value;
6348
+ },
6349
+ provide: f => Tooltip.show.deriveN(state => {
6350
+ let tooltips = [];
6351
+ for (let d of state.field(f))
6352
+ if (d.tooltip)
6353
+ tooltips.push(d.tooltip);
6354
+ return tooltips;
6355
+ })
6356
+ });
6357
+ const renderDialogs = /*@__PURE__*/Wordgard.Plugin.fromClass(class {
6358
+ active = [];
6359
+ elements = [];
6360
+ mustPosition = false;
6361
+ constructor(wg) {
6362
+ this.positionDialogs = this.positionDialogs.bind(this);
6363
+ this.updateTo(wg, wg.state.field(dialogs));
6364
+ if (this.mustPosition)
6365
+ wg.scheduleDOMRead(this.positionDialogs);
6366
+ }
6367
+ update(update) {
6368
+ let active = update.state.field(dialogs), changed = this.active != active;
6369
+ if (changed)
6370
+ this.updateTo(update.editor, active);
6371
+ if ((update.geometryChanged || changed) && this.mustPosition)
6372
+ update.editor.scheduleDOMRead(this.positionDialogs);
6373
+ }
6374
+ updateTo(wg, active) {
6375
+ for (let i = 0; i < this.active.length; i++)
6376
+ if (!active.includes(this.active[i])) {
6377
+ let elt = this.elements[i], dialog = this.active[i];
6378
+ if (elt)
6379
+ elt.remove();
6380
+ dialog.resolve(null);
6381
+ }
6382
+ this.mustPosition = false;
6383
+ this.elements = active.map((dialog, i) => {
6384
+ if (dialog.tooltip)
6385
+ return null;
6386
+ this.mustPosition = true;
6387
+ let panel;
6388
+ let found = this.active.indexOf(dialog);
6389
+ if (found > -1) {
6390
+ panel = this.elements[found];
6361
6391
  }
6362
6392
  else {
6363
- let tooltipView = tooltipViews[i] = this.tooltipViews[known];
6364
- if (newAbove)
6365
- newAbove[i] = above[known];
6366
- if (tooltipView.update)
6367
- tooltipView.update(update);
6393
+ let { dom, connect } = createDialog(wg, dialog);
6394
+ dom.classList.add("wg-free-dialog");
6395
+ wg.dom.appendChild(dom);
6396
+ connect();
6397
+ panel = dom;
6398
+ }
6399
+ panel.style.zIndex = String(400 + i);
6400
+ return panel;
6401
+ });
6402
+ this.active = active;
6403
+ }
6404
+ positionDialogs(wg) {
6405
+ let sizes = this.elements.map(e => e && e.getBoundingClientRect());
6406
+ let editor = wg.dom.getBoundingClientRect();
6407
+ let windowWidth = wg.win.innerWidth, windowHeight = wg.win.innerHeight;
6408
+ let midX = (Math.max(0, editor.left) + Math.min(windowWidth, editor.right)) / 2;
6409
+ let midY = (Math.max(0, editor.top) + Math.min(windowHeight, editor.bottom)) / 2;
6410
+ let ltr = wg.state.textLTR;
6411
+ wg.scheduleDOMWrite(() => {
6412
+ for (let i = 0; i < sizes.length; i++) {
6413
+ let size = sizes[i];
6414
+ if (!size)
6415
+ continue;
6416
+ let { offset } = this.active[i];
6417
+ let baseX, baseY;
6418
+ if (offset) {
6419
+ baseY = editor.top + offset.y;
6420
+ baseX = ltr ? editor.left + offset.x : editor.right - offset.x - size.width;
6421
+ }
6422
+ else {
6423
+ baseX = midX - size.width / 2;
6424
+ baseY = midY - size.height / 2;
6425
+ }
6426
+ if (baseX < 0)
6427
+ baseX = 0;
6428
+ else if (baseX + size.width > windowWidth && size.width < windowWidth)
6429
+ baseX = windowWidth - size.width;
6430
+ if (baseY < 0)
6431
+ baseY = 0;
6432
+ else if (baseY + size.height > windowHeight && size.height < windowHeight)
6433
+ baseY = windowHeight - size.height;
6434
+ let elt = this.elements[i];
6435
+ elt.style.left = baseX + "px";
6436
+ elt.style.top = baseY + "px";
6368
6437
  }
6438
+ });
6439
+ }
6440
+ destroy() {
6441
+ for (let elt of this.elements)
6442
+ if (elt)
6443
+ elt.remove();
6444
+ for (let dialog of this.active)
6445
+ dialog.resolve(null);
6446
+ }
6447
+ }, plugin => plugin.eventHandler("scroll", (event, wg, val) => {
6448
+ if (val.mustPosition)
6449
+ wg.scheduleDOMRead(val.positionDialogs);
6450
+ }));
6451
+ const PositioningHint = /*@__PURE__*/(function (PositioningHint) {
6452
+ let currentHint = null;
6453
+ function runWith(hint, run) {
6454
+ let prev = currentHint;
6455
+ currentHint = hint;
6456
+ try {
6457
+ return run();
6369
6458
  }
6370
- for (let t of this.tooltipViews)
6371
- if (tooltipViews.indexOf(t) < 0) {
6372
- this.removeTooltipView(t);
6373
- if (update.editor.connected)
6374
- t.disconnect?.(update.editor);
6375
- t.remove?.(update.editor);
6376
- }
6377
- if (above) {
6378
- newAbove.forEach((val, i) => above[i] = val);
6379
- above.length = newAbove.length;
6459
+ finally {
6460
+ currentHint = prev;
6380
6461
  }
6381
- this.input = input;
6382
- this.tooltips = tooltips;
6383
- this.tooltipViews = tooltipViews;
6384
- return true;
6385
6462
  }
6463
+ PositioningHint.runWith = runWith;
6464
+ function get() {
6465
+ return currentHint ? currentHint() : null;
6466
+ }
6467
+ PositioningHint.get = get;
6468
+ ;return PositioningHint})({});
6469
+
6470
+ function collectKeyDescs(bindings) {
6471
+ let groups = [];
6472
+ let add = (group, key, desc) => {
6473
+ let found = groups.find(g => g.group == group);
6474
+ if (!found)
6475
+ groups.push(found = { group, bindings: [] });
6476
+ found.bindings.push({ key, desc });
6477
+ };
6478
+ for (let binding of bindings) {
6479
+ let group = binding.spec.group || null;
6480
+ let name = binding.spec[currentPlatform] || binding.spec.key;
6481
+ if (name) {
6482
+ if (binding.spec.description)
6483
+ add(group, normalizeKeyName(name, currentPlatform), binding.spec.description);
6484
+ if (binding.spec.shiftDescription)
6485
+ add(group, "Shift-" + normalizeKeyName(name, currentPlatform), binding.spec.shiftDescription);
6486
+ }
6487
+ }
6488
+ return groups.sort((a, b) => (a.group?.rank ?? 0) - (b.group?.rank || 0));
6489
+ }
6490
+ function resolveText(state, text) {
6491
+ return typeof text == "string" ? text : text(state);
6492
+ }
6493
+ function renderKeyDescs(state, groups) {
6494
+ let frag = document.createDocumentFragment();
6495
+ frag.append(cr("h3", { class: "wg-dialog-title" }, phrases.get(state, "key_bindings")));
6496
+ frag.append(cr("div", { class: "wg-key-list", tabindex: "-1" }, groups.map(group => [
6497
+ group.group ? cr("h4", resolveText(state, group.group.description)) : null,
6498
+ group.bindings.map(({ key, desc }) => [
6499
+ cr("div", cr("kbd", key)),
6500
+ cr("div", resolveText(state, desc))
6501
+ ])
6502
+ ])));
6503
+ return frag;
6504
+ }
6505
+ function keyBindingHelp() {
6506
+ return [keyBindingHelp.keyBindings, keyBindingHelp.button];
6507
+ }
6508
+ ;keyBindingHelp = /*@__PURE__*/(function (keyBindingHelp) {
6509
+ keyBindingHelp.showKeyBindings = wg => {
6510
+ let open = Dialog.get(wg, keyBindingHelp.showKeyBindings);
6511
+ if (open) {
6512
+ open.close();
6513
+ return true;
6514
+ }
6515
+ let descs = collectKeyDescs(wg.state.field(KeyBinding.keymap));
6516
+ if (!descs.length)
6517
+ return false;
6518
+ Dialog.show(wg, {
6519
+ key: keyBindingHelp.showKeyBindings,
6520
+ content: () => renderKeyDescs(wg.state, descs),
6521
+ class: "wg-key-help",
6522
+ focus: ".wg-key-list",
6523
+ positioningHint: true
6524
+ });
6525
+ return true;
6526
+ };
6527
+ keyBindingHelp.button = Menu.Button.define({
6528
+ run: keyBindingHelp.showKeyBindings,
6529
+ label: { icon: "M24 31c-2 0-4-2-4-4C22 14 32 5 49 5c17 0 28 10 28 24 0 10-5 17-13 22-8 5-11 8-11 15v0a4 4 0 0 1-4 4h-6a4 4 0 0 1-4-4l0-1c0-9 4-15 12-20 8-5 10-9 10-15 0-7-6-13-14-13-8 0-13 4-15 11 0 2-2 3-4 3h-6ZM46 95c5 0 8-4 8-8 0-5-4-8-8-8-5 0-8 4-8 8S42 95 46 95" },
6530
+ description: phrases.ref("show_keys"),
6531
+ parent: Menu.Group.top,
6532
+ rank: 99
6533
+ });
6534
+ keyBindingHelp.keyBindings = [
6535
+ KeyBinding.of({ key: "Mod-?", run: keyBindingHelp.showKeyBindings, description: phrases.ref("show_keys"),
6536
+ shift: keyBindingHelp.showKeyBindings }),
6537
+ KeyBinding.of({ key: "Mod-Shift-/", run: keyBindingHelp.showKeyBindings }),
6538
+ ];
6539
+ ;return keyBindingHelp})(keyBindingHelp);
6540
+
6541
+ function offset(add, offset) {
6542
+ return (v, from, to) => add(v, from + offset, to == null ? undefined : to + offset);
6386
6543
  }
6387
- const tooltipConfig = /*@__PURE__*/GardState.Facet.define({
6388
- combine: values => ({
6389
- position: browser.ios ? "absolute" : values.find(conf => conf.position)?.position || "fixed",
6390
- parent: values.find(conf => conf.parent)?.parent || null,
6391
- tooltipSpace: values.find(conf => conf.tooltipSpace)?.tooltipSpace || (wg => windowRect(wg.win)),
6392
- })
6393
- });
6394
- const knownHeight = /*@__PURE__*/(() => new WeakMap())();
6395
- const tooltipPlugin = /*@__PURE__*/Wordgard.Plugin.fromClass(class {
6396
- wg;
6397
- manager;
6398
- above = [];
6399
- inView = true;
6400
- position;
6401
- madeAbsolute = false;
6402
- parent;
6403
- classes;
6404
- intersectionObserver;
6405
- resizeObserver;
6406
- lastTransaction = 0;
6407
- measureTimeout = -1;
6408
- constructor(wg) {
6409
- this.wg = wg;
6410
- let config = wg.state.facet(tooltipConfig);
6411
- this.position = config.position;
6412
- this.parent = config.parent;
6413
- this.classes = wg.themeClasses;
6414
- this.createContainer();
6415
- this.measure = this.measure.bind(this);
6416
- this.resizeObserver = typeof ResizeObserver == "function" ? new ResizeObserver(() => this.measureSoon()) : null;
6417
- this.manager = new TooltipViewManager(wg, Tooltip.show, (t, p) => this.createTooltip(t, p), t => {
6418
- if (this.resizeObserver)
6419
- this.resizeObserver.unobserve(t.dom);
6420
- t.dom.remove();
6544
+ function init(type, create, source, doc) {
6545
+ return create(add => {
6546
+ doc.iterate((node, pos) => {
6547
+ if (node.isPlot && doc.schema.matchNode(node.type, type))
6548
+ source(node, pos + 1, add);
6421
6549
  });
6422
- this.above = this.manager.tooltips.map(t => !!t.above);
6423
- this.intersectionObserver = typeof IntersectionObserver == "function" ? new IntersectionObserver(entries => {
6424
- if (Date.now() > this.lastTransaction - 50 &&
6425
- entries.length > 0 && entries[entries.length - 1].intersectionRatio < 1)
6426
- this.measureSoon();
6427
- }, { threshold: [1] }) : null;
6428
- this.observeIntersection();
6429
- this.maybeMeasure();
6430
- }
6431
- createContainer() {
6432
- if (this.parent) {
6433
- this.container = document.createElement("wg-tooltip-root");
6434
- this.container.style.position = "relative";
6435
- this.container.className = this.wg.themeClasses;
6436
- this.parent.appendChild(this.container);
6437
- }
6438
- else {
6439
- this.container = this.wg.dom;
6550
+ });
6551
+ }
6552
+ function refreshByPred(type, source, doc, deco, pred) {
6553
+ let recreate = [];
6554
+ doc.iterate((node, pos) => {
6555
+ if (doc.schema.matchNode(node.type, type) && pred(node))
6556
+ recreate.push({
6557
+ from: pos, to: pos + node.length,
6558
+ add: add => source(node, pos + 1, add)
6559
+ });
6560
+ });
6561
+ return recreate.length ? deco.modify({ replace: recreate }) : deco;
6562
+ }
6563
+ function update(config, create, source, deco, tr) {
6564
+ let doc = tr.newDoc;
6565
+ let refresh = config.refresh && config.refresh(tr);
6566
+ if (refresh === true)
6567
+ return init(config.type, create, source, doc);
6568
+ if (refresh)
6569
+ deco = refreshByPred(config.type, source, tr.startState.doc, deco, refresh);
6570
+ if (!tr.docChanged && !refresh)
6571
+ return deco;
6572
+ let recreate = [];
6573
+ let clear = [];
6574
+ tr.changes.iterChangedRanges((fromA, toA, fromB, toB) => {
6575
+ let covered = false;
6576
+ doc.iterate(fromB, toB, (node, pos) => {
6577
+ if (doc.schema.matchNode(node.type, config.type) && !recreate.some(r => r.from == pos)) {
6578
+ recreate.push({
6579
+ from: pos, to: pos + node.length,
6580
+ add: add => source(node, pos + 1, add)
6581
+ });
6582
+ if (pos < toB && pos + node.length > toB)
6583
+ covered = true;
6584
+ return false;
6585
+ }
6586
+ });
6587
+ if (!covered) {
6588
+ let end = tr.startState.doc.resolve(toA);
6589
+ let open = end.matchingParent(p => doc.schema.matchNode(p.type, config.type));
6590
+ if (open && open.before >= fromA)
6591
+ clear.push({ from: open.before, to: open.after });
6440
6592
  }
6593
+ });
6594
+ if (clear.length)
6595
+ deco = deco.modify({ replace: clear });
6596
+ deco = deco.map(tr.changes, recreate);
6597
+ return deco;
6598
+ }
6599
+ function decoratePlots(config) {
6600
+ let result = [];
6601
+ let { type, points, ranges } = config;
6602
+ if (points) {
6603
+ let source = (plot, start, add) => points(plot, offset(add, start));
6604
+ let field = GardState.Field.define({
6605
+ create(state) { return init(type, PointSet.create, source, state.doc); },
6606
+ update(value, tr) { return update(config, PointSet.create, source, value, tr); }
6607
+ });
6608
+ result.push(field, Decoration.Point.source.of(s => s.field(field)));
6441
6609
  }
6442
- observeIntersection() {
6443
- if (this.intersectionObserver && this.wg.connected) {
6444
- this.intersectionObserver.disconnect();
6445
- for (let tooltip of this.manager.tooltipViews)
6446
- this.intersectionObserver.observe(tooltip.dom);
6610
+ if (ranges) {
6611
+ let source = (plot, start, add) => ranges(plot, offset(add, start));
6612
+ let field = GardState.Field.define({
6613
+ create(state) { return init(type, RangeSet.create, source, state.doc); },
6614
+ update(value, tr) { return update(config, RangeSet.create, source, value, tr); }
6615
+ });
6616
+ result.push(field, Decoration.Range.source.of(s => s.field(field)));
6617
+ }
6618
+ return result;
6619
+ }
6620
+
6621
+ const panelConfig = /*@__PURE__*/GardState.Facet.define({
6622
+ combine(configs) {
6623
+ let topContainer, bottomContainer;
6624
+ for (let c of configs) {
6625
+ topContainer ||= c.topContainer;
6626
+ bottomContainer ||= c.bottomContainer;
6447
6627
  }
6628
+ return { topContainer, bottomContainer };
6448
6629
  }
6449
- measureSoon() {
6450
- if (this.measureTimeout < 0)
6451
- this.measureTimeout = setTimeout(() => {
6452
- this.measureTimeout = -1;
6453
- this.maybeMeasure();
6454
- }, 50);
6630
+ });
6631
+ const panelPlugin = /*@__PURE__*/Wordgard.Plugin.fromClass(class {
6632
+ input;
6633
+ specs;
6634
+ panels;
6635
+ top;
6636
+ bottom;
6637
+ constructor(wg) {
6638
+ this.input = wg.state.facet(Panel.show);
6639
+ this.specs = this.input.filter(s => s);
6640
+ this.panels = this.specs.map(spec => spec(wg));
6641
+ for (let p of this.panels)
6642
+ p.dom.classList.add("wg-panel");
6643
+ let conf = wg.state.facet(panelConfig);
6644
+ this.top = new PanelGroup(wg, true, conf.topContainer);
6645
+ this.bottom = new PanelGroup(wg, false, conf.bottomContainer);
6646
+ this.top.sync(this.panels.filter(p => p.top), wg);
6647
+ this.bottom.sync(this.panels.filter(p => !p.top), wg);
6455
6648
  }
6456
6649
  update(update) {
6457
- if (update.transactions.length)
6458
- this.lastTransaction = Date.now();
6459
- let updated = this.manager.update(update, this.above);
6460
- if (updated)
6461
- this.observeIntersection();
6462
- let shouldMeasure = updated || update.geometryChanged;
6463
- let newConfig = update.state.facet(tooltipConfig);
6464
- if (newConfig.position != this.position && !this.madeAbsolute) {
6465
- this.position = newConfig.position;
6466
- for (let t of this.manager.tooltipViews)
6467
- t.dom.style.position = this.position;
6468
- shouldMeasure = true;
6650
+ let conf = update.state.facet(panelConfig);
6651
+ if (this.top.container != conf.topContainer) {
6652
+ this.top.sync([], update.editor);
6653
+ this.top = new PanelGroup(update.editor, true, conf.topContainer);
6469
6654
  }
6470
- if (newConfig.parent != this.parent) {
6471
- if (this.parent)
6472
- this.container.remove();
6473
- this.parent = newConfig.parent;
6474
- this.createContainer();
6475
- for (let t of this.manager.tooltipViews)
6476
- this.container.appendChild(t.dom);
6477
- shouldMeasure = true;
6655
+ if (this.bottom.container != conf.bottomContainer) {
6656
+ this.bottom.sync([], update.editor);
6657
+ this.bottom = new PanelGroup(update.editor, false, conf.bottomContainer);
6478
6658
  }
6479
- else if (this.parent && this.wg.themeClasses != this.classes) {
6480
- this.classes = this.container.className = this.wg.themeClasses;
6659
+ this.top.syncClasses();
6660
+ this.bottom.syncClasses();
6661
+ let input = update.state.facet(Panel.show);
6662
+ if (input != this.input) {
6663
+ let specs = input.filter(x => x);
6664
+ let panels = [], top = [], bottom = [], mount = [];
6665
+ for (let spec of specs) {
6666
+ let known = this.specs.indexOf(spec), panel;
6667
+ if (known < 0) {
6668
+ panel = spec(update.editor);
6669
+ mount.push(panel);
6670
+ }
6671
+ else {
6672
+ panel = this.panels[known];
6673
+ if (panel.update)
6674
+ panel.update(update);
6675
+ }
6676
+ panels.push(panel);
6677
+ (panel.top ? top : bottom).push(panel);
6678
+ }
6679
+ this.specs = specs;
6680
+ this.panels = panels;
6681
+ this.top.sync(top, update.editor);
6682
+ this.bottom.sync(bottom, update.editor);
6683
+ for (let p of mount) {
6684
+ p.dom.classList.add("wg-panel");
6685
+ if (p.connect && update.editor.connected)
6686
+ p.connect(update.editor);
6687
+ }
6481
6688
  }
6482
- if (shouldMeasure)
6483
- this.maybeMeasure();
6484
- }
6485
- createTooltip(tooltip, prev) {
6486
- let tooltipView = tooltip.create(this.wg);
6487
- let before = prev ? prev.dom : null;
6488
- tooltipView.dom.classList.add("wg-tooltip");
6489
- if (tooltip.arrow && !tooltipView.dom.querySelector(".wg-tooltip > wg-tooltip-arrow")) {
6490
- let arrow = document.createElement("wg-tooltip-arrow");
6491
- tooltipView.dom.appendChild(arrow);
6689
+ else {
6690
+ for (let p of this.panels)
6691
+ if (p.update)
6692
+ p.update(update);
6492
6693
  }
6493
- tooltipView.dom.style.position = this.position;
6494
- tooltipView.dom.style.top = Outside;
6495
- tooltipView.dom.style.left = "0px";
6496
- this.container.insertBefore(tooltipView.dom, before);
6497
- if (this.wg.connected)
6498
- tooltipView.connect?.(this.wg);
6499
- if (this.resizeObserver && this.wg.connected)
6500
- this.resizeObserver.observe(tooltipView.dom);
6501
- return tooltipView;
6502
6694
  }
6503
6695
  connect(wg) {
6504
- wg.win.addEventListener("resize", this.measureSoon = this.measureSoon.bind(this));
6505
- for (let t of this.manager.tooltipViews) {
6506
- t.connect?.(wg);
6507
- if (this.resizeObserver)
6508
- this.resizeObserver.observe(t.dom);
6509
- }
6510
- this.observeIntersection();
6696
+ for (let p of this.panels)
6697
+ p.connect?.(wg);
6511
6698
  }
6512
6699
  disconnect(wg) {
6513
- this.wg.win.removeEventListener("resize", this.measureSoon);
6514
- for (let t of this.manager.tooltipViews) {
6515
- t.disconnect?.(wg);
6516
- if (this.resizeObserver)
6517
- this.resizeObserver.unobserve(t.dom);
6518
- }
6519
- if (this.intersectionObserver)
6520
- this.intersectionObserver.disconnect();
6700
+ for (let p of this.panels)
6701
+ p.disconnect?.(wg);
6521
6702
  }
6522
- remove() {
6523
- for (let tooltipView of this.manager.tooltipViews) {
6524
- tooltipView.dom.remove();
6525
- if (this.wg.connected)
6526
- tooltipView.disconnect?.(this.wg);
6527
- tooltipView.remove?.(this.wg);
6528
- }
6529
- if (this.parent)
6530
- this.container.remove();
6531
- clearTimeout(this.measureTimeout);
6703
+ remove(wg) {
6704
+ this.top.sync([], wg);
6705
+ this.bottom.sync([], wg);
6532
6706
  }
6533
- measure() {
6534
- let measure = this.readMeasure();
6535
- this.wg.scheduleDOMWrite(() => this.writeMeasure(measure));
6707
+ }, plugin => Wordgard.coveredMargins.of(wg => {
6708
+ let value = wg.plugin(plugin);
6709
+ return value && { top: value.top.scrollMargin(), bottom: value.bottom.scrollMargin() };
6710
+ }));
6711
+ const Panel = /*@__PURE__*/(function (Panel) {
6712
+ Panel.show = GardState.Facet.define({
6713
+ enables: panelPlugin
6714
+ });
6715
+ function get(wg, constructor) {
6716
+ let plugin = wg.plugin(panelPlugin);
6717
+ let index = plugin ? plugin.specs.indexOf(constructor) : -1;
6718
+ return index > -1 ? plugin.panels[index] : null;
6536
6719
  }
6537
- readMeasure() {
6538
- let scaleX = 1, scaleY = 1, makeAbsolute = false;
6539
- if (this.position == "fixed" && this.manager.tooltipViews.length) {
6540
- let { dom } = this.manager.tooltipViews[0];
6541
- if (browser.safari) {
6542
- let rect = dom.getBoundingClientRect();
6543
- makeAbsolute = Math.abs(rect.top + 10000) > 1 || Math.abs(rect.left) > 1;
6544
- }
6545
- else {
6546
- makeAbsolute = !!dom.offsetParent && dom.offsetParent != this.container.ownerDocument.body;
6720
+ Panel.get = get;
6721
+ function configure(config) {
6722
+ return config ? [panelConfig.of(config)] : [];
6723
+ }
6724
+ Panel.configure = configure;
6725
+ ;return Panel})({});
6726
+ class PanelGroup {
6727
+ wg;
6728
+ top;
6729
+ container;
6730
+ dom = undefined;
6731
+ classes = "";
6732
+ panels = [];
6733
+ constructor(wg, top, container) {
6734
+ this.wg = wg;
6735
+ this.top = top;
6736
+ this.container = container;
6737
+ this.syncClasses();
6738
+ }
6739
+ sync(panels, wg) {
6740
+ for (let p of this.panels)
6741
+ if (!panels.includes(p)) {
6742
+ if (wg.connected)
6743
+ p.disconnect?.(wg);
6744
+ p.remove?.(wg);
6547
6745
  }
6548
- }
6549
- if (makeAbsolute || this.position == "absolute") {
6550
- let measure = this.parent || this.container, rect = measure.getBoundingClientRect();
6551
- if (rect.width && rect.height) {
6552
- scaleX = rect.width / measure.offsetWidth;
6553
- scaleY = rect.height / measure.offsetHeight;
6746
+ this.panels = panels;
6747
+ this.syncDOM();
6748
+ }
6749
+ syncDOM() {
6750
+ if (this.panels.length == 0) {
6751
+ if (this.dom) {
6752
+ this.dom.remove();
6753
+ this.dom = undefined;
6554
6754
  }
6755
+ return;
6555
6756
  }
6556
- let visible = this.wg.scrollDOM.getBoundingClientRect(), margins = this.wg.getScrollMargins();
6557
- let visLeft = visible.left + margins.left, visTop = visible.top + margins.top;
6558
- return {
6559
- visible: new DOMRect(visLeft, visTop, visible.right - margins.right - visLeft, visible.bottom - margins.bottom - visTop),
6560
- parent: this.parent ? this.container.getBoundingClientRect() : this.wg.dom.getBoundingClientRect(),
6561
- pos: this.manager.tooltips.map((t, i) => {
6562
- let tv = this.manager.tooltipViews[i];
6563
- return tv.getCoords ? tv.getCoords(t.pos) : this.wg.coordsAtPos(t.pos);
6564
- }),
6565
- size: this.manager.tooltipViews.map(({ dom }) => dom.getBoundingClientRect()),
6566
- space: this.wg.state.facet(tooltipConfig).tooltipSpace(this.wg),
6567
- scaleX, scaleY, makeAbsolute
6568
- };
6569
- }
6570
- writeMeasure(measured) {
6571
- if (measured.makeAbsolute) {
6572
- this.madeAbsolute = true;
6573
- this.position = "absolute";
6574
- for (let t of this.manager.tooltipViews)
6575
- t.dom.style.position = "absolute";
6757
+ if (!this.dom) {
6758
+ this.dom = document.createElement("wg-panels");
6759
+ this.dom.className = this.top ? "wg-panels-top" : "wg-panels-bottom";
6760
+ let parent = this.container || this.wg.dom;
6761
+ parent.insertBefore(this.dom, this.top ? parent.firstChild : null);
6576
6762
  }
6577
- let { visible, space, scaleX, scaleY } = measured;
6578
- let others = [];
6579
- for (let i = 0; i < this.manager.tooltips.length; i++) {
6580
- let tooltip = this.manager.tooltips[i], tView = this.manager.tooltipViews[i], { dom } = tView;
6581
- let pos = measured.pos[i], size = measured.size[i];
6582
- if (!pos || tooltip.clip !== false && (pos.bottom <= Math.max(visible.top, space.top) ||
6583
- pos.top >= Math.min(visible.bottom, space.bottom) ||
6584
- pos.right < Math.max(visible.left, space.left) - .1 ||
6585
- pos.left > Math.min(visible.right, space.right) + .1)) {
6586
- dom.style.top = Outside;
6587
- continue;
6588
- }
6589
- let arrow = tooltip.arrow ? tView.dom.querySelector("wg-tooltip-arrow") : null;
6590
- let arrowHeight = arrow ? 7 : 0;
6591
- let width = size.right - size.left, height = knownHeight.get(tView) ?? size.bottom - size.top;
6592
- let offset = tView.offset || noOffset, ltr = this.wg.state.textLTR;
6593
- let left = size.width > space.right - space.left
6594
- ? (ltr ? space.left : space.right - size.width)
6595
- : ltr ? Math.max(space.left, Math.min(pos.left - (arrow ? 14 : 0) + offset.x, space.right - width))
6596
- : Math.min(Math.max(space.left, pos.left - width + (arrow ? 14 : 0) - offset.x), space.right - width);
6597
- let above = this.above[i];
6598
- if (!tooltip.strictSide && (above
6599
- ? pos.top - height - arrowHeight - offset.y < space.top
6600
- : pos.bottom + height + arrowHeight + offset.y > space.bottom) &&
6601
- above == (space.bottom - pos.bottom > pos.top - space.top))
6602
- above = this.above[i] = !above;
6603
- let spaceVert = (above ? pos.top - space.top : space.bottom - pos.bottom) - arrowHeight;
6604
- if (spaceVert < height && tView.resize !== false) {
6605
- if (spaceVert < 15) {
6606
- dom.style.top = Outside;
6607
- continue;
6608
- }
6609
- knownHeight.set(tView, height);
6610
- dom.style.height = (height = spaceVert) / scaleY + "px";
6611
- }
6612
- else if (dom.style.height) {
6613
- dom.style.height = "";
6614
- }
6615
- let top = above ? pos.top - height - arrowHeight - offset.y : pos.bottom + arrowHeight + offset.y;
6616
- let right = left + width;
6617
- if (tView.overlap !== true)
6618
- for (let r of others)
6619
- if (r.left < right && r.right > left && r.top < top + height && r.bottom > top)
6620
- top = above ? r.top - height - 2 - arrowHeight : r.bottom + arrowHeight + 2;
6621
- if (this.position == "absolute") {
6622
- dom.style.top = (top - measured.parent.top) / scaleY + "px";
6623
- setLeftStyle(dom, (left - measured.parent.left) / scaleX);
6763
+ let curDOM = this.dom.firstChild;
6764
+ for (let panel of this.panels) {
6765
+ if (panel.dom.parentNode == this.dom) {
6766
+ while (curDOM != panel.dom)
6767
+ curDOM = rmDOM(curDOM);
6768
+ curDOM = curDOM.nextSibling;
6624
6769
  }
6625
6770
  else {
6626
- dom.style.top = top / scaleY + "px";
6627
- setLeftStyle(dom, left / scaleX);
6628
- }
6629
- if (arrow) {
6630
- let arrowLeft = pos.left + (ltr ? offset.x : -offset.x) - (left + 14 - 7);
6631
- arrow.style.left = arrowLeft / scaleX + "px";
6771
+ this.dom.insertBefore(panel.dom, curDOM);
6632
6772
  }
6633
- if (tView.overlap !== true)
6634
- others.push({ left, top, right, bottom: top + height });
6635
- dom.classList.toggle("wg-tooltip-above", above);
6636
- dom.classList.toggle("wg-tooltip-below", !above);
6637
- if (tView.positioned)
6638
- tView.positioned(measured.space);
6639
6773
  }
6774
+ while (curDOM)
6775
+ curDOM = rmDOM(curDOM);
6640
6776
  }
6641
- maybeMeasure() {
6642
- if (this.manager.tooltips.length)
6643
- this.wg.scheduleDOMRead(this.measure);
6777
+ scrollMargin() {
6778
+ return !this.dom || this.container ? 0
6779
+ : Math.max(0, this.top ?
6780
+ this.dom.getBoundingClientRect().bottom - Math.max(0, this.wg.scrollDOM.getBoundingClientRect().top) :
6781
+ Math.min(innerHeight, this.wg.scrollDOM.getBoundingClientRect().bottom) - this.dom.getBoundingClientRect().top);
6782
+ }
6783
+ syncClasses() {
6784
+ if (!this.container || this.classes == this.wg.themeClasses)
6785
+ return;
6786
+ for (let cls of this.classes.split(" "))
6787
+ if (cls)
6788
+ this.container.classList.remove(cls);
6789
+ for (let cls of (this.classes = this.wg.themeClasses).split(" "))
6790
+ if (cls)
6791
+ this.container.classList.add(cls);
6644
6792
  }
6645
- }, plugin => plugin.eventObserver("scroll", (event, wg, value) => value.maybeMeasure()));
6646
- function setLeftStyle(elt, value) {
6647
- let current = parseInt(elt.style.left, 10);
6648
- if (isNaN(current) || Math.abs(value - current) > 1)
6649
- elt.style.left = value + "px";
6650
6793
  }
6651
- const styles = /*@__PURE__*/Wordgard.styles({
6652
- ".wg-tooltip": {
6653
- zIndex: 500,
6654
- boxSizing: "border-box",
6655
- backgroundColor: "var(--wg-panel-color)",
6656
- boxShadow: "0 0 8px 0 rgba(128, 128, 128, 0.2)",
6657
- font: "var(--wg-dialog-font)",
6658
- },
6659
- ".wg-tooltip-section:not(:first-child)": {
6660
- borderTop: "1px solid var(--wg-border-color)",
6661
- },
6662
- "wg-tooltip-arrow": {
6663
- display: "block",
6664
- height: `${7}px`,
6665
- width: `${7 * 2}px`,
6666
- position: "absolute",
6667
- zIndex: -1,
6668
- overflow: "hidden",
6669
- "&:before, &:after": {
6670
- content: "''",
6671
- position: "absolute",
6672
- width: 0,
6673
- height: 0,
6674
- borderLeft: `${7}px solid transparent`,
6675
- borderRight: `${7}px solid transparent`,
6676
- },
6677
- ".wg-tooltip-above &": {
6678
- bottom: `-${7}px`,
6679
- "&:before": {
6680
- borderTop: `${7}px solid var(--wg-border-color)`,
6681
- },
6682
- "&:after": {
6683
- borderTop: `${7}px solid var(--wg-panel-color)`,
6684
- bottom: "1px"
6794
+
6795
+ let nextID = 0;
6796
+ function id(prefix) {
6797
+ return prefix + "-" + (nextID++ % 0xffffff).toString(16);
6798
+ }
6799
+ const SVG = "http://www.w3.org/2000/svg";
6800
+ function labelButton(wg, button, label) {
6801
+ button.textContent = "";
6802
+ if (!label) ;
6803
+ else if (typeof label == "function" || typeof label == "string") {
6804
+ let span = button.appendChild(document.createElement("span"));
6805
+ span.className = "wg-button-label";
6806
+ span.textContent = typeof label == "string" ? label : label(wg.state);
6807
+ }
6808
+ else if ("icon" in label) {
6809
+ let svg = button.appendChild(document.createElementNS(SVG, "svg"));
6810
+ svg.classList.add("wg-icon");
6811
+ svg.setAttribute("viewBox", "0 0 100 100");
6812
+ let path = svg.appendChild(document.createElementNS(SVG, "path"));
6813
+ path.setAttribute("d", label.icon);
6814
+ if (label.directional && !wg.state.textLTR)
6815
+ svg.setAttribute("transform", "scale(-1, 1)");
6816
+ }
6817
+ }
6818
+ class BarButton {
6819
+ item;
6820
+ dom;
6821
+ flags = 0;
6822
+ index = 0;
6823
+ constructor(item, wg) {
6824
+ this.item = item;
6825
+ this.dom = document.createElement("button");
6826
+ this.dom.className = "wg-menu-button";
6827
+ this.dom.type = "button";
6828
+ this.dom.tabIndex = -1;
6829
+ labelButton(wg, this.dom, item.label);
6830
+ this.setDesc(wg);
6831
+ }
6832
+ setDesc(wg) {
6833
+ let { item: { label, description } } = this;
6834
+ let someDesc = description || (typeof label == "string" || typeof label == "function" ? label : null);
6835
+ let key = someDesc ? KeyBinding.lookupByDescription(wg.state, someDesc) : null;
6836
+ let desc = typeof someDesc == "function" ? someDesc(wg.state) : someDesc;
6837
+ if (description)
6838
+ this.dom.setAttribute("aria-label", desc);
6839
+ else
6840
+ this.dom.removeAttribute("aria-label");
6841
+ if (key)
6842
+ this.dom.setAttribute("aria-keyshortcuts", key);
6843
+ else
6844
+ this.dom.removeAttribute("aria-keyshortcuts");
6845
+ this.dom.title = desc ? desc + (key ? ` (${key})` : "") : key || "";
6846
+ }
6847
+ get focusDOM() { return this.dom; }
6848
+ update(flags, wg, update) {
6849
+ if (flags != this.flags) {
6850
+ if ((flags & 32) != (this.flags & 32))
6851
+ this.dom.style.display = flags & 32 ? "none" : "";
6852
+ if ((flags & 8) != (this.flags & 8)) {
6853
+ if (flags & 8)
6854
+ this.dom.setAttribute("aria-disabled", "true");
6855
+ else
6856
+ this.dom.removeAttribute("aria-disabled");
6685
6857
  }
6686
- },
6687
- ".wg-tooltip-below &": {
6688
- top: `-${7}px`,
6689
- "&:before": {
6690
- borderBottom: `${7}px solid var(--wg-border-color)`,
6691
- },
6692
- "&:after": {
6693
- borderBottom: `${7}px solid var(--wg-panel-color)`,
6694
- top: "1px"
6858
+ if ((flags & 4) != (this.flags & 4)) {
6859
+ if (flags & 4)
6860
+ this.dom.setAttribute("aria-selected", "true");
6861
+ else
6862
+ this.dom.removeAttribute("aria-selected");
6863
+ this.dom.tabIndex = flags & 4 ? 0 : -1;
6695
6864
  }
6696
- },
6697
- },
6698
- });
6699
- const closeHoverTooltipEffect = /*@__PURE__*/Transaction.Effect.define();
6700
- const Tooltip = /*@__PURE__*/(function (Tooltip) {
6701
- function configure(config = {}) {
6702
- return tooltipConfig.of(config);
6865
+ if ((flags & 16) != (this.flags & 16)) {
6866
+ if (flags & 16)
6867
+ this.dom.setAttribute("aria-pressed", "true");
6868
+ else
6869
+ this.dom.removeAttribute("aria-pressed");
6870
+ }
6871
+ this.flags = flags;
6872
+ }
6703
6873
  }
6704
- Tooltip.configure = configure;
6705
- Tooltip.show = GardState.Facet.define({
6706
- enables: [tooltipPlugin, styles]
6707
- });
6708
- function get(wg, tooltip) {
6709
- let plugin = wg.plugin(tooltipPlugin);
6710
- if (!plugin)
6711
- return null;
6712
- let found = plugin.manager.tooltips.findIndex(typeof tooltip == "function" ? p => p.create == tooltip : p => p == tooltip);
6713
- return found < 0 ? null : plugin.manager.tooltipViews[found];
6874
+ get children() { return null; }
6875
+ run(wg) {
6876
+ PositioningHint.runWith(() => this.dom.getBoundingClientRect(), () => {
6877
+ Command.dispatch(wg, this.item.run);
6878
+ });
6714
6879
  }
6715
- Tooltip.get = get;
6716
- function reposition(wg) {
6717
- let plugin = wg.plugin(tooltipPlugin);
6718
- if (plugin)
6719
- plugin.maybeMeasure();
6880
+ }
6881
+ class BarControl {
6882
+ item;
6883
+ dom;
6884
+ focusDOM;
6885
+ flags = 0;
6886
+ index = 0;
6887
+ constructor(item, wg, done) {
6888
+ this.item = item;
6889
+ let { dom, focus } = item.render(wg, done);
6890
+ this.dom = dom;
6891
+ this.focusDOM = focus || dom;
6892
+ this.focusDOM.tabIndex = -1;
6720
6893
  }
6721
- Tooltip.reposition = reposition;
6722
- function hover(source, options = {}) {
6723
- let setHover = Transaction.Effect.define();
6724
- let hoverState = GardState.Field.define({
6725
- create() { return []; },
6726
- update(value, tr) {
6727
- if (value.length) {
6728
- if (options.hideOnChange && (tr.docChanged || tr.selection))
6729
- value = [];
6730
- else if (options.hideOn)
6731
- value = value.filter(v => !options.hideOn(tr, v));
6732
- if (tr.docChanged) {
6733
- let mapped = [];
6734
- for (let tooltip of value) {
6735
- let newPos = tr.changes.mapPos(tooltip.pos, -1, "around");
6736
- if (newPos != null) {
6737
- let copy = Object.assign(Object.create(null), tooltip);
6738
- copy.pos = newPos;
6739
- if (copy.end != null)
6740
- copy.end = tr.changes.mapPos(copy.end);
6741
- mapped.push(copy);
6742
- }
6743
- }
6744
- value = mapped;
6745
- }
6746
- }
6747
- for (let effect of tr.effects) {
6748
- if (effect.is(setHover))
6749
- value = effect.value;
6750
- if (effect.is(closeHoverTooltipEffect))
6751
- value = [];
6752
- }
6753
- return value;
6754
- },
6755
- provide: f => showHoverTooltip.from(f)
6756
- });
6757
- return {
6758
- active: hoverState,
6759
- extension: [
6760
- hoverState,
6761
- Wordgard.Plugin.define(wg => new HoverPlugin(wg, source, hoverState, setHover, options.hoverTime || 300)),
6762
- showHoverTooltipHost
6763
- ]
6764
- };
6894
+ update(flags, wg, update) {
6895
+ if (flags != this.flags) {
6896
+ if ((flags & 32) != (this.flags & 32))
6897
+ this.dom.style.display = flags & 32 ? "none" : "";
6898
+ if ((flags & 8) != (this.flags & 8) && this.item.setEnabled)
6899
+ this.item.setEnabled(this.dom, !(flags & 8));
6900
+ if ((flags & 4) != (this.flags & 4)) {
6901
+ if (flags & 4)
6902
+ this.focusDOM.setAttribute("aria-selected", "true");
6903
+ else
6904
+ this.focusDOM.removeAttribute("aria-selected");
6905
+ this.focusDOM.tabIndex = flags & 4 ? 0 : -1;
6906
+ }
6907
+ this.flags = flags;
6908
+ }
6765
6909
  }
6766
- Tooltip.hover = hover;
6767
- (function (hover) {
6768
- function has(state) {
6769
- return state.facet(showHoverTooltip).some(x => x);
6910
+ get children() { return null; }
6911
+ get run() { return null; }
6912
+ }
6913
+ class BarSubmenu {
6914
+ item;
6915
+ dom;
6916
+ button;
6917
+ list;
6918
+ flags = 0;
6919
+ activeChild = -3;
6920
+ index = 0;
6921
+ children;
6922
+ constructor(item, children, wg) {
6923
+ this.item = item;
6924
+ this.dom = document.createElement("wg-submenu");
6925
+ this.button = this.dom.appendChild(document.createElement("button"));
6926
+ this.button.type = "button";
6927
+ this.button.tabIndex = -1;
6928
+ this.button.className = "wg-menu-button";
6929
+ this.button.setAttribute("aria-haspopup", "true");
6930
+ this.button.setAttribute("aria-expanded", "false");
6931
+ if (item.description) {
6932
+ let desc = typeof item.description == "function" ? item.description(wg.state) : item.description;
6933
+ this.dom.title = desc;
6934
+ this.dom.setAttribute("aria-label", desc);
6935
+ }
6936
+ if (item.label) {
6937
+ labelButton(wg, this.button, item.label);
6938
+ this.activeChild = -2;
6939
+ }
6940
+ if (item.width != null)
6941
+ this.dom.style.setProperty("--wg-submenu-width", item.width + "ch");
6942
+ if (item.arrow)
6943
+ this.button.classList.add("wg-submenu-arrow");
6944
+ this.list = this.dom.appendChild(document.createElement("wg-menu-list"));
6945
+ this.list.style.display = "none";
6946
+ this.list.role = "menu";
6947
+ this.list.id = id("wg-popup");
6948
+ this.list.setAttribute("aria-label", this.button.title);
6949
+ this.button.setAttribute("aria-controls", this.list.id);
6950
+ this.children = children.filter((ch) => !(ch instanceof BarSpacer));
6951
+ for (let child of children) {
6952
+ this.list.appendChild(child.dom);
6953
+ if (!(child instanceof BarSpacer))
6954
+ child.focusDOM.role = "menuitem";
6955
+ }
6956
+ }
6957
+ get focusDOM() { return this.button; }
6958
+ update(flags, wg) {
6959
+ if (flags != this.flags) {
6960
+ if ((flags & 32) != (this.flags & 32))
6961
+ this.dom.style.display = flags & 32 ? "none" : "";
6962
+ if ((flags & 8) != (this.flags & 8)) {
6963
+ if (flags & 8)
6964
+ this.button.setAttribute("aria-disabled", "true");
6965
+ else
6966
+ this.button.removeAttribute("aria-disabled");
6967
+ }
6968
+ if ((flags & 4) != (this.flags & 4)) {
6969
+ if (flags & 4)
6970
+ this.button.setAttribute("aria-selected", "true");
6971
+ else
6972
+ this.button.removeAttribute("aria-selected");
6973
+ this.button.tabIndex = flags & 4 ? 0 : -1;
6974
+ }
6975
+ if ((flags & 2) != (this.flags & 2))
6976
+ this.list.style.display = flags & 2 ? "" : "none";
6977
+ this.flags = flags;
6770
6978
  }
6771
- hover.has = has;
6772
- hover.closeAll = closeHoverTooltipEffect.of(null);
6773
- })(hover = Tooltip.hover || (Tooltip.hover = {}));
6774
- ;return Tooltip})({});
6775
- const noOffset = { x: 0, y: 0 };
6776
- const showHoverTooltip = /*@__PURE__*/GardState.Facet.define({
6777
- combine: inputs => inputs.reduce((a, i) => a.concat(i), [])
6778
- });
6779
- class HoverTooltipHost {
6780
- wg;
6781
- manager;
6979
+ if (this.activeChild != -2) {
6980
+ let activeChild = this.children.findIndex(ch => ch.flags & 16);
6981
+ if (this.activeChild != activeChild) {
6982
+ this.activeChild = activeChild;
6983
+ let label = activeChild < 0 ? this.item.defaultLabel : this.children[activeChild].item.label;
6984
+ labelButton(wg, this.button, label);
6985
+ }
6986
+ }
6987
+ }
6988
+ get run() { return null; }
6989
+ }
6990
+ class BarSpacer {
6782
6991
  dom;
6783
- connected = false;
6784
- static create(wg) {
6785
- return new HoverTooltipHost(wg);
6992
+ constructor() {
6993
+ this.dom = document.createElement("wg-menu-spacer");
6786
6994
  }
6995
+ }
6996
+ function instantiate(item, bar, flat) {
6997
+ let elt;
6998
+ if (item instanceof Menu.Submenu.Resolved)
6999
+ elt = new BarSubmenu(item.item, item.content.map(i => instantiate(i, bar, flat)), bar.wg);
7000
+ else if (item === "|")
7001
+ return new BarSpacer();
7002
+ else if (item instanceof Menu.Button)
7003
+ elt = new BarButton(item, bar.wg);
7004
+ else
7005
+ elt = new BarControl(item, bar.wg, () => bar.up());
7006
+ elt.index = flat.length;
7007
+ flat.push(elt);
7008
+ return elt;
7009
+ }
7010
+ class MenuBar {
7011
+ wg;
7012
+ dom;
7013
+ focusTimeout = -1;
7014
+ items;
6787
7015
  constructor(wg) {
6788
7016
  this.wg = wg;
6789
- this.dom = document.createElement("wg-tooltip-hover");
6790
- this.manager = new TooltipViewManager(wg, showHoverTooltip, (t, p) => this.createHostedView(t, p), t => t.dom.remove());
6791
- }
6792
- createHostedView(tooltip, prev) {
6793
- let hostedView = tooltip.create(this.wg);
6794
- hostedView.dom.classList.add("wg-tooltip-section");
6795
- this.dom.insertBefore(hostedView.dom, prev ? prev.dom.nextSibling : this.dom.firstChild);
6796
- if (this.connected && hostedView.connect)
6797
- hostedView.connect(this.wg);
6798
- return hostedView;
6799
- }
6800
- connect(wg) {
6801
- for (let t of this.manager.tooltipViews)
6802
- t.connect?.(wg);
6803
- this.connected = true;
6804
- }
6805
- disconnect(wg) {
6806
- for (let t of this.manager.tooltipViews)
6807
- t.disconnect?.(wg);
6808
- this.connected = false;
7017
+ this.dom = document.createElement("wg-menubar");
7018
+ this.dom.role = "toolbar";
7019
+ this.dom.addEventListener("keydown", this.key.bind(this));
7020
+ this.dom.addEventListener("mousedown", this.click.bind(this));
7021
+ this.dom.addEventListener("focusout", this.focusout.bind(this));
7022
+ this.items = wg.state.facet(Menu.Item.source);
7023
+ this.init();
7024
+ this.globalClick = this.globalClick.bind(this);
6809
7025
  }
6810
- positioned(space) {
6811
- for (let hostedView of this.manager.tooltipViews) {
6812
- if (hostedView.positioned)
6813
- hostedView.positioned(space);
6814
- }
7026
+ static create(wg) { return new MenuBar(wg); }
7027
+ init() {
7028
+ let elts = [];
7029
+ this.elts = elts;
7030
+ let children = Menu.resolve(this.items, this.wg.state.facet(barTemplate)).map(i => instantiate(i, this, elts));
7031
+ this.children = children.filter((ch) => !(ch instanceof BarSpacer));
7032
+ for (let elt of children)
7033
+ this.dom.appendChild(elt.dom);
7034
+ this.selection = this.children.length ? [this.children[0]] : [];
7035
+ this.updateElts(true, this.selection);
6815
7036
  }
6816
7037
  update(update) {
6817
- this.manager.update(update);
7038
+ let items = update.state.facet(Menu.Item.source);
7039
+ if (items != this.items ||
7040
+ update.startState.facet(barTemplate) != update.state.facet(barTemplate) ||
7041
+ update.startState.textLTR != update.state.textLTR ||
7042
+ PhraseSet.didChange(update.startState, update.state)) {
7043
+ this.items = items;
7044
+ this.dom.textContent = "";
7045
+ this.init();
7046
+ }
7047
+ else {
7048
+ this.updateElts(update, this.selection);
7049
+ if (update.startState.facet(KeyBinding.source) != update.state.facet(KeyBinding.source)) {
7050
+ for (let elt of this.elts)
7051
+ if (elt instanceof BarButton)
7052
+ elt.setDesc(update.editor);
7053
+ }
7054
+ }
6818
7055
  }
6819
- remove(wg) {
6820
- for (let t of this.manager.tooltipViews)
6821
- t.remove?.(wg);
7056
+ connect() {
7057
+ this.dom.setAttribute("aria-controls", this.wg.contentDOM.id);
6822
7058
  }
6823
- passProp(name) {
6824
- let value = undefined;
6825
- for (let view of this.manager.tooltipViews) {
6826
- let given = view[name];
6827
- if (given !== undefined) {
6828
- if (value === undefined)
6829
- value = given;
6830
- else if (value !== given)
6831
- return undefined;
7059
+ updateElts(update, selection) {
7060
+ let { state } = this.wg;
7061
+ let changed = typeof update == "boolean" ? update : update.docChanged || update.selectionSet;
7062
+ let updateObj = typeof update == "boolean" ? null : update;
7063
+ for (let i = 0; i < this.elts.length; i++) {
7064
+ let elt = this.elts[i], flags;
7065
+ if (update && (update === true || (elt.item.updateFor ? update.transactions.some(tr => elt.item.updateFor(tr)) : changed))) {
7066
+ flags = ((elt.item.select ? elt.item.select(state) : true) ? 0 : 32) |
7067
+ ((elt.item.enable ? elt.item.enable(state) : true) ? 0 : 8) |
7068
+ ((elt.item instanceof Menu.Button && elt.item.active ? elt.item.active(state) : false) ? 16 : 0);
6832
7069
  }
7070
+ else {
7071
+ flags = elt.flags & (32 | 8 | 16);
7072
+ }
7073
+ if (selection.length) {
7074
+ let selected = selection.indexOf(elt);
7075
+ if (selected == selection.length - 1)
7076
+ flags |= 4;
7077
+ else if (selected > -1)
7078
+ flags |= 2;
7079
+ }
7080
+ elt.update(flags, this.wg, updateObj);
7081
+ }
7082
+ if (update && selection.some(e => e.flags & 32)) {
7083
+ let reset = selection[0].flags & 32 ? findChild(this.children, true) : selection[0];
7084
+ this.setSelection(reset ? [reset] : [], this.dom.contains(this.wg.root.activeElement));
6833
7085
  }
6834
- return value;
6835
7086
  }
6836
- get offset() { return this.passProp("offset"); }
6837
- get getCoords() { return this.passProp("getCoords"); }
6838
- get overlap() { return this.passProp("overlap"); }
6839
- get resize() { return this.passProp("resize"); }
6840
- }
6841
- const showHoverTooltipHost = /*@__PURE__*/Tooltip.show.compute(state => {
6842
- let tooltips = state.facet(showHoverTooltip);
6843
- if (tooltips.length === 0)
6844
- return null;
6845
- return {
6846
- pos: Math.min(...tooltips.map(t => t.pos)),
6847
- end: Math.max(...tooltips.map(t => t.end ?? t.pos)),
6848
- create: HoverTooltipHost.create,
6849
- above: tooltips[0].above,
6850
- arrow: tooltips.some(t => t.arrow),
6851
- };
6852
- });
6853
- class HoverPlugin {
6854
- wg;
6855
- source;
6856
- field;
6857
- setHover;
6858
- hoverTime;
6859
- lastMove;
6860
- hoverTimeout = -1;
6861
- restartTimeout = -1;
6862
- pending = null;
6863
- constructor(wg, source, field, setHover, hoverTime) {
6864
- this.wg = wg;
6865
- this.source = source;
6866
- this.field = field;
6867
- this.setHover = setHover;
6868
- this.hoverTime = hoverTime;
6869
- this.lastMove = { x: 0, y: 0, target: wg.dom, time: 0 };
6870
- this.checkHover = this.checkHover.bind(this);
6871
- wg.dom.addEventListener("mouseleave", this.mouseleave = this.mouseleave.bind(this));
6872
- wg.dom.addEventListener("mousemove", this.mousemove = this.mousemove.bind(this));
7087
+ setSelection(selection, focus = true) {
7088
+ this.updateElts(false, selection);
7089
+ if (selection.length > 1 && this.selection.length <= 1)
7090
+ this.dom.ownerDocument.addEventListener("mousedown", this.globalClick);
7091
+ this.selection = selection;
7092
+ if (focus && selection.length)
7093
+ selection[selection.length - 1].focusDOM.focus();
6873
7094
  }
6874
- update() {
6875
- if (this.pending) {
6876
- this.pending = null;
6877
- clearTimeout(this.restartTimeout);
6878
- this.restartTimeout = setTimeout(() => this.startHover(), 20);
7095
+ key(event) {
7096
+ if (event.ctrlKey || event.altKey || event.metaKey || event.defaultPrevented)
7097
+ return;
7098
+ let sLen = this.selection.length;
7099
+ if (event.key == "ArrowLeft" || event.key == "ArrowRight") {
7100
+ if (sLen) {
7101
+ let forward = (event.key == "ArrowRight") == (getComputedStyle(this.dom).direction == "ltr");
7102
+ let next = findNextChild(this.children, this.selection[0], forward ? 1 : -1);
7103
+ this.setSelection(next ? [next] : []);
7104
+ }
7105
+ }
7106
+ else if (event.key == "ArrowDown" || event.key == "ArrowUp") {
7107
+ if (sLen > 1) {
7108
+ let parent = this.selection[sLen - 2];
7109
+ let next = findNextChild(parent.children, this.selection[sLen - 1], event.key == "ArrowUp" ? -1 : 1);
7110
+ if (next)
7111
+ this.setSelection(this.selection.slice(0, sLen - 1).concat(next));
7112
+ }
7113
+ else if (sLen == 1 && this.selection[0].children) {
7114
+ let inner = defaultChild(this.selection[0].children);
7115
+ if (inner)
7116
+ this.setSelection([this.selection[0], inner]);
7117
+ }
7118
+ }
7119
+ else if (event.key == "Home" || event.key == "End") {
7120
+ let child = findChild(sLen > 1 ? this.selection[sLen - 2].children : this.children, event.key == "Home");
7121
+ if (child)
7122
+ this.setSelection(this.selection.slice(0, sLen - 1).concat(child));
7123
+ }
7124
+ else if (event.key == " " || event.key == "Enter") {
7125
+ if (sLen) {
7126
+ let child = this.selection[sLen - 1];
7127
+ if (child.flags & 8) ;
7128
+ else if (child.children) {
7129
+ let inner = defaultChild(child.children);
7130
+ if (inner)
7131
+ this.setSelection(this.selection.concat(inner));
7132
+ }
7133
+ else {
7134
+ if (child.run)
7135
+ child.run(this.wg);
7136
+ this.setSelection([this.selection[0]]);
7137
+ }
7138
+ }
7139
+ }
7140
+ else if (event.key == "Escape" && sLen > 1) {
7141
+ this.setSelection(this.selection.slice(0, sLen - 1));
7142
+ }
7143
+ else {
7144
+ return;
6879
7145
  }
7146
+ event.preventDefault();
6880
7147
  }
6881
- get active() {
6882
- return this.wg.state.field(this.field);
6883
- }
6884
- checkHover() {
6885
- this.hoverTimeout = -1;
6886
- if (this.active.length)
7148
+ click(event) {
7149
+ if (event.defaultPrevented)
6887
7150
  return;
6888
- let hovered = Date.now() - this.lastMove.time;
6889
- if (hovered < this.hoverTime)
6890
- this.hoverTimeout = setTimeout(this.checkHover, this.hoverTime - hovered);
6891
- else
6892
- this.startHover();
6893
- }
6894
- startHover() {
6895
- clearTimeout(this.restartTimeout);
6896
- let { wg, lastMove } = this;
6897
- let { pos, side } = wg.posAtCoords(lastMove);
6898
- let open = this.source(wg, pos, side || -1);
6899
- if (open?.then) {
6900
- let pending = this.pending = { pos };
6901
- open.then(result => {
6902
- if (this.pending == pending) {
6903
- this.pending = null;
6904
- if (result && !(Array.isArray(result) && !result.length))
6905
- wg.dispatch({ effects: this.setHover.of(Array.isArray(result) ? result : [result]) });
7151
+ let target, idx;
7152
+ for (let node = event.target;; node = node.parentNode) {
7153
+ if (!node || node == this.dom)
7154
+ return;
7155
+ target = this.elts.find(e => e.dom == node);
7156
+ if (target)
7157
+ break;
7158
+ }
7159
+ if (target.flags & 8) ;
7160
+ else if (target.children) {
7161
+ if ((idx = this.selection.indexOf(target)) > -1 && idx < this.selection.length - 1) {
7162
+ this.setSelection(this.selection.slice(0, idx + 1), false);
7163
+ }
7164
+ else {
7165
+ for (let i = 0; i < this.selection.length; i++) {
7166
+ let { children } = i ? this.selection[i - 1] : this;
7167
+ if (children.includes(target)) {
7168
+ let next = defaultChild(target.children);
7169
+ if (next)
7170
+ this.setSelection(this.selection.slice(0, i).concat([target, next]), false);
7171
+ }
6906
7172
  }
6907
- }, e => logException(wg.state, e, "hover tooltip"));
7173
+ }
6908
7174
  }
6909
- else if (open && !(Array.isArray(open) && !open.length)) {
6910
- wg.dispatch({ effects: this.setHover.of(Array.isArray(open) ? open : [open]) });
7175
+ else {
7176
+ if (target.run)
7177
+ target.run(this.wg);
7178
+ this.setSelection(this.children.includes(target) ? [target] : this.selection.length ? [this.selection[0]] : [], false);
6911
7179
  }
7180
+ event.preventDefault();
6912
7181
  }
6913
- get tooltip() {
6914
- let plugin = this.wg.plugin(tooltipPlugin);
6915
- let index = plugin ? plugin.manager.tooltips.findIndex(t => t.create == HoverTooltipHost.create) : -1;
6916
- return index > -1 ? plugin.manager.tooltipViews[index] : null;
6917
- }
6918
- mousemove(event) {
6919
- this.lastMove = { x: event.clientX, y: event.clientY, target: event.target, time: Date.now() };
6920
- if (this.hoverTimeout < 0)
6921
- this.hoverTimeout = setTimeout(this.checkHover, this.hoverTime);
6922
- let { active, tooltip } = this;
6923
- if (active.length && tooltip && !isInTooltip(tooltip.dom, event) || this.pending) {
6924
- let { pos } = active[0] || this.pending, end = active[0]?.end ?? pos;
6925
- if ((pos == end ? this.wg.posAtCoords(this.lastMove).pos != pos
6926
- : !isOverRange(this.wg, pos, end, event.clientX, event.clientY))) {
6927
- this.wg.dispatch({ effects: this.setHover.of([]) });
6928
- this.pending = null;
6929
- }
7182
+ globalClick(event) {
7183
+ if (!event.composedPath().includes(this.dom)) {
7184
+ this.dom.ownerDocument.removeEventListener("mousedown", this.globalClick);
7185
+ if (this.selection.length > 1)
7186
+ this.setSelection([this.selection[0]], false);
6930
7187
  }
6931
7188
  }
6932
- mouseleave(event) {
6933
- clearTimeout(this.hoverTimeout);
6934
- this.hoverTimeout = -1;
6935
- let { active } = this;
6936
- if (active.length) {
6937
- let { tooltip } = this;
6938
- let inTooltip = tooltip && tooltip.dom.contains(event.relatedTarget);
6939
- if (!inTooltip)
6940
- this.wg.dispatch({ effects: this.setHover.of([]) });
6941
- else
6942
- this.watchTooltipLeave(tooltip.dom);
7189
+ focusout() {
7190
+ clearTimeout(this.focusTimeout);
7191
+ if (this.selection.length > 1) {
7192
+ this.focusTimeout = setTimeout(() => {
7193
+ let active = this.wg.root.activeElement;
7194
+ for (let i = 0; i < this.selection.length - 1; i++) {
7195
+ if (!active || !this.selection[i].dom.contains(active)) {
7196
+ this.setSelection([this.selection[0]], false);
7197
+ break;
7198
+ }
7199
+ }
7200
+ }, 20);
6943
7201
  }
6944
7202
  }
6945
- watchTooltipLeave(tooltip) {
6946
- let watch = (event) => {
6947
- tooltip.removeEventListener("mouseleave", watch);
6948
- if (this.active.length && !this.wg.dom.contains(event.relatedTarget))
6949
- this.wg.dispatch({ effects: this.setHover.of([]) });
6950
- };
6951
- tooltip.addEventListener("mouseleave", watch);
7203
+ up() {
7204
+ if (this.selection.length > 1)
7205
+ this.setSelection([this.selection[0]], false);
6952
7206
  }
6953
- remove() {
6954
- clearTimeout(this.hoverTimeout);
6955
- this.wg.dom.removeEventListener("mouseleave", this.mouseleave);
6956
- this.wg.dom.removeEventListener("mousemove", this.mousemove);
7207
+ get top() { return true; }
7208
+ }
7209
+ const menuBarPanel = /*@__PURE__*/(() => Panel.show.of(MenuBar.create))();
7210
+ function findChild(children, start) {
7211
+ for (let i = start ? 0 : children.length - 1; start ? i < children.length : i >= 0; start ? i++ : i--) {
7212
+ let child = children[i];
7213
+ if (!(child.flags & 8))
7214
+ return child;
6957
7215
  }
7216
+ return null;
6958
7217
  }
6959
- const tooltipMargin = 4;
6960
- function isInTooltip(tooltip, event) {
6961
- let { left, right, top, bottom } = tooltip.getBoundingClientRect(), arrow;
6962
- if (arrow = tooltip.querySelector(".wg-tooltip-arrow")) {
6963
- let arrowRect = arrow.getBoundingClientRect();
6964
- top = Math.min(arrowRect.top, top);
6965
- bottom = Math.max(arrowRect.bottom, bottom);
7218
+ function findNextChild(children, child, dir) {
7219
+ let index = children.indexOf(child);
7220
+ if (index < 0)
7221
+ return null;
7222
+ for (let i = index + dir;; i += dir) {
7223
+ if (i < 0)
7224
+ i = children.length - 1;
7225
+ else if (i >= children.length)
7226
+ i = 0;
7227
+ if (i == index)
7228
+ return null;
7229
+ let child = children[i];
7230
+ if (!(child.flags & 32))
7231
+ return child;
6966
7232
  }
6967
- return event.clientX >= left - tooltipMargin && event.clientX <= right + tooltipMargin &&
6968
- event.clientY >= top - tooltipMargin && event.clientY <= bottom + tooltipMargin;
6969
7233
  }
6970
- function isOverRange(wg, from, to, x, y, margin) {
6971
- let rect = wg.contentDOM.getBoundingClientRect();
6972
- if (rect.left > x || rect.right < x || rect.top > y || rect.bottom < y)
6973
- return false;
6974
- let pos = wg.posAtCoords({ x, y }).pos;
6975
- return pos >= from && pos <= to;
7234
+ function defaultChild(children) {
7235
+ return children.length ? children.find(ch => ch.flags & 16) || children[0] : null;
7236
+ }
7237
+ const theme = /*@__PURE__*/Wordgard.styles({
7238
+ "&": {
7239
+ "--wg-menu-item-size": "20px"
7240
+ },
7241
+ "&light": {
7242
+ "--wg-menu-color": "#555"
7243
+ },
7244
+ "&dark": {
7245
+ "--wg-menu-color": "#ccc"
7246
+ },
7247
+ "wg-menubar": {
7248
+ display: "flex",
7249
+ flexWrap: "wrap",
7250
+ gap: "5px",
7251
+ padding: "3px",
7252
+ color: "var(--wg-menu-color)",
7253
+ borderBottom: "1px solid var(--wg-border-color)",
7254
+ },
7255
+ ".wg-menu-button:focus": {
7256
+ outline: "1.5px solid var(--wg-highlight-color)"
7257
+ },
7258
+ ".wg-menu-button": {
7259
+ border: 0,
7260
+ padding: "3px",
7261
+ borderRadius: "4px",
7262
+ boxSizing: "content-box",
7263
+ backgroundColor: "transparent",
7264
+ font: "inherit",
7265
+ color: "inherit",
7266
+ height: "var(--wg-menu-item-size)",
7267
+ textAlign: "left",
7268
+ "&[aria-disabled]": {
7269
+ opacity: "0.3"
7270
+ },
7271
+ "&[aria-pressed]": {
7272
+ color: "var(--wg-highlight-color)"
7273
+ },
7274
+ "&:hover": {
7275
+ backgroundColor: "#88888820",
7276
+ },
7277
+ },
7278
+ ".wg-button-label": {
7279
+ display: "inline-block",
7280
+ minWidth: "var(--wg-submenu-width)",
7281
+ padding: "0 3px",
7282
+ },
7283
+ "svg.wg-icon": {
7284
+ fill: "currentColor",
7285
+ width: "var(--wg-menu-item-size)",
7286
+ height: "var(--wg-menu-item-size)",
7287
+ },
7288
+ "wg-menu-spacer": {
7289
+ display: "block",
7290
+ width: "7px"
7291
+ },
7292
+ "wg-submenu": {
7293
+ display: "block",
7294
+ position: "relative",
7295
+ lineHeight: ".6",
7296
+ whiteSpace: "nowrap"
7297
+ },
7298
+ "wg-menu-list": {
7299
+ display: "flex",
7300
+ flexDirection: "column",
7301
+ alignItems: "stretch",
7302
+ gap: "5px",
7303
+ padding: "5px",
7304
+ borderRadius: "4px",
7305
+ backgroundColor: "var(--wg-panel-color)",
7306
+ position: "absolute",
7307
+ zIndex: "10",
7308
+ top: "100%",
7309
+ boxShadow: "0 2px 8px 0 rgba(128, 128, 128, 0.2)",
7310
+ "& wg-menu-list": {
7311
+ left: "100%",
7312
+ top: 0
7313
+ }
7314
+ },
7315
+ ".wg-submenu-arrow:after": {
7316
+ padding: "0 2px",
7317
+ fontSize: "80%",
7318
+ verticalAlign: "10%",
7319
+ opacity: "0.4",
7320
+ content: "'▾'"
7321
+ },
7322
+ ".wg-submenu-arrow.wg-submenu-arrow-open:after": {
7323
+ content: "'▴'"
7324
+ },
7325
+ });
7326
+ const barTemplate = /*@__PURE__*/GardState.Facet.define({
7327
+ combine: inputs => inputs.length ? inputs[0] : [Menu.Group.top.template()]
7328
+ });
7329
+ function menuBar(config = {}) {
7330
+ let extensions = [menuBarPanel, theme];
7331
+ if (config.template)
7332
+ extensions.push(barTemplate.of(Array.isArray(config.template) ? config.template : [config.template]));
7333
+ return extensions;
6976
7334
  }
6977
7335
 
6978
7336
  const inputRule = /*@__PURE__*/GardState.Facet.define();
@@ -7237,4 +7595,4 @@ function dropCursor() {
7237
7595
  return [dropCursorPos, drawDropCursor];
7238
7596
  }
7239
7597
 
7240
- export { Decoration, Dialog, InputRule, KeyBinding, Panel, Tooltip, Widget, Wordgard, decoratePlots, dropCursor, menuBar, placeholder };
7598
+ export { Decoration, Dialog, InputRule, KeyBinding, Panel, PositioningHint, Tooltip, Widget, Wordgard, decoratePlots, dropCursor, keyBindingHelp, menuBar, placeholder };