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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/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) {
@@ -62,7 +63,7 @@ function separateChange(changes, fromB, toB) {
62
63
  let lenI = 0, dLen = 0;
63
64
  for (let posB = 0, done = false, i = 0; i < changes.length;) {
64
65
  let len = changes[i++], ins = changes[i++], endB = posB + (ins < 0 ? len : ins);
65
- if (fromB > endB || toB < posB) {
66
+ if (fromB >= endB || toB <= posB) {
66
67
  result.push(len, ins);
67
68
  }
68
69
  else {
@@ -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,13 +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);
519
+ let globalChange = globalDecoFacets.some(facet => prevState.facet(facet) !== state.facet(facet));
504
520
  let shapeChanges = [];
505
521
  for (let i = 0, posA = 0, posB = 0; i < sections.length;) {
506
522
  let len = sections[i++], ins = sections[i++];
@@ -652,24 +668,6 @@ class SpanIterator {
652
668
  return this;
653
669
  }
654
670
  }
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
671
  function tagScope(tag, atom) {
674
672
  return 4 |
675
673
  (atom ? 1 | (tag.type.isInline ? 2 : 0) : 0);
@@ -690,6 +688,7 @@ class DecoIterator {
690
688
  globalWidgets;
691
689
  globalWrappers;
692
690
  globalAttrs;
691
+ markShapes;
693
692
  schema;
694
693
  pos;
695
694
  rangeCursor;
@@ -705,6 +704,7 @@ class DecoIterator {
705
704
  .some(w => (w.place == 1 || w.place == 3) && typeof w.widget == "function");
706
705
  this.globalWrappers = state.facet(tagWrapper);
707
706
  this.globalAttrs = state.facet(tagAttribute);
707
+ this.markShapes = state.facet(markShape);
708
708
  this.pos = state.doc.resolve(0);
709
709
  this.schema = state.schema;
710
710
  this.rangeCursor = RangeSet.cursor(state.facet(Decoration.Range.source).map(s => s(state)));
@@ -729,6 +729,26 @@ class DecoIterator {
729
729
  (tw.place == 3 || tw.place == 1) &&
730
730
  typeof tw.widget == "function");
731
731
  }
732
+ nodeWrappers(tag, active, atom) {
733
+ let wrappers;
734
+ for (let mark of tag.marks) {
735
+ let shape = this.markShapes.get(mark.type) || mark.type;
736
+ if (shape.element)
737
+ (wrappers || (wrappers = [])).push(shape == mark.type ? mark : shape.of(mark.value));
738
+ }
739
+ if (active.length) {
740
+ for (let val of active) {
741
+ if (val instanceof WrapperRangeDecoration && (tagScope(tag, atom) & val.scope) &&
742
+ (!val.query || this.schema.matchNode(tag.type, val.query)))
743
+ (wrappers || (wrappers = [])).push(val);
744
+ }
745
+ }
746
+ if (!wrappers)
747
+ return none;
748
+ if (wrappers.length > 1)
749
+ wrappers.sort((a, b) => (a.spanning == b.spanning ? 0 : a.spanning ? -1 : 1) || a.rank - b.rank);
750
+ return wrappers;
751
+ }
732
752
  walk(from, inclusiveStart, to, walker) {
733
753
  this.rangeCursor.goto(from);
734
754
  this.pointCursor.goto(from, inclusiveStart ? -1e9 : 1000000000);
@@ -753,7 +773,7 @@ class DecoIterator {
753
773
  shape = applyDeco(shape, deco, node.tag);
754
774
  if (shape.hasContent)
755
775
  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);
776
+ walker.node(node, shape, this.nodeWrappers(node.tag, iter.active, true), undefined);
757
777
  this.widgets(node.tag, 1, walker);
758
778
  },
759
779
  enterPlot: (node, pos) => {
@@ -766,7 +786,7 @@ class DecoIterator {
766
786
  if (pendingPos == pos)
767
787
  for (let deco of pendingDeco)
768
788
  shape = applyDeco(shape, deco, node.tag);
769
- let wrappers = nodeWrappers(this.schema, node.tag, iter.active, !shape.hasContent);
789
+ let wrappers = this.nodeWrappers(node.tag, iter.active, !shape.hasContent);
770
790
  let atom = !shape.hasContent;
771
791
  if (atom)
772
792
  walker.node(node, shape, wrappers, pos + node.length > to ? to - pos : undefined);
@@ -842,15 +862,10 @@ class DecoIterator {
842
862
  this.pos = pos;
843
863
  }
844
864
  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);
865
+ let override = this.tagShapes.get(tag.type);
866
+ let shape = override ? override(tag) : baseTagShape(tag);
867
+ if (tag.marks.length)
868
+ shape = addMarkAttributes(this.markShapes, shape, tag);
854
869
  let add;
855
870
  for (let src of this.globalAttrs)
856
871
  if (tag.type == src.type) {
@@ -1423,6 +1438,13 @@ class CompositeTile extends Tile {
1423
1438
  return null;
1424
1439
  let { closest, rect } = result;
1425
1440
  let pos = this.posBeforeChild(closest, start);
1441
+ if (textblock && textblock.start == start && (x < rect.left || x > rect.right)) {
1442
+ let start = (x < rect.left) == textblock.ltr;
1443
+ let span = start ? textblock.order[0] : textblock.order[textblock.order.length - 1];
1444
+ if (span.ltr != textblock.ltr && pos >= span.from + textblock.start && pos <= span.to + textblock.start)
1445
+ return start ? CoordPos.create(textblock.start, -1)
1446
+ : CoordPos.create(textblock.start + textblock.node.contentLength, 1);
1447
+ }
1426
1448
  if (closest.dom.nodeName == "BR")
1427
1449
  return CoordPos.create(pos, 1);
1428
1450
  if (closest.tag && closest.tag.isPlot && closest.tag.type.isInline) {
@@ -1529,6 +1551,8 @@ class DocTile extends CompositeTile {
1529
1551
  changes = separated;
1530
1552
  }
1531
1553
  let builder = new ContentUpdate(state, this, wg, new DecoIterator(state, decoSet), wrapper);
1554
+ if (composition?.target && Tile.get(composition.target))
1555
+ builder.reused.set(Tile.get(composition.target), 2);
1532
1556
  for (let i = 0, posB = 0, startCovered = false; i < changes.length;) {
1533
1557
  let len = changes[i++], ins = changes[i++];
1534
1558
  if (composition && posB == composition.fromB && ins >= 0) {
@@ -1947,7 +1971,7 @@ class TilePointer {
1947
1971
  }
1948
1972
  tileAfter() {
1949
1973
  let { tile, index } = this;
1950
- if (tile.isText)
1974
+ if (tile.isAtom)
1951
1975
  return tile;
1952
1976
  return index < tile.children.length ? tile.children[index] : null;
1953
1977
  }
@@ -1973,25 +1997,26 @@ class TilePointer {
1973
1997
  }
1974
1998
  matchingWidget(widget, sideFlag, reused) {
1975
1999
  let { index, tile, parent } = this;
1976
- for (;;) {
1977
- if (!index) {
1978
- if (!parent || (tile instanceof EltTile ? tile.tag : !(tile instanceof TextTile)))
1979
- break;
1980
- ({ index, tile, parent } = parent);
1981
- }
1982
- else {
1983
- if (tile instanceof TextTile)
1984
- break;
1985
- let before = tile.children[--index];
1986
- if (!before.isPoint)
1987
- break;
1988
- if (!reused.has(before) && before instanceof WidgetTile && before.widget.eq(widget) &&
1989
- (before.flags & 96) == sideFlag && !(before.flags & 8192)) {
1990
- reused.set(before, 1);
1991
- return before;
2000
+ if (!tile.isAtom)
2001
+ for (;;) {
2002
+ if (!index) {
2003
+ if (!parent || (tile instanceof EltTile ? tile.tag : !(tile instanceof TextTile)))
2004
+ break;
2005
+ ({ index, tile, parent } = parent);
2006
+ }
2007
+ else {
2008
+ if (tile instanceof TextTile)
2009
+ break;
2010
+ let before = tile.children[--index];
2011
+ if (!before.isPoint)
2012
+ break;
2013
+ if (!reused.has(before) && before instanceof WidgetTile && before.widget.eq(widget) &&
2014
+ (before.flags & 96) == sideFlag && !(before.flags & 8192)) {
2015
+ reused.set(before, 1);
2016
+ return before;
2017
+ }
1992
2018
  }
1993
2019
  }
1994
- }
1995
2020
  return null;
1996
2021
  }
1997
2022
  }
@@ -2091,7 +2116,7 @@ class ContentUpdate {
2091
2116
  let cut = [], end = this.posB + len;
2092
2117
  if (this.deco.endWidgets)
2093
2118
  for (let nw = this.new, { tile, index, parent } = this.old, pos = this.posB; pos < end;) {
2094
- if (tile instanceof TextTile) {
2119
+ if (tile.isAtom) {
2095
2120
  pos += tile.length - index;
2096
2121
  ({ tile, index, parent } = parent);
2097
2122
  index++;
@@ -2541,6 +2566,16 @@ class Coords {
2541
2566
  }
2542
2567
  }
2543
2568
  function coordsAtPos(wg, pos, assoc) {
2569
+ let textblock = wg.state.doc.resolve(pos).textblockParent;
2570
+ let map = textblock ? wg.state.textblockMap(textblock) : null;
2571
+ if (textblock && textblock.node.contentLength &&
2572
+ (pos == textblock.start && assoc < 0 || pos == textblock.end && assoc > 0)) {
2573
+ let span = map.order[pos == textblock.start ? 0 : map.order.length - 1];
2574
+ if (span.ltr != map.ltr)
2575
+ pos = textblock.start + (pos == textblock.start ? span.to : span.from);
2576
+ else
2577
+ assoc = -assoc;
2578
+ }
2544
2579
  let { offset, tile, pos: tilePos } = wg.docTile.resolve(pos, assoc);
2545
2580
  if (tile instanceof TextTile) {
2546
2581
  let from = offset, to = offset;
@@ -2549,7 +2584,7 @@ function coordsAtPos(wg, pos, assoc) {
2549
2584
  to++;
2550
2585
  else
2551
2586
  from--;
2552
- return new Coords(tile, flattenV(singleRect(textRange(tile.dom, from, to), side, true), (side < 0) == ltrAt(wg.state, pos, assoc)));
2587
+ return new Coords(tile, flattenV(singleRect(textRange(tile.dom, from, to), side, true), (side < 0) == ltrAt(wg.state, pos, assoc, map)));
2553
2588
  }
2554
2589
  let tagTile = tile;
2555
2590
  while (!tagTile.tag)
@@ -2560,7 +2595,7 @@ function coordsAtPos(wg, pos, assoc) {
2560
2595
  if (tile.widget.type.inFlow) {
2561
2596
  let rect = singleRect(tile.dom, after ? 1 : -1);
2562
2597
  if (rect.width || rect.height)
2563
- return new Coords(tile, horizontal ? flattenH(rect, !after) : flattenV(rect, ltrAt(wg.state, pos, 1) == !after));
2598
+ return new Coords(tile, horizontal ? flattenH(rect, !after) : flattenV(rect, ltrAt(wg.state, pos, 1, map) == !after));
2564
2599
  }
2565
2600
  if (!tile.parent)
2566
2601
  return new Coords(tile, new DOMRect);
@@ -2577,7 +2612,7 @@ function coordsAtPos(wg, pos, assoc) {
2577
2612
  break;
2578
2613
  let rect = singleRect(before.dom, 1);
2579
2614
  if (rect.width || rect.height)
2580
- return new Coords(before, horizontal ? flattenH(rect, false) : flattenV(rect, !ltrAt(wg.state, pos, 1)));
2615
+ return new Coords(before, horizontal ? flattenH(rect, false) : flattenV(rect, !ltrAt(wg.state, pos, 1, map)));
2581
2616
  }
2582
2617
  }
2583
2618
  else { for (let i = offset; i < tile.children.length; i++) {
@@ -2586,7 +2621,7 @@ function coordsAtPos(wg, pos, assoc) {
2586
2621
  continue;
2587
2622
  let rect = singleRect(after.dom, -1);
2588
2623
  if (rect.width || rect.height)
2589
- return new Coords(after, horizontal ? flattenH(rect, true) : flattenV(rect, ltrAt(wg.state, pos, 1)));
2624
+ return new Coords(after, horizontal ? flattenH(rect, true) : flattenV(rect, ltrAt(wg.state, pos, 1, map)));
2590
2625
  }
2591
2626
  }
2592
2627
  }
@@ -2630,19 +2665,18 @@ const clipboardInputFilter = /*@__PURE__*/GardState.Facet.define();
2630
2665
  const clipboardInputHTMLFilter = /*@__PURE__*/GardState.Facet.define();
2631
2666
  const clipboardTextParser = /*@__PURE__*/GardState.Facet.define();
2632
2667
  const clipboardInputTextFilter = /*@__PURE__*/GardState.Facet.define();
2633
- function writeClipboard(state, slice, context, data) {
2668
+ function writeClipboard(state, slice, data) {
2634
2669
  for (let filter of state.facet(clipboardOutputFilter))
2635
2670
  slice = filter(slice, state);
2636
2671
  let includeContext = 0;
2637
- for (let i = 0; i < context.length; i++) {
2638
- let next = context[i];
2639
- if (next.type.defining && (!includeContext || next.type != context[includeContext - 1].type))
2672
+ for (let i = 0; i < slice.context.length; i++) {
2673
+ let next = slice.context[i];
2674
+ if (next.type.defining && (!includeContext || next.type != slice.context[includeContext - 1].type))
2640
2675
  includeContext = i + 1;
2641
2676
  else if (next.type.defining || !next.isTextblock)
2642
2677
  break;
2643
2678
  }
2644
2679
  let doc = detachedDoc(), dom = serialize.slice(slice, {
2645
- context,
2646
2680
  includeContext,
2647
2681
  openAttr: "wg-open"
2648
2682
  }).toDOM();
@@ -2666,7 +2700,7 @@ function writeClipboard(state, slice, context, data) {
2666
2700
  data.setData("text/html", html);
2667
2701
  let text;
2668
2702
  for (let serialize of state.facet(clipboardTextSerializer)) {
2669
- if ((text = serialize(slice, context, state)) != null)
2703
+ if ((text = serialize(slice, state)) != null)
2670
2704
  break;
2671
2705
  }
2672
2706
  if (text == null)
@@ -2681,7 +2715,7 @@ function isOpen(elt) {
2681
2715
  function readClipboard(state, data, targetContext, plain) {
2682
2716
  let html = data.getData("text/html");
2683
2717
  let text = data.getData("text/plain") || data.getData("Text") || data.getData("text/uri-list").replace(/\r?\n/g, " ");
2684
- let slice, context = [];
2718
+ let slice;
2685
2719
  if (text && (targetContext.parent.node.type.hasRole(Node.Role.Code) || !html || plain)) {
2686
2720
  for (let filter of state.facet(clipboardInputTextFilter))
2687
2721
  text = filter(text, state);
@@ -2697,14 +2731,14 @@ function readClipboard(state, data, targetContext, plain) {
2697
2731
  if (browser.webkit)
2698
2732
  restoreReplacedSpaces(dom);
2699
2733
  let fromWordgard = dom.querySelector("[wg-content=true]");
2700
- ({ slice, context } = parse.slice(state.schema, dom, {
2734
+ slice = parse.slice(state.schema, dom, {
2701
2735
  collapseWhiteSpace: !fromWordgard,
2702
2736
  isOpen: fromWordgard ? isOpen : undefined
2703
- }));
2737
+ });
2704
2738
  }
2705
2739
  for (let filter of state.facet(clipboardInputFilter))
2706
2740
  slice = filter(slice, state);
2707
- return { slice, context };
2741
+ return slice;
2708
2742
  }
2709
2743
  function readClipboardText(state, text, context, plain) {
2710
2744
  if (!plain)
@@ -2837,7 +2871,6 @@ const baseStyles = /*@__PURE__*/buildTheme("." + styleID, {
2837
2871
  whiteSpace: "pre-wrap",
2838
2872
  overflowWrap: "anywhere",
2839
2873
  wordBreak: "break-word", boxSizing: "border-box",
2840
- minHeight: "100%",
2841
2874
  padding: "4px 12px",
2842
2875
  outline: "none",
2843
2876
  caretColor: "transparent",
@@ -2922,14 +2955,26 @@ const baseStyles = /*@__PURE__*/buildTheme("." + styleID, {
2922
2955
  },
2923
2956
  ".wg-panels-top": { top: "0" },
2924
2957
  ".wg-panels-bottom": { bottom: "0" },
2925
- "wg-dialog": {
2926
- display: "block",
2927
- padding: "5px 19px 5px 6px",
2928
- position: "relative",
2929
- "& label, & .wg-label": {
2930
- fontSize: "90%"
2931
- },
2932
- borderBottom: "1px solid var(--wg-border-color)"
2958
+ dialog: {
2959
+ font: "var(--wg-dialog-font)",
2960
+ padding: "7px 22px 7px 8px",
2961
+ minWidth: "120px",
2962
+ border: "1px solid var(--wg-border-color)",
2963
+ borderRadius: "5px",
2964
+ boxShadow: "0 0 8px 0 rgba(128, 128, 128, 0.2)",
2965
+ inset: "unset",
2966
+ "&.wg-free-dialog": {
2967
+ position: "fixed"
2968
+ }
2969
+ },
2970
+ "h3.wg-dialog-title": {
2971
+ fontSize: "90%",
2972
+ fontWeight: "bold",
2973
+ textAlign: "center",
2974
+ margin: "0 0 0.8em"
2975
+ },
2976
+ "dialog label, dialog .wg-label": {
2977
+ fontSize: "90%"
2933
2978
  },
2934
2979
  ".wg-dialog-close": {
2935
2980
  position: "absolute",
@@ -2959,6 +3004,24 @@ const baseStyles = /*@__PURE__*/buildTheme("." + styleID, {
2959
3004
  backgroundColor: "#222"
2960
3005
  }
2961
3006
  },
3007
+ ".wg-key-help": {
3008
+ padding: "15px 4px 4px",
3009
+ maxWidth: "350px"
3010
+ },
3011
+ ".wg-key-list": {
3012
+ padding: "11px",
3013
+ outline: "none",
3014
+ maxHeight: "calc(min(70vh, 300px))",
3015
+ overflowY: "auto",
3016
+ display: "grid",
3017
+ gridTemplateColumns: "max-content auto",
3018
+ gap: "8px 15px",
3019
+ "& h4": {
3020
+ gridColumn: "span 2",
3021
+ margin: "0.5em 0",
3022
+ fontSize: "110%"
3023
+ }
3024
+ }
2962
3025
  }, lightDarkIDs);
2963
3026
 
2964
3027
  function setDOMSelection(wg) {
@@ -3011,7 +3074,7 @@ function rangeForClick(wg, pos, type) {
3011
3074
  }
3012
3075
  else { let cx = wg.state.doc.resolve(pos.pos), block = cx.textblockParent;
3013
3076
  if (block)
3014
- return GardSelection.range(block.start, block.end);
3077
+ return GardSelection.undirectionalRange(block.start, block.end);
3015
3078
  else
3016
3079
  return GardSelection.near(wg.state, pos.pos, pos.side || -1);
3017
3080
  }
@@ -3124,12 +3187,23 @@ function moveToLineBoundary(wg, start, forward) {
3124
3187
  let startCoords = wg.coordsAtPos(start.head, start.headSide);
3125
3188
  let ltr = wg.state.textblockLTR(block.node);
3126
3189
  let y = (startCoords.top + startCoords.bottom) / 2, left = forward != ltr;
3127
- let { pos } = wg.posAtCoords({ x: left ? -1e7 : 1e7, y });
3190
+ let { pos, side } = wg.posAtCoords({ x: left ? -1e7 : 1e7, y });
3128
3191
  if (pos < block.start || pos > block.end) {
3129
3192
  let blockRect = wg.docTile.nodeTile(block.before).dom.getBoundingClientRect();
3130
- pos = wg.posAtCoords({ x: left ? blockRect.left : blockRect.right, y }).pos;
3193
+ let result = wg.posAtCoords({ x: left ? blockRect.left + 0.2 : blockRect.right - 0.2, y });
3194
+ if (result.pos < block.start) {
3195
+ pos = block.start;
3196
+ side = -1;
3197
+ }
3198
+ else if (result.pos > block.end) {
3199
+ pos = block.end;
3200
+ side = 1;
3201
+ }
3202
+ else {
3203
+ ({ pos, side } = result);
3204
+ }
3131
3205
  }
3132
- return GardSelection.cursor(pos, forward ? -1 : 1);
3206
+ return GardSelection.cursor(pos, side);
3133
3207
  }
3134
3208
 
3135
3209
  const observeOptions = {
@@ -3267,7 +3341,7 @@ class DOMObserver {
3267
3341
  if (selRange)
3268
3342
  range = buildSelectionRangeFromRange(wg, selRange);
3269
3343
  }
3270
- if (!range || this.selectionRange.eq(range))
3344
+ if (!range || !range.focusNode || this.selectionRange.eq(range))
3271
3345
  return false;
3272
3346
  let context = range.anchorNode && wg.docTile.nearest(range.anchorNode);
3273
3347
  if (context instanceof WidgetTile)
@@ -3279,10 +3353,6 @@ class DOMObserver {
3279
3353
  this.selectionRange.set(anchor.dom, anchor.offset, head.dom, head.offset);
3280
3354
  this.selectionChanged = false;
3281
3355
  }
3282
- clearSelectionRange() {
3283
- this.selectionRange.set(null, 0, null, 0);
3284
- this.selectionChanged = false;
3285
- }
3286
3356
  ignore(f) {
3287
3357
  let result = f();
3288
3358
  this.clear();
@@ -3376,18 +3446,46 @@ class KeyBinding {
3376
3446
  static of(spec) { return new KeyBinding(spec); }
3377
3447
  }
3378
3448
  ;KeyBinding = /*@__PURE__*/(function (KeyBinding) {
3379
- function runScopeHandlers(wg, event, scope) {
3380
- let map = getKeymap(wg.state.facet(KeyBinding.source), wg.state.facet(KeyBinding.useDefaultKeymap));
3381
- return runHandlers(map, event, wg, scope);
3382
- }
3383
- KeyBinding.runScopeHandlers = runScopeHandlers;
3384
- KeyBinding.source = GardState.Facet.define();
3449
+ function handle(wg, event) {
3450
+ let map = getKeymap(wg.state);
3451
+ return runHandlers(map, event, wg);
3452
+ }
3453
+ KeyBinding.handle = handle;
3454
+ KeyBinding.keymap = GardState.Field.Derived.define(s => {
3455
+ let bindings = s.facet(KeyBinding.source);
3456
+ return s.facet(KeyBinding.useDefaultKeymap) ? bindings.concat(KeyBinding.defaultKeymap) : bindings;
3457
+ });
3458
+ KeyBinding.source = GardState.Facet.define({
3459
+ enables: KeyBinding.keymap
3460
+ });
3385
3461
  KeyBinding.useDefaultKeymap = GardState.Facet.define({
3386
3462
  combine: input => input.length ? input[0] : true
3387
3463
  });
3464
+ class Group {
3465
+ description;
3466
+ rank;
3467
+ constructor(
3468
+ description,
3469
+ rank) {
3470
+ this.description = description;
3471
+ this.rank = rank;
3472
+ }
3473
+ static define(spec) {
3474
+ return new Group(spec.description, spec.rank || 1000);
3475
+ }
3476
+ static Inline = new Group(phrases.ref("group_inline"), 20);
3477
+ static Block = new Group(phrases.ref("group_block"), 30);
3478
+ static History = new Group(phrases.ref("group_history"), 70);
3479
+ }
3480
+ KeyBinding.Group = Group;
3481
+ function lookupByDescription(state, desc) {
3482
+ return getDescMap(state).get(desc) || null;
3483
+ }
3484
+ KeyBinding.lookupByDescription = lookupByDescription;
3388
3485
  KeyBinding.defaultKeymap = [
3389
3486
  { key: "Enter", run: enter },
3390
- { key: "Shift-Enter", run: insertLineBreak },
3487
+ { key: "Shift-Enter", run: insertLineBreak,
3488
+ description: phrases.ref("insert_break"), group: Group.Inline },
3391
3489
  { key: "Backspace", run: Command.bind(deleteUnit, "backward") },
3392
3490
  { key: "Delete", run: Command.bind(deleteUnit, "forward") },
3393
3491
  { key: "Ctrl-Backspace", mac: "Alt-Backspace", run: Command.bind(deleteWord, "backward") },
@@ -3431,9 +3529,9 @@ class KeyBinding {
3431
3529
  { key: "Mod-End", run: Command.bind(moveToDocSide, { side: "end" }),
3432
3530
  shift: Command.bind(moveToDocSide, { side: "end", extend: true }) },
3433
3531
  { key: "Mod-a", run: selectAll },
3434
- { key: "Mod-z", run: undo },
3435
- { key: "Mod-y", mac: "Mod-Shift-z", run: redo },
3436
- { linux: "Ctrl-Shift-z", run: redo },
3532
+ { key: "Mod-z", run: undo, description: phrases.ref("undo"), group: Group.History },
3533
+ { key: "Mod-y", mac: "Mod-Shift-z", run: redo, description: phrases.ref("redo"), group: Group.History },
3534
+ { linux: "Ctrl-Shift-z", run: redo, description: phrases.ref("redo"), group: Group.History },
3437
3535
  { mac: "Ctrl-b", run: Command.bind(moveByUnit, { dir: "backward" }),
3438
3536
  shift: Command.bind(moveByUnit, { dir: "backward", extend: true }) },
3439
3537
  { mac: "Ctrl-f", run: Command.bind(moveByUnit, { dir: "forward" }),
@@ -3467,15 +3565,15 @@ function normalizeKeyName(name, platform) {
3467
3565
  let alt, ctrl, shift, meta;
3468
3566
  for (let i = 0; i < parts.length - 1; ++i) {
3469
3567
  const mod = parts[i];
3470
- if (/^(cmd|meta|m)$/i.test(mod))
3568
+ if (mod == "Cmd" || mod == "Meta")
3471
3569
  meta = true;
3472
- else if (/^a(lt)?$/i.test(mod))
3570
+ else if (mod == "Alt")
3473
3571
  alt = true;
3474
- else if (/^(c|ctrl|control)$/i.test(mod))
3572
+ else if (mod == "Ctrl")
3475
3573
  ctrl = true;
3476
- else if (/^s(hift)?$/i.test(mod))
3574
+ else if (mod == "Shift")
3477
3575
  shift = true;
3478
- else if (/^mod$/i.test(mod)) {
3576
+ else if (mod == "Mod") {
3479
3577
  if (platform == "mac")
3480
3578
  meta = true;
3481
3579
  else
@@ -3489,18 +3587,18 @@ function normalizeKeyName(name, platform) {
3489
3587
  if (ctrl)
3490
3588
  result = "Ctrl-" + result;
3491
3589
  if (meta)
3492
- result = "Meta-" + result;
3590
+ result = (platform == "mac" ? "Cmd-" : "Meta-") + result;
3493
3591
  if (shift)
3494
3592
  result = "Shift-" + result;
3495
3593
  return result;
3496
3594
  }
3497
- function modifiers(name, event) {
3595
+ function modifiers(name, event, platform) {
3498
3596
  if (event.altKey)
3499
3597
  name = "Alt-" + name;
3500
3598
  if (event.ctrlKey)
3501
3599
  name = "Ctrl-" + name;
3502
3600
  if (event.metaKey)
3503
- name = "Meta-" + name;
3601
+ name = (platform == "mac" ? "Cmd-" : "Meta-") + name;
3504
3602
  if (event.shiftKey)
3505
3603
  name = "Shift-" + name;
3506
3604
  return name;
@@ -3516,56 +3614,65 @@ class NormalizedBinding {
3516
3614
  }
3517
3615
  }
3518
3616
  const keymapCache = /*@__PURE__*/(() => new WeakMap())();
3519
- function getKeymap(bindings, addDefault) {
3617
+ function getKeymap(state) {
3618
+ let bindings = state.field(KeyBinding.keymap);
3520
3619
  let found = keymapCache.get(bindings);
3521
- if (!found || found.deflt != addDefault) {
3522
- found = {
3523
- map: buildKeymap(addDefault ? bindings.concat(KeyBinding.defaultKeymap) : bindings, currentPlatform),
3524
- deflt: addDefault
3525
- };
3526
- keymapCache.set(bindings, found);
3620
+ if (!found)
3621
+ keymapCache.set(bindings, found = buildKeymap(bindings, currentPlatform));
3622
+ return found;
3623
+ }
3624
+ const lookupCache = /*@__PURE__*/(() => new WeakMap())();
3625
+ function getDescMap(state) {
3626
+ let bindings = state.field(KeyBinding.keymap);
3627
+ let map = lookupCache.get(bindings);
3628
+ if (!map) {
3629
+ lookupCache.set(bindings, map = new Map);
3630
+ for (let binding of bindings) {
3631
+ let { description, shiftDescription } = binding.spec;
3632
+ if (description || shiftDescription) {
3633
+ let name = binding.spec[currentPlatform] || binding.spec.key;
3634
+ if (name && description)
3635
+ map.set(description, normalizeKeyName(name, currentPlatform));
3636
+ if (name && shiftDescription)
3637
+ map.set(shiftDescription, "Shift-" + normalizeKeyName(name, currentPlatform));
3638
+ }
3639
+ }
3527
3640
  }
3528
- return found.map;
3641
+ return map;
3529
3642
  }
3530
3643
  function bind(run) {
3531
3644
  return (wg) => Command.dispatch(wg, run);
3532
3645
  }
3533
3646
  function buildKeymap(bindings, platform) {
3534
- let scopes = Object.create(null);
3647
+ let map = [];
3535
3648
  for (let { spec: b } of bindings) {
3536
3649
  let baseFlags = (b.allowDefault ? 8 : 0);
3537
- for (let scope of b.scope ? b.scope.split(" ") : ["editor"]) {
3538
- let array = scopes[scope] || (scopes[scope] = []);
3539
- let key = b[platform] || b.key;
3540
- if (b.char) {
3541
- if (key)
3542
- throw new Error("A key binding may not provide both a char and a key field");
3543
- if (b.shift)
3544
- throw new Error("Shift-modified bindings are not supported for char bindings");
3545
- array.push(new NormalizedBinding(baseFlags | 1, b.char, bind(b.run)));
3546
- }
3650
+ let key = b[platform] || b.key;
3651
+ if (b.char) {
3547
3652
  if (key)
3548
- array.push(new NormalizedBinding(baseFlags | 2, normalizeKeyName(key, platform), bind(b.run)));
3549
- if (key && b.shift)
3550
- array.push(new NormalizedBinding(baseFlags | 2, normalizeKeyName("Shift-" + key, platform), bind(b.shift)));
3551
- if (b.any)
3552
- array.push(new NormalizedBinding(4, "", b.any));
3653
+ throw new Error("A key binding may not provide both a char and a key field");
3654
+ if (b.shift)
3655
+ throw new Error("Shift-modified bindings are not supported for char bindings");
3656
+ map.push(new NormalizedBinding(baseFlags | 1, b.char, bind(b.run)));
3553
3657
  }
3658
+ if (key)
3659
+ map.push(new NormalizedBinding(baseFlags | 2, normalizeKeyName(key, platform), bind(b.run)));
3660
+ if (key && b.shift)
3661
+ map.push(new NormalizedBinding(baseFlags | 2, normalizeKeyName("Shift-" + key, platform), bind(b.shift)));
3662
+ if (b.any)
3663
+ map.push(new NormalizedBinding(4, "", b.any));
3554
3664
  }
3555
- return scopes;
3665
+ return map;
3556
3666
  }
3557
- function runHandlers(map, event, wg, scope) {
3558
- let handlers = map[scope];
3559
- if (!handlers)
3560
- return false;
3667
+ function runHandlers(map, event, wg) {
3561
3668
  let key = event.key, charCode = key.codePointAt(0);
3562
3669
  let altGr = event.getModifierState("AltGraph"), fromCode = charKeyCodes[event.keyCode];
3563
3670
  let isChar = codePointSize(charCode) == key.length;
3564
3671
  let char = isChar ? String.fromCodePoint(charCode) : null;
3565
- let base = modifiers(key, event);
3566
- let fallback = isChar && !altGr && fromCode && fromCode != base ? modifiers(fromCode, event) : null;
3672
+ let base = modifiers(key, event, currentPlatform);
3673
+ let fallback = isChar && !altGr && fromCode && fromCode != base ? modifiers(fromCode, event, currentPlatform) : null;
3567
3674
  let handled = false, didMatch = false, allowDefault = false;
3568
- for (let binding of handlers) {
3675
+ for (let binding of map) {
3569
3676
  let matched = ((binding.flags & 1) && (altGr || !event.ctrlKey && !event.metaKey) && binding.name == char) ||
3570
3677
  ((binding.flags & 2) && (binding.name == base || binding.name == fallback)) ||
3571
3678
  (binding.flags & 4);
@@ -3612,6 +3719,11 @@ const eventObserver = /*@__PURE__*/GardState.Facet.define({
3612
3719
  return result;
3613
3720
  }
3614
3721
  });
3722
+ const compositionInputTypes = /*@__PURE__*/(() => new Set([
3723
+ "insertCompositionText",
3724
+ "deleteCompositionText",
3725
+ "insertFromComposition"
3726
+ ]))();
3615
3727
  class InputState {
3616
3728
  wg;
3617
3729
  shiftKey = false;
@@ -3631,6 +3743,7 @@ class InputState {
3631
3743
  mouseSelection = null;
3632
3744
  draggedContent = null;
3633
3745
  notifiedFocused;
3746
+ suppressEvents = false;
3634
3747
  pendingInputEvent = null;
3635
3748
  domDoc;
3636
3749
  _domMapping;
@@ -3646,6 +3759,8 @@ class InputState {
3646
3759
  this._domMapping = ChangeSet.empty(this.domDoc.length);
3647
3760
  }
3648
3761
  handleEvent(event) {
3762
+ if (this.suppressEvents)
3763
+ return;
3649
3764
  if (!eventBelongsToEditor(this.wg, event) || this.ignoreDuringComposition(event))
3650
3765
  return;
3651
3766
  if (event.type == "keydown" && this.keydown(event))
@@ -3767,11 +3882,12 @@ class InputState {
3767
3882
  }
3768
3883
  }
3769
3884
  let command = inputTypeCommands[type];
3770
- if ((type == "deleteContentBackward" || type == "deleteContentForward") && range &&
3771
- range.from != range.to && !this.unflushedSelection && (sel.empty
3772
- ? !isSingleChar(this.domDoc, data.domRange.from, data.domRange.to) ||
3773
- sel.head != (type == "deleteContentBackward" ? range.to : range.from)
3774
- : sel.from != range.from || sel.to != range.to)) {
3885
+ if (isDeletionInputEvent(type) && range &&
3886
+ (!command ||
3887
+ (range.from != range.to && !this.unflushedSelection && (sel.empty
3888
+ ? !isSingleChar(this.domDoc, data.domRange.from, data.domRange.to) ||
3889
+ sel.head != (type == "deleteContentBackward" ? range.to : range.from)
3890
+ : sel.from != range.from || sel.to != range.to)))) {
3775
3891
  wg.dispatch({ changes: { from: range.from, to: range.to, fit: true }, userEvent: "delete" });
3776
3892
  }
3777
3893
  else if (command) {
@@ -3782,13 +3898,28 @@ class InputState {
3782
3898
  let { from, to } = this.unflushedSelection ? wg.state.selection : range;
3783
3899
  Command.dispatch(wg, insertText, { from, to, insert, userEvent: "input.type" });
3784
3900
  }
3901
+ else if (type == "insertFromPaste") {
3902
+ let { from, to } = this.unflushedSelection ? wg.state.selection.replacementRange : range;
3903
+ let slice = readClipboard(wg.state, event.dataTransfer, wg.state.doc.resolve(from), wg.inputState.shiftKey);
3904
+ let isSel = from == wg.state.selection.replacementRange.from && to == wg.state.selection.replacementRange.to;
3905
+ if (isSel && wg.state.facet(pasteHandler).some(h => h(wg, event, slice || Slice.empty)))
3906
+ return;
3907
+ if (slice) {
3908
+ wg.dispatch({
3909
+ changes: { from, to, insert: slice, fit: true },
3910
+ selection: isSel ? (cx, changes) => GardSelection.near(cx, changes.mapPos(to, 1), -1) : undefined,
3911
+ userEvent: "input.paste",
3912
+ scrollIntoView: isSel
3913
+ });
3914
+ }
3915
+ }
3785
3916
  else if ((type == "insertReplacementText" || type == "insertFromYank")) {
3786
3917
  let read = readClipboard(wg.state, event.dataTransfer, wg.state.sel.head, true);
3787
3918
  let { from, to } = this.unflushedSelection ? wg.state.selection : range;
3788
3919
  let sel = wg.state.selection, touchesSel = from <= sel.to && to >= sel.from;
3789
3920
  if (read)
3790
3921
  wg.dispatch({
3791
- changes: { from, to, insert: read.slice, fit: read.context },
3922
+ changes: { from, to, insert: read, fit: true },
3792
3923
  selection: touchesSel ? (cx, changes) => {
3793
3924
  return GardSelection.near(cx, changes.mapPos(to, 1), -1);
3794
3925
  } : undefined,
@@ -3796,14 +3927,17 @@ class InputState {
3796
3927
  userEvent: "insert.replacementText"
3797
3928
  });
3798
3929
  }
3799
- else if (type == "insertCompositionText") {
3800
- let compositionStart = !wg.inputState.composing.changes;
3801
- wg.inputState.composing.changes++;
3930
+ else if (compositionInputTypes.has(type)) {
3931
+ let compositionStart = true;
3932
+ if (wg.inputState.composing) {
3933
+ compositionStart = !wg.inputState.composing.changes;
3934
+ wg.inputState.composing.changes++;
3935
+ }
3802
3936
  let sel = wg.observer.selectionRange;
3803
3937
  if (!sel.focusNode)
3804
3938
  return false;
3805
3939
  let userEvent = "input.type.compose" + (compositionStart ? ".start" : "");
3806
- Command.dispatch(wg, insertText, { from: range.from, to: range.to, insert: event.data, userEvent });
3940
+ Command.dispatch(wg, insertText, { from: range.from, to: range.to, insert: event.data || "", userEvent });
3807
3941
  }
3808
3942
  else if (type == "formatSetBlockTextDirection") {
3809
3943
  if (event.data == "ltr" || event.data == "rtl")
@@ -3856,6 +3990,58 @@ class InputState {
3856
3990
  return !tileBefore ? before : !tileAfter ? after : prev == after ? after : before;
3857
3991
  }
3858
3992
  }
3993
+ getCompositionInfo(wg) {
3994
+ let wrap = this.wrappingComposition;
3995
+ if (wrap) {
3996
+ let sel = wg.state.selection.head;
3997
+ return {
3998
+ fromB: sel, toB: sel,
3999
+ text: "",
4000
+ target: null,
4001
+ wrapCursor: wrap
4002
+ };
4003
+ }
4004
+ let comp = this.composing;
4005
+ if (!comp || !(comp.target = this.findComposition(comp.target)))
4006
+ return null;
4007
+ let value = comp.target.nodeValue;
4008
+ let fromB = this.posAtDOM(comp.target, 0, 1), toB = fromB + value.length;
4009
+ let disrupted = false;
4010
+ this.domMapping.iterChanges((fA, tA, fB, tB) => {
4011
+ if (fB < toB && tB > fromB)
4012
+ disrupted = true;
4013
+ });
4014
+ return disrupted ? null : { fromB, toB, text: value, target: comp.target };
4015
+ }
4016
+ clearComposition() {
4017
+ let comp = this.composing;
4018
+ this.composing = null;
4019
+ this.compositionPendingKey = true;
4020
+ this.compositionEndedAt = Date.now();
4021
+ if (!comp.target)
4022
+ return false;
4023
+ let pos = this.posAtDOM(comp.target, 0, 1);
4024
+ this.wg.observer.addDirtyRange(pos, pos + comp.target.nodeValue.length);
4025
+ return true;
4026
+ }
4027
+ abortComposition() {
4028
+ if (!this.composing || !this.wg.hasFocus)
4029
+ return;
4030
+ this.clearComposition();
4031
+ this.wg.win.setTimeout(() => {
4032
+ this.suppressEvents = true;
4033
+ try {
4034
+ getSelection(this.wg.root)?.collapse(document.body, 0);
4035
+ let dummy = this.wg.win.document.body.appendChild(document.createElement("input"));
4036
+ dummy.focus();
4037
+ dummy.remove();
4038
+ this.wg.focus();
4039
+ }
4040
+ finally {
4041
+ this.suppressEvents = false;
4042
+ }
4043
+ }, 0);
4044
+ }
3859
4045
  recordTouch(e) {
3860
4046
  this.lastTouchTime = Date.now();
3861
4047
  this.lastTouchEvent = e;
@@ -4072,32 +4258,38 @@ function basicMouseSelection(wg, event) {
4072
4258
  get(event, extend) {
4073
4259
  let cur = queryPos(wg, event), range = rangeForClick(wg, cur, type), { from, to } = range;
4074
4260
  if (extend) {
4261
+ let startAnchor = !(startSel instanceof GardSelection.Text) || startSel.directional ? startSel.anchor
4262
+ : from < startSel.from ? startSel.to : startSel.from;
4075
4263
  if (from < startSel.anchor)
4076
- return GardSelection.range(startSel.anchor, from, from < to ? 1 : cur.side);
4264
+ return GardSelection.range(startAnchor, from, from < to ? 1 : cur.side);
4077
4265
  else
4078
- return GardSelection.range(startSel.anchor, to, from < to ? -1 : cur.side);
4266
+ return GardSelection.range(startAnchor, to, from < to ? -1 : cur.side);
4079
4267
  }
4268
+ let { anchor, head } = range;
4080
4269
  if (start.pos != cur.pos) {
4081
4270
  let startRange = rangeForClick(wg, start, type);
4082
- from = Math.min(startRange.from, from);
4083
- to = Math.max(startRange.to, to);
4271
+ let from = Math.min(range.from, startRange.from), to = Math.max(range.to, startRange.to);
4272
+ if (startRange.from < range.from) {
4273
+ anchor = from;
4274
+ head = to;
4275
+ }
4276
+ else {
4277
+ anchor = to;
4278
+ head = from;
4279
+ }
4084
4280
  }
4085
- return from == range.from && to == range.to ? range : GardSelection.range(from, to, cur.side);
4281
+ return anchor == range.anchor && head == range.head ? range
4282
+ : GardSelection.range(anchor, head, head == cur.pos ? cur.side : undefined);
4086
4283
  }
4087
4284
  };
4088
4285
  }
4089
4286
  const dropHandler = /*@__PURE__*/GardState.Facet.define();
4090
4287
  const pasteHandler = /*@__PURE__*/GardState.Facet.define();
4091
- function selectionSlice(state) {
4092
- return {
4093
- slice: state.doc.slice(state.selection.from, state.selection.to),
4094
- context: state.doc.contextAt(state.selection.from)
4095
- };
4096
- }
4097
4288
  function copy(wg, event) {
4098
4289
  let { state } = wg;
4099
- if (!state.selection.empty && event.clipboardData) { let { slice, context } = selectionSlice(state);
4100
- writeClipboard(state, slice, context, event.clipboardData);
4290
+ if (!state.selection.empty && event.clipboardData) {
4291
+ let slice = state.selection.toSlice(state.doc);
4292
+ writeClipboard(state, slice, event.clipboardData);
4101
4293
  if (event.type == "cut" && !state.readOnly)
4102
4294
  wg.dispatch({
4103
4295
  changes: state.selection.ranges.map(r => ({ from: r.from, to: r.to, fit: true })),
@@ -4118,37 +4310,9 @@ function updateForFocusChange(wg) {
4118
4310
  }
4119
4311
  }, 10);
4120
4312
  }
4121
- function getCompositionInfo(wg) {
4122
- let wrap = wg.inputState.wrappingComposition;
4123
- if (wrap) {
4124
- let sel = wg.state.selection.head;
4125
- return {
4126
- fromB: sel, toB: sel,
4127
- text: "",
4128
- target: null,
4129
- wrapCursor: wrap
4130
- };
4131
- }
4132
- let comp = wg.inputState.composing;
4133
- if (!comp || !(comp.target = wg.inputState.findComposition(comp.target)))
4134
- return null;
4135
- let value = comp.target.nodeValue;
4136
- let pos = wg.inputState.posAtDOM(comp.target, 0);
4137
- return {
4138
- fromB: pos, toB: pos + value.length,
4139
- text: value,
4140
- target: comp.target
4141
- };
4142
- }
4143
4313
  function compositionEnd(wg) {
4144
- let comp = wg.inputState.composing;
4145
- wg.inputState.composing = null;
4146
- wg.inputState.compositionEndedAt = Date.now();
4147
- if (comp && comp.target) {
4148
- let pos = wg.inputState.posAtDOM(comp.target, 0);
4149
- wg.observer.addDirtyRange(pos, pos + comp.target.nodeValue.length);
4314
+ if (wg.inputState.composing && wg.inputState.clearComposition())
4150
4315
  wg.flush();
4151
- }
4152
4316
  }
4153
4317
  function compositionUpdate(wg, event) {
4154
4318
  if (!wg.inputState.composing) {
@@ -4185,7 +4349,7 @@ function inEditableDOM(wg, node) {
4185
4349
  let tile = wg.docTile.nearest(node);
4186
4350
  return tile ? !(tile.isPoint || tile instanceof WidgetTile) : false;
4187
4351
  }
4188
- function isDeletionInputEvent(type) { return /^delete(Content|Word)/.test(type); }
4352
+ function isDeletionInputEvent(type) { return /^delete(Content|Word|ByComposition)/.test(type); }
4189
4353
  const inputTypeCommands = /*@__PURE__*/(() => ({
4190
4354
  historyUndo: undo,
4191
4355
  historyRedo: redo,
@@ -4218,7 +4382,7 @@ const baseHandlers = {
4218
4382
  keydown(wg, event) {
4219
4383
  if ((browser.ios || browser.android) && (event.key == "Backspace" || event.key == "Enter"))
4220
4384
  return false;
4221
- return KeyBinding.runScopeHandlers(wg, event, "editor");
4385
+ return KeyBinding.handle(wg, event);
4222
4386
  },
4223
4387
  mousedown(wg, event) {
4224
4388
  wg.inputState.shiftKey = event.shiftKey;
@@ -4257,8 +4421,8 @@ const baseHandlers = {
4257
4421
  inputState.mouseSelection.dragging = true;
4258
4422
  inputState.draggedContent = selection;
4259
4423
  if (event.dataTransfer) {
4260
- let { slice, context } = selectionSlice(wg.state);
4261
- writeClipboard(wg.state, slice, context, event.dataTransfer);
4424
+ let slice = wg.state.selection.toSlice(wg.state.doc);
4425
+ writeClipboard(wg.state, slice, event.dataTransfer);
4262
4426
  event.dataTransfer.effectAllowed = "copyMove";
4263
4427
  }
4264
4428
  return false;
@@ -4272,52 +4436,31 @@ const baseHandlers = {
4272
4436
  drop(wg, event) {
4273
4437
  if (!event.dataTransfer || wg.state.readOnly)
4274
4438
  return true;
4275
- let content = readClipboard(wg.state, event.dataTransfer, wg.state.sel.head, false);
4276
- if (!content)
4439
+ let slice = readClipboard(wg.state, event.dataTransfer, wg.state.sel.head, false);
4440
+ if (!slice)
4277
4441
  return false;
4278
4442
  let dropPos = wg.posAtCoords({ x: event.clientX, y: event.clientY }).pos;
4279
4443
  let { draggedContent } = wg.inputState;
4280
4444
  let del = draggedContent && dragMovesSelection(wg, event)
4281
4445
  ? { from: draggedContent.from, to: draggedContent.to } : null;
4282
- if (wg.state.facet(dropHandler).some(f => f(wg, event, dropPos, del, content.slice, content.context)))
4446
+ if (wg.state.facet(dropHandler).some(f => f(wg, event, dropPos, del, slice)))
4283
4447
  return true;
4284
- let ins = { from: dropPos, insert: content.slice, fit: content.context };
4448
+ let ins = { from: dropPos, insert: slice, fit: true };
4285
4449
  let changes = ChangeSet.create(wg.state.doc, del ? [del, ins] : ins);
4286
4450
  wg.focus();
4287
4451
  wg.dispatch({
4288
4452
  changes,
4289
- selection: GardSelection.range(changes.mapPos(dropPos, -1), changes.mapPos(dropPos, 1)),
4453
+ selection: GardSelection.undirectionalRange(changes.mapPos(dropPos, -1), changes.mapPos(dropPos, 1)),
4290
4454
  userEvent: del ? "move.drop" : "input.drop"
4291
4455
  });
4292
4456
  wg.inputState.draggedContent = null;
4293
4457
  return true;
4294
4458
  },
4295
- paste(wg, event) {
4296
- if (wg.state.readOnly || !event.clipboardData)
4297
- return true;
4298
- let { state } = wg;
4299
- let content = readClipboard(state, event.clipboardData, state.sel.head, wg.inputState.shiftKey);
4300
- if (wg.state.facet(pasteHandler).some(h => h(wg, event, content ? content.slice : Slice.empty, content ? content.context : [])))
4301
- return true;
4302
- if (content) { wg.dispatch({
4303
- changes: {
4304
- from: state.selection.from,
4305
- to: state.selection.to,
4306
- insert: content.slice,
4307
- fit: content.context
4308
- },
4309
- selection: (cx, changes) => GardSelection.near(cx, changes.mapPos(state.selection.to, 1), -1),
4310
- userEvent: "input.paste",
4311
- scrollIntoView: true
4312
- });
4313
- }
4314
- return true;
4315
- },
4316
4459
  beforeinput(wg, event) {
4317
4460
  let type = event.inputType;
4318
4461
  if (browser.safari && type == "insertText" && wg.inputState.composing)
4319
4462
  compositionEnd(wg);
4320
- if (type == "insertCompositionText" && !wg.inputState.composing)
4463
+ if (compositionInputTypes.has(type) && !wg.inputState.composing)
4321
4464
  wg.inputState.composing = { changes: 0, target: null };
4322
4465
  let data = {
4323
4466
  inputType: type,
@@ -4333,7 +4476,7 @@ const baseHandlers = {
4333
4476
  }
4334
4477
  wg.inputState.beforeInput(event, wg.inputState.pendingInputEvent = data);
4335
4478
  wg.scheduleFlush();
4336
- let allow = type == "insertCompositionText" ||
4479
+ let allow = compositionInputTypes.has(type) ||
4337
4480
  editable && (type == "insertText" || isDeletionInputEvent(type) &&
4338
4481
  data.domRange && inlineContext(wg.inputState.domDoc, data.domRange));
4339
4482
  return !allow;
@@ -4344,8 +4487,8 @@ const baseHandlers = {
4344
4487
  return false;
4345
4488
  wg.inputState.pendingInputEvent = null;
4346
4489
  let change;
4347
- if (event.inputType == "insertCompositionText" || event.inputType == "insertText") {
4348
- change = { from: pending.domRange.from, to: pending.domRange.to, insert: [Leaf.text(pending.data)] };
4490
+ if (compositionInputTypes.has(event.inputType) || event.inputType == "insertText") {
4491
+ change = { from: pending.domRange.from, to: pending.domRange.to, insert: [Leaf.text(pending.data || "")] };
4349
4492
  }
4350
4493
  else if (isDeletionInputEvent(event.inputType)) {
4351
4494
  change = { from: pending.domRange.from, to: pending.domRange.to };
@@ -4372,7 +4515,6 @@ const baseObservers = {
4372
4515
  updateForFocusChange(wg);
4373
4516
  },
4374
4517
  blur(wg) {
4375
- wg.observer.clearSelectionRange();
4376
4518
  updateForFocusChange(wg);
4377
4519
  },
4378
4520
  compositionstart: compositionUpdate,
@@ -4456,15 +4598,18 @@ class ViewState {
4456
4598
  measure(wg) {
4457
4599
  let dom = wg.contentDOM, style = window.getComputedStyle(dom);
4458
4600
  this.styleLTR = style.direction == "ltr";
4459
- let domRect = dom.getBoundingClientRect();
4460
- this.contentDOMHeight = domRect.height;
4461
4601
  let result = 0;
4602
+ let domRect = dom.getBoundingClientRect();
4603
+ if (Math.abs(this.contentDOMHeight - domRect.height) > 0.5) {
4604
+ result |= 2;
4605
+ this.contentDOMHeight = domRect.height;
4606
+ }
4462
4607
  if (this.editorWidth != wg.scrollDOM.clientWidth) {
4463
4608
  this.editorWidth = wg.scrollDOM.clientWidth;
4464
4609
  result |= 2;
4465
4610
  }
4466
4611
  let contentWidth = domRect.width;
4467
- if (this.contentDOMWidth != contentWidth || this.editorHeight != wg.scrollDOM.clientHeight ||
4612
+ if (Math.abs(this.contentDOMWidth - contentWidth) > 0.5 || this.editorHeight != wg.scrollDOM.clientHeight ||
4468
4613
  this.editorOffset != wg.scrollDOM.offsetTop) {
4469
4614
  this.contentDOMWidth = domRect.width;
4470
4615
  this.editorHeight = wg.scrollDOM.clientHeight;
@@ -4653,7 +4798,7 @@ function getCursorInfo(wg, plugin, cont) {
4653
4798
  return finish(null);
4654
4799
  let target = sel.head.parent.node.isDoc ? wg.contentDOM : wg.nodeDOM(sel.head.parent.before);
4655
4800
  wg.scheduleDOMWrite(() => {
4656
- let temp = renderMarks(marks, "M");
4801
+ let temp = renderMarks(wg.state, marks, "M");
4657
4802
  temp.style.position = "absolute";
4658
4803
  wg.observer.ignore(() => target.insertBefore(temp, target.firstChild));
4659
4804
  wg.scheduleDOMRead(() => {
@@ -4789,8 +4934,8 @@ class Wordgard {
4789
4934
  this.lastFlush = Date.now();
4790
4935
  try {
4791
4936
  let domChanges = this.observer.takeDirty();
4792
- this.viewState.flush();
4793
4937
  this.observer.ignore(() => this.runUpdate(update, domChanges));
4938
+ this.viewState.flush();
4794
4939
  domChanges = null;
4795
4940
  for (let i = 0;; i++) {
4796
4941
  if (i > 5) {
@@ -4856,7 +5001,9 @@ class Wordgard {
4856
5001
  scrollRectIntoView(this.scrollDOM, targetRect, assoc, target.spec.x, target.spec.y, Math.max(Math.min(target.spec.xMargin, offsetWidth), -offsetWidth), Math.max(Math.min(target.spec.yMargin, offsetHeight), -offsetHeight), this.state.textLTR);
4857
5002
  }
4858
5003
  runUpdate(update, domChanges) {
4859
- let composition = this.composing ? getCompositionInfo(this) : null;
5004
+ let composition = this.composing ? this.inputState.getCompositionInfo(this) : null;
5005
+ if (this.composing && !composition)
5006
+ this.inputState.abortComposition();
4860
5007
  let changes = domChanges ? addUpdated(update.changes.sections, domChanges) : update.changes.sections;
4861
5008
  let prevDocTile = this.docTile;
4862
5009
  if (!update.empty) {
@@ -5035,7 +5182,7 @@ class Wordgard {
5035
5182
  if (this.connected)
5036
5183
  this.observer.ignore(() => {
5037
5184
  this.contentDOM.focus({ preventScroll: true });
5038
- if (this.willFlush && this.flushing == 0)
5185
+ if (this.willFlush && this.flushing == 0 && this.viewState.pending.some(tr => tr.docChanged))
5039
5186
  this.flush();
5040
5187
  setDOMSelection(this);
5041
5188
  });
@@ -5365,844 +5512,719 @@ class PluginInstance {
5365
5512
  }
5366
5513
  }
5367
5514
 
5368
- function offset(add, offset) {
5369
- return (v, from, to) => add(v, from + offset, to == null ? undefined : to + offset);
5370
- }
5371
- function init(type, create, source, doc) {
5372
- return create(add => {
5373
- doc.iterate((node, pos) => {
5374
- if (node.isPlot && doc.schema.matchNode(node.type, type))
5375
- source(node, pos + 1, add);
5376
- });
5377
- });
5378
- }
5379
- function refreshByPred(type, source, doc, deco, pred) {
5380
- let recreate = [];
5381
- doc.iterate((node, pos) => {
5382
- if (doc.schema.matchNode(node.type, type) && pred(node))
5383
- recreate.push({
5384
- from: pos, to: pos + node.length,
5385
- add: add => source(node, pos + 1, add)
5386
- });
5387
- });
5388
- return recreate.length ? deco.modify({ replace: recreate }) : deco;
5389
- }
5390
- function update(config, create, source, deco, tr) {
5391
- let doc = tr.newDoc;
5392
- let refresh = config.refresh && config.refresh(tr);
5393
- if (refresh === true)
5394
- return init(config.type, create, source, doc);
5395
- if (refresh)
5396
- deco = refreshByPred(config.type, source, tr.startState.doc, deco, refresh);
5397
- if (!tr.docChanged && !refresh)
5398
- return deco;
5399
- let recreate = [];
5400
- let clear = [];
5401
- tr.changes.iterChangedRanges((fromA, toA, fromB, toB) => {
5402
- let covered = false;
5403
- doc.iterate(fromB, toB, (node, pos) => {
5404
- if (doc.schema.matchNode(node.type, config.type)) {
5405
- recreate.push({
5406
- from: pos, to: pos + node.length,
5407
- add: add => source(node, pos + 1, add)
5408
- });
5409
- if (pos < toB && pos + node.length > toB)
5410
- covered = true;
5411
- return false;
5515
+ const Outside = "-10000px";
5516
+ class TooltipViewManager {
5517
+ facet;
5518
+ createTooltipView;
5519
+ removeTooltipView;
5520
+ input;
5521
+ tooltips;
5522
+ tooltipViews;
5523
+ constructor(wg, facet, createTooltipView, removeTooltipView) {
5524
+ this.facet = facet;
5525
+ this.createTooltipView = createTooltipView;
5526
+ this.removeTooltipView = removeTooltipView;
5527
+ this.input = wg.state.facet(facet);
5528
+ this.tooltips = this.input.filter(t => t);
5529
+ let prev = null;
5530
+ this.tooltipViews = this.tooltips.map(t => prev = createTooltipView(t, prev));
5531
+ }
5532
+ update(update, above) {
5533
+ let input = update.state.facet(this.facet);
5534
+ let tooltips = input.filter(x => x);
5535
+ if (input === this.input) {
5536
+ for (let t of this.tooltipViews)
5537
+ if (t.update)
5538
+ t.update(update);
5539
+ return false;
5540
+ }
5541
+ let tooltipViews = [], newAbove = above ? [] : null;
5542
+ for (let i = 0; i < tooltips.length; i++) {
5543
+ let tip = tooltips[i], known = -1;
5544
+ if (!tip)
5545
+ continue;
5546
+ for (let i = 0; i < this.tooltips.length; i++) {
5547
+ let other = this.tooltips[i];
5548
+ if (other && other.create == tip.create)
5549
+ known = i;
5550
+ }
5551
+ if (known < 0) {
5552
+ tooltipViews[i] = this.createTooltipView(tip, i ? tooltipViews[i - 1] : null);
5553
+ if (newAbove)
5554
+ newAbove[i] = !!tip.above;
5555
+ }
5556
+ else {
5557
+ let tooltipView = tooltipViews[i] = this.tooltipViews[known];
5558
+ if (newAbove)
5559
+ newAbove[i] = above[known];
5560
+ if (tooltipView.update)
5561
+ tooltipView.update(update);
5412
5562
  }
5413
- });
5414
- if (!covered) {
5415
- let end = tr.startState.doc.resolve(toA);
5416
- let open = end.matchingParent(p => doc.schema.matchNode(p.type, config.type));
5417
- if (open && open.before >= fromA)
5418
- clear.push({ from: open.before, to: open.after });
5419
5563
  }
5420
- });
5421
- if (clear.length)
5422
- deco = deco.modify({ replace: clear });
5423
- deco = deco.map(tr.changes, recreate);
5424
- return deco;
5425
- }
5426
- function decoratePlots(config) {
5427
- let result = [];
5428
- let { type, points, ranges } = config;
5429
- if (points) {
5430
- let source = (plot, start, add) => points(plot, offset(add, start));
5431
- let field = GardState.Field.define({
5432
- create(state) { return init(type, PointSet.create, source, state.doc); },
5433
- update(value, tr) { return update(config, PointSet.create, source, value, tr); }
5434
- });
5435
- result.push(field, Decoration.Point.source.of(s => s.field(field)));
5436
- }
5437
- if (ranges) {
5438
- let source = (plot, start, add) => ranges(plot, offset(add, start));
5439
- let field = GardState.Field.define({
5440
- create(state) { return init(type, RangeSet.create, source, state.doc); },
5441
- update(value, tr) { return update(config, RangeSet.create, source, value, tr); }
5442
- });
5443
- result.push(field, Decoration.Range.source.of(s => s.field(field)));
5444
- }
5445
- return result;
5446
- }
5447
-
5448
- const panelConfig = /*@__PURE__*/GardState.Facet.define({
5449
- combine(configs) {
5450
- let topContainer, bottomContainer;
5451
- for (let c of configs) {
5452
- topContainer ||= c.topContainer;
5453
- bottomContainer ||= c.bottomContainer;
5564
+ for (let t of this.tooltipViews)
5565
+ if (tooltipViews.indexOf(t) < 0) {
5566
+ this.removeTooltipView(t);
5567
+ if (update.editor.connected)
5568
+ t.disconnect?.(update.editor);
5569
+ t.remove?.(update.editor);
5570
+ }
5571
+ if (above) {
5572
+ newAbove.forEach((val, i) => above[i] = val);
5573
+ above.length = newAbove.length;
5454
5574
  }
5455
- return { topContainer, bottomContainer };
5575
+ this.input = input;
5576
+ this.tooltips = tooltips;
5577
+ this.tooltipViews = tooltipViews;
5578
+ return true;
5456
5579
  }
5580
+ }
5581
+ const tooltipConfig = /*@__PURE__*/GardState.Facet.define({
5582
+ combine: values => ({
5583
+ position: browser.ios ? "absolute" : values.find(conf => conf.position)?.position || "fixed",
5584
+ parent: values.find(conf => conf.parent)?.parent || null,
5585
+ tooltipSpace: values.find(conf => conf.tooltipSpace)?.tooltipSpace || (wg => windowRect(wg.win)),
5586
+ })
5457
5587
  });
5458
- const panelPlugin = /*@__PURE__*/Wordgard.Plugin.fromClass(class {
5459
- input;
5460
- specs;
5461
- panels;
5462
- top;
5463
- bottom;
5588
+ const knownHeight = /*@__PURE__*/(() => new WeakMap())();
5589
+ const tooltipPlugin = /*@__PURE__*/Wordgard.Plugin.fromClass(class {
5590
+ wg;
5591
+ manager;
5592
+ above = [];
5593
+ inView = true;
5594
+ position;
5595
+ madeAbsolute = false;
5596
+ parent;
5597
+ classes;
5598
+ intersectionObserver;
5599
+ resizeObserver;
5600
+ lastTransaction = 0;
5601
+ measureTimeout = -1;
5464
5602
  constructor(wg) {
5465
- this.input = wg.state.facet(Panel.show);
5466
- this.specs = this.input.filter(s => s);
5467
- this.panels = this.specs.map(spec => spec(wg));
5468
- for (let p of this.panels)
5469
- p.dom.classList.add("wg-panel");
5470
- let conf = wg.state.facet(panelConfig);
5471
- this.top = new PanelGroup(wg, true, conf.topContainer);
5472
- this.bottom = new PanelGroup(wg, false, conf.bottomContainer);
5473
- this.top.sync(this.panels.filter(p => p.top), wg);
5474
- this.bottom.sync(this.panels.filter(p => !p.top), wg);
5603
+ this.wg = wg;
5604
+ let config = wg.state.facet(tooltipConfig);
5605
+ this.position = config.position;
5606
+ this.parent = config.parent;
5607
+ this.classes = wg.themeClasses;
5608
+ this.createContainer();
5609
+ this.measure = this.measure.bind(this);
5610
+ this.resizeObserver = typeof ResizeObserver == "function" ? new ResizeObserver(() => this.measureSoon()) : null;
5611
+ this.manager = new TooltipViewManager(wg, Tooltip.show, (t, p) => this.createTooltip(t, p), t => {
5612
+ if (this.resizeObserver)
5613
+ this.resizeObserver.unobserve(t.dom);
5614
+ t.dom.remove();
5615
+ });
5616
+ this.above = this.manager.tooltips.map(t => !!t.above);
5617
+ this.intersectionObserver = typeof IntersectionObserver == "function" ? new IntersectionObserver(entries => {
5618
+ if (Date.now() > this.lastTransaction - 50 &&
5619
+ entries.length > 0 && entries[entries.length - 1].intersectionRatio < 1)
5620
+ this.measureSoon();
5621
+ }, { threshold: [1] }) : null;
5622
+ this.observeIntersection();
5623
+ this.maybeMeasure();
5624
+ }
5625
+ createContainer() {
5626
+ if (this.parent) {
5627
+ this.container = document.createElement("wg-tooltip-root");
5628
+ this.container.style.position = "relative";
5629
+ this.container.className = this.wg.themeClasses;
5630
+ this.parent.appendChild(this.container);
5631
+ }
5632
+ else {
5633
+ this.container = this.wg.dom;
5634
+ }
5635
+ }
5636
+ observeIntersection() {
5637
+ if (this.intersectionObserver && this.wg.connected) {
5638
+ this.intersectionObserver.disconnect();
5639
+ for (let tooltip of this.manager.tooltipViews)
5640
+ this.intersectionObserver.observe(tooltip.dom);
5641
+ }
5642
+ }
5643
+ measureSoon() {
5644
+ if (this.measureTimeout < 0)
5645
+ this.measureTimeout = setTimeout(() => {
5646
+ this.measureTimeout = -1;
5647
+ this.maybeMeasure();
5648
+ }, 50);
5475
5649
  }
5476
5650
  update(update) {
5477
- let conf = update.state.facet(panelConfig);
5478
- if (this.top.container != conf.topContainer) {
5479
- this.top.sync([], update.editor);
5480
- this.top = new PanelGroup(update.editor, true, conf.topContainer);
5651
+ if (update.transactions.length)
5652
+ this.lastTransaction = Date.now();
5653
+ let updated = this.manager.update(update, this.above);
5654
+ if (updated)
5655
+ this.observeIntersection();
5656
+ let shouldMeasure = updated || update.geometryChanged;
5657
+ let newConfig = update.state.facet(tooltipConfig);
5658
+ if (newConfig.position != this.position && !this.madeAbsolute) {
5659
+ this.position = newConfig.position;
5660
+ for (let t of this.manager.tooltipViews)
5661
+ t.dom.style.position = this.position;
5662
+ shouldMeasure = true;
5481
5663
  }
5482
- if (this.bottom.container != conf.bottomContainer) {
5483
- this.bottom.sync([], update.editor);
5484
- this.bottom = new PanelGroup(update.editor, false, conf.bottomContainer);
5664
+ if (newConfig.parent != this.parent) {
5665
+ if (this.parent)
5666
+ this.container.remove();
5667
+ this.parent = newConfig.parent;
5668
+ this.createContainer();
5669
+ for (let t of this.manager.tooltipViews)
5670
+ this.container.appendChild(t.dom);
5671
+ shouldMeasure = true;
5485
5672
  }
5486
- this.top.syncClasses();
5487
- this.bottom.syncClasses();
5488
- let input = update.state.facet(Panel.show);
5489
- if (input != this.input) {
5490
- let specs = input.filter(x => x);
5491
- let panels = [], top = [], bottom = [], mount = [];
5492
- for (let spec of specs) {
5493
- let known = this.specs.indexOf(spec), panel;
5494
- if (known < 0) {
5495
- panel = spec(update.editor);
5496
- mount.push(panel);
5497
- }
5498
- else {
5499
- panel = this.panels[known];
5500
- if (panel.update)
5501
- panel.update(update);
5502
- }
5503
- panels.push(panel);
5504
- (panel.top ? top : bottom).push(panel);
5505
- }
5506
- this.specs = specs;
5507
- this.panels = panels;
5508
- this.top.sync(top, update.editor);
5509
- this.bottom.sync(bottom, update.editor);
5510
- for (let p of mount) {
5511
- p.dom.classList.add("wg-panel");
5512
- if (p.connect && update.editor.connected)
5513
- p.connect(update.editor);
5514
- }
5673
+ else if (this.parent && this.wg.themeClasses != this.classes) {
5674
+ this.classes = this.container.className = this.wg.themeClasses;
5515
5675
  }
5516
- else {
5517
- for (let p of this.panels)
5518
- if (p.update)
5519
- p.update(update);
5676
+ if (shouldMeasure)
5677
+ this.maybeMeasure();
5678
+ }
5679
+ createTooltip(tooltip, prev) {
5680
+ let tooltipView = tooltip.create(this.wg);
5681
+ let before = prev ? prev.dom : null;
5682
+ tooltipView.dom.classList.add("wg-tooltip");
5683
+ if (tooltip.arrow && !tooltipView.dom.querySelector(".wg-tooltip > wg-tooltip-arrow")) {
5684
+ let arrow = document.createElement("wg-tooltip-arrow");
5685
+ tooltipView.dom.appendChild(arrow);
5520
5686
  }
5687
+ tooltipView.dom.style.position = this.position;
5688
+ tooltipView.dom.style.top = Outside;
5689
+ tooltipView.dom.style.left = "0px";
5690
+ this.container.insertBefore(tooltipView.dom, before);
5691
+ if (this.wg.connected)
5692
+ tooltipView.connect?.(this.wg);
5693
+ if (this.resizeObserver && this.wg.connected)
5694
+ this.resizeObserver.observe(tooltipView.dom);
5695
+ return tooltipView;
5521
5696
  }
5522
5697
  connect(wg) {
5523
- for (let p of this.panels)
5524
- p.connect?.(wg);
5698
+ wg.win.addEventListener("resize", this.measureSoon = this.measureSoon.bind(this));
5699
+ for (let t of this.manager.tooltipViews) {
5700
+ t.connect?.(wg);
5701
+ if (this.resizeObserver)
5702
+ this.resizeObserver.observe(t.dom);
5703
+ }
5704
+ this.observeIntersection();
5525
5705
  }
5526
5706
  disconnect(wg) {
5527
- for (let p of this.panels)
5528
- p.disconnect?.(wg);
5529
- }
5530
- remove(wg) {
5531
- this.top.sync([], wg);
5532
- this.bottom.sync([], wg);
5533
- }
5534
- }, plugin => Wordgard.coveredMargins.of(wg => {
5535
- let value = wg.plugin(plugin);
5536
- return value && { top: value.top.scrollMargin(), bottom: value.bottom.scrollMargin() };
5537
- }));
5538
- const Panel = /*@__PURE__*/(function (Panel) {
5539
- Panel.show = GardState.Facet.define({
5540
- enables: panelPlugin
5541
- });
5542
- function get(wg, constructor) {
5543
- let plugin = wg.plugin(panelPlugin);
5544
- let index = plugin ? plugin.specs.indexOf(constructor) : -1;
5545
- return index > -1 ? plugin.panels[index] : null;
5707
+ this.wg.win.removeEventListener("resize", this.measureSoon);
5708
+ for (let t of this.manager.tooltipViews) {
5709
+ t.disconnect?.(wg);
5710
+ if (this.resizeObserver)
5711
+ this.resizeObserver.unobserve(t.dom);
5712
+ }
5713
+ if (this.intersectionObserver)
5714
+ this.intersectionObserver.disconnect();
5546
5715
  }
5547
- Panel.get = get;
5548
- function configure(config) {
5549
- return config ? [panelConfig.of(config)] : [];
5716
+ remove() {
5717
+ for (let tooltipView of this.manager.tooltipViews) {
5718
+ tooltipView.dom.remove();
5719
+ if (this.wg.connected)
5720
+ tooltipView.disconnect?.(this.wg);
5721
+ tooltipView.remove?.(this.wg);
5722
+ }
5723
+ if (this.parent)
5724
+ this.container.remove();
5725
+ clearTimeout(this.measureTimeout);
5550
5726
  }
5551
- Panel.configure = configure;
5552
- ;return Panel})({});
5553
- class PanelGroup {
5554
- wg;
5555
- top;
5556
- container;
5557
- dom = undefined;
5558
- classes = "";
5559
- panels = [];
5560
- constructor(wg, top, container) {
5561
- this.wg = wg;
5562
- this.top = top;
5563
- this.container = container;
5564
- this.syncClasses();
5727
+ measure() {
5728
+ let measure = this.readMeasure();
5729
+ this.wg.scheduleDOMWrite(() => this.writeMeasure(measure));
5565
5730
  }
5566
- sync(panels, wg) {
5567
- for (let p of this.panels)
5568
- if (!panels.includes(p)) {
5569
- if (wg.connected)
5570
- p.disconnect?.(wg);
5571
- p.remove?.(wg);
5731
+ readMeasure() {
5732
+ let scaleX = 1, scaleY = 1, makeAbsolute = false;
5733
+ if (this.position == "fixed" && this.manager.tooltipViews.length) {
5734
+ let { dom } = this.manager.tooltipViews[0];
5735
+ if (browser.safari) {
5736
+ let rect = dom.getBoundingClientRect();
5737
+ makeAbsolute = Math.abs(rect.top + 10000) > 1 || Math.abs(rect.left) > 1;
5572
5738
  }
5573
- this.panels = panels;
5574
- this.syncDOM();
5575
- }
5576
- syncDOM() {
5577
- if (this.panels.length == 0) {
5578
- if (this.dom) {
5579
- this.dom.remove();
5580
- this.dom = undefined;
5739
+ else {
5740
+ makeAbsolute = !!dom.offsetParent && dom.offsetParent != this.container.ownerDocument.body;
5581
5741
  }
5582
- return;
5583
5742
  }
5584
- if (!this.dom) {
5585
- this.dom = document.createElement("wg-panels");
5586
- this.dom.className = this.top ? "wg-panels-top" : "wg-panels-bottom";
5587
- let parent = this.container || this.wg.dom;
5588
- parent.insertBefore(this.dom, this.top ? parent.firstChild : null);
5589
- }
5590
- let curDOM = this.dom.firstChild;
5591
- for (let panel of this.panels) {
5592
- if (panel.dom.parentNode == this.dom) {
5593
- while (curDOM != panel.dom)
5594
- curDOM = rmDOM(curDOM);
5595
- curDOM = curDOM.nextSibling;
5596
- }
5597
- else {
5598
- this.dom.insertBefore(panel.dom, curDOM);
5743
+ if (makeAbsolute || this.position == "absolute") {
5744
+ let measure = this.parent || this.container, rect = measure.getBoundingClientRect();
5745
+ if (rect.width && rect.height) {
5746
+ scaleX = rect.width / measure.offsetWidth;
5747
+ scaleY = rect.height / measure.offsetHeight;
5599
5748
  }
5600
5749
  }
5601
- while (curDOM)
5602
- curDOM = rmDOM(curDOM);
5603
- }
5604
- scrollMargin() {
5605
- return !this.dom || this.container ? 0
5606
- : Math.max(0, this.top ?
5607
- this.dom.getBoundingClientRect().bottom - Math.max(0, this.wg.scrollDOM.getBoundingClientRect().top) :
5608
- Math.min(innerHeight, this.wg.scrollDOM.getBoundingClientRect().bottom) - this.dom.getBoundingClientRect().top);
5609
- }
5610
- syncClasses() {
5611
- if (!this.container || this.classes == this.wg.themeClasses)
5612
- return;
5613
- for (let cls of this.classes.split(" "))
5614
- if (cls)
5615
- this.container.classList.remove(cls);
5616
- for (let cls of (this.classes = this.wg.themeClasses).split(" "))
5617
- if (cls)
5618
- this.container.classList.add(cls);
5619
- }
5620
- }
5621
-
5622
- let nextID = 0;
5623
- function id(prefix) {
5624
- return prefix + "-" + (nextID++ % 0xffffff).toString(16);
5625
- }
5626
- const SVG = "http://www.w3.org/2000/svg";
5627
- function labelButton(wg, button, label) {
5628
- button.textContent = "";
5629
- if (!label) ;
5630
- else if (typeof label == "function" || typeof label == "string") {
5631
- let span = button.appendChild(document.createElement("span"));
5632
- span.className = "wg-button-label";
5633
- span.textContent = typeof label == "string" ? label : label(wg.state);
5634
- }
5635
- else if ("icon" in label) {
5636
- let svg = button.appendChild(document.createElementNS(SVG, "svg"));
5637
- svg.classList.add("wg-icon");
5638
- svg.setAttribute("viewBox", "0 0 100 100");
5639
- let path = svg.appendChild(document.createElementNS(SVG, "path"));
5640
- path.setAttribute("d", label.icon);
5641
- if (label.directional && !wg.state.textLTR)
5642
- svg.setAttribute("transform", "scale(-1, 1)");
5750
+ let visible = this.wg.scrollDOM.getBoundingClientRect(), margins = this.wg.getScrollMargins();
5751
+ let visLeft = visible.left + margins.left, visTop = visible.top + margins.top;
5752
+ return {
5753
+ visible: new DOMRect(visLeft, visTop, visible.right - margins.right - visLeft, visible.bottom - margins.bottom - visTop),
5754
+ parent: this.parent ? this.container.getBoundingClientRect() : this.wg.dom.getBoundingClientRect(),
5755
+ pos: this.manager.tooltips.map((t, i) => {
5756
+ let tv = this.manager.tooltipViews[i];
5757
+ return tv.getCoords ? tv.getCoords(t.pos) : this.wg.coordsAtPos(t.pos);
5758
+ }),
5759
+ size: this.manager.tooltipViews.map(({ dom }) => dom.getBoundingClientRect()),
5760
+ space: this.wg.state.facet(tooltipConfig).tooltipSpace(this.wg),
5761
+ scaleX, scaleY, makeAbsolute
5762
+ };
5643
5763
  }
5644
- }
5645
- class BarButton {
5646
- item;
5647
- dom;
5648
- flags = 0;
5649
- index = 0;
5650
- constructor(item, wg) {
5651
- this.item = item;
5652
- this.dom = document.createElement("button");
5653
- this.dom.className = "wg-menu-button";
5654
- this.dom.type = "button";
5655
- this.dom.tabIndex = -1;
5656
- labelButton(wg, this.dom, item.label);
5657
- if (item.description) {
5658
- let desc = typeof item.description == "function" ? item.description(wg.state) : item.description;
5659
- this.dom.title = desc;
5660
- this.dom.setAttribute("aria-label", desc);
5764
+ writeMeasure(measured) {
5765
+ if (measured.makeAbsolute) {
5766
+ this.madeAbsolute = true;
5767
+ this.position = "absolute";
5768
+ for (let t of this.manager.tooltipViews)
5769
+ t.dom.style.position = "absolute";
5661
5770
  }
5662
- }
5663
- get focusDOM() { return this.dom; }
5664
- update(flags, wg, update) {
5665
- if (flags != this.flags) {
5666
- if ((flags & 32) != (this.flags & 32))
5667
- this.dom.style.display = flags & 32 ? "none" : "";
5668
- if ((flags & 8) != (this.flags & 8)) {
5669
- if (flags & 8)
5670
- this.dom.setAttribute("aria-disabled", "true");
5671
- else
5672
- this.dom.removeAttribute("aria-disabled");
5771
+ let { visible, space, scaleX, scaleY } = measured;
5772
+ let others = [];
5773
+ for (let i = 0; i < this.manager.tooltips.length; i++) {
5774
+ let tooltip = this.manager.tooltips[i], tView = this.manager.tooltipViews[i], { dom } = tView;
5775
+ let pos = measured.pos[i], size = measured.size[i];
5776
+ if (!pos || tooltip.clip !== false && (pos.bottom <= Math.max(visible.top, space.top) ||
5777
+ pos.top >= Math.min(visible.bottom, space.bottom) ||
5778
+ pos.right < Math.max(visible.left, space.left) - .1 ||
5779
+ pos.left > Math.min(visible.right, space.right) + .1)) {
5780
+ dom.style.top = Outside;
5781
+ continue;
5673
5782
  }
5674
- if ((flags & 4) != (this.flags & 4)) {
5675
- if (flags & 4)
5676
- this.dom.setAttribute("aria-selected", "true");
5677
- else
5678
- this.dom.removeAttribute("aria-selected");
5679
- this.dom.tabIndex = flags & 4 ? 0 : -1;
5783
+ let arrow = tooltip.arrow ? tView.dom.querySelector("wg-tooltip-arrow") : null;
5784
+ let arrowHeight = arrow ? 7 : 0;
5785
+ let width = size.right - size.left, height = knownHeight.get(tView) ?? size.bottom - size.top;
5786
+ let offset = tView.offset || noOffset, ltr = this.wg.state.textLTR;
5787
+ let left = size.width > space.right - space.left
5788
+ ? (ltr ? space.left : space.right - size.width)
5789
+ : ltr ? Math.max(space.left, Math.min(pos.left - (arrow ? 14 : 0) + offset.x, space.right - width))
5790
+ : Math.min(Math.max(space.left, pos.left - width + (arrow ? 14 : 0) - offset.x), space.right - width);
5791
+ let above = this.above[i];
5792
+ if (!tooltip.strictSide && (above
5793
+ ? pos.top - height - arrowHeight - offset.y < space.top
5794
+ : pos.bottom + height + arrowHeight + offset.y > space.bottom) &&
5795
+ above == (space.bottom - pos.bottom > pos.top - space.top))
5796
+ above = this.above[i] = !above;
5797
+ let spaceVert = (above ? pos.top - space.top : space.bottom - pos.bottom) - arrowHeight;
5798
+ if (spaceVert < height && tView.resize !== false) {
5799
+ if (spaceVert < 15) {
5800
+ dom.style.top = Outside;
5801
+ continue;
5802
+ }
5803
+ knownHeight.set(tView, height);
5804
+ dom.style.height = (height = spaceVert) / scaleY + "px";
5680
5805
  }
5681
- if ((flags & 16) != (this.flags & 16)) {
5682
- if (flags & 16)
5683
- this.dom.setAttribute("aria-pressed", "true");
5684
- else
5685
- this.dom.removeAttribute("aria-pressed");
5806
+ else if (dom.style.height) {
5807
+ dom.style.height = "";
5686
5808
  }
5687
- this.flags = flags;
5809
+ let top = above ? pos.top - height - arrowHeight - offset.y : pos.bottom + arrowHeight + offset.y;
5810
+ let right = left + width;
5811
+ if (tView.overlap !== true)
5812
+ for (let r of others)
5813
+ if (r.left < right && r.right > left && r.top < top + height && r.bottom > top)
5814
+ top = above ? r.top - height - 2 - arrowHeight : r.bottom + arrowHeight + 2;
5815
+ if (this.position == "absolute") {
5816
+ dom.style.top = (top - measured.parent.top) / scaleY + "px";
5817
+ setLeftStyle(dom, (left - measured.parent.left) / scaleX);
5818
+ }
5819
+ else {
5820
+ dom.style.top = top / scaleY + "px";
5821
+ setLeftStyle(dom, left / scaleX);
5822
+ }
5823
+ if (arrow) {
5824
+ let arrowLeft = pos.left + (ltr ? offset.x : -offset.x) - (left + 14 - 7);
5825
+ arrow.style.left = arrowLeft / scaleX + "px";
5826
+ }
5827
+ if (tView.overlap !== true)
5828
+ others.push({ left, top, right, bottom: top + height });
5829
+ dom.classList.toggle("wg-tooltip-above", above);
5830
+ dom.classList.toggle("wg-tooltip-below", !above);
5831
+ if (tView.positioned)
5832
+ tView.positioned(measured.space);
5688
5833
  }
5689
5834
  }
5690
- get children() { return null; }
5691
- run(wg) {
5692
- Command.dispatch(wg, this.item.run);
5835
+ maybeMeasure() {
5836
+ if (this.manager.tooltips.length)
5837
+ this.wg.scheduleDOMRead(this.measure);
5693
5838
  }
5839
+ }, plugin => plugin.eventObserver("scroll", (event, wg, value) => value.maybeMeasure()));
5840
+ function setLeftStyle(elt, value) {
5841
+ let current = parseInt(elt.style.left, 10);
5842
+ if (isNaN(current) || Math.abs(value - current) > 1)
5843
+ elt.style.left = value + "px";
5694
5844
  }
5695
- class BarControl {
5696
- item;
5697
- dom;
5698
- focusDOM;
5699
- flags = 0;
5700
- index = 0;
5701
- constructor(item, wg, done) {
5702
- this.item = item;
5703
- let { dom, focus } = item.render(wg, done);
5704
- this.dom = dom;
5705
- this.focusDOM = focus || dom;
5706
- this.focusDOM.tabIndex = -1;
5707
- }
5708
- update(flags, wg, update) {
5709
- if (flags != this.flags) {
5710
- if ((flags & 32) != (this.flags & 32))
5711
- this.dom.style.display = flags & 32 ? "none" : "";
5712
- if ((flags & 8) != (this.flags & 8) && this.item.setEnabled)
5713
- this.item.setEnabled(this.dom, !(flags & 8));
5714
- if ((flags & 4) != (this.flags & 4)) {
5715
- if (flags & 4)
5716
- this.focusDOM.setAttribute("aria-selected", "true");
5717
- else
5718
- this.focusDOM.removeAttribute("aria-selected");
5719
- this.focusDOM.tabIndex = flags & 4 ? 0 : -1;
5845
+ const styles = /*@__PURE__*/Wordgard.styles({
5846
+ ".wg-tooltip": {
5847
+ zIndex: 500,
5848
+ boxSizing: "border-box",
5849
+ backgroundColor: "var(--wg-panel-color)",
5850
+ boxShadow: "0 0 8px 0 rgba(128, 128, 128, 0.2)",
5851
+ font: "var(--wg-dialog-font)",
5852
+ },
5853
+ ".wg-tooltip-section:not(:first-child)": {
5854
+ borderTop: "1px solid var(--wg-border-color)",
5855
+ },
5856
+ "wg-tooltip-arrow": {
5857
+ display: "block",
5858
+ height: `${7}px`,
5859
+ width: `${7 * 2}px`,
5860
+ position: "absolute",
5861
+ zIndex: -1,
5862
+ overflow: "hidden",
5863
+ "&:before, &:after": {
5864
+ content: "''",
5865
+ position: "absolute",
5866
+ width: 0,
5867
+ height: 0,
5868
+ borderLeft: `${7}px solid transparent`,
5869
+ borderRight: `${7}px solid transparent`,
5870
+ },
5871
+ ".wg-tooltip-above &": {
5872
+ bottom: `-${7}px`,
5873
+ "&:before": {
5874
+ borderTop: `${7}px solid var(--wg-border-color)`,
5875
+ },
5876
+ "&:after": {
5877
+ borderTop: `${7}px solid var(--wg-panel-color)`,
5878
+ bottom: "1px"
5720
5879
  }
5721
- this.flags = flags;
5722
- }
5723
- }
5724
- get children() { return null; }
5725
- get run() { return null; }
5726
- }
5727
- class BarSubmenu {
5728
- item;
5729
- dom;
5730
- button;
5731
- list;
5732
- flags = 0;
5733
- activeChild = -3;
5734
- index = 0;
5735
- children;
5736
- constructor(item, children, wg) {
5737
- this.item = item;
5738
- this.dom = document.createElement("wg-submenu");
5739
- this.button = this.dom.appendChild(document.createElement("button"));
5740
- this.button.type = "button";
5741
- this.button.tabIndex = -1;
5742
- this.button.className = "wg-menu-button";
5743
- this.button.setAttribute("aria-haspopup", "true");
5744
- this.button.setAttribute("aria-expanded", "false");
5745
- if (item.description) {
5746
- let desc = typeof item.description == "function" ? item.description(wg.state) : item.description;
5747
- this.dom.title = desc;
5748
- this.dom.setAttribute("aria-label", desc);
5749
- }
5750
- if (item.label) {
5751
- labelButton(wg, this.button, item.label);
5752
- this.activeChild = -2;
5753
- }
5754
- if (item.width != null)
5755
- this.dom.style.setProperty("--wg-submenu-width", item.width + "ch");
5756
- if (item.arrow)
5757
- this.button.classList.add("wg-submenu-arrow");
5758
- this.list = this.dom.appendChild(document.createElement("wg-menu-list"));
5759
- this.list.style.display = "none";
5760
- this.list.role = "menu";
5761
- this.list.id = id("wg-popup");
5762
- this.list.setAttribute("aria-label", this.button.title);
5763
- this.button.setAttribute("aria-controls", this.list.id);
5764
- this.children = children.filter((ch) => !(ch instanceof BarSpacer));
5765
- for (let child of children) {
5766
- this.list.appendChild(child.dom);
5767
- if (!(child instanceof BarSpacer))
5768
- child.focusDOM.role = "menuitem";
5769
- }
5770
- }
5771
- get focusDOM() { return this.button; }
5772
- update(flags, wg) {
5773
- if (flags != this.flags) {
5774
- if ((flags & 32) != (this.flags & 32))
5775
- this.dom.style.display = flags & 32 ? "none" : "";
5776
- if ((flags & 8) != (this.flags & 8)) {
5777
- if (flags & 8)
5778
- this.button.setAttribute("aria-disabled", "true");
5779
- else
5780
- this.button.removeAttribute("aria-disabled");
5781
- }
5782
- if ((flags & 4) != (this.flags & 4)) {
5783
- if (flags & 4)
5784
- this.button.setAttribute("aria-selected", "true");
5785
- else
5786
- this.button.removeAttribute("aria-selected");
5787
- this.button.tabIndex = flags & 4 ? 0 : -1;
5788
- }
5789
- if ((flags & 2) != (this.flags & 2))
5790
- this.list.style.display = flags & 2 ? "" : "none";
5791
- this.flags = flags;
5792
- }
5793
- if (this.activeChild != -2) {
5794
- let activeChild = this.children.findIndex(ch => ch.flags & 16);
5795
- if (this.activeChild != activeChild) {
5796
- this.activeChild = activeChild;
5797
- let label = activeChild < 0 ? this.item.defaultLabel : this.children[activeChild].item.label;
5798
- labelButton(wg, this.button, label);
5880
+ },
5881
+ ".wg-tooltip-below &": {
5882
+ top: `-${7}px`,
5883
+ "&:before": {
5884
+ borderBottom: `${7}px solid var(--wg-border-color)`,
5885
+ },
5886
+ "&:after": {
5887
+ borderBottom: `${7}px solid var(--wg-panel-color)`,
5888
+ top: "1px"
5799
5889
  }
5800
- }
5890
+ },
5891
+ },
5892
+ });
5893
+ const closeHoverTooltipEffect = /*@__PURE__*/Transaction.Effect.define();
5894
+ const Tooltip = /*@__PURE__*/(function (Tooltip) {
5895
+ function configure(config = {}) {
5896
+ return tooltipConfig.of(config);
5801
5897
  }
5802
- get run() { return null; }
5803
- }
5804
- class BarSpacer {
5805
- dom;
5806
- constructor() {
5807
- this.dom = document.createElement("wg-menu-spacer");
5898
+ Tooltip.configure = configure;
5899
+ Tooltip.show = GardState.Facet.define({
5900
+ enables: [tooltipPlugin, styles]
5901
+ });
5902
+ function get(wg, tooltip) {
5903
+ let plugin = wg.plugin(tooltipPlugin);
5904
+ if (!plugin)
5905
+ return null;
5906
+ let found = plugin.manager.tooltips.findIndex(typeof tooltip == "function" ? p => p.create == tooltip : p => p == tooltip);
5907
+ return found < 0 ? null : plugin.manager.tooltipViews[found];
5808
5908
  }
5809
- }
5810
- function instantiate(item, bar, flat) {
5811
- let elt;
5812
- if (item instanceof Menu.Submenu.Resolved)
5813
- elt = new BarSubmenu(item.item, item.content.map(i => instantiate(i, bar, flat)), bar.wg);
5814
- else if (item === "|")
5815
- return new BarSpacer();
5816
- else if (item instanceof Menu.Button)
5817
- elt = new BarButton(item, bar.wg);
5818
- else
5819
- elt = new BarControl(item, bar.wg, () => bar.up());
5820
- elt.index = flat.length;
5821
- flat.push(elt);
5822
- return elt;
5823
- }
5824
- class MenuBar {
5909
+ Tooltip.get = get;
5910
+ function reposition(wg) {
5911
+ let plugin = wg.plugin(tooltipPlugin);
5912
+ if (plugin)
5913
+ plugin.maybeMeasure();
5914
+ }
5915
+ Tooltip.reposition = reposition;
5916
+ function hover(source, options = {}) {
5917
+ let setHover = Transaction.Effect.define();
5918
+ let hoverState = GardState.Field.define({
5919
+ create() { return []; },
5920
+ update(value, tr) {
5921
+ if (value.length) {
5922
+ if (options.hideOnChange && (tr.docChanged || tr.selection))
5923
+ value = [];
5924
+ else if (options.hideOn)
5925
+ value = value.filter(v => !options.hideOn(tr, v));
5926
+ if (tr.docChanged) {
5927
+ let mapped = [];
5928
+ for (let tooltip of value) {
5929
+ let newPos = tr.changes.mapPos(tooltip.pos, -1, "around");
5930
+ if (newPos != null) {
5931
+ let copy = Object.assign(Object.create(null), tooltip);
5932
+ copy.pos = newPos;
5933
+ if (copy.end != null)
5934
+ copy.end = tr.changes.mapPos(copy.end);
5935
+ mapped.push(copy);
5936
+ }
5937
+ }
5938
+ value = mapped;
5939
+ }
5940
+ }
5941
+ for (let effect of tr.effects) {
5942
+ if (effect.is(setHover))
5943
+ value = effect.value;
5944
+ if (effect.is(closeHoverTooltipEffect))
5945
+ value = [];
5946
+ }
5947
+ return value;
5948
+ },
5949
+ provide: f => showHoverTooltip.from(f)
5950
+ });
5951
+ return {
5952
+ active: hoverState,
5953
+ extension: [
5954
+ hoverState,
5955
+ Wordgard.Plugin.define(wg => new HoverPlugin(wg, source, hoverState, setHover, options.hoverTime || 300)),
5956
+ showHoverTooltipHost
5957
+ ]
5958
+ };
5959
+ }
5960
+ Tooltip.hover = hover;
5961
+ (function (hover) {
5962
+ function has(state) {
5963
+ return state.facet(showHoverTooltip).some(x => x);
5964
+ }
5965
+ hover.has = has;
5966
+ hover.closeAll = closeHoverTooltipEffect.of(null);
5967
+ })(hover = Tooltip.hover || (Tooltip.hover = {}));
5968
+ ;return Tooltip})({});
5969
+ const noOffset = { x: 0, y: 0 };
5970
+ const showHoverTooltip = /*@__PURE__*/GardState.Facet.define({
5971
+ combine: inputs => inputs.reduce((a, i) => a.concat(i), [])
5972
+ });
5973
+ class HoverTooltipHost {
5825
5974
  wg;
5975
+ manager;
5826
5976
  dom;
5827
- focusTimeout = -1;
5828
- items;
5977
+ connected = false;
5978
+ static create(wg) {
5979
+ return new HoverTooltipHost(wg);
5980
+ }
5829
5981
  constructor(wg) {
5830
5982
  this.wg = wg;
5831
- this.dom = document.createElement("wg-menubar");
5832
- this.dom.role = "toolbar";
5833
- this.dom.addEventListener("keydown", this.key.bind(this));
5834
- this.dom.addEventListener("mousedown", this.click.bind(this));
5835
- this.dom.addEventListener("focusout", this.focusout.bind(this));
5836
- this.items = wg.state.facet(Menu.Item.source);
5837
- this.init();
5838
- this.globalClick = this.globalClick.bind(this);
5983
+ this.dom = document.createElement("wg-tooltip-hover");
5984
+ this.manager = new TooltipViewManager(wg, showHoverTooltip, (t, p) => this.createHostedView(t, p), t => t.dom.remove());
5839
5985
  }
5840
- static create(wg) { return new MenuBar(wg); }
5841
- init() {
5842
- let elts = [];
5843
- this.elts = elts;
5844
- let children = Menu.resolve(this.items, this.wg.state.facet(barTemplate)).map(i => instantiate(i, this, elts));
5845
- this.children = children.filter((ch) => !(ch instanceof BarSpacer));
5846
- for (let elt of children)
5847
- this.dom.appendChild(elt.dom);
5848
- this.selection = this.children.length ? [this.children[0]] : [];
5849
- this.updateElts(true, this.selection);
5986
+ createHostedView(tooltip, prev) {
5987
+ let hostedView = tooltip.create(this.wg);
5988
+ hostedView.dom.classList.add("wg-tooltip-section");
5989
+ this.dom.insertBefore(hostedView.dom, prev ? prev.dom.nextSibling : this.dom.firstChild);
5990
+ if (this.connected && hostedView.connect)
5991
+ hostedView.connect(this.wg);
5992
+ return hostedView;
5850
5993
  }
5851
- update(update) {
5852
- let items = update.state.facet(Menu.Item.source);
5853
- if (items != this.items ||
5854
- update.startState.facet(barTemplate) != update.state.facet(barTemplate) ||
5855
- update.startState.textLTR != update.state.textLTR ||
5856
- PhraseSet.didChange(update.startState, update.state)) {
5857
- this.items = items;
5858
- this.dom.textContent = "";
5859
- this.init();
5860
- }
5861
- else {
5862
- this.updateElts(update, this.selection);
5994
+ connect(wg) {
5995
+ for (let t of this.manager.tooltipViews)
5996
+ t.connect?.(wg);
5997
+ this.connected = true;
5998
+ }
5999
+ disconnect(wg) {
6000
+ for (let t of this.manager.tooltipViews)
6001
+ t.disconnect?.(wg);
6002
+ this.connected = false;
6003
+ }
6004
+ positioned(space) {
6005
+ for (let hostedView of this.manager.tooltipViews) {
6006
+ if (hostedView.positioned)
6007
+ hostedView.positioned(space);
5863
6008
  }
5864
6009
  }
5865
- connect() {
5866
- this.dom.setAttribute("aria-controls", this.wg.contentDOM.id);
6010
+ update(update) {
6011
+ this.manager.update(update);
5867
6012
  }
5868
- updateElts(update, selection) {
5869
- let { state } = this.wg;
5870
- let changed = typeof update == "boolean" ? update : update.docChanged || update.selectionSet;
5871
- let updateObj = typeof update == "boolean" ? null : update;
5872
- for (let i = 0; i < this.elts.length; i++) {
5873
- let elt = this.elts[i], flags;
5874
- if (update && (update === true || (elt.item.updateFor ? update.transactions.some(tr => elt.item.updateFor(tr)) : changed))) {
5875
- flags = ((elt.item.select ? elt.item.select(state) : true) ? 0 : 32) |
5876
- ((elt.item.enable ? elt.item.enable(state) : true) ? 0 : 8) |
5877
- ((elt.item instanceof Menu.Button && elt.item.active ? elt.item.active(state) : false) ? 16 : 0);
5878
- }
5879
- else {
5880
- flags = elt.flags & (32 | 8 | 16);
5881
- }
5882
- if (selection.length) {
5883
- let selected = selection.indexOf(elt);
5884
- if (selected == selection.length - 1)
5885
- flags |= 4;
5886
- else if (selected > -1)
5887
- flags |= 2;
6013
+ remove(wg) {
6014
+ for (let t of this.manager.tooltipViews)
6015
+ t.remove?.(wg);
6016
+ }
6017
+ passProp(name) {
6018
+ let value = undefined;
6019
+ for (let view of this.manager.tooltipViews) {
6020
+ let given = view[name];
6021
+ if (given !== undefined) {
6022
+ if (value === undefined)
6023
+ value = given;
6024
+ else if (value !== given)
6025
+ return undefined;
5888
6026
  }
5889
- elt.update(flags, this.wg, updateObj);
5890
- }
5891
- if (update && selection.some(e => e.flags & 32)) {
5892
- let reset = selection[0].flags & 32 ? findChild(this.children, true) : selection[0];
5893
- this.setSelection(reset ? [reset] : [], this.dom.contains(this.wg.root.activeElement));
5894
6027
  }
6028
+ return value;
5895
6029
  }
5896
- setSelection(selection, focus = true) {
5897
- this.updateElts(false, selection);
5898
- if (selection.length > 1 && this.selection.length <= 1)
5899
- this.dom.ownerDocument.addEventListener("mousedown", this.globalClick);
5900
- this.selection = selection;
5901
- if (focus && selection.length)
5902
- selection[selection.length - 1].focusDOM.focus();
6030
+ get offset() { return this.passProp("offset"); }
6031
+ get getCoords() { return this.passProp("getCoords"); }
6032
+ get overlap() { return this.passProp("overlap"); }
6033
+ get resize() { return this.passProp("resize"); }
6034
+ }
6035
+ const showHoverTooltipHost = /*@__PURE__*/Tooltip.show.derive(state => {
6036
+ let tooltips = state.facet(showHoverTooltip);
6037
+ if (tooltips.length === 0)
6038
+ return null;
6039
+ return {
6040
+ pos: Math.min(...tooltips.map(t => t.pos)),
6041
+ end: Math.max(...tooltips.map(t => t.end ?? t.pos)),
6042
+ create: HoverTooltipHost.create,
6043
+ above: tooltips[0].above,
6044
+ arrow: tooltips.some(t => t.arrow),
6045
+ };
6046
+ });
6047
+ class HoverPlugin {
6048
+ wg;
6049
+ source;
6050
+ field;
6051
+ setHover;
6052
+ hoverTime;
6053
+ lastMove;
6054
+ hoverTimeout = -1;
6055
+ restartTimeout = -1;
6056
+ pending = null;
6057
+ constructor(wg, source, field, setHover, hoverTime) {
6058
+ this.wg = wg;
6059
+ this.source = source;
6060
+ this.field = field;
6061
+ this.setHover = setHover;
6062
+ this.hoverTime = hoverTime;
6063
+ this.lastMove = { x: 0, y: 0, target: wg.dom, time: 0 };
6064
+ this.checkHover = this.checkHover.bind(this);
6065
+ wg.dom.addEventListener("mouseleave", this.mouseleave = this.mouseleave.bind(this));
6066
+ wg.dom.addEventListener("mousemove", this.mousemove = this.mousemove.bind(this));
5903
6067
  }
5904
- key(event) {
5905
- if (event.ctrlKey || event.altKey || event.metaKey || event.defaultPrevented)
5906
- return;
5907
- let sLen = this.selection.length;
5908
- if (event.key == "ArrowLeft" || event.key == "ArrowRight") {
5909
- if (sLen) {
5910
- let forward = (event.key == "ArrowRight") == (getComputedStyle(this.dom).direction == "ltr");
5911
- let next = findNextChild(this.children, this.selection[0], forward ? 1 : -1);
5912
- this.setSelection(next ? [next] : []);
5913
- }
5914
- }
5915
- else if (event.key == "ArrowDown" || event.key == "ArrowUp") {
5916
- if (sLen > 1) {
5917
- let parent = this.selection[sLen - 2];
5918
- let next = findNextChild(parent.children, this.selection[sLen - 1], event.key == "ArrowUp" ? -1 : 1);
5919
- if (next)
5920
- this.setSelection(this.selection.slice(0, sLen - 1).concat(next));
5921
- }
5922
- else if (sLen == 1 && this.selection[0].children) {
5923
- let inner = defaultChild(this.selection[0].children);
5924
- if (inner)
5925
- this.setSelection([this.selection[0], inner]);
5926
- }
5927
- }
5928
- else if (event.key == "Home" || event.key == "End") {
5929
- let child = findChild(sLen > 1 ? this.selection[sLen - 2].children : this.children, event.key == "Home");
5930
- if (child)
5931
- this.setSelection(this.selection.slice(0, sLen - 1).concat(child));
5932
- }
5933
- else if (event.key == " " || event.key == "Enter") {
5934
- if (sLen) {
5935
- let child = this.selection[sLen - 1];
5936
- if (child.flags & 8) ;
5937
- else if (child.children) {
5938
- let inner = defaultChild(child.children);
5939
- if (inner)
5940
- this.setSelection(this.selection.concat(inner));
5941
- }
5942
- else {
5943
- if (child.run)
5944
- child.run(this.wg);
5945
- this.setSelection([this.selection[0]]);
5946
- }
5947
- }
5948
- }
5949
- else if (event.key == "Escape" && sLen > 1) {
5950
- this.setSelection(this.selection.slice(0, sLen - 1));
5951
- }
5952
- else {
5953
- return;
6068
+ update() {
6069
+ if (this.pending) {
6070
+ this.pending = null;
6071
+ clearTimeout(this.restartTimeout);
6072
+ this.restartTimeout = setTimeout(() => this.startHover(), 20);
5954
6073
  }
5955
- event.preventDefault();
5956
6074
  }
5957
- click(event) {
5958
- if (event.defaultPrevented)
6075
+ get active() {
6076
+ return this.wg.state.field(this.field);
6077
+ }
6078
+ checkHover() {
6079
+ this.hoverTimeout = -1;
6080
+ if (this.active.length)
5959
6081
  return;
5960
- let target, idx;
5961
- for (let node = event.target;; node = node.parentNode) {
5962
- if (!node || node == this.dom)
5963
- return;
5964
- target = this.elts.find(e => e.dom == node);
5965
- if (target)
5966
- break;
5967
- }
5968
- if (target.flags & 8) ;
5969
- else if (target.children) {
5970
- if ((idx = this.selection.indexOf(target)) > -1 && idx < this.selection.length - 1) {
5971
- this.setSelection(this.selection.slice(0, idx + 1), false);
5972
- }
5973
- else {
5974
- for (let i = 0; i < this.selection.length; i++) {
5975
- let { children } = i ? this.selection[i - 1] : this;
5976
- if (children.includes(target)) {
5977
- let next = defaultChild(target.children);
5978
- if (next)
5979
- this.setSelection(this.selection.slice(0, i).concat([target, next]), false);
5980
- }
6082
+ let hovered = Date.now() - this.lastMove.time;
6083
+ if (hovered < this.hoverTime)
6084
+ this.hoverTimeout = setTimeout(this.checkHover, this.hoverTime - hovered);
6085
+ else
6086
+ this.startHover();
6087
+ }
6088
+ startHover() {
6089
+ clearTimeout(this.restartTimeout);
6090
+ let { wg, lastMove } = this;
6091
+ let { pos, side } = wg.posAtCoords(lastMove);
6092
+ let open = this.source(wg, pos, side || -1);
6093
+ if (open?.then) {
6094
+ let pending = this.pending = { pos };
6095
+ open.then(result => {
6096
+ if (this.pending == pending) {
6097
+ this.pending = null;
6098
+ if (result && !(Array.isArray(result) && !result.length))
6099
+ wg.dispatch({ effects: this.setHover.of(Array.isArray(result) ? result : [result]) });
5981
6100
  }
5982
- }
6101
+ }, e => logException(wg.state, e, "hover tooltip"));
5983
6102
  }
5984
- else {
5985
- if (target.run)
5986
- target.run(this.wg);
5987
- this.setSelection(this.children.includes(target) ? [target] : this.selection.length ? [this.selection[0]] : [], false);
6103
+ else if (open && !(Array.isArray(open) && !open.length)) {
6104
+ wg.dispatch({ effects: this.setHover.of(Array.isArray(open) ? open : [open]) });
5988
6105
  }
5989
- event.preventDefault();
5990
6106
  }
5991
- globalClick(event) {
5992
- if (!event.composedPath().includes(this.dom)) {
5993
- this.dom.ownerDocument.removeEventListener("mousedown", this.globalClick);
5994
- if (this.selection.length > 1)
5995
- this.setSelection([this.selection[0]], false);
6107
+ get tooltip() {
6108
+ let plugin = this.wg.plugin(tooltipPlugin);
6109
+ let index = plugin ? plugin.manager.tooltips.findIndex(t => t.create == HoverTooltipHost.create) : -1;
6110
+ return index > -1 ? plugin.manager.tooltipViews[index] : null;
6111
+ }
6112
+ mousemove(event) {
6113
+ this.lastMove = { x: event.clientX, y: event.clientY, target: event.target, time: Date.now() };
6114
+ if (this.hoverTimeout < 0)
6115
+ this.hoverTimeout = setTimeout(this.checkHover, this.hoverTime);
6116
+ let { active, tooltip } = this;
6117
+ if (active.length && tooltip && !isInTooltip(tooltip.dom, event) || this.pending) {
6118
+ let { pos } = active[0] || this.pending, end = active[0]?.end ?? pos;
6119
+ if ((pos == end ? this.wg.posAtCoords(this.lastMove).pos != pos
6120
+ : !isOverRange(this.wg, pos, end, event.clientX, event.clientY))) {
6121
+ this.wg.dispatch({ effects: this.setHover.of([]) });
6122
+ this.pending = null;
6123
+ }
5996
6124
  }
5997
6125
  }
5998
- focusout() {
5999
- clearTimeout(this.focusTimeout);
6000
- if (this.selection.length > 1) {
6001
- this.focusTimeout = setTimeout(() => {
6002
- let active = this.wg.root.activeElement;
6003
- for (let i = 0; i < this.selection.length - 1; i++) {
6004
- if (!active || !this.selection[i].dom.contains(active)) {
6005
- this.setSelection([this.selection[0]], false);
6006
- break;
6007
- }
6008
- }
6009
- }, 20);
6126
+ mouseleave(event) {
6127
+ clearTimeout(this.hoverTimeout);
6128
+ this.hoverTimeout = -1;
6129
+ let { active } = this;
6130
+ if (active.length) {
6131
+ let { tooltip } = this;
6132
+ let inTooltip = tooltip && tooltip.dom.contains(event.relatedTarget);
6133
+ if (!inTooltip)
6134
+ this.wg.dispatch({ effects: this.setHover.of([]) });
6135
+ else
6136
+ this.watchTooltipLeave(tooltip.dom);
6010
6137
  }
6011
6138
  }
6012
- up() {
6013
- if (this.selection.length > 1)
6014
- this.setSelection([this.selection[0]], false);
6139
+ watchTooltipLeave(tooltip) {
6140
+ let watch = (event) => {
6141
+ tooltip.removeEventListener("mouseleave", watch);
6142
+ if (this.active.length && !this.wg.dom.contains(event.relatedTarget))
6143
+ this.wg.dispatch({ effects: this.setHover.of([]) });
6144
+ };
6145
+ tooltip.addEventListener("mouseleave", watch);
6015
6146
  }
6016
- get top() { return true; }
6017
- }
6018
- const menuBarPanel = /*@__PURE__*/(() => Panel.show.of(MenuBar.create))();
6019
- function findChild(children, start) {
6020
- for (let i = start ? 0 : children.length - 1; start ? i < children.length : i >= 0; start ? i++ : i--) {
6021
- let child = children[i];
6022
- if (!(child.flags & 8))
6023
- return child;
6147
+ remove() {
6148
+ clearTimeout(this.hoverTimeout);
6149
+ this.wg.dom.removeEventListener("mouseleave", this.mouseleave);
6150
+ this.wg.dom.removeEventListener("mousemove", this.mousemove);
6024
6151
  }
6025
- return null;
6026
6152
  }
6027
- function findNextChild(children, child, dir) {
6028
- let index = children.indexOf(child);
6029
- if (index < 0)
6030
- return null;
6031
- for (let i = index + dir;; i += dir) {
6032
- if (i < 0)
6033
- i = children.length - 1;
6034
- else if (i >= children.length)
6035
- i = 0;
6036
- if (i == index)
6037
- return null;
6038
- let child = children[i];
6039
- if (!(child.flags & 32))
6040
- return child;
6153
+ const tooltipMargin = 4;
6154
+ function isInTooltip(tooltip, event) {
6155
+ let { left, right, top, bottom } = tooltip.getBoundingClientRect(), arrow;
6156
+ if (arrow = tooltip.querySelector(".wg-tooltip-arrow")) {
6157
+ let arrowRect = arrow.getBoundingClientRect();
6158
+ top = Math.min(arrowRect.top, top);
6159
+ bottom = Math.max(arrowRect.bottom, bottom);
6041
6160
  }
6161
+ return event.clientX >= left - tooltipMargin && event.clientX <= right + tooltipMargin &&
6162
+ event.clientY >= top - tooltipMargin && event.clientY <= bottom + tooltipMargin;
6042
6163
  }
6043
- function defaultChild(children) {
6044
- return children.length ? children.find(ch => ch.flags & 16) || children[0] : null;
6045
- }
6046
- const theme = /*@__PURE__*/Wordgard.styles({
6047
- "&": {
6048
- "--wg-menu-item-size": "20px"
6049
- },
6050
- "&light": {
6051
- "--wg-menu-color": "#555"
6052
- },
6053
- "&dark": {
6054
- "--wg-menu-color": "#ccc"
6055
- },
6056
- "wg-menubar": {
6057
- display: "flex",
6058
- flexWrap: "wrap",
6059
- gap: "5px",
6060
- padding: "3px",
6061
- color: "var(--wg-menu-color)",
6062
- borderBottom: "1px solid var(--wg-border-color)",
6063
- },
6064
- ".wg-menu-button:focus": {
6065
- outline: "1.5px solid var(--wg-highlight-color)"
6066
- },
6067
- ".wg-menu-button": {
6068
- border: 0,
6069
- padding: "3px",
6070
- borderRadius: "4px",
6071
- boxSizing: "content-box",
6072
- backgroundColor: "transparent",
6073
- font: "inherit",
6074
- color: "inherit",
6075
- height: "var(--wg-menu-item-size)",
6076
- textAlign: "left",
6077
- "&[aria-disabled]": {
6078
- opacity: "0.3"
6079
- },
6080
- "&[aria-pressed]": {
6081
- color: "var(--wg-highlight-color)"
6082
- },
6083
- "&:hover": {
6084
- backgroundColor: "#88888820",
6085
- },
6086
- },
6087
- ".wg-button-label": {
6088
- display: "inline-block",
6089
- minWidth: "var(--wg-submenu-width)",
6090
- padding: "0 3px",
6091
- },
6092
- "svg.wg-icon": {
6093
- fill: "currentColor",
6094
- width: "var(--wg-menu-item-size)",
6095
- height: "var(--wg-menu-item-size)",
6096
- },
6097
- "wg-menu-spacer": {
6098
- display: "block",
6099
- width: "7px"
6100
- },
6101
- "wg-submenu": {
6102
- display: "block",
6103
- position: "relative",
6104
- lineHeight: ".6",
6105
- whiteSpace: "nowrap"
6106
- },
6107
- "wg-menu-list": {
6108
- display: "flex",
6109
- flexDirection: "column",
6110
- alignItems: "stretch",
6111
- gap: "5px",
6112
- padding: "5px",
6113
- borderRadius: "4px",
6114
- backgroundColor: "var(--wg-panel-color)",
6115
- position: "absolute",
6116
- zIndex: "10",
6117
- top: "100%",
6118
- boxShadow: "0 2px 8px 0 rgba(128, 128, 128, 0.2)",
6119
- "& wg-menu-list": {
6120
- left: "100%",
6121
- top: 0
6122
- }
6123
- },
6124
- ".wg-submenu-arrow:after": {
6125
- padding: "0 2px",
6126
- fontSize: "80%",
6127
- verticalAlign: "10%",
6128
- opacity: "0.4",
6129
- content: "'▾'"
6130
- },
6131
- ".wg-submenu-arrow.wg-submenu-arrow-open:after": {
6132
- content: "'▴'"
6133
- },
6134
- });
6135
- const barTemplate = /*@__PURE__*/GardState.Facet.define({
6136
- combine: inputs => inputs.length ? inputs[0] : [Menu.Group.top.template()]
6137
- });
6138
- function menuBar(config = {}) {
6139
- let extensions = [menuBarPanel, theme];
6140
- if (config.template)
6141
- extensions.push(barTemplate.of(Array.isArray(config.template) ? config.template : [config.template]));
6142
- return extensions;
6164
+ function isOverRange(wg, from, to, x, y, margin) {
6165
+ let rect = wg.contentDOM.getBoundingClientRect();
6166
+ if (rect.left > x || rect.right < x || rect.top > y || rect.bottom < y)
6167
+ return false;
6168
+ let pos = wg.posAtCoords({ x, y }).pos;
6169
+ return pos >= from && pos <= to;
6143
6170
  }
6144
6171
 
6145
6172
  const Dialog = /*@__PURE__*/(function (Dialog) {
6146
6173
  function show(wg, config) {
6147
6174
  let resolve;
6148
- let promise = new Promise(r => resolve = r);
6149
- let panelCtor = (wg) => createDialog(wg, config, resolve);
6150
- if (wg.state.field(dialogField, false)) {
6151
- wg.dispatch({ effects: openDialogEffect.of(panelCtor) });
6175
+ let result = new Promise(r => resolve = r);
6176
+ let hint = config.positioningHint === true ? PositioningHint.get() : config.positioningHint;
6177
+ let tooltip = hint || !config.tooltip ? null : {
6178
+ ...config.tooltip,
6179
+ create: (wg) => createDialog(wg, dialog),
6180
+ }, offset = null;
6181
+ if (hint) {
6182
+ let editorRect = wg.dom.getBoundingClientRect();
6183
+ offset = {
6184
+ x: wg.state.textLTR ? hint.left - editorRect.left : editorRect.right - hint.right,
6185
+ y: hint.bottom - editorRect.top
6186
+ };
6152
6187
  }
6153
- else {
6154
- wg.dispatch({ effects: GardState.appendConfig.of(dialogField.init(() => [panelCtor])) });
6155
- }
6156
- let close = closeDialogEffect.of(panelCtor);
6157
- return { close, result: promise.then(form => {
6158
- let queue = wg.win.queueMicrotask || ((f) => wg.win.setTimeout(f, 10));
6159
- queue(() => {
6160
- if (wg.state.field(dialogField).indexOf(panelCtor) > -1)
6161
- wg.dispatch({ effects: close });
6162
- });
6163
- return form;
6164
- }) };
6188
+ let dialog = new OpenDialog(config, resolve, tooltip, offset);
6189
+ let effects = [openDialog.of(dialog)];
6190
+ if (!wg.state.field(dialogs, false))
6191
+ effects.push(GardState.appendConfig.of([dialogs, renderDialogs]));
6192
+ wg.dispatch({ effects });
6193
+ wg.scheduleDOMRead(() => {
6194
+ if (!wg.state.field(dialogs).includes(dialog))
6195
+ dialog.resolve(null);
6196
+ });
6197
+ return {
6198
+ close: () => doCloseDialog(wg, dialog),
6199
+ result
6200
+ };
6165
6201
  }
6166
6202
  Dialog.show = show;
6167
- function get(wg, className) {
6168
- let dialogs = wg.state.field(dialogField, false) || [];
6169
- for (let open of dialogs) {
6170
- let panel = Panel.get(wg, open);
6171
- if (panel && panel.dom.classList.contains(className))
6172
- return panel;
6173
- }
6174
- return null;
6203
+ function get(wg, key) {
6204
+ let active = wg.state.field(dialogs, false);
6205
+ let found = active && active.find(d => d.spec.key === key);
6206
+ if (!found)
6207
+ return null;
6208
+ return { dom: dialogDOM(wg, found), close: () => doCloseDialog(wg, found) };
6175
6209
  }
6176
6210
  Dialog.get = get;
6177
- function close(wg, className) {
6178
- let dialogs = wg.state.field(dialogField, false) || [];
6179
- for (let open of dialogs) {
6180
- let panel = Panel.get(wg, open);
6181
- if (panel && panel.dom.classList.contains(className)) {
6182
- wg.dispatch({ effects: closeDialogEffect.of(open) });
6183
- return true;
6184
- }
6185
- }
6186
- return false;
6187
- }
6188
- Dialog.close = close;
6189
6211
  ;return Dialog})({});
6190
- const dialogField = /*@__PURE__*/GardState.Field.define({
6191
- create() { return []; },
6192
- update(dialogs, tr) {
6193
- for (let e of tr.effects) {
6194
- if (e.is(openDialogEffect))
6195
- dialogs = [e.value].concat(dialogs);
6196
- else if (e.is(closeDialogEffect))
6197
- dialogs = dialogs.filter(d => d != e.value);
6198
- }
6199
- return dialogs;
6200
- },
6201
- provide: f => Panel.show.computeN(state => state.field(f))
6202
- });
6203
- const openDialogEffect = /*@__PURE__*/Transaction.Effect.define();
6204
- const closeDialogEffect = /*@__PURE__*/Transaction.Effect.define();
6205
- function createDialog(wg, config, result) {
6212
+ function dialogDOM(wg, dialog) {
6213
+ let plugin = wg.plugin(renderDialogs);
6214
+ let index = plugin ? plugin.active.indexOf(dialog) : -1;
6215
+ return index > -1 ? plugin.elements[index] : null;
6216
+ }
6217
+ function doCloseDialog(wg, dialog) {
6218
+ let active = wg.state.field(dialogs, false);
6219
+ if (active && active.includes(dialog)) {
6220
+ let dom = dialogDOM(wg, dialog);
6221
+ if (dom && dom.contains(wg.win.document.activeElement))
6222
+ wg.focus();
6223
+ wg.dispatch({ effects: closeDialog.of(dialog) });
6224
+ }
6225
+ }
6226
+ function createDialog(wg, dialog) {
6227
+ let config = dialog.spec;
6206
6228
  let content = config.content ? config.content(wg, () => done(null)) : null;
6207
6229
  if (!content) {
6208
6230
  content = document.createElement("form");
@@ -6237,10 +6259,7 @@ function createDialog(wg, config, result) {
6237
6259
  for (let i = 0; i < forms.length; i++) {
6238
6260
  let form = forms[i];
6239
6261
  form.addEventListener("keydown", (event) => {
6240
- if (event.keyCode == 27) { event.preventDefault();
6241
- done(null);
6242
- }
6243
- else if (event.keyCode == 13) { event.preventDefault();
6262
+ if (event.keyCode == 13) { event.preventDefault();
6244
6263
  done(form);
6245
6264
  }
6246
6265
  });
@@ -6255,691 +6274,1057 @@ function createDialog(wg, config, result) {
6255
6274
  close.className = "wg-dialog-close";
6256
6275
  close.type = "button";
6257
6276
  close.append("×");
6258
- let panel = document.createElement("wg-dialog");
6259
- panel.append(content, close);
6277
+ let dom = document.createElement("dialog");
6278
+ dom.open = true;
6279
+ dom.append(content, close);
6280
+ dom.addEventListener("keydown", event => {
6281
+ if (event.keyCode == 27) { event.preventDefault();
6282
+ done(null);
6283
+ }
6284
+ });
6260
6285
  if (config.class)
6261
- panel.className = config.class;
6286
+ dom.className = config.class;
6262
6287
  function done(form) {
6263
- if (panel.contains(panel.ownerDocument.activeElement))
6264
- wg.focus();
6265
- result(form);
6288
+ dialog.resolve(form);
6289
+ doCloseDialog(wg, dialog);
6290
+ }
6291
+ let focus = null;
6292
+ if (config.focus) {
6293
+ if (typeof config.focus == "string")
6294
+ focus = dom.querySelector(config.focus);
6295
+ else
6296
+ focus = content.querySelector("input") || content.querySelector("button");
6266
6297
  }
6267
- let mustFocus = config.focus;
6268
6298
  return {
6269
- dom: panel,
6270
- top: config.top !== false,
6299
+ dom,
6271
6300
  connect: () => {
6272
- if (mustFocus) {
6273
- mustFocus = false;
6274
- let focus;
6275
- if (typeof config.focus == "string")
6276
- focus = content.querySelector(config.focus);
6277
- else
6278
- focus = content.querySelector("input") || content.querySelector("button");
6279
- if (focus && "select" in focus)
6301
+ if (focus) {
6302
+ if ("select" in focus)
6280
6303
  focus.select();
6281
- else if (focus && "focus" in focus)
6304
+ else if ("focus" in focus)
6282
6305
  focus.focus();
6306
+ focus = null;
6283
6307
  }
6284
6308
  }
6285
6309
  };
6286
6310
  }
6287
-
6288
- const Outside = "-10000px";
6289
- class TooltipViewManager {
6290
- facet;
6291
- createTooltipView;
6292
- removeTooltipView;
6293
- input;
6294
- tooltips;
6295
- tooltipViews;
6296
- constructor(wg, facet, createTooltipView, removeTooltipView) {
6297
- this.facet = facet;
6298
- this.createTooltipView = createTooltipView;
6299
- this.removeTooltipView = removeTooltipView;
6300
- this.input = wg.state.facet(facet);
6301
- this.tooltips = this.input.filter(t => t);
6302
- let prev = null;
6303
- this.tooltipViews = this.tooltips.map(t => prev = createTooltipView(t, prev));
6311
+ class OpenDialog {
6312
+ spec;
6313
+ resolve;
6314
+ tooltip;
6315
+ offset;
6316
+ constructor(spec, resolve, tooltip, offset) {
6317
+ this.spec = spec;
6318
+ this.resolve = resolve;
6319
+ this.tooltip = tooltip;
6320
+ this.offset = offset;
6304
6321
  }
6305
- update(update, above) {
6306
- let input = update.state.facet(this.facet);
6307
- let tooltips = input.filter(x => x);
6308
- if (input === this.input) {
6309
- for (let t of this.tooltipViews)
6310
- if (t.update)
6311
- t.update(update);
6312
- return false;
6313
- }
6314
- let tooltipViews = [], newAbove = above ? [] : null;
6315
- for (let i = 0; i < tooltips.length; i++) {
6316
- let tip = tooltips[i], known = -1;
6317
- if (!tip)
6318
- continue;
6319
- for (let i = 0; i < this.tooltips.length; i++) {
6320
- let other = this.tooltips[i];
6321
- if (other && other.create == tip.create)
6322
- known = i;
6322
+ }
6323
+ const openDialog = /*@__PURE__*/Transaction.Effect.define();
6324
+ const closeDialog = /*@__PURE__*/Transaction.Effect.define();
6325
+ const dialogs = /*@__PURE__*/GardState.Field.define({
6326
+ create: () => [],
6327
+ update: (value, tr) => {
6328
+ for (let e of tr.effects) {
6329
+ if (e.is(openDialog))
6330
+ value = value.concat(e.value);
6331
+ else if (e.is(closeDialog))
6332
+ value = value.filter(d => d != e.value);
6333
+ }
6334
+ for (let i = 0; i < value.length; i++) {
6335
+ let { closedBy } = value[i].spec;
6336
+ if (closedBy && closedBy(tr)) {
6337
+ value = value.slice(0, i).concat(value.slice(i + 1));
6338
+ i--;
6323
6339
  }
6324
- if (known < 0) {
6325
- tooltipViews[i] = this.createTooltipView(tip, i ? tooltipViews[i - 1] : null);
6326
- if (newAbove)
6327
- newAbove[i] = !!tip.above;
6340
+ }
6341
+ return value;
6342
+ },
6343
+ provide: f => Tooltip.show.deriveN(state => {
6344
+ let tooltips = [];
6345
+ for (let d of state.field(f))
6346
+ if (d.tooltip)
6347
+ tooltips.push(d.tooltip);
6348
+ return tooltips;
6349
+ })
6350
+ });
6351
+ const renderDialogs = /*@__PURE__*/Wordgard.Plugin.fromClass(class {
6352
+ active = [];
6353
+ elements = [];
6354
+ mustPosition = false;
6355
+ constructor(wg) {
6356
+ this.positionDialogs = this.positionDialogs.bind(this);
6357
+ this.updateTo(wg, wg.state.field(dialogs));
6358
+ if (this.mustPosition)
6359
+ wg.scheduleDOMRead(this.positionDialogs);
6360
+ }
6361
+ update(update) {
6362
+ let active = update.state.field(dialogs), changed = this.active != active;
6363
+ if (changed)
6364
+ this.updateTo(update.editor, active);
6365
+ if ((update.geometryChanged || changed) && this.mustPosition)
6366
+ update.editor.scheduleDOMRead(this.positionDialogs);
6367
+ }
6368
+ updateTo(wg, active) {
6369
+ for (let i = 0; i < this.active.length; i++)
6370
+ if (!active.includes(this.active[i])) {
6371
+ let elt = this.elements[i], dialog = this.active[i];
6372
+ if (elt)
6373
+ elt.remove();
6374
+ dialog.resolve(null);
6375
+ }
6376
+ this.mustPosition = false;
6377
+ this.elements = active.map((dialog, i) => {
6378
+ if (dialog.tooltip)
6379
+ return null;
6380
+ this.mustPosition = true;
6381
+ let panel;
6382
+ let found = this.active.indexOf(dialog);
6383
+ if (found > -1) {
6384
+ panel = this.elements[found];
6328
6385
  }
6329
6386
  else {
6330
- let tooltipView = tooltipViews[i] = this.tooltipViews[known];
6331
- if (newAbove)
6332
- newAbove[i] = above[known];
6333
- if (tooltipView.update)
6334
- tooltipView.update(update);
6387
+ let { dom, connect } = createDialog(wg, dialog);
6388
+ dom.classList.add("wg-free-dialog");
6389
+ wg.dom.appendChild(dom);
6390
+ connect();
6391
+ panel = dom;
6392
+ }
6393
+ panel.style.zIndex = String(400 + i);
6394
+ return panel;
6395
+ });
6396
+ this.active = active;
6397
+ }
6398
+ positionDialogs(wg) {
6399
+ let sizes = this.elements.map(e => e && e.getBoundingClientRect());
6400
+ let editor = wg.dom.getBoundingClientRect();
6401
+ let windowWidth = wg.win.innerWidth, windowHeight = wg.win.innerHeight;
6402
+ let midX = (Math.max(0, editor.left) + Math.min(windowWidth, editor.right)) / 2;
6403
+ let midY = (Math.max(0, editor.top) + Math.min(windowHeight, editor.bottom)) / 2;
6404
+ let ltr = wg.state.textLTR;
6405
+ wg.scheduleDOMWrite(() => {
6406
+ for (let i = 0; i < sizes.length; i++) {
6407
+ let size = sizes[i];
6408
+ if (!size)
6409
+ continue;
6410
+ let { offset } = this.active[i];
6411
+ let baseX, baseY;
6412
+ if (offset) {
6413
+ baseY = editor.top + offset.y;
6414
+ baseX = ltr ? editor.left + offset.x : editor.right - offset.x - size.width;
6415
+ }
6416
+ else {
6417
+ baseX = midX - size.width / 2;
6418
+ baseY = midY - size.height / 2;
6419
+ }
6420
+ if (baseX < 0)
6421
+ baseX = 0;
6422
+ else if (baseX + size.width > windowWidth && size.width < windowWidth)
6423
+ baseX = windowWidth - size.width;
6424
+ if (baseY < 0)
6425
+ baseY = 0;
6426
+ else if (baseY + size.height > windowHeight && size.height < windowHeight)
6427
+ baseY = windowHeight - size.height;
6428
+ let elt = this.elements[i];
6429
+ elt.style.left = baseX + "px";
6430
+ elt.style.top = baseY + "px";
6335
6431
  }
6432
+ });
6433
+ }
6434
+ destroy() {
6435
+ for (let elt of this.elements)
6436
+ if (elt)
6437
+ elt.remove();
6438
+ for (let dialog of this.active)
6439
+ dialog.resolve(null);
6440
+ }
6441
+ }, plugin => plugin.eventHandler("scroll", (event, wg, val) => {
6442
+ if (val.mustPosition)
6443
+ wg.scheduleDOMRead(val.positionDialogs);
6444
+ }));
6445
+ const PositioningHint = /*@__PURE__*/(function (PositioningHint) {
6446
+ let currentHint = null;
6447
+ function runWith(hint, run) {
6448
+ let prev = currentHint;
6449
+ currentHint = hint;
6450
+ try {
6451
+ return run();
6336
6452
  }
6337
- for (let t of this.tooltipViews)
6338
- if (tooltipViews.indexOf(t) < 0) {
6339
- this.removeTooltipView(t);
6340
- if (update.editor.connected)
6341
- t.disconnect?.(update.editor);
6342
- t.remove?.(update.editor);
6343
- }
6344
- if (above) {
6345
- newAbove.forEach((val, i) => above[i] = val);
6346
- above.length = newAbove.length;
6453
+ finally {
6454
+ currentHint = prev;
6347
6455
  }
6348
- this.input = input;
6349
- this.tooltips = tooltips;
6350
- this.tooltipViews = tooltipViews;
6351
- return true;
6352
6456
  }
6457
+ PositioningHint.runWith = runWith;
6458
+ function get() {
6459
+ return currentHint ? currentHint() : null;
6460
+ }
6461
+ PositioningHint.get = get;
6462
+ ;return PositioningHint})({});
6463
+
6464
+ function collectKeyDescs(bindings) {
6465
+ let groups = [];
6466
+ let add = (group, key, desc) => {
6467
+ let found = groups.find(g => g.group == group);
6468
+ if (!found)
6469
+ groups.push(found = { group, bindings: [] });
6470
+ found.bindings.push({ key, desc });
6471
+ };
6472
+ for (let binding of bindings) {
6473
+ let group = binding.spec.group || null;
6474
+ let name = binding.spec[currentPlatform] || binding.spec.key;
6475
+ if (name) {
6476
+ if (binding.spec.description)
6477
+ add(group, normalizeKeyName(name, currentPlatform), binding.spec.description);
6478
+ if (binding.spec.shiftDescription)
6479
+ add(group, "Shift-" + normalizeKeyName(name, currentPlatform), binding.spec.shiftDescription);
6480
+ }
6481
+ }
6482
+ return groups.sort((a, b) => (a.group?.rank ?? 0) - (b.group?.rank || 0));
6483
+ }
6484
+ function resolveText(state, text) {
6485
+ return typeof text == "string" ? text : text(state);
6486
+ }
6487
+ function renderKeyDescs(state, groups) {
6488
+ let frag = document.createDocumentFragment();
6489
+ frag.append(cr("h3", { class: "wg-dialog-title" }, phrases.get(state, "key_bindings")));
6490
+ frag.append(cr("div", { class: "wg-key-list", tabindex: "-1" }, groups.map(group => [
6491
+ group.group ? cr("h4", resolveText(state, group.group.description)) : null,
6492
+ group.bindings.map(({ key, desc }) => [
6493
+ cr("div", cr("kbd", key)),
6494
+ cr("div", resolveText(state, desc))
6495
+ ])
6496
+ ])));
6497
+ return frag;
6498
+ }
6499
+ function keyBindingHelp() {
6500
+ return [keyBindingHelp.keyBindings, keyBindingHelp.button];
6501
+ }
6502
+ ;keyBindingHelp = /*@__PURE__*/(function (keyBindingHelp) {
6503
+ keyBindingHelp.showKeyBindings = wg => {
6504
+ let open = Dialog.get(wg, keyBindingHelp.showKeyBindings);
6505
+ if (open) {
6506
+ open.close();
6507
+ return true;
6508
+ }
6509
+ let descs = collectKeyDescs(wg.state.field(KeyBinding.keymap));
6510
+ if (!descs.length)
6511
+ return false;
6512
+ Dialog.show(wg, {
6513
+ key: keyBindingHelp.showKeyBindings,
6514
+ content: () => renderKeyDescs(wg.state, descs),
6515
+ class: "wg-key-help",
6516
+ focus: ".wg-key-list",
6517
+ positioningHint: true
6518
+ });
6519
+ return true;
6520
+ };
6521
+ keyBindingHelp.button = Menu.Button.define({
6522
+ run: keyBindingHelp.showKeyBindings,
6523
+ 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" },
6524
+ description: phrases.ref("show_keys"),
6525
+ parent: Menu.Group.top,
6526
+ rank: 99
6527
+ });
6528
+ keyBindingHelp.keyBindings = [
6529
+ KeyBinding.of({ key: "Mod-?", run: keyBindingHelp.showKeyBindings, description: phrases.ref("show_keys"),
6530
+ shift: keyBindingHelp.showKeyBindings }),
6531
+ KeyBinding.of({ key: "Mod-Shift-/", run: keyBindingHelp.showKeyBindings }),
6532
+ ];
6533
+ ;return keyBindingHelp})(keyBindingHelp);
6534
+
6535
+ function offset(add, offset) {
6536
+ return (v, from, to) => add(v, from + offset, to == null ? undefined : to + offset);
6353
6537
  }
6354
- const tooltipConfig = /*@__PURE__*/GardState.Facet.define({
6355
- combine: values => ({
6356
- position: browser.ios ? "absolute" : values.find(conf => conf.position)?.position || "fixed",
6357
- parent: values.find(conf => conf.parent)?.parent || null,
6358
- tooltipSpace: values.find(conf => conf.tooltipSpace)?.tooltipSpace || (wg => windowRect(wg.win)),
6359
- })
6360
- });
6361
- const knownHeight = /*@__PURE__*/(() => new WeakMap())();
6362
- const tooltipPlugin = /*@__PURE__*/Wordgard.Plugin.fromClass(class {
6363
- wg;
6364
- manager;
6365
- above = [];
6366
- inView = true;
6367
- position;
6368
- madeAbsolute = false;
6369
- parent;
6370
- classes;
6371
- intersectionObserver;
6372
- resizeObserver;
6373
- lastTransaction = 0;
6374
- measureTimeout = -1;
6375
- constructor(wg) {
6376
- this.wg = wg;
6377
- let config = wg.state.facet(tooltipConfig);
6378
- this.position = config.position;
6379
- this.parent = config.parent;
6380
- this.classes = wg.themeClasses;
6381
- this.createContainer();
6382
- this.measure = this.measure.bind(this);
6383
- this.resizeObserver = typeof ResizeObserver == "function" ? new ResizeObserver(() => this.measureSoon()) : null;
6384
- this.manager = new TooltipViewManager(wg, Tooltip.show, (t, p) => this.createTooltip(t, p), t => {
6385
- if (this.resizeObserver)
6386
- this.resizeObserver.unobserve(t.dom);
6387
- t.dom.remove();
6538
+ function init(type, create, source, doc) {
6539
+ return create(add => {
6540
+ doc.iterate((node, pos) => {
6541
+ if (node.isPlot && doc.schema.matchNode(node.type, type))
6542
+ source(node, pos + 1, add);
6388
6543
  });
6389
- this.above = this.manager.tooltips.map(t => !!t.above);
6390
- this.intersectionObserver = typeof IntersectionObserver == "function" ? new IntersectionObserver(entries => {
6391
- if (Date.now() > this.lastTransaction - 50 &&
6392
- entries.length > 0 && entries[entries.length - 1].intersectionRatio < 1)
6393
- this.measureSoon();
6394
- }, { threshold: [1] }) : null;
6395
- this.observeIntersection();
6396
- this.maybeMeasure();
6397
- }
6398
- createContainer() {
6399
- if (this.parent) {
6400
- this.container = document.createElement("wg-tooltip-root");
6401
- this.container.style.position = "relative";
6402
- this.container.className = this.wg.themeClasses;
6403
- this.parent.appendChild(this.container);
6404
- }
6405
- else {
6406
- this.container = this.wg.dom;
6544
+ });
6545
+ }
6546
+ function refreshByPred(type, source, doc, deco, pred) {
6547
+ let recreate = [];
6548
+ doc.iterate((node, pos) => {
6549
+ if (doc.schema.matchNode(node.type, type) && pred(node))
6550
+ recreate.push({
6551
+ from: pos, to: pos + node.length,
6552
+ add: add => source(node, pos + 1, add)
6553
+ });
6554
+ });
6555
+ return recreate.length ? deco.modify({ replace: recreate }) : deco;
6556
+ }
6557
+ function update(config, create, source, deco, tr) {
6558
+ let doc = tr.newDoc;
6559
+ let refresh = config.refresh && config.refresh(tr);
6560
+ if (refresh === true)
6561
+ return init(config.type, create, source, doc);
6562
+ if (refresh)
6563
+ deco = refreshByPred(config.type, source, tr.startState.doc, deco, refresh);
6564
+ if (!tr.docChanged && !refresh)
6565
+ return deco;
6566
+ let recreate = [];
6567
+ let clear = [];
6568
+ tr.changes.iterChangedRanges((fromA, toA, fromB, toB) => {
6569
+ let covered = false;
6570
+ doc.iterate(fromB, toB, (node, pos) => {
6571
+ if (doc.schema.matchNode(node.type, config.type) && !recreate.some(r => r.from == pos)) {
6572
+ recreate.push({
6573
+ from: pos, to: pos + node.length,
6574
+ add: add => source(node, pos + 1, add)
6575
+ });
6576
+ if (pos < toB && pos + node.length > toB)
6577
+ covered = true;
6578
+ return false;
6579
+ }
6580
+ });
6581
+ if (!covered) {
6582
+ let end = tr.startState.doc.resolve(toA);
6583
+ let open = end.matchingParent(p => doc.schema.matchNode(p.type, config.type));
6584
+ if (open && open.before >= fromA)
6585
+ clear.push({ from: open.before, to: open.after });
6407
6586
  }
6587
+ });
6588
+ if (clear.length)
6589
+ deco = deco.modify({ replace: clear });
6590
+ deco = deco.map(tr.changes, recreate);
6591
+ return deco;
6592
+ }
6593
+ function decoratePlots(config) {
6594
+ let result = [];
6595
+ let { type, points, ranges } = config;
6596
+ if (points) {
6597
+ let source = (plot, start, add) => points(plot, offset(add, start));
6598
+ let field = GardState.Field.define({
6599
+ create(state) { return init(type, PointSet.create, source, state.doc); },
6600
+ update(value, tr) { return update(config, PointSet.create, source, value, tr); }
6601
+ });
6602
+ result.push(field, Decoration.Point.source.of(s => s.field(field)));
6408
6603
  }
6409
- observeIntersection() {
6410
- if (this.intersectionObserver && this.wg.connected) {
6411
- this.intersectionObserver.disconnect();
6412
- for (let tooltip of this.manager.tooltipViews)
6413
- this.intersectionObserver.observe(tooltip.dom);
6604
+ if (ranges) {
6605
+ let source = (plot, start, add) => ranges(plot, offset(add, start));
6606
+ let field = GardState.Field.define({
6607
+ create(state) { return init(type, RangeSet.create, source, state.doc); },
6608
+ update(value, tr) { return update(config, RangeSet.create, source, value, tr); }
6609
+ });
6610
+ result.push(field, Decoration.Range.source.of(s => s.field(field)));
6611
+ }
6612
+ return result;
6613
+ }
6614
+
6615
+ const panelConfig = /*@__PURE__*/GardState.Facet.define({
6616
+ combine(configs) {
6617
+ let topContainer, bottomContainer;
6618
+ for (let c of configs) {
6619
+ topContainer ||= c.topContainer;
6620
+ bottomContainer ||= c.bottomContainer;
6414
6621
  }
6622
+ return { topContainer, bottomContainer };
6415
6623
  }
6416
- measureSoon() {
6417
- if (this.measureTimeout < 0)
6418
- this.measureTimeout = setTimeout(() => {
6419
- this.measureTimeout = -1;
6420
- this.maybeMeasure();
6421
- }, 50);
6624
+ });
6625
+ const panelPlugin = /*@__PURE__*/Wordgard.Plugin.fromClass(class {
6626
+ input;
6627
+ specs;
6628
+ panels;
6629
+ top;
6630
+ bottom;
6631
+ constructor(wg) {
6632
+ this.input = wg.state.facet(Panel.show);
6633
+ this.specs = this.input.filter(s => s);
6634
+ this.panels = this.specs.map(spec => spec(wg));
6635
+ for (let p of this.panels)
6636
+ p.dom.classList.add("wg-panel");
6637
+ let conf = wg.state.facet(panelConfig);
6638
+ this.top = new PanelGroup(wg, true, conf.topContainer);
6639
+ this.bottom = new PanelGroup(wg, false, conf.bottomContainer);
6640
+ this.top.sync(this.panels.filter(p => p.top), wg);
6641
+ this.bottom.sync(this.panels.filter(p => !p.top), wg);
6422
6642
  }
6423
6643
  update(update) {
6424
- if (update.transactions.length)
6425
- this.lastTransaction = Date.now();
6426
- let updated = this.manager.update(update, this.above);
6427
- if (updated)
6428
- this.observeIntersection();
6429
- let shouldMeasure = updated || update.geometryChanged;
6430
- let newConfig = update.state.facet(tooltipConfig);
6431
- if (newConfig.position != this.position && !this.madeAbsolute) {
6432
- this.position = newConfig.position;
6433
- for (let t of this.manager.tooltipViews)
6434
- t.dom.style.position = this.position;
6435
- shouldMeasure = true;
6644
+ let conf = update.state.facet(panelConfig);
6645
+ if (this.top.container != conf.topContainer) {
6646
+ this.top.sync([], update.editor);
6647
+ this.top = new PanelGroup(update.editor, true, conf.topContainer);
6436
6648
  }
6437
- if (newConfig.parent != this.parent) {
6438
- if (this.parent)
6439
- this.container.remove();
6440
- this.parent = newConfig.parent;
6441
- this.createContainer();
6442
- for (let t of this.manager.tooltipViews)
6443
- this.container.appendChild(t.dom);
6444
- shouldMeasure = true;
6649
+ if (this.bottom.container != conf.bottomContainer) {
6650
+ this.bottom.sync([], update.editor);
6651
+ this.bottom = new PanelGroup(update.editor, false, conf.bottomContainer);
6445
6652
  }
6446
- else if (this.parent && this.wg.themeClasses != this.classes) {
6447
- this.classes = this.container.className = this.wg.themeClasses;
6653
+ this.top.syncClasses();
6654
+ this.bottom.syncClasses();
6655
+ let input = update.state.facet(Panel.show);
6656
+ if (input != this.input) {
6657
+ let specs = input.filter(x => x);
6658
+ let panels = [], top = [], bottom = [], mount = [];
6659
+ for (let spec of specs) {
6660
+ let known = this.specs.indexOf(spec), panel;
6661
+ if (known < 0) {
6662
+ panel = spec(update.editor);
6663
+ mount.push(panel);
6664
+ }
6665
+ else {
6666
+ panel = this.panels[known];
6667
+ if (panel.update)
6668
+ panel.update(update);
6669
+ }
6670
+ panels.push(panel);
6671
+ (panel.top ? top : bottom).push(panel);
6672
+ }
6673
+ this.specs = specs;
6674
+ this.panels = panels;
6675
+ this.top.sync(top, update.editor);
6676
+ this.bottom.sync(bottom, update.editor);
6677
+ for (let p of mount) {
6678
+ p.dom.classList.add("wg-panel");
6679
+ if (p.connect && update.editor.connected)
6680
+ p.connect(update.editor);
6681
+ }
6448
6682
  }
6449
- if (shouldMeasure)
6450
- this.maybeMeasure();
6451
- }
6452
- createTooltip(tooltip, prev) {
6453
- let tooltipView = tooltip.create(this.wg);
6454
- let before = prev ? prev.dom : null;
6455
- tooltipView.dom.classList.add("wg-tooltip");
6456
- if (tooltip.arrow && !tooltipView.dom.querySelector(".wg-tooltip > wg-tooltip-arrow")) {
6457
- let arrow = document.createElement("wg-tooltip-arrow");
6458
- tooltipView.dom.appendChild(arrow);
6683
+ else {
6684
+ for (let p of this.panels)
6685
+ if (p.update)
6686
+ p.update(update);
6459
6687
  }
6460
- tooltipView.dom.style.position = this.position;
6461
- tooltipView.dom.style.top = Outside;
6462
- tooltipView.dom.style.left = "0px";
6463
- this.container.insertBefore(tooltipView.dom, before);
6464
- if (this.wg.connected)
6465
- tooltipView.connect?.(this.wg);
6466
- if (this.resizeObserver && this.wg.connected)
6467
- this.resizeObserver.observe(tooltipView.dom);
6468
- return tooltipView;
6469
6688
  }
6470
6689
  connect(wg) {
6471
- wg.win.addEventListener("resize", this.measureSoon = this.measureSoon.bind(this));
6472
- for (let t of this.manager.tooltipViews) {
6473
- t.connect?.(wg);
6474
- if (this.resizeObserver)
6475
- this.resizeObserver.observe(t.dom);
6476
- }
6477
- this.observeIntersection();
6690
+ for (let p of this.panels)
6691
+ p.connect?.(wg);
6478
6692
  }
6479
6693
  disconnect(wg) {
6480
- this.wg.win.removeEventListener("resize", this.measureSoon);
6481
- for (let t of this.manager.tooltipViews) {
6482
- t.disconnect?.(wg);
6483
- if (this.resizeObserver)
6484
- this.resizeObserver.unobserve(t.dom);
6485
- }
6486
- if (this.intersectionObserver)
6487
- this.intersectionObserver.disconnect();
6694
+ for (let p of this.panels)
6695
+ p.disconnect?.(wg);
6488
6696
  }
6489
- remove() {
6490
- for (let tooltipView of this.manager.tooltipViews) {
6491
- tooltipView.dom.remove();
6492
- if (this.wg.connected)
6493
- tooltipView.disconnect?.(this.wg);
6494
- tooltipView.remove?.(this.wg);
6495
- }
6496
- if (this.parent)
6497
- this.container.remove();
6498
- clearTimeout(this.measureTimeout);
6697
+ remove(wg) {
6698
+ this.top.sync([], wg);
6699
+ this.bottom.sync([], wg);
6499
6700
  }
6500
- measure() {
6501
- let measure = this.readMeasure();
6502
- this.wg.scheduleDOMWrite(() => this.writeMeasure(measure));
6701
+ }, plugin => Wordgard.coveredMargins.of(wg => {
6702
+ let value = wg.plugin(plugin);
6703
+ return value && { top: value.top.scrollMargin(), bottom: value.bottom.scrollMargin() };
6704
+ }));
6705
+ const Panel = /*@__PURE__*/(function (Panel) {
6706
+ Panel.show = GardState.Facet.define({
6707
+ enables: panelPlugin
6708
+ });
6709
+ function get(wg, constructor) {
6710
+ let plugin = wg.plugin(panelPlugin);
6711
+ let index = plugin ? plugin.specs.indexOf(constructor) : -1;
6712
+ return index > -1 ? plugin.panels[index] : null;
6503
6713
  }
6504
- readMeasure() {
6505
- let scaleX = 1, scaleY = 1, makeAbsolute = false;
6506
- if (this.position == "fixed" && this.manager.tooltipViews.length) {
6507
- let { dom } = this.manager.tooltipViews[0];
6508
- if (browser.safari) {
6509
- let rect = dom.getBoundingClientRect();
6510
- makeAbsolute = Math.abs(rect.top + 10000) > 1 || Math.abs(rect.left) > 1;
6511
- }
6512
- else {
6513
- makeAbsolute = !!dom.offsetParent && dom.offsetParent != this.container.ownerDocument.body;
6714
+ Panel.get = get;
6715
+ function configure(config) {
6716
+ return config ? [panelConfig.of(config)] : [];
6717
+ }
6718
+ Panel.configure = configure;
6719
+ ;return Panel})({});
6720
+ class PanelGroup {
6721
+ wg;
6722
+ top;
6723
+ container;
6724
+ dom = undefined;
6725
+ classes = "";
6726
+ panels = [];
6727
+ constructor(wg, top, container) {
6728
+ this.wg = wg;
6729
+ this.top = top;
6730
+ this.container = container;
6731
+ this.syncClasses();
6732
+ }
6733
+ sync(panels, wg) {
6734
+ for (let p of this.panels)
6735
+ if (!panels.includes(p)) {
6736
+ if (wg.connected)
6737
+ p.disconnect?.(wg);
6738
+ p.remove?.(wg);
6514
6739
  }
6515
- }
6516
- if (makeAbsolute || this.position == "absolute") {
6517
- let measure = this.parent || this.container, rect = measure.getBoundingClientRect();
6518
- if (rect.width && rect.height) {
6519
- scaleX = rect.width / measure.offsetWidth;
6520
- scaleY = rect.height / measure.offsetHeight;
6740
+ this.panels = panels;
6741
+ this.syncDOM();
6742
+ }
6743
+ syncDOM() {
6744
+ if (this.panels.length == 0) {
6745
+ if (this.dom) {
6746
+ this.dom.remove();
6747
+ this.dom = undefined;
6521
6748
  }
6749
+ return;
6522
6750
  }
6523
- let visible = this.wg.scrollDOM.getBoundingClientRect(), margins = this.wg.getScrollMargins();
6524
- let visLeft = visible.left + margins.left, visTop = visible.top + margins.top;
6525
- return {
6526
- visible: new DOMRect(visLeft, visTop, visible.right - margins.right - visLeft, visible.bottom - margins.bottom - visTop),
6527
- parent: this.parent ? this.container.getBoundingClientRect() : this.wg.dom.getBoundingClientRect(),
6528
- pos: this.manager.tooltips.map((t, i) => {
6529
- let tv = this.manager.tooltipViews[i];
6530
- return tv.getCoords ? tv.getCoords(t.pos) : this.wg.coordsAtPos(t.pos);
6531
- }),
6532
- size: this.manager.tooltipViews.map(({ dom }) => dom.getBoundingClientRect()),
6533
- space: this.wg.state.facet(tooltipConfig).tooltipSpace(this.wg),
6534
- scaleX, scaleY, makeAbsolute
6535
- };
6536
- }
6537
- writeMeasure(measured) {
6538
- if (measured.makeAbsolute) {
6539
- this.madeAbsolute = true;
6540
- this.position = "absolute";
6541
- for (let t of this.manager.tooltipViews)
6542
- t.dom.style.position = "absolute";
6751
+ if (!this.dom) {
6752
+ this.dom = document.createElement("wg-panels");
6753
+ this.dom.className = this.top ? "wg-panels-top" : "wg-panels-bottom";
6754
+ let parent = this.container || this.wg.dom;
6755
+ parent.insertBefore(this.dom, this.top ? parent.firstChild : null);
6543
6756
  }
6544
- let { visible, space, scaleX, scaleY } = measured;
6545
- let others = [];
6546
- for (let i = 0; i < this.manager.tooltips.length; i++) {
6547
- let tooltip = this.manager.tooltips[i], tView = this.manager.tooltipViews[i], { dom } = tView;
6548
- let pos = measured.pos[i], size = measured.size[i];
6549
- if (!pos || tooltip.clip !== false && (pos.bottom <= Math.max(visible.top, space.top) ||
6550
- pos.top >= Math.min(visible.bottom, space.bottom) ||
6551
- pos.right < Math.max(visible.left, space.left) - .1 ||
6552
- pos.left > Math.min(visible.right, space.right) + .1)) {
6553
- dom.style.top = Outside;
6554
- continue;
6555
- }
6556
- let arrow = tooltip.arrow ? tView.dom.querySelector("wg-tooltip-arrow") : null;
6557
- let arrowHeight = arrow ? 7 : 0;
6558
- let width = size.right - size.left, height = knownHeight.get(tView) ?? size.bottom - size.top;
6559
- let offset = tView.offset || noOffset, ltr = this.wg.state.textLTR;
6560
- let left = size.width > space.right - space.left
6561
- ? (ltr ? space.left : space.right - size.width)
6562
- : ltr ? Math.max(space.left, Math.min(pos.left - (arrow ? 14 : 0) + offset.x, space.right - width))
6563
- : Math.min(Math.max(space.left, pos.left - width + (arrow ? 14 : 0) - offset.x), space.right - width);
6564
- let above = this.above[i];
6565
- if (!tooltip.strictSide && (above
6566
- ? pos.top - height - arrowHeight - offset.y < space.top
6567
- : pos.bottom + height + arrowHeight + offset.y > space.bottom) &&
6568
- above == (space.bottom - pos.bottom > pos.top - space.top))
6569
- above = this.above[i] = !above;
6570
- let spaceVert = (above ? pos.top - space.top : space.bottom - pos.bottom) - arrowHeight;
6571
- if (spaceVert < height && tView.resize !== false) {
6572
- if (spaceVert < 15) {
6573
- dom.style.top = Outside;
6574
- continue;
6575
- }
6576
- knownHeight.set(tView, height);
6577
- dom.style.height = (height = spaceVert) / scaleY + "px";
6578
- }
6579
- else if (dom.style.height) {
6580
- dom.style.height = "";
6581
- }
6582
- let top = above ? pos.top - height - arrowHeight - offset.y : pos.bottom + arrowHeight + offset.y;
6583
- let right = left + width;
6584
- if (tView.overlap !== true)
6585
- for (let r of others)
6586
- if (r.left < right && r.right > left && r.top < top + height && r.bottom > top)
6587
- top = above ? r.top - height - 2 - arrowHeight : r.bottom + arrowHeight + 2;
6588
- if (this.position == "absolute") {
6589
- dom.style.top = (top - measured.parent.top) / scaleY + "px";
6590
- setLeftStyle(dom, (left - measured.parent.left) / scaleX);
6757
+ let curDOM = this.dom.firstChild;
6758
+ for (let panel of this.panels) {
6759
+ if (panel.dom.parentNode == this.dom) {
6760
+ while (curDOM != panel.dom)
6761
+ curDOM = rmDOM(curDOM);
6762
+ curDOM = curDOM.nextSibling;
6591
6763
  }
6592
6764
  else {
6593
- dom.style.top = top / scaleY + "px";
6594
- setLeftStyle(dom, left / scaleX);
6595
- }
6596
- if (arrow) {
6597
- let arrowLeft = pos.left + (ltr ? offset.x : -offset.x) - (left + 14 - 7);
6598
- arrow.style.left = arrowLeft / scaleX + "px";
6765
+ this.dom.insertBefore(panel.dom, curDOM);
6599
6766
  }
6600
- if (tView.overlap !== true)
6601
- others.push({ left, top, right, bottom: top + height });
6602
- dom.classList.toggle("wg-tooltip-above", above);
6603
- dom.classList.toggle("wg-tooltip-below", !above);
6604
- if (tView.positioned)
6605
- tView.positioned(measured.space);
6606
6767
  }
6768
+ while (curDOM)
6769
+ curDOM = rmDOM(curDOM);
6607
6770
  }
6608
- maybeMeasure() {
6609
- if (this.manager.tooltips.length)
6610
- this.wg.scheduleDOMRead(this.measure);
6771
+ scrollMargin() {
6772
+ return !this.dom || this.container ? 0
6773
+ : Math.max(0, this.top ?
6774
+ this.dom.getBoundingClientRect().bottom - Math.max(0, this.wg.scrollDOM.getBoundingClientRect().top) :
6775
+ Math.min(innerHeight, this.wg.scrollDOM.getBoundingClientRect().bottom) - this.dom.getBoundingClientRect().top);
6776
+ }
6777
+ syncClasses() {
6778
+ if (!this.container || this.classes == this.wg.themeClasses)
6779
+ return;
6780
+ for (let cls of this.classes.split(" "))
6781
+ if (cls)
6782
+ this.container.classList.remove(cls);
6783
+ for (let cls of (this.classes = this.wg.themeClasses).split(" "))
6784
+ if (cls)
6785
+ this.container.classList.add(cls);
6611
6786
  }
6612
- }, plugin => plugin.eventObserver("scroll", (event, wg, value) => value.maybeMeasure()));
6613
- function setLeftStyle(elt, value) {
6614
- let current = parseInt(elt.style.left, 10);
6615
- if (isNaN(current) || Math.abs(value - current) > 1)
6616
- elt.style.left = value + "px";
6617
6787
  }
6618
- const styles = /*@__PURE__*/Wordgard.styles({
6619
- ".wg-tooltip": {
6620
- zIndex: 500,
6621
- boxSizing: "border-box",
6622
- backgroundColor: "var(--wg-panel-color)",
6623
- boxShadow: "0 0 8px 0 rgba(128, 128, 128, 0.2)",
6624
- font: "var(--wg-dialog-font)",
6625
- },
6626
- ".wg-tooltip-section:not(:first-child)": {
6627
- borderTop: "1px solid var(--wg-border-color)",
6628
- },
6629
- "wg-tooltip-arrow": {
6630
- display: "block",
6631
- height: `${7}px`,
6632
- width: `${7 * 2}px`,
6633
- position: "absolute",
6634
- zIndex: -1,
6635
- overflow: "hidden",
6636
- "&:before, &:after": {
6637
- content: "''",
6638
- position: "absolute",
6639
- width: 0,
6640
- height: 0,
6641
- borderLeft: `${7}px solid transparent`,
6642
- borderRight: `${7}px solid transparent`,
6643
- },
6644
- ".wg-tooltip-above &": {
6645
- bottom: `-${7}px`,
6646
- "&:before": {
6647
- borderTop: `${7}px solid var(--wg-border-color)`,
6648
- },
6649
- "&:after": {
6650
- borderTop: `${7}px solid var(--wg-panel-color)`,
6651
- bottom: "1px"
6788
+
6789
+ let nextID = 0;
6790
+ function id(prefix) {
6791
+ return prefix + "-" + (nextID++ % 0xffffff).toString(16);
6792
+ }
6793
+ const SVG = "http://www.w3.org/2000/svg";
6794
+ function labelButton(wg, button, label) {
6795
+ button.textContent = "";
6796
+ if (!label) ;
6797
+ else if (typeof label == "function" || typeof label == "string") {
6798
+ let span = button.appendChild(document.createElement("span"));
6799
+ span.className = "wg-button-label";
6800
+ span.textContent = typeof label == "string" ? label : label(wg.state);
6801
+ }
6802
+ else if ("icon" in label) {
6803
+ let svg = button.appendChild(document.createElementNS(SVG, "svg"));
6804
+ svg.classList.add("wg-icon");
6805
+ svg.setAttribute("viewBox", "0 0 100 100");
6806
+ let path = svg.appendChild(document.createElementNS(SVG, "path"));
6807
+ path.setAttribute("d", label.icon);
6808
+ if (label.directional && !wg.state.textLTR)
6809
+ svg.setAttribute("transform", "scale(-1, 1)");
6810
+ }
6811
+ }
6812
+ class BarButton {
6813
+ item;
6814
+ dom;
6815
+ flags = 0;
6816
+ index = 0;
6817
+ constructor(item, wg) {
6818
+ this.item = item;
6819
+ this.dom = document.createElement("button");
6820
+ this.dom.className = "wg-menu-button";
6821
+ this.dom.type = "button";
6822
+ this.dom.tabIndex = -1;
6823
+ labelButton(wg, this.dom, item.label);
6824
+ this.setDesc(wg);
6825
+ }
6826
+ setDesc(wg) {
6827
+ let { item: { label, description } } = this;
6828
+ let someDesc = description || (typeof label == "string" || typeof label == "function" ? label : null);
6829
+ let key = someDesc ? KeyBinding.lookupByDescription(wg.state, someDesc) : null;
6830
+ let desc = typeof someDesc == "function" ? someDesc(wg.state) : someDesc;
6831
+ if (description)
6832
+ this.dom.setAttribute("aria-label", desc);
6833
+ else
6834
+ this.dom.removeAttribute("aria-label");
6835
+ if (key)
6836
+ this.dom.setAttribute("aria-keyshortcuts", key);
6837
+ else
6838
+ this.dom.removeAttribute("aria-keyshortcuts");
6839
+ this.dom.title = desc ? desc + (key ? ` (${key})` : "") : key || "";
6840
+ }
6841
+ get focusDOM() { return this.dom; }
6842
+ update(flags, wg, update) {
6843
+ if (flags != this.flags) {
6844
+ if ((flags & 32) != (this.flags & 32))
6845
+ this.dom.style.display = flags & 32 ? "none" : "";
6846
+ if ((flags & 8) != (this.flags & 8)) {
6847
+ if (flags & 8)
6848
+ this.dom.setAttribute("aria-disabled", "true");
6849
+ else
6850
+ this.dom.removeAttribute("aria-disabled");
6652
6851
  }
6653
- },
6654
- ".wg-tooltip-below &": {
6655
- top: `-${7}px`,
6656
- "&:before": {
6657
- borderBottom: `${7}px solid var(--wg-border-color)`,
6658
- },
6659
- "&:after": {
6660
- borderBottom: `${7}px solid var(--wg-panel-color)`,
6661
- top: "1px"
6852
+ if ((flags & 4) != (this.flags & 4)) {
6853
+ if (flags & 4)
6854
+ this.dom.setAttribute("aria-selected", "true");
6855
+ else
6856
+ this.dom.removeAttribute("aria-selected");
6857
+ this.dom.tabIndex = flags & 4 ? 0 : -1;
6662
6858
  }
6663
- },
6664
- },
6665
- });
6666
- const closeHoverTooltipEffect = /*@__PURE__*/Transaction.Effect.define();
6667
- const Tooltip = /*@__PURE__*/(function (Tooltip) {
6668
- function configure(config = {}) {
6669
- return tooltipConfig.of(config);
6859
+ if ((flags & 16) != (this.flags & 16)) {
6860
+ if (flags & 16)
6861
+ this.dom.setAttribute("aria-pressed", "true");
6862
+ else
6863
+ this.dom.removeAttribute("aria-pressed");
6864
+ }
6865
+ this.flags = flags;
6866
+ }
6670
6867
  }
6671
- Tooltip.configure = configure;
6672
- Tooltip.show = GardState.Facet.define({
6673
- enables: [tooltipPlugin, styles]
6674
- });
6675
- function get(wg, tooltip) {
6676
- let plugin = wg.plugin(tooltipPlugin);
6677
- if (!plugin)
6678
- return null;
6679
- let found = plugin.manager.tooltips.findIndex(typeof tooltip == "function" ? p => p.create == tooltip : p => p == tooltip);
6680
- return found < 0 ? null : plugin.manager.tooltipViews[found];
6868
+ get children() { return null; }
6869
+ run(wg) {
6870
+ PositioningHint.runWith(() => this.dom.getBoundingClientRect(), () => {
6871
+ Command.dispatch(wg, this.item.run);
6872
+ });
6681
6873
  }
6682
- Tooltip.get = get;
6683
- function reposition(wg) {
6684
- let plugin = wg.plugin(tooltipPlugin);
6685
- if (plugin)
6686
- plugin.maybeMeasure();
6874
+ }
6875
+ class BarControl {
6876
+ item;
6877
+ dom;
6878
+ focusDOM;
6879
+ flags = 0;
6880
+ index = 0;
6881
+ constructor(item, wg, done) {
6882
+ this.item = item;
6883
+ let { dom, focus } = item.render(wg, done);
6884
+ this.dom = dom;
6885
+ this.focusDOM = focus || dom;
6886
+ this.focusDOM.tabIndex = -1;
6687
6887
  }
6688
- Tooltip.reposition = reposition;
6689
- function hover(source, options = {}) {
6690
- let setHover = Transaction.Effect.define();
6691
- let hoverState = GardState.Field.define({
6692
- create() { return []; },
6693
- update(value, tr) {
6694
- if (value.length) {
6695
- if (options.hideOnChange && (tr.docChanged || tr.selection))
6696
- value = [];
6697
- else if (options.hideOn)
6698
- value = value.filter(v => !options.hideOn(tr, v));
6699
- if (tr.docChanged) {
6700
- let mapped = [];
6701
- for (let tooltip of value) {
6702
- let newPos = tr.changes.mapPos(tooltip.pos, -1, "around");
6703
- if (newPos != null) {
6704
- let copy = Object.assign(Object.create(null), tooltip);
6705
- copy.pos = newPos;
6706
- if (copy.end != null)
6707
- copy.end = tr.changes.mapPos(copy.end);
6708
- mapped.push(copy);
6709
- }
6710
- }
6711
- value = mapped;
6712
- }
6713
- }
6714
- for (let effect of tr.effects) {
6715
- if (effect.is(setHover))
6716
- value = effect.value;
6717
- if (effect.is(closeHoverTooltipEffect))
6718
- value = [];
6719
- }
6720
- return value;
6721
- },
6722
- provide: f => showHoverTooltip.from(f)
6723
- });
6724
- return {
6725
- active: hoverState,
6726
- extension: [
6727
- hoverState,
6728
- Wordgard.Plugin.define(wg => new HoverPlugin(wg, source, hoverState, setHover, options.hoverTime || 300)),
6729
- showHoverTooltipHost
6730
- ]
6731
- };
6888
+ update(flags, wg, update) {
6889
+ if (flags != this.flags) {
6890
+ if ((flags & 32) != (this.flags & 32))
6891
+ this.dom.style.display = flags & 32 ? "none" : "";
6892
+ if ((flags & 8) != (this.flags & 8) && this.item.setEnabled)
6893
+ this.item.setEnabled(this.dom, !(flags & 8));
6894
+ if ((flags & 4) != (this.flags & 4)) {
6895
+ if (flags & 4)
6896
+ this.focusDOM.setAttribute("aria-selected", "true");
6897
+ else
6898
+ this.focusDOM.removeAttribute("aria-selected");
6899
+ this.focusDOM.tabIndex = flags & 4 ? 0 : -1;
6900
+ }
6901
+ this.flags = flags;
6902
+ }
6732
6903
  }
6733
- Tooltip.hover = hover;
6734
- (function (hover) {
6735
- function has(state) {
6736
- return state.facet(showHoverTooltip).some(x => x);
6904
+ get children() { return null; }
6905
+ get run() { return null; }
6906
+ }
6907
+ class BarSubmenu {
6908
+ item;
6909
+ dom;
6910
+ button;
6911
+ list;
6912
+ flags = 0;
6913
+ activeChild = -3;
6914
+ index = 0;
6915
+ children;
6916
+ constructor(item, children, wg) {
6917
+ this.item = item;
6918
+ this.dom = document.createElement("wg-submenu");
6919
+ this.button = this.dom.appendChild(document.createElement("button"));
6920
+ this.button.type = "button";
6921
+ this.button.tabIndex = -1;
6922
+ this.button.className = "wg-menu-button";
6923
+ this.button.setAttribute("aria-haspopup", "true");
6924
+ this.button.setAttribute("aria-expanded", "false");
6925
+ if (item.description) {
6926
+ let desc = typeof item.description == "function" ? item.description(wg.state) : item.description;
6927
+ this.dom.title = desc;
6928
+ this.dom.setAttribute("aria-label", desc);
6929
+ }
6930
+ if (item.label) {
6931
+ labelButton(wg, this.button, item.label);
6932
+ this.activeChild = -2;
6933
+ }
6934
+ if (item.width != null)
6935
+ this.dom.style.setProperty("--wg-submenu-width", item.width + "ch");
6936
+ if (item.arrow)
6937
+ this.button.classList.add("wg-submenu-arrow");
6938
+ this.list = this.dom.appendChild(document.createElement("wg-menu-list"));
6939
+ this.list.style.display = "none";
6940
+ this.list.role = "menu";
6941
+ this.list.id = id("wg-popup");
6942
+ this.list.setAttribute("aria-label", this.button.title);
6943
+ this.button.setAttribute("aria-controls", this.list.id);
6944
+ this.children = children.filter((ch) => !(ch instanceof BarSpacer));
6945
+ for (let child of children) {
6946
+ this.list.appendChild(child.dom);
6947
+ if (!(child instanceof BarSpacer))
6948
+ child.focusDOM.role = "menuitem";
6949
+ }
6950
+ }
6951
+ get focusDOM() { return this.button; }
6952
+ update(flags, wg) {
6953
+ if (flags != this.flags) {
6954
+ if ((flags & 32) != (this.flags & 32))
6955
+ this.dom.style.display = flags & 32 ? "none" : "";
6956
+ if ((flags & 8) != (this.flags & 8)) {
6957
+ if (flags & 8)
6958
+ this.button.setAttribute("aria-disabled", "true");
6959
+ else
6960
+ this.button.removeAttribute("aria-disabled");
6961
+ }
6962
+ if ((flags & 4) != (this.flags & 4)) {
6963
+ if (flags & 4)
6964
+ this.button.setAttribute("aria-selected", "true");
6965
+ else
6966
+ this.button.removeAttribute("aria-selected");
6967
+ this.button.tabIndex = flags & 4 ? 0 : -1;
6968
+ }
6969
+ if ((flags & 2) != (this.flags & 2))
6970
+ this.list.style.display = flags & 2 ? "" : "none";
6971
+ this.flags = flags;
6737
6972
  }
6738
- hover.has = has;
6739
- hover.closeAll = closeHoverTooltipEffect.of(null);
6740
- })(hover = Tooltip.hover || (Tooltip.hover = {}));
6741
- ;return Tooltip})({});
6742
- const noOffset = { x: 0, y: 0 };
6743
- const showHoverTooltip = /*@__PURE__*/GardState.Facet.define({
6744
- combine: inputs => inputs.reduce((a, i) => a.concat(i), [])
6745
- });
6746
- class HoverTooltipHost {
6747
- wg;
6748
- manager;
6973
+ if (this.activeChild != -2) {
6974
+ let activeChild = this.children.findIndex(ch => ch.flags & 16);
6975
+ if (this.activeChild != activeChild) {
6976
+ this.activeChild = activeChild;
6977
+ let label = activeChild < 0 ? this.item.defaultLabel : this.children[activeChild].item.label;
6978
+ labelButton(wg, this.button, label);
6979
+ }
6980
+ }
6981
+ }
6982
+ get run() { return null; }
6983
+ }
6984
+ class BarSpacer {
6749
6985
  dom;
6750
- connected = false;
6751
- static create(wg) {
6752
- return new HoverTooltipHost(wg);
6986
+ constructor() {
6987
+ this.dom = document.createElement("wg-menu-spacer");
6753
6988
  }
6989
+ }
6990
+ function instantiate(item, bar, flat) {
6991
+ let elt;
6992
+ if (item instanceof Menu.Submenu.Resolved)
6993
+ elt = new BarSubmenu(item.item, item.content.map(i => instantiate(i, bar, flat)), bar.wg);
6994
+ else if (item === "|")
6995
+ return new BarSpacer();
6996
+ else if (item instanceof Menu.Button)
6997
+ elt = new BarButton(item, bar.wg);
6998
+ else
6999
+ elt = new BarControl(item, bar.wg, () => bar.up());
7000
+ elt.index = flat.length;
7001
+ flat.push(elt);
7002
+ return elt;
7003
+ }
7004
+ class MenuBar {
7005
+ wg;
7006
+ dom;
7007
+ focusTimeout = -1;
7008
+ items;
6754
7009
  constructor(wg) {
6755
7010
  this.wg = wg;
6756
- this.dom = document.createElement("wg-tooltip-hover");
6757
- this.manager = new TooltipViewManager(wg, showHoverTooltip, (t, p) => this.createHostedView(t, p), t => t.dom.remove());
6758
- }
6759
- createHostedView(tooltip, prev) {
6760
- let hostedView = tooltip.create(this.wg);
6761
- hostedView.dom.classList.add("wg-tooltip-section");
6762
- this.dom.insertBefore(hostedView.dom, prev ? prev.dom.nextSibling : this.dom.firstChild);
6763
- if (this.connected && hostedView.connect)
6764
- hostedView.connect(this.wg);
6765
- return hostedView;
6766
- }
6767
- connect(wg) {
6768
- for (let t of this.manager.tooltipViews)
6769
- t.connect?.(wg);
6770
- this.connected = true;
6771
- }
6772
- disconnect(wg) {
6773
- for (let t of this.manager.tooltipViews)
6774
- t.disconnect?.(wg);
6775
- this.connected = false;
7011
+ this.dom = document.createElement("wg-menubar");
7012
+ this.dom.role = "toolbar";
7013
+ this.dom.addEventListener("keydown", this.key.bind(this));
7014
+ this.dom.addEventListener("mousedown", this.click.bind(this));
7015
+ this.dom.addEventListener("focusout", this.focusout.bind(this));
7016
+ this.items = wg.state.facet(Menu.Item.source);
7017
+ this.init();
7018
+ this.globalClick = this.globalClick.bind(this);
6776
7019
  }
6777
- positioned(space) {
6778
- for (let hostedView of this.manager.tooltipViews) {
6779
- if (hostedView.positioned)
6780
- hostedView.positioned(space);
6781
- }
7020
+ static create(wg) { return new MenuBar(wg); }
7021
+ init() {
7022
+ let elts = [];
7023
+ this.elts = elts;
7024
+ let children = Menu.resolve(this.items, this.wg.state.facet(barTemplate)).map(i => instantiate(i, this, elts));
7025
+ this.children = children.filter((ch) => !(ch instanceof BarSpacer));
7026
+ for (let elt of children)
7027
+ this.dom.appendChild(elt.dom);
7028
+ this.selection = this.children.length ? [this.children[0]] : [];
7029
+ this.updateElts(true, this.selection);
6782
7030
  }
6783
7031
  update(update) {
6784
- this.manager.update(update);
7032
+ let items = update.state.facet(Menu.Item.source);
7033
+ if (items != this.items ||
7034
+ update.startState.facet(barTemplate) != update.state.facet(barTemplate) ||
7035
+ update.startState.textLTR != update.state.textLTR ||
7036
+ PhraseSet.didChange(update.startState, update.state)) {
7037
+ this.items = items;
7038
+ this.dom.textContent = "";
7039
+ this.init();
7040
+ }
7041
+ else {
7042
+ this.updateElts(update, this.selection);
7043
+ if (update.startState.facet(KeyBinding.source) != update.state.facet(KeyBinding.source)) {
7044
+ for (let elt of this.elts)
7045
+ if (elt instanceof BarButton)
7046
+ elt.setDesc(update.editor);
7047
+ }
7048
+ }
6785
7049
  }
6786
- remove(wg) {
6787
- for (let t of this.manager.tooltipViews)
6788
- t.remove?.(wg);
7050
+ connect() {
7051
+ this.dom.setAttribute("aria-controls", this.wg.contentDOM.id);
6789
7052
  }
6790
- passProp(name) {
6791
- let value = undefined;
6792
- for (let view of this.manager.tooltipViews) {
6793
- let given = view[name];
6794
- if (given !== undefined) {
6795
- if (value === undefined)
6796
- value = given;
6797
- else if (value !== given)
6798
- return undefined;
7053
+ updateElts(update, selection) {
7054
+ let { state } = this.wg;
7055
+ let changed = typeof update == "boolean" ? update : update.docChanged || update.selectionSet;
7056
+ let updateObj = typeof update == "boolean" ? null : update;
7057
+ for (let i = 0; i < this.elts.length; i++) {
7058
+ let elt = this.elts[i], flags;
7059
+ if (update && (update === true || (elt.item.updateFor ? update.transactions.some(tr => elt.item.updateFor(tr)) : changed))) {
7060
+ flags = ((elt.item.select ? elt.item.select(state) : true) ? 0 : 32) |
7061
+ ((elt.item.enable ? elt.item.enable(state) : true) ? 0 : 8) |
7062
+ ((elt.item instanceof Menu.Button && elt.item.active ? elt.item.active(state) : false) ? 16 : 0);
6799
7063
  }
7064
+ else {
7065
+ flags = elt.flags & (32 | 8 | 16);
7066
+ }
7067
+ if (selection.length) {
7068
+ let selected = selection.indexOf(elt);
7069
+ if (selected == selection.length - 1)
7070
+ flags |= 4;
7071
+ else if (selected > -1)
7072
+ flags |= 2;
7073
+ }
7074
+ elt.update(flags, this.wg, updateObj);
7075
+ }
7076
+ if (update && selection.some(e => e.flags & 32)) {
7077
+ let reset = selection[0].flags & 32 ? findChild(this.children, true) : selection[0];
7078
+ this.setSelection(reset ? [reset] : [], this.dom.contains(this.wg.root.activeElement));
6800
7079
  }
6801
- return value;
6802
7080
  }
6803
- get offset() { return this.passProp("offset"); }
6804
- get getCoords() { return this.passProp("getCoords"); }
6805
- get overlap() { return this.passProp("overlap"); }
6806
- get resize() { return this.passProp("resize"); }
6807
- }
6808
- const showHoverTooltipHost = /*@__PURE__*/Tooltip.show.compute(state => {
6809
- let tooltips = state.facet(showHoverTooltip);
6810
- if (tooltips.length === 0)
6811
- return null;
6812
- return {
6813
- pos: Math.min(...tooltips.map(t => t.pos)),
6814
- end: Math.max(...tooltips.map(t => t.end ?? t.pos)),
6815
- create: HoverTooltipHost.create,
6816
- above: tooltips[0].above,
6817
- arrow: tooltips.some(t => t.arrow),
6818
- };
6819
- });
6820
- class HoverPlugin {
6821
- wg;
6822
- source;
6823
- field;
6824
- setHover;
6825
- hoverTime;
6826
- lastMove;
6827
- hoverTimeout = -1;
6828
- restartTimeout = -1;
6829
- pending = null;
6830
- constructor(wg, source, field, setHover, hoverTime) {
6831
- this.wg = wg;
6832
- this.source = source;
6833
- this.field = field;
6834
- this.setHover = setHover;
6835
- this.hoverTime = hoverTime;
6836
- this.lastMove = { x: 0, y: 0, target: wg.dom, time: 0 };
6837
- this.checkHover = this.checkHover.bind(this);
6838
- wg.dom.addEventListener("mouseleave", this.mouseleave = this.mouseleave.bind(this));
6839
- wg.dom.addEventListener("mousemove", this.mousemove = this.mousemove.bind(this));
7081
+ setSelection(selection, focus = true) {
7082
+ this.updateElts(false, selection);
7083
+ if (selection.length > 1 && this.selection.length <= 1)
7084
+ this.dom.ownerDocument.addEventListener("mousedown", this.globalClick);
7085
+ this.selection = selection;
7086
+ if (focus && selection.length)
7087
+ selection[selection.length - 1].focusDOM.focus();
6840
7088
  }
6841
- update() {
6842
- if (this.pending) {
6843
- this.pending = null;
6844
- clearTimeout(this.restartTimeout);
6845
- this.restartTimeout = setTimeout(() => this.startHover(), 20);
7089
+ key(event) {
7090
+ if (event.ctrlKey || event.altKey || event.metaKey || event.defaultPrevented)
7091
+ return;
7092
+ let sLen = this.selection.length;
7093
+ if (event.key == "ArrowLeft" || event.key == "ArrowRight") {
7094
+ if (sLen) {
7095
+ let forward = (event.key == "ArrowRight") == (getComputedStyle(this.dom).direction == "ltr");
7096
+ let next = findNextChild(this.children, this.selection[0], forward ? 1 : -1);
7097
+ this.setSelection(next ? [next] : []);
7098
+ }
7099
+ }
7100
+ else if (event.key == "ArrowDown" || event.key == "ArrowUp") {
7101
+ if (sLen > 1) {
7102
+ let parent = this.selection[sLen - 2];
7103
+ let next = findNextChild(parent.children, this.selection[sLen - 1], event.key == "ArrowUp" ? -1 : 1);
7104
+ if (next)
7105
+ this.setSelection(this.selection.slice(0, sLen - 1).concat(next));
7106
+ }
7107
+ else if (sLen == 1 && this.selection[0].children) {
7108
+ let inner = defaultChild(this.selection[0].children);
7109
+ if (inner)
7110
+ this.setSelection([this.selection[0], inner]);
7111
+ }
7112
+ }
7113
+ else if (event.key == "Home" || event.key == "End") {
7114
+ let child = findChild(sLen > 1 ? this.selection[sLen - 2].children : this.children, event.key == "Home");
7115
+ if (child)
7116
+ this.setSelection(this.selection.slice(0, sLen - 1).concat(child));
7117
+ }
7118
+ else if (event.key == " " || event.key == "Enter") {
7119
+ if (sLen) {
7120
+ let child = this.selection[sLen - 1];
7121
+ if (child.flags & 8) ;
7122
+ else if (child.children) {
7123
+ let inner = defaultChild(child.children);
7124
+ if (inner)
7125
+ this.setSelection(this.selection.concat(inner));
7126
+ }
7127
+ else {
7128
+ if (child.run)
7129
+ child.run(this.wg);
7130
+ this.setSelection([this.selection[0]]);
7131
+ }
7132
+ }
7133
+ }
7134
+ else if (event.key == "Escape" && sLen > 1) {
7135
+ this.setSelection(this.selection.slice(0, sLen - 1));
7136
+ }
7137
+ else {
7138
+ return;
6846
7139
  }
7140
+ event.preventDefault();
6847
7141
  }
6848
- get active() {
6849
- return this.wg.state.field(this.field);
6850
- }
6851
- checkHover() {
6852
- this.hoverTimeout = -1;
6853
- if (this.active.length)
7142
+ click(event) {
7143
+ if (event.defaultPrevented)
6854
7144
  return;
6855
- let hovered = Date.now() - this.lastMove.time;
6856
- if (hovered < this.hoverTime)
6857
- this.hoverTimeout = setTimeout(this.checkHover, this.hoverTime - hovered);
6858
- else
6859
- this.startHover();
6860
- }
6861
- startHover() {
6862
- clearTimeout(this.restartTimeout);
6863
- let { wg, lastMove } = this;
6864
- let { pos, side } = wg.posAtCoords(lastMove);
6865
- let open = this.source(wg, pos, side || -1);
6866
- if (open?.then) {
6867
- let pending = this.pending = { pos };
6868
- open.then(result => {
6869
- if (this.pending == pending) {
6870
- this.pending = null;
6871
- if (result && !(Array.isArray(result) && !result.length))
6872
- wg.dispatch({ effects: this.setHover.of(Array.isArray(result) ? result : [result]) });
7145
+ let target, idx;
7146
+ for (let node = event.target;; node = node.parentNode) {
7147
+ if (!node || node == this.dom)
7148
+ return;
7149
+ target = this.elts.find(e => e.dom == node);
7150
+ if (target)
7151
+ break;
7152
+ }
7153
+ if (target.flags & 8) ;
7154
+ else if (target.children) {
7155
+ if ((idx = this.selection.indexOf(target)) > -1 && idx < this.selection.length - 1) {
7156
+ this.setSelection(this.selection.slice(0, idx + 1), false);
7157
+ }
7158
+ else {
7159
+ for (let i = 0; i < this.selection.length; i++) {
7160
+ let { children } = i ? this.selection[i - 1] : this;
7161
+ if (children.includes(target)) {
7162
+ let next = defaultChild(target.children);
7163
+ if (next)
7164
+ this.setSelection(this.selection.slice(0, i).concat([target, next]), false);
7165
+ }
6873
7166
  }
6874
- }, e => logException(wg.state, e, "hover tooltip"));
7167
+ }
6875
7168
  }
6876
- else if (open && !(Array.isArray(open) && !open.length)) {
6877
- wg.dispatch({ effects: this.setHover.of(Array.isArray(open) ? open : [open]) });
7169
+ else {
7170
+ if (target.run)
7171
+ target.run(this.wg);
7172
+ this.setSelection(this.children.includes(target) ? [target] : this.selection.length ? [this.selection[0]] : [], false);
6878
7173
  }
7174
+ event.preventDefault();
6879
7175
  }
6880
- get tooltip() {
6881
- let plugin = this.wg.plugin(tooltipPlugin);
6882
- let index = plugin ? plugin.manager.tooltips.findIndex(t => t.create == HoverTooltipHost.create) : -1;
6883
- return index > -1 ? plugin.manager.tooltipViews[index] : null;
6884
- }
6885
- mousemove(event) {
6886
- this.lastMove = { x: event.clientX, y: event.clientY, target: event.target, time: Date.now() };
6887
- if (this.hoverTimeout < 0)
6888
- this.hoverTimeout = setTimeout(this.checkHover, this.hoverTime);
6889
- let { active, tooltip } = this;
6890
- if (active.length && tooltip && !isInTooltip(tooltip.dom, event) || this.pending) {
6891
- let { pos } = active[0] || this.pending, end = active[0]?.end ?? pos;
6892
- if ((pos == end ? this.wg.posAtCoords(this.lastMove).pos != pos
6893
- : !isOverRange(this.wg, pos, end, event.clientX, event.clientY))) {
6894
- this.wg.dispatch({ effects: this.setHover.of([]) });
6895
- this.pending = null;
6896
- }
7176
+ globalClick(event) {
7177
+ if (!event.composedPath().includes(this.dom)) {
7178
+ this.dom.ownerDocument.removeEventListener("mousedown", this.globalClick);
7179
+ if (this.selection.length > 1)
7180
+ this.setSelection([this.selection[0]], false);
6897
7181
  }
6898
7182
  }
6899
- mouseleave(event) {
6900
- clearTimeout(this.hoverTimeout);
6901
- this.hoverTimeout = -1;
6902
- let { active } = this;
6903
- if (active.length) {
6904
- let { tooltip } = this;
6905
- let inTooltip = tooltip && tooltip.dom.contains(event.relatedTarget);
6906
- if (!inTooltip)
6907
- this.wg.dispatch({ effects: this.setHover.of([]) });
6908
- else
6909
- this.watchTooltipLeave(tooltip.dom);
7183
+ focusout() {
7184
+ clearTimeout(this.focusTimeout);
7185
+ if (this.selection.length > 1) {
7186
+ this.focusTimeout = setTimeout(() => {
7187
+ let active = this.wg.root.activeElement;
7188
+ for (let i = 0; i < this.selection.length - 1; i++) {
7189
+ if (!active || !this.selection[i].dom.contains(active)) {
7190
+ this.setSelection([this.selection[0]], false);
7191
+ break;
7192
+ }
7193
+ }
7194
+ }, 20);
6910
7195
  }
6911
7196
  }
6912
- watchTooltipLeave(tooltip) {
6913
- let watch = (event) => {
6914
- tooltip.removeEventListener("mouseleave", watch);
6915
- if (this.active.length && !this.wg.dom.contains(event.relatedTarget))
6916
- this.wg.dispatch({ effects: this.setHover.of([]) });
6917
- };
6918
- tooltip.addEventListener("mouseleave", watch);
7197
+ up() {
7198
+ if (this.selection.length > 1)
7199
+ this.setSelection([this.selection[0]], false);
6919
7200
  }
6920
- remove() {
6921
- clearTimeout(this.hoverTimeout);
6922
- this.wg.dom.removeEventListener("mouseleave", this.mouseleave);
6923
- this.wg.dom.removeEventListener("mousemove", this.mousemove);
7201
+ get top() { return true; }
7202
+ }
7203
+ const menuBarPanel = /*@__PURE__*/(() => Panel.show.of(MenuBar.create))();
7204
+ function findChild(children, start) {
7205
+ for (let i = start ? 0 : children.length - 1; start ? i < children.length : i >= 0; start ? i++ : i--) {
7206
+ let child = children[i];
7207
+ if (!(child.flags & 8))
7208
+ return child;
6924
7209
  }
7210
+ return null;
6925
7211
  }
6926
- const tooltipMargin = 4;
6927
- function isInTooltip(tooltip, event) {
6928
- let { left, right, top, bottom } = tooltip.getBoundingClientRect(), arrow;
6929
- if (arrow = tooltip.querySelector(".wg-tooltip-arrow")) {
6930
- let arrowRect = arrow.getBoundingClientRect();
6931
- top = Math.min(arrowRect.top, top);
6932
- bottom = Math.max(arrowRect.bottom, bottom);
7212
+ function findNextChild(children, child, dir) {
7213
+ let index = children.indexOf(child);
7214
+ if (index < 0)
7215
+ return null;
7216
+ for (let i = index + dir;; i += dir) {
7217
+ if (i < 0)
7218
+ i = children.length - 1;
7219
+ else if (i >= children.length)
7220
+ i = 0;
7221
+ if (i == index)
7222
+ return null;
7223
+ let child = children[i];
7224
+ if (!(child.flags & 32))
7225
+ return child;
6933
7226
  }
6934
- return event.clientX >= left - tooltipMargin && event.clientX <= right + tooltipMargin &&
6935
- event.clientY >= top - tooltipMargin && event.clientY <= bottom + tooltipMargin;
6936
7227
  }
6937
- function isOverRange(wg, from, to, x, y, margin) {
6938
- let rect = wg.contentDOM.getBoundingClientRect();
6939
- if (rect.left > x || rect.right < x || rect.top > y || rect.bottom < y)
6940
- return false;
6941
- let pos = wg.posAtCoords({ x, y }).pos;
6942
- return pos >= from && pos <= to;
7228
+ function defaultChild(children) {
7229
+ return children.length ? children.find(ch => ch.flags & 16) || children[0] : null;
7230
+ }
7231
+ const theme = /*@__PURE__*/Wordgard.styles({
7232
+ "&": {
7233
+ "--wg-menu-item-size": "20px"
7234
+ },
7235
+ "&light": {
7236
+ "--wg-menu-color": "#555"
7237
+ },
7238
+ "&dark": {
7239
+ "--wg-menu-color": "#ccc"
7240
+ },
7241
+ "wg-menubar": {
7242
+ display: "flex",
7243
+ flexWrap: "wrap",
7244
+ gap: "5px",
7245
+ padding: "3px",
7246
+ color: "var(--wg-menu-color)",
7247
+ borderBottom: "1px solid var(--wg-border-color)",
7248
+ },
7249
+ ".wg-menu-button:focus": {
7250
+ outline: "1.5px solid var(--wg-highlight-color)"
7251
+ },
7252
+ ".wg-menu-button": {
7253
+ border: 0,
7254
+ padding: "3px",
7255
+ borderRadius: "4px",
7256
+ boxSizing: "content-box",
7257
+ backgroundColor: "transparent",
7258
+ font: "inherit",
7259
+ color: "inherit",
7260
+ height: "var(--wg-menu-item-size)",
7261
+ textAlign: "left",
7262
+ "&[aria-disabled]": {
7263
+ opacity: "0.3"
7264
+ },
7265
+ "&[aria-pressed]": {
7266
+ color: "var(--wg-highlight-color)"
7267
+ },
7268
+ "&:hover": {
7269
+ backgroundColor: "#88888820",
7270
+ },
7271
+ },
7272
+ ".wg-button-label": {
7273
+ display: "inline-block",
7274
+ minWidth: "var(--wg-submenu-width)",
7275
+ padding: "0 3px",
7276
+ },
7277
+ "svg.wg-icon": {
7278
+ fill: "currentColor",
7279
+ width: "var(--wg-menu-item-size)",
7280
+ height: "var(--wg-menu-item-size)",
7281
+ },
7282
+ "wg-menu-spacer": {
7283
+ display: "block",
7284
+ width: "7px"
7285
+ },
7286
+ "wg-submenu": {
7287
+ display: "block",
7288
+ position: "relative",
7289
+ lineHeight: ".6",
7290
+ whiteSpace: "nowrap"
7291
+ },
7292
+ "wg-menu-list": {
7293
+ display: "flex",
7294
+ flexDirection: "column",
7295
+ alignItems: "stretch",
7296
+ gap: "5px",
7297
+ padding: "5px",
7298
+ borderRadius: "4px",
7299
+ backgroundColor: "var(--wg-panel-color)",
7300
+ position: "absolute",
7301
+ zIndex: "10",
7302
+ top: "100%",
7303
+ boxShadow: "0 2px 8px 0 rgba(128, 128, 128, 0.2)",
7304
+ "& wg-menu-list": {
7305
+ left: "100%",
7306
+ top: 0
7307
+ }
7308
+ },
7309
+ ".wg-submenu-arrow:after": {
7310
+ padding: "0 2px",
7311
+ fontSize: "80%",
7312
+ verticalAlign: "10%",
7313
+ opacity: "0.4",
7314
+ content: "'▾'"
7315
+ },
7316
+ ".wg-submenu-arrow.wg-submenu-arrow-open:after": {
7317
+ content: "'▴'"
7318
+ },
7319
+ });
7320
+ const barTemplate = /*@__PURE__*/GardState.Facet.define({
7321
+ combine: inputs => inputs.length ? inputs[0] : [Menu.Group.top.template()]
7322
+ });
7323
+ function menuBar(config = {}) {
7324
+ let extensions = [menuBarPanel, theme];
7325
+ if (config.template)
7326
+ extensions.push(barTemplate.of(Array.isArray(config.template) ? config.template : [config.template]));
7327
+ return extensions;
6943
7328
  }
6944
7329
 
6945
7330
  const inputRule = /*@__PURE__*/GardState.Facet.define();
@@ -7204,4 +7589,4 @@ function dropCursor() {
7204
7589
  return [dropCursorPos, drawDropCursor];
7205
7590
  }
7206
7591
 
7207
- export { Decoration, Dialog, InputRule, KeyBinding, Panel, Tooltip, Widget, Wordgard, decoratePlots, dropCursor, menuBar, placeholder };
7592
+ export { Decoration, Dialog, InputRule, KeyBinding, Panel, PositioningHint, Tooltip, Widget, Wordgard, decoratePlots, dropCursor, keyBindingHelp, menuBar, placeholder };