@dxos/react-ui-editor 0.3.11-main.690ca41 → 0.3.11-main.695e6e6

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.
Files changed (70) hide show
  1. package/dist/lib/browser/index.mjs +201 -334
  2. package/dist/lib/browser/index.mjs.map +4 -4
  3. package/dist/lib/browser/meta.json +1 -1
  4. package/dist/types/src/components/TextEditor/MarkdownEditor.stories.d.ts +6 -2
  5. package/dist/types/src/components/TextEditor/MarkdownEditor.stories.d.ts.map +1 -1
  6. package/dist/types/src/components/TextEditor/TextEditor.d.ts +12 -17
  7. package/dist/types/src/components/TextEditor/TextEditor.d.ts.map +1 -1
  8. package/dist/types/src/components/TextEditor/TextEditor.stories.d.ts +6 -6
  9. package/dist/types/src/components/TextEditor/TextEditor.stories.d.ts.map +1 -1
  10. package/dist/types/src/components/TextEditor/comments.stories.d.ts +26 -0
  11. package/dist/types/src/components/TextEditor/comments.stories.d.ts.map +1 -0
  12. package/dist/types/src/components/TextEditor/yjs.stories.d.ts +1 -1
  13. package/dist/types/src/extensions/autocomplete.d.ts.map +1 -1
  14. package/dist/types/src/extensions/automerge/cursor.d.ts +1 -1
  15. package/dist/types/src/extensions/automerge/cursor.d.ts.map +1 -1
  16. package/dist/types/src/extensions/automerge/index.d.ts +1 -0
  17. package/dist/types/src/extensions/automerge/index.d.ts.map +1 -1
  18. package/dist/types/src/extensions/code.d.ts.map +1 -1
  19. package/dist/types/src/extensions/comments.d.ts +2 -2
  20. package/dist/types/src/extensions/comments.d.ts.map +1 -1
  21. package/dist/types/src/{util → extensions}/cursor.d.ts +9 -1
  22. package/dist/types/src/extensions/cursor.d.ts.map +1 -0
  23. package/dist/types/src/extensions/index.d.ts +1 -0
  24. package/dist/types/src/extensions/index.d.ts.map +1 -1
  25. package/dist/types/src/extensions/markdown/highlight.d.ts.map +1 -1
  26. package/dist/types/src/extensions/yjs/cursor.d.ts +1 -1
  27. package/dist/types/src/extensions/yjs/cursor.d.ts.map +1 -1
  28. package/dist/types/src/hooks/useTextModel.d.ts +0 -5
  29. package/dist/types/src/hooks/useTextModel.d.ts.map +1 -1
  30. package/dist/types/src/hooks/yjs.d.ts.map +1 -1
  31. package/dist/types/src/index.d.ts +2 -0
  32. package/dist/types/src/index.d.ts.map +1 -1
  33. package/dist/types/src/styles/markdown.d.ts.map +1 -1
  34. package/dist/types/src/themes/default.d.ts.map +1 -1
  35. package/dist/types/src/util.d.ts.map +1 -0
  36. package/package.json +23 -22
  37. package/src/components/TextEditor/MarkdownEditor.stories.tsx +19 -16
  38. package/src/components/TextEditor/TextEditor.stories.tsx +12 -46
  39. package/src/components/TextEditor/TextEditor.tsx +68 -64
  40. package/src/components/TextEditor/comments.stories.tsx +315 -0
  41. package/src/extensions/autocomplete.ts +18 -17
  42. package/src/extensions/automerge/automerge.ts +2 -2
  43. package/src/extensions/automerge/cursor.ts +2 -1
  44. package/src/extensions/automerge/index.ts +1 -0
  45. package/src/extensions/awareness.ts +25 -32
  46. package/src/extensions/code.ts +10 -2
  47. package/src/extensions/comments.ts +20 -115
  48. package/src/extensions/cursor.ts +46 -0
  49. package/src/extensions/hr.ts +2 -2
  50. package/src/extensions/image.ts +2 -2
  51. package/src/extensions/index.ts +1 -0
  52. package/src/extensions/link.ts +4 -4
  53. package/src/extensions/markdown/highlight.ts +0 -8
  54. package/src/extensions/table.ts +6 -3
  55. package/src/extensions/tasklist.ts +2 -2
  56. package/src/extensions/yjs/cursor.ts +2 -1
  57. package/src/extensions/yjs/yjs.ts +2 -2
  58. package/src/hooks/useTextModel.ts +4 -7
  59. package/src/hooks/yjs.ts +1 -23
  60. package/src/index.ts +2 -0
  61. package/src/styles/markdown.ts +0 -2
  62. package/src/themes/default.ts +17 -50
  63. package/dist/types/src/util/cursor.d.ts.map +0 -1
  64. package/dist/types/src/util/index.d.ts +0 -3
  65. package/dist/types/src/util/index.d.ts.map +0 -1
  66. package/dist/types/src/util/util.d.ts.map +0 -1
  67. package/src/util/cursor.ts +0 -29
  68. package/src/util/index.ts +0 -6
  69. /package/dist/types/src/{util/util.d.ts → util.d.ts} +0 -0
  70. /package/src/{util/util.ts → util.ts} +0 -0
@@ -11,40 +11,41 @@ import { EditorView as EditorView12 } from "@codemirror/view";
11
11
  import { useFocusableGroup } from "@fluentui/react-tabster";
12
12
  import { vim } from "@replit/codemirror-vim";
13
13
  import defaultsDeep2 from "lodash.defaultsdeep";
14
- import React, { forwardRef, useCallback, useEffect, useImperativeHandle, useState } from "react";
14
+ import React, { forwardRef, useCallback, useEffect, useImperativeHandle, useState, useRef } from "react";
15
15
  import { generateName } from "@dxos/display-name";
16
16
  import { useThemeContext } from "@dxos/react-ui";
17
17
  import { getColorForValue, inputSurface, mx as mx3 } from "@dxos/react-ui-theme";
18
18
 
19
19
  // packages/ui/react-ui-editor/src/extensions/autocomplete.ts
20
20
  import { autocompletion, completionKeymap } from "@codemirror/autocomplete";
21
+ import { markdownLanguage } from "@codemirror/lang-markdown";
21
22
  import { keymap } from "@codemirror/view";
22
23
  var autocomplete = ({ onSearch }) => {
23
24
  return [
24
25
  // https://codemirror.net/docs/ref/#view.keymap
25
26
  // https://discuss.codemirror.net/t/how-can-i-replace-the-default-autocompletion-keymap-v6/3322
27
+ // TODO(burdon): Set custom keymap.
26
28
  keymap.of(completionKeymap),
27
29
  // https://codemirror.net/examples/autocompletion
28
30
  // https://codemirror.net/docs/ref/#autocomplete.autocompletion
29
31
  autocompletion({
30
- // TODO(burdon): Set custom keymap.
31
- // defaultKeymap: false,
32
+ defaultKeymap: false,
32
33
  // Don't start unless key press.
33
- activateOnTyping: false,
34
- // TODO(burdon): Option to create new page?
35
- // TODO(burdon): Optional decoration via addToOptions
36
- override: [
37
- (context) => {
38
- const match = context.matchBefore(/\w*/);
39
- if (!match || match.from === match.to && !context.explicit) {
40
- return null;
41
- }
42
- return {
43
- from: match.from,
44
- options: onSearch(match.text.toLowerCase())
45
- };
34
+ activateOnTyping: false
35
+ }),
36
+ // TODO(burdon): Option to create new page?
37
+ // TODO(burdon): Optional decoration via addToOptions
38
+ markdownLanguage.data.of({
39
+ autocomplete: (context) => {
40
+ const match = context.matchBefore(/\w*/);
41
+ if (!match || match.from === match.to && !context.explicit) {
42
+ return null;
46
43
  }
47
- ]
44
+ return {
45
+ from: match.from,
46
+ options: onSearch(match.text.toLowerCase())
47
+ };
48
+ }
48
49
  })
49
50
  ];
50
51
  };
@@ -273,30 +274,39 @@ var PatchSemaphore = class {
273
274
  }
274
275
  };
275
276
 
276
- // packages/ui/react-ui-editor/src/util/cursor.ts
277
+ // packages/ui/react-ui-editor/src/extensions/cursor.ts
277
278
  import { Facet } from "@codemirror/state";
278
- var DEFAULT_CURSOR_CONVERTER = {
279
+ var defaultCursorConverter = {
279
280
  toCursor: (position) => position.toString(),
280
281
  fromCursor: (cursor) => parseInt(cursor)
281
282
  };
282
- var CursorConverter = Facet.define({
283
- combine: (providers) => providers[0] ?? DEFAULT_CURSOR_CONVERTER
284
- });
285
-
286
- // packages/ui/react-ui-editor/src/util/util.ts
287
- import { log } from "@dxos/log";
288
- var __dxlog_file = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/util/util.ts";
289
- var callbackWrapper = (fn) => (...args) => {
290
- try {
291
- return fn(...args);
292
- } catch (error) {
293
- log.catch(error, void 0, {
294
- F: __dxlog_file,
295
- L: 16,
296
- S: void 0,
297
- C: (f, a) => f(...a)
283
+ var Cursor = class {
284
+ static {
285
+ this.converter = Facet.define({
286
+ combine: (providers) => providers[0] ?? defaultCursorConverter
298
287
  });
299
288
  }
289
+ static {
290
+ this.getCursorFromRange = (cursorConverter3, range) => {
291
+ const from = cursorConverter3.toCursor(range.from);
292
+ const to = cursorConverter3.toCursor(range.to, -1);
293
+ return [
294
+ from,
295
+ to
296
+ ].join(":");
297
+ };
298
+ }
299
+ static {
300
+ this.getRangeFromCursor = (cursorConverter3, cursor) => {
301
+ const parts = cursor.split(":");
302
+ const from = cursorConverter3.fromCursor(parts[0]);
303
+ const to = cursorConverter3.fromCursor(parts[1]);
304
+ return from !== void 0 && to !== void 0 ? {
305
+ from,
306
+ to
307
+ } : void 0;
308
+ };
309
+ }
300
310
  };
301
311
 
302
312
  // packages/ui/react-ui-editor/src/extensions/automerge/automerge.ts
@@ -355,7 +365,7 @@ var automerge3 = ({ handle, path }) => {
355
365
  });
356
366
  return {
357
367
  extension: [
358
- CursorConverter.of(cursorConverter(handle, path)),
368
+ Cursor.converter.of(cursorConverter(handle, path)),
359
369
  semaphoreFacet.of(semaphore),
360
370
  stateField,
361
371
  viewPlugin
@@ -397,7 +407,7 @@ var RemoteSelectionsDecorator = class {
397
407
  this._ctx = new Context();
398
408
  this._lastAnchor = void 0;
399
409
  this._lastHead = void 0;
400
- this._cursorConverter = view.state.facet(CursorConverter);
410
+ this._cursorConverter = view.state.facet(Cursor.converter);
401
411
  this._provider = view.state.facet(AwarenessProvider);
402
412
  this._provider.open();
403
413
  this._provider.remoteStateChange.on(this._ctx, () => {
@@ -452,7 +462,7 @@ var RemoteSelectionsDecorator = class {
452
462
  attributes: {
453
463
  style: `background-color: ${lightColor}`
454
464
  },
455
- class: "cm-ySelection"
465
+ class: "cm-collab-selection"
456
466
  })
457
467
  });
458
468
  } else {
@@ -463,7 +473,7 @@ var RemoteSelectionsDecorator = class {
463
473
  attributes: {
464
474
  style: `background-color: ${color}`
465
475
  },
466
- class: "cm-ySelection"
476
+ class: "cm-collab-selection"
467
477
  })
468
478
  });
469
479
  decorations.push({
@@ -473,7 +483,7 @@ var RemoteSelectionsDecorator = class {
473
483
  attributes: {
474
484
  style: `background-color: ${color}`
475
485
  },
476
- class: "cm-ySelection"
486
+ class: "cm-collab-selection"
477
487
  })
478
488
  });
479
489
  for (let i = startLine.number + 1; i < endLine.number; i++) {
@@ -484,7 +494,7 @@ var RemoteSelectionsDecorator = class {
484
494
  value: Decoration.line({
485
495
  attributes: {
486
496
  style: `background-color: ${color}`,
487
- class: "cm-yLineSelection"
497
+ class: "cm-collab-selectionLine"
488
498
  }
489
499
  })
490
500
  });
@@ -504,23 +514,21 @@ var RemoteSelectionsDecorator = class {
504
514
  }
505
515
  };
506
516
  var RemoteCaretWidget = class extends WidgetType {
507
- constructor(name, color) {
517
+ constructor(_name, _color) {
508
518
  super();
509
- this.name = name;
510
- this.color = color;
511
- this.name = name;
512
- this.color = color;
519
+ this._name = _name;
520
+ this._color = _color;
513
521
  }
514
522
  toDOM() {
515
523
  const span = document.createElement("span");
516
- span.className = "cm-ySelectionCaret";
517
- span.style.backgroundColor = this.color;
518
- span.style.borderColor = this.color;
524
+ span.className = "cm-collab-selectionCaret";
525
+ span.style.backgroundColor = this._color;
526
+ span.style.borderColor = this._color;
519
527
  const dot = document.createElement("div");
520
- dot.className = "cm-ySelectionCaretDot";
528
+ dot.className = "cm-collab-selectionCaretDot";
521
529
  const info = document.createElement("div");
522
- info.className = "cm-ySelectionInfo";
523
- info.innerText = this.name;
530
+ info.className = "cm-collab-selectionInfo";
531
+ info.innerText = this._name;
524
532
  span.appendChild(document.createTextNode("\u2060"));
525
533
  span.appendChild(dot);
526
534
  span.appendChild(document.createTextNode("\u2060"));
@@ -528,15 +536,12 @@ var RemoteCaretWidget = class extends WidgetType {
528
536
  span.appendChild(document.createTextNode("\u2060"));
529
537
  return span;
530
538
  }
531
- eq(widget) {
532
- return widget.color === this.color;
533
- }
534
- compare(widget) {
535
- return widget.color === this.color;
536
- }
537
539
  updateDOM() {
538
540
  return false;
539
541
  }
542
+ eq(widget) {
543
+ return widget._color === this._color;
544
+ }
540
545
  get estimatedHeight() {
541
546
  return -1;
542
547
  }
@@ -545,12 +550,12 @@ var RemoteCaretWidget = class extends WidgetType {
545
550
  }
546
551
  };
547
552
  var styles = EditorView.baseTheme({
548
- ".cm-ySelection": {},
549
- ".cm-yLineSelection": {
553
+ ".cm-collab-selection": {},
554
+ ".cm-collab-selectionLine": {
550
555
  padding: 0,
551
556
  margin: "0px 2px 0px 4px"
552
557
  },
553
- ".cm-ySelectionCaret": {
558
+ ".cm-collab-selectionCaret": {
554
559
  position: "relative",
555
560
  borderLeft: "1px solid black",
556
561
  borderRight: "1px solid black",
@@ -559,7 +564,7 @@ var styles = EditorView.baseTheme({
559
564
  boxSizing: "border-box",
560
565
  display: "inline"
561
566
  },
562
- ".cm-ySelectionCaretDot": {
567
+ ".cm-collab-selectionCaretDot": {
563
568
  borderRadius: "50%",
564
569
  position: "absolute",
565
570
  width: ".4em",
@@ -570,11 +575,11 @@ var styles = EditorView.baseTheme({
570
575
  transition: "transform .3s ease-in-out",
571
576
  boxSizing: "border-box"
572
577
  },
573
- ".cm-ySelectionCaret:hover > .cm-ySelectionCaretDot": {
578
+ ".cm-collab-selectionCaret:hover > .cm-collab-selectionCaretDot": {
574
579
  transformOrigin: "bottom center",
575
580
  transform: "scale(0)"
576
581
  },
577
- ".cm-ySelectionInfo": {
582
+ ".cm-collab-selectionInfo": {
578
583
  position: "absolute",
579
584
  top: "-1.05em",
580
585
  left: "-1px",
@@ -595,7 +600,7 @@ var styles = EditorView.baseTheme({
595
600
  transitionDelay: "0s",
596
601
  whiteSpace: "nowrap"
597
602
  },
598
- ".cm-ySelectionCaret:hover > .cm-ySelectionInfo": {
603
+ ".cm-collab-selectionCaret:hover > .cm-collab-selectionInfo": {
599
604
  opacity: 1,
600
605
  transitionDelay: "0s"
601
606
  }
@@ -622,7 +627,7 @@ var basicBundle = ({ readonly, themeMode, placeholder: _placeholder }) => [
622
627
  import { EditorView as EditorView3, keymap as keymap2 } from "@codemirror/view";
623
628
  import defaultsDeep from "lodash.defaultsdeep";
624
629
  import { invariant } from "@dxos/invariant";
625
- var __dxlog_file2 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/extensions/blast.ts";
630
+ var __dxlog_file = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/extensions/blast.ts";
626
631
  var defaultOptions = {
627
632
  effect: 2,
628
633
  maxParticles: 200,
@@ -749,7 +754,7 @@ var Blaster = class {
749
754
  }
750
755
  initialize() {
751
756
  invariant(!this._canvas && !this._ctx, void 0, {
752
- F: __dxlog_file2,
757
+ F: __dxlog_file,
753
758
  L: 138,
754
759
  S: this,
755
760
  A: [
@@ -786,7 +791,7 @@ var Blaster = class {
786
791
  }
787
792
  start() {
788
793
  invariant(this._canvas && this._ctx, void 0, {
789
- F: __dxlog_file2,
794
+ F: __dxlog_file,
790
795
  L: 177,
791
796
  S: this,
792
797
  A: [
@@ -1056,7 +1061,7 @@ var getLineRange = (lines, from, to) => {
1056
1061
  var code2 = () => {
1057
1062
  const getDecorations = (view) => {
1058
1063
  const decorations = [];
1059
- if (view.state.readOnly) {
1064
+ if (!view.hasFocus || view.state.readOnly) {
1060
1065
  const text = view.state.doc.sliceString(0);
1061
1066
  const matches = text.matchAll(CODE_REGEX);
1062
1067
  const blocks = view.viewportLineBlocks;
@@ -1075,10 +1080,15 @@ var code2 = () => {
1075
1080
  return [
1076
1081
  ViewPlugin3.fromClass(class {
1077
1082
  constructor(view) {
1083
+ this.hasFocus = false;
1078
1084
  this.decorations = getDecorations(view);
1079
1085
  }
1080
1086
  update(update2) {
1081
- if (update2.docChanged || update2.viewportChanged) {
1087
+ if (
1088
+ // TODO(burdon): Generalize for other extensions.
1089
+ this.hasFocus !== update2.view.hasFocus || update2.startState.readOnly !== update2.view.state.readOnly || update2.docChanged || update2.viewportChanged
1090
+ ) {
1091
+ this.hasFocus = update2.view.hasFocus;
1082
1092
  this.decorations = getDecorations(update2.view);
1083
1093
  }
1084
1094
  }
@@ -1091,23 +1101,32 @@ var code2 = () => {
1091
1101
 
1092
1102
  // packages/ui/react-ui-editor/src/extensions/comments.ts
1093
1103
  import { StateEffect as StateEffect2, StateField as StateField2 } from "@codemirror/state";
1094
- import { hoverTooltip, keymap as keymap3, Decoration as Decoration3, EditorView as EditorView5, MatchDecorator, ViewPlugin as ViewPlugin4, WidgetType as WidgetType2 } from "@codemirror/view";
1104
+ import { hoverTooltip, keymap as keymap3, Decoration as Decoration3, EditorView as EditorView5 } from "@codemirror/view";
1095
1105
  import sortBy from "lodash.sortby";
1096
1106
  import { debounce } from "@dxos/async";
1097
1107
  import { invariant as invariant2 } from "@dxos/invariant";
1098
1108
  import { log as log2 } from "@dxos/log";
1099
1109
  import { nonNullable } from "@dxos/util";
1110
+
1111
+ // packages/ui/react-ui-editor/src/util.ts
1112
+ import { log } from "@dxos/log";
1113
+ var __dxlog_file2 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/util.ts";
1114
+ var callbackWrapper = (fn) => (...args) => {
1115
+ try {
1116
+ return fn(...args);
1117
+ } catch (error) {
1118
+ log.catch(error, void 0, {
1119
+ F: __dxlog_file2,
1120
+ L: 16,
1121
+ S: void 0,
1122
+ C: (f, a) => f(...a)
1123
+ });
1124
+ }
1125
+ };
1126
+
1127
+ // packages/ui/react-ui-editor/src/extensions/comments.ts
1100
1128
  var __dxlog_file3 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/extensions/comments.ts";
1101
1129
  var styles3 = EditorView5.baseTheme({
1102
- "& .cm-bookmark": {
1103
- cursor: "pointer",
1104
- margin: "4px",
1105
- padding: "4px",
1106
- backgroundColor: "yellow"
1107
- },
1108
- "& .cm-bookmark-selected": {
1109
- backgroundColor: "orange"
1110
- },
1111
1130
  "& .cm-comment": {
1112
1131
  backgroundColor: getToken("extend.colors.yellow.50")
1113
1132
  },
@@ -1146,7 +1165,7 @@ var commentsStateField = StateField2.define({
1146
1165
  ranges: []
1147
1166
  }),
1148
1167
  update: (value, tr) => {
1149
- const cursorConverter3 = tr.state.facet(CursorConverter);
1168
+ const cursorConverter3 = tr.state.facet(Cursor.converter);
1150
1169
  for (const effect of tr.effects) {
1151
1170
  if (effect.is(setSelection)) {
1152
1171
  return {
@@ -1157,7 +1176,7 @@ var commentsStateField = StateField2.define({
1157
1176
  if (effect.is(setCommentRange)) {
1158
1177
  const { comments: comments2 } = effect.value;
1159
1178
  const ranges = comments2.map((comment) => {
1160
- const range = getRangeFromCursor(cursorConverter3, comment.cursor);
1179
+ const range = Cursor.getRangeFromCursor(cursorConverter3, comment.cursor);
1161
1180
  return range && {
1162
1181
  ...comment,
1163
1182
  ...range
@@ -1195,42 +1214,14 @@ var highlightDecorations = EditorView5.decorations.compute([
1195
1214
  }).filter(nonNullable) ?? [];
1196
1215
  return Decoration3.set(decorations);
1197
1216
  });
1198
- var BookmarkWidget = class extends WidgetType2 {
1199
- constructor(_anchor, _id, _handleClick) {
1200
- super();
1201
- this._anchor = _anchor;
1202
- this._id = _id;
1203
- this._handleClick = _handleClick;
1204
- invariant2(this._id, void 0, {
1205
- F: __dxlog_file3,
1206
- L: 168,
1207
- S: this,
1208
- A: [
1209
- "this._id",
1210
- ""
1211
- ]
1212
- });
1213
- }
1214
- eq(other) {
1215
- return this._anchor === other._anchor && this._id === other._id;
1216
- }
1217
- toDOM() {
1218
- const span = document.createElement("span");
1219
- span.className = "cm-bookmark";
1220
- span.textContent = "\u203B";
1221
- if (this._handleClick) {
1222
- span.onclick = () => this._handleClick();
1223
- }
1224
- return span;
1225
- }
1226
- };
1227
1217
  var comments = (options = {}) => {
1218
+ const { key: shortcut = "meta-'" } = options;
1228
1219
  const handleSelect = debounce((state) => options.onSelect?.(state), 200);
1229
1220
  const createCommentThread = (view) => {
1230
- const cursorConverter3 = view.state.facet(CursorConverter);
1221
+ const cursorConverter3 = view.state.facet(Cursor.converter);
1231
1222
  invariant2(options.onCreate, void 0, {
1232
1223
  F: __dxlog_file3,
1233
- L: 224,
1224
+ L: 177,
1234
1225
  S: void 0,
1235
1226
  A: [
1236
1227
  "options.onCreate",
@@ -1241,12 +1232,12 @@ var comments = (options = {}) => {
1241
1232
  if (from === to) {
1242
1233
  return false;
1243
1234
  }
1244
- const relPos = getCursorFromRange(cursorConverter3, {
1235
+ const cursor = Cursor.getCursorFromRange(cursorConverter3, {
1245
1236
  from,
1246
1237
  to
1247
1238
  });
1248
- if (relPos) {
1249
- const id = callbackWrapper(options.onCreate)(relPos);
1239
+ if (cursor) {
1240
+ const id = callbackWrapper(options.onCreate)(cursor, view.coordsAtPos(from));
1250
1241
  if (id) {
1251
1242
  view.dispatch({
1252
1243
  effects: setSelection.of({
@@ -1273,35 +1264,7 @@ var comments = (options = {}) => {
1273
1264
  }
1274
1265
  return false;
1275
1266
  };
1276
- const bookmarksViewPlugin = ViewPlugin4.fromClass(class BookmarkViewPlugin {
1277
- static {
1278
- // Match markdown footnotes (option).
1279
- // https://www.markdownguide.org/extended-syntax/#footnotes
1280
- this.bookmarkMatcher = new MatchDecorator({
1281
- regexp: /\[\^(\w+)\]/g,
1282
- decoration: (match, view, pos) => {
1283
- const id = match[1];
1284
- return Decoration3.replace({
1285
- id,
1286
- widget: new BookmarkWidget(pos, id)
1287
- });
1288
- }
1289
- });
1290
- }
1291
- constructor(view) {
1292
- this.bookmarks = BookmarkViewPlugin.bookmarkMatcher.createDeco(view);
1293
- }
1294
- update(update2) {
1295
- this.bookmarks = BookmarkViewPlugin.bookmarkMatcher.updateDeco(update2, this.bookmarks);
1296
- }
1297
- }, {
1298
- decorations: (instance) => instance.bookmarks,
1299
- provide: (plugin) => EditorView5.atomicRanges.of((view) => {
1300
- return view.plugin(plugin)?.bookmarks || Decoration3.none;
1301
- })
1302
- });
1303
1267
  return [
1304
- bookmarksViewPlugin,
1305
1268
  commentsStateField,
1306
1269
  highlightDecorations,
1307
1270
  styles3,
@@ -1310,7 +1273,7 @@ var comments = (options = {}) => {
1310
1273
  //
1311
1274
  options.onCreate ? keymap3.of([
1312
1275
  {
1313
- key: options?.key ?? "meta-'",
1276
+ key: shortcut,
1314
1277
  run: callbackWrapper(createCommentThread)
1315
1278
  }
1316
1279
  ]) : [],
@@ -1327,7 +1290,7 @@ var comments = (options = {}) => {
1327
1290
  above: true,
1328
1291
  create: () => {
1329
1292
  const el = document.createElement("div");
1330
- options.onHover?.(el);
1293
+ options.onHover?.(el, shortcut);
1331
1294
  return {
1332
1295
  dom: el,
1333
1296
  offset: {
@@ -1348,27 +1311,27 @@ var comments = (options = {}) => {
1348
1311
  // TODO(burdon): Is there a better (finer grained) way to do this?
1349
1312
  //
1350
1313
  EditorView5.updateListener.of(({ view, state, changes }) => {
1351
- const cursorConverter3 = view.state.facet(CursorConverter);
1314
+ const cursorConverter3 = view.state.facet(Cursor.converter);
1352
1315
  {
1353
1316
  let mod = false;
1354
1317
  const { active, ranges } = state.field(commentsStateField);
1355
1318
  changes.iterChanges((from, to, from2, to2) => {
1356
1319
  ranges.forEach((range) => {
1357
1320
  if (from2 === to2) {
1358
- const newRange = getRangeFromCursor(cursorConverter3, range.cursor);
1321
+ const newRange = Cursor.getRangeFromCursor(cursorConverter3, range.cursor);
1359
1322
  if (!newRange || newRange.to - newRange.from === 0) {
1360
1323
  log2.info("deleted comment", {
1361
1324
  thread: range.id
1362
1325
  }, {
1363
1326
  F: __dxlog_file3,
1364
- L: 358,
1327
+ L: 274,
1365
1328
  S: void 0,
1366
1329
  C: (f, a) => f(...a)
1367
1330
  });
1368
1331
  }
1369
1332
  }
1370
1333
  if (from <= range.to) {
1371
- const newRange = getRangeFromCursor(cursorConverter3, range.cursor);
1334
+ const newRange = Cursor.getRangeFromCursor(cursorConverter3, range.cursor);
1372
1335
  Object.assign(range, newRange);
1373
1336
  mod = true;
1374
1337
  }
@@ -1417,26 +1380,9 @@ var comments = (options = {}) => {
1417
1380
  })
1418
1381
  ];
1419
1382
  };
1420
- var getCursorFromRange = (cursorConverter3, range) => {
1421
- const from = cursorConverter3.toCursor(range.from);
1422
- const to = cursorConverter3.toCursor(range.to, -1);
1423
- return [
1424
- from,
1425
- to
1426
- ].join(":");
1427
- };
1428
- var getRangeFromCursor = (cursorConverter3, cursor) => {
1429
- const parts = cursor.split(":");
1430
- const from = cursorConverter3.fromCursor(parts[0]);
1431
- const to = cursorConverter3.fromCursor(parts[1]);
1432
- return from !== void 0 && to !== void 0 ? {
1433
- from,
1434
- to
1435
- } : void 0;
1436
- };
1437
1383
 
1438
1384
  // packages/ui/react-ui-editor/src/extensions/hr.ts
1439
- import { Decoration as Decoration4, EditorView as EditorView6, MatchDecorator as MatchDecorator2, ViewPlugin as ViewPlugin5, WidgetType as WidgetType3 } from "@codemirror/view";
1385
+ import { Decoration as Decoration4, EditorView as EditorView6, MatchDecorator, ViewPlugin as ViewPlugin4, WidgetType as WidgetType2 } from "@codemirror/view";
1440
1386
  var styles4 = EditorView6.baseTheme({
1441
1387
  "& .cm-hr": {
1442
1388
  // TODO(burdon): ???
@@ -1444,7 +1390,7 @@ var styles4 = EditorView6.baseTheme({
1444
1390
  borderBottom: `1px solid ${getToken("extend.colors.neutral.200")}`
1445
1391
  }
1446
1392
  });
1447
- var HorizontalRuleWidget = class extends WidgetType3 {
1393
+ var HorizontalRuleWidget = class extends WidgetType2 {
1448
1394
  constructor(_pos) {
1449
1395
  super();
1450
1396
  this._pos = _pos;
@@ -1458,7 +1404,7 @@ var HorizontalRuleWidget = class extends WidgetType3 {
1458
1404
  return el;
1459
1405
  }
1460
1406
  };
1461
- var placeholderMatcher = new MatchDecorator2({
1407
+ var placeholderMatcher = new MatchDecorator({
1462
1408
  regexp: /^---$/g,
1463
1409
  decoration: (match, view, pos) => Decoration4.replace({
1464
1410
  widget: new HorizontalRuleWidget(pos)
@@ -1466,7 +1412,7 @@ var placeholderMatcher = new MatchDecorator2({
1466
1412
  });
1467
1413
  var hr = () => [
1468
1414
  styles4,
1469
- ViewPlugin5.fromClass(class {
1415
+ ViewPlugin4.fromClass(class {
1470
1416
  constructor(view) {
1471
1417
  this.rules = placeholderMatcher.createDeco(view);
1472
1418
  }
@@ -1485,7 +1431,7 @@ var hr = () => [
1485
1431
  // packages/ui/react-ui-editor/src/extensions/image.ts
1486
1432
  import { syntaxTree } from "@codemirror/language";
1487
1433
  import { StateField as StateField3 } from "@codemirror/state";
1488
- import { Decoration as Decoration5, EditorView as EditorView7, WidgetType as WidgetType4 } from "@codemirror/view";
1434
+ import { Decoration as Decoration5, EditorView as EditorView7, WidgetType as WidgetType3 } from "@codemirror/view";
1489
1435
  var image = (options = {}) => {
1490
1436
  return StateField3.define({
1491
1437
  create: (state) => {
@@ -1537,7 +1483,7 @@ var buildDecorations = (from, to, state) => {
1537
1483
  });
1538
1484
  return decorations;
1539
1485
  };
1540
- var ImageWidget = class extends WidgetType4 {
1486
+ var ImageWidget = class extends WidgetType3 {
1541
1487
  constructor(_url) {
1542
1488
  super();
1543
1489
  this._url = _url;
@@ -1557,11 +1503,11 @@ var ImageWidget = class extends WidgetType4 {
1557
1503
  // packages/ui/react-ui-editor/src/extensions/link.ts
1558
1504
  import { syntaxTree as syntaxTree2 } from "@codemirror/language";
1559
1505
  import { RangeSetBuilder } from "@codemirror/state";
1560
- import { Decoration as Decoration6, WidgetType as WidgetType5, hoverTooltip as hoverTooltip2, ViewPlugin as ViewPlugin6 } from "@codemirror/view";
1506
+ import { Decoration as Decoration6, WidgetType as WidgetType4, hoverTooltip as hoverTooltip2, ViewPlugin as ViewPlugin5 } from "@codemirror/view";
1561
1507
  import { tooltipContent } from "@dxos/react-ui-theme";
1562
1508
  var link = (options = {}) => {
1563
1509
  const extensions = [
1564
- ViewPlugin6.fromClass(class {
1510
+ ViewPlugin5.fromClass(class {
1565
1511
  constructor(view) {
1566
1512
  this.decorations = buildDecorations2(view, options);
1567
1513
  }
@@ -1612,7 +1558,7 @@ var link = (options = {}) => {
1612
1558
  }
1613
1559
  return extensions;
1614
1560
  };
1615
- var LinkButton = class extends WidgetType5 {
1561
+ var LinkButton = class extends WidgetType4 {
1616
1562
  constructor(_url, _onAttach) {
1617
1563
  super();
1618
1564
  this._url = _url;
@@ -1627,7 +1573,7 @@ var LinkButton = class extends WidgetType5 {
1627
1573
  return el;
1628
1574
  }
1629
1575
  };
1630
- var LinkText = class extends WidgetType5 {
1576
+ var LinkText = class extends WidgetType4 {
1631
1577
  constructor(_text, _url) {
1632
1578
  super();
1633
1579
  this._text = _text;
@@ -1708,7 +1654,7 @@ var listener = ({ onFocus, onChange }) => {
1708
1654
  // packages/ui/react-ui-editor/src/extensions/markdown/bundle.ts
1709
1655
  import { closeBrackets as closeBrackets2, closeBracketsKeymap } from "@codemirror/autocomplete";
1710
1656
  import { history, historyKeymap, indentWithTab, standardKeymap } from "@codemirror/commands";
1711
- import { markdownLanguage as markdownLanguage2, markdown } from "@codemirror/lang-markdown";
1657
+ import { markdownLanguage as markdownLanguage3, markdown } from "@codemirror/lang-markdown";
1712
1658
  import { bracketMatching as bracketMatching2, defaultHighlightStyle as defaultHighlightStyle2, indentOnInput, syntaxHighlighting as syntaxHighlighting2 } from "@codemirror/language";
1713
1659
  import { languages } from "@codemirror/language-data";
1714
1660
  import { searchKeymap } from "@codemirror/search";
@@ -1717,7 +1663,7 @@ import { oneDarkHighlightStyle as oneDarkHighlightStyle2 } from "@codemirror/the
1717
1663
  import { crosshairCursor, drawSelection as drawSelection2, dropCursor, EditorView as EditorView9, highlightActiveLine, keymap as keymap4, placeholder as placeholder2 } from "@codemirror/view";
1718
1664
 
1719
1665
  // packages/ui/react-ui-editor/src/extensions/markdown/highlight.ts
1720
- import { markdownLanguage } from "@codemirror/lang-markdown";
1666
+ import { markdownLanguage as markdownLanguage2 } from "@codemirror/lang-markdown";
1721
1667
  import { HighlightStyle } from "@codemirror/language";
1722
1668
  import { tags, styleTags, Tag } from "@lezer/highlight";
1723
1669
  import { Table } from "@lezer/markdown";
@@ -1727,7 +1673,6 @@ var markdownTags = {
1727
1673
  CodeText: Tag.define(),
1728
1674
  EmphasisMark: Tag.define(),
1729
1675
  HeaderMark: Tag.define(),
1730
- // HorizontalRule: Tag.define(),
1731
1676
  InlineCode: Tag.define(),
1732
1677
  LinkLabel: Tag.define(),
1733
1678
  LinkReference: Tag.define(),
@@ -1891,12 +1836,6 @@ var markdownHighlightStyle = (readonly) => {
1891
1836
  ],
1892
1837
  class: blockquote
1893
1838
  },
1894
- // TODO(burdon): Replace with extension.
1895
- // {
1896
- // tag: [markdownTags.HorizontalRule],
1897
- // class: mx(horizontalRule, readonly && '-indent-[100rem]'),
1898
- // },
1899
- // TODO(burdon): Only if being edited.
1900
1839
  {
1901
1840
  tag: [
1902
1841
  markdownTags.TableCell
@@ -1904,7 +1843,7 @@ var markdownHighlightStyle = (readonly) => {
1904
1843
  class: "font-mono"
1905
1844
  }
1906
1845
  ], {
1907
- scope: markdownLanguage,
1846
+ scope: markdownLanguage2,
1908
1847
  all: {
1909
1848
  fontFamily: getToken("fontFamily.body", []).join(",")
1910
1849
  }
@@ -1936,7 +1875,7 @@ var markdownBundle = ({ readonly, themeMode, placeholder: _placeholder }) => {
1936
1875
  // NOTE: This extends the parser; it doesn't affect rendering.
1937
1876
  // https://github.github.com/gfm
1938
1877
  // https://github.com/lezer-parser/markdown?tab=readme-ov-file#github-flavored-markdown
1939
- base: markdownLanguage2,
1878
+ base: markdownLanguage3,
1940
1879
  // Languages for syntax highlighting fenced code blocks.
1941
1880
  codeLanguages: languages,
1942
1881
  // Parser extensions.
@@ -1990,7 +1929,7 @@ var mention = ({ onSearch }) => {
1990
1929
  // packages/ui/react-ui-editor/src/extensions/table.ts
1991
1930
  import { syntaxTree as syntaxTree3 } from "@codemirror/language";
1992
1931
  import { RangeSetBuilder as RangeSetBuilder2, StateField as StateField4 } from "@codemirror/state";
1993
- import { Decoration as Decoration7, EditorView as EditorView10, WidgetType as WidgetType6 } from "@codemirror/view";
1932
+ import { Decoration as Decoration7, EditorView as EditorView10, WidgetType as WidgetType5 } from "@codemirror/view";
1994
1933
  var table = (options = {}) => {
1995
1934
  return StateField4.define({
1996
1935
  create: (state) => update(state, options),
@@ -2040,7 +1979,6 @@ var update = (state, options) => {
2040
1979
  tables.forEach((table2) => {
2041
1980
  const hide = state.readOnly || cursor < table2.from || cursor > table2.to;
2042
1981
  hide && builder.add(table2.from, table2.to, Decoration7.replace({
2043
- block: true,
2044
1982
  widget: new TableWidget(table2)
2045
1983
  }));
2046
1984
  builder.add(table2.from, table2.to, Decoration7.mark({
@@ -2049,7 +1987,7 @@ var update = (state, options) => {
2049
1987
  });
2050
1988
  return builder.finish();
2051
1989
  };
2052
- var TableWidget = class extends WidgetType6 {
1990
+ var TableWidget = class extends WidgetType5 {
2053
1991
  constructor(_table) {
2054
1992
  super();
2055
1993
  this._table = _table;
@@ -2081,17 +2019,20 @@ var TableWidget = class extends WidgetType6 {
2081
2019
  }
2082
2020
  return table2;
2083
2021
  }
2022
+ ignoreEvent(e) {
2023
+ return !/^mouse/.test(e.type);
2024
+ }
2084
2025
  };
2085
2026
 
2086
2027
  // packages/ui/react-ui-editor/src/extensions/tasklist.ts
2087
- import { EditorView as EditorView11, Decoration as Decoration8, WidgetType as WidgetType7, MatchDecorator as MatchDecorator3, ViewPlugin as ViewPlugin7 } from "@codemirror/view";
2028
+ import { EditorView as EditorView11, Decoration as Decoration8, WidgetType as WidgetType6, MatchDecorator as MatchDecorator2, ViewPlugin as ViewPlugin6 } from "@codemirror/view";
2088
2029
  var styles5 = EditorView11.baseTheme({
2089
2030
  "& .cm-task-item": {
2090
2031
  paddingLeft: "4px",
2091
2032
  paddingRight: "8px"
2092
2033
  }
2093
2034
  });
2094
- var CheckboxWidget = class extends WidgetType7 {
2035
+ var CheckboxWidget = class extends WidgetType6 {
2095
2036
  constructor(_checked, _indent, _onCheck) {
2096
2037
  super();
2097
2038
  this._checked = _checked;
@@ -2125,7 +2066,7 @@ var CheckboxWidget = class extends WidgetType7 {
2125
2066
  }
2126
2067
  };
2127
2068
  var tasklist = (options = {}) => {
2128
- const taskMatcher = new MatchDecorator3({
2069
+ const taskMatcher = new MatchDecorator2({
2129
2070
  regexp: /^(\s*)- \[([ xX])\]\s/g,
2130
2071
  decoration: (match, view, pos) => {
2131
2072
  const indent = Math.floor(match[1].length / 2);
@@ -2148,7 +2089,7 @@ var tasklist = (options = {}) => {
2148
2089
  });
2149
2090
  }
2150
2091
  });
2151
- const tasks = ViewPlugin7.fromClass(class {
2092
+ const tasks = ViewPlugin6.fromClass(class {
2152
2093
  constructor(view) {
2153
2094
  this.tasks = taskMatcher.createDeco(view);
2154
2095
  }
@@ -2241,7 +2182,7 @@ var cursorConverter2 = (text) => ({
2241
2182
 
2242
2183
  // packages/ui/react-ui-editor/src/extensions/yjs/yjs.ts
2243
2184
  var yjs = (content, awareness2) => [
2244
- CursorConverter.of(cursorConverter2(content)),
2185
+ Cursor.converter.of(cursorConverter2(content)),
2245
2186
  yCollab(content, awareness2)
2246
2187
  ];
2247
2188
 
@@ -2338,33 +2279,6 @@ var defaultTheme = {
2338
2279
  backgroundColor: get3(tokens, "extend.colors.yellow.700")
2339
2280
  },
2340
2281
  //
2341
- // collaboration
2342
- // TODO(burdon): Review classnames (YJS dependent?) Note: e2e tests depend on this class.
2343
- //
2344
- "&light .cm-ySelection, &light .cm-yLineSelection": {
2345
- mixBlendMode: "multiply"
2346
- },
2347
- "&dark .cm-ySelection, &dark .cm-yLineSelection": {
2348
- mixBlendMode: "screen"
2349
- },
2350
- ".cm-ySelectionInfo": {
2351
- padding: "2px 4px",
2352
- marginBlockStart: "-4px"
2353
- },
2354
- ".cm-ySelection, & .cm-selectionMatch": {
2355
- paddingBlockStart: ".15em",
2356
- paddingBlockEnd: ".15em"
2357
- },
2358
- ".cm-ySelectionCaret": {
2359
- display: "inline-block",
2360
- insetBlockStart: ".1em",
2361
- blockSize: "1.4em",
2362
- verticalAlign: "top"
2363
- },
2364
- ".cm-yLineSelection": {
2365
- margin: 0
2366
- },
2367
- //
2368
2282
  // link
2369
2283
  //
2370
2284
  ".cm-link": {
@@ -2401,13 +2315,6 @@ var defaultTheme = {
2401
2315
  textDecoration: "none"
2402
2316
  },
2403
2317
  //
2404
- // widgets
2405
- //
2406
- ".cm-widgetBuffer": {
2407
- display: "none",
2408
- height: 0
2409
- },
2410
- //
2411
2318
  // table
2412
2319
  //
2413
2320
  ".cm-table *": {
@@ -2438,27 +2345,17 @@ var defaultTheme = {
2438
2345
  },
2439
2346
  //
2440
2347
  // font size
2441
- // TODO(thure): This appears to be the best or only way to set selection caret heights, but it's far more verbose than it needs to be.
2348
+ // TODO(thure): This appears to be the best or only way to set selection caret heights,
2349
+ // but it's far more verbose than it needs to be.
2442
2350
  //
2443
- ...Object.keys(get3(tokens, "extend.fontSize", {})).reduce((acc, fontSize) => {
2444
- const height = get3(tokens, [
2445
- "extend",
2446
- "fontSize",
2447
- fontSize,
2448
- 1,
2449
- "lineHeight"
2450
- ]);
2451
- acc[`& .text-${fontSize} + .cm-ySelectionCaret`] = {
2452
- height
2453
- };
2454
- acc[`& .text-${fontSize} + .cm-ySelection + .cm-ySelectionCaret`] = {
2455
- height
2456
- };
2457
- acc[`& .text-${fontSize} + .cm-widgetBuffer + .cm-ySelectionCaret`] = {
2458
- height
2459
- };
2460
- return acc;
2461
- }, {}),
2351
+ // ...Object.keys(get(tokens, 'extend.fontSize', {})).reduce((acc: Record<string, any>, fontSize) => {
2352
+ // const height = get(tokens, ['extend', 'fontSize', fontSize, 1, 'lineHeight']);
2353
+ //
2354
+ // acc[`& .text-${fontSize} + .cm-ySelectionCaret`] = { height };
2355
+ // acc[`& .text-${fontSize} + .cm-ySelection + .cm-ySelectionCaret`] = { height };
2356
+ // acc[`& .text-${fontSize} + .cm-widgetBuffer + .cm-ySelectionCaret`] = { height };
2357
+ // return acc;
2358
+ // }, {}),
2462
2359
  /**
2463
2360
  * Panels
2464
2361
  * TODO(burdon): Needs styling attention (esp. dark mode).
@@ -2509,7 +2406,9 @@ var textTheme = {
2509
2406
  }
2510
2407
  };
2511
2408
  var markdownTheme = {
2512
- // NOTE: Must leave base font family as is (i.e., monospace) due to fenced code blocks.
2409
+ ".cm-scroller": {
2410
+ fontFamily: get3(tokens, "fontFamily.body", []).join(",")
2411
+ },
2513
2412
  ".cm-placeholder": {
2514
2413
  fontFamily: get3(tokens, "fontFamily.body", []).join(",")
2515
2414
  }
@@ -2528,21 +2427,23 @@ var EditorModes = [
2528
2427
  "default",
2529
2428
  "vim"
2530
2429
  ];
2531
- var BaseTextEditor = /* @__PURE__ */ forwardRef(({ model, readonly, comments: comments2, extensions = [], editorMode, slots = defaultSlots }, forwardedRef) => {
2532
- const { themeMode } = useThemeContext();
2430
+ var BaseTextEditor = /* @__PURE__ */ forwardRef(({ model, focus, selection, readonly, comments: comments2, extensions = [], editorMode, theme, slots = defaultSlots }, forwardedRef) => {
2533
2431
  const tabsterDOMAttribute = useFocusableGroup({
2534
2432
  tabBehavior: "limited"
2535
2433
  });
2536
- const [root, setRoot] = useState(null);
2537
- const [{ state = void 0, view = void 0 } = {}, setEditor] = useState();
2538
- useImperativeHandle(forwardedRef, () => ({
2539
- root,
2540
- state,
2434
+ const { themeMode } = useThemeContext();
2435
+ const rootRef = useRef(null);
2436
+ const [view, setView] = useState(null);
2437
+ useImperativeHandle(forwardedRef, () => view, [
2541
2438
  view
2542
- }), [
2439
+ ]);
2440
+ useEffect(() => {
2441
+ if (view && focus) {
2442
+ view.focus();
2443
+ }
2444
+ }, [
2543
2445
  view,
2544
- state,
2545
- root
2446
+ focus
2546
2447
  ]);
2547
2448
  const { awareness: awareness2, peer } = model;
2548
2449
  useEffect(() => {
@@ -2566,7 +2467,7 @@ var BaseTextEditor = /* @__PURE__ */ forwardRef(({ model, readonly, comments: co
2566
2467
  themeMode
2567
2468
  ]);
2568
2469
  useEffect(() => {
2569
- if (view && comments2?.length) {
2470
+ if (view && comments2 !== void 0) {
2570
2471
  view.dispatch({
2571
2472
  effects: setCommentRange.of({
2572
2473
  model,
@@ -2579,11 +2480,13 @@ var BaseTextEditor = /* @__PURE__ */ forwardRef(({ model, readonly, comments: co
2579
2480
  comments2
2580
2481
  ]);
2581
2482
  useEffect(() => {
2582
- if (!root) {
2483
+ if (!model || !rootRef.current) {
2583
2484
  return;
2584
2485
  }
2585
- const state2 = EditorState2.create({
2486
+ const state = EditorState2.create({
2586
2487
  doc: model.text(),
2488
+ // TODO(burdon): Composer should store and set selection when switching documents.
2489
+ selection,
2587
2490
  extensions: [
2588
2491
  readonly && EditorState2.readOnly.of(readonly),
2589
2492
  // TODO(burdon): Factor out VIM mode? (manage via MarkdownPlugin).
@@ -2591,7 +2494,7 @@ var BaseTextEditor = /* @__PURE__ */ forwardRef(({ model, readonly, comments: co
2591
2494
  // Theme.
2592
2495
  // TODO(burdon): Make theme configurable.
2593
2496
  EditorView12.baseTheme(defaultTheme),
2594
- EditorView12.theme(slots?.editor?.theme ?? {}),
2497
+ EditorView12.theme(theme ?? {}),
2595
2498
  EditorView12.darkTheme.of(themeMode === "dark"),
2596
2499
  // Storage and replication.
2597
2500
  model.extension,
@@ -2600,19 +2503,17 @@ var BaseTextEditor = /* @__PURE__ */ forwardRef(({ model, readonly, comments: co
2600
2503
  ].filter(Boolean)
2601
2504
  });
2602
2505
  view?.destroy();
2603
- setEditor({
2604
- state: state2,
2605
- view: new EditorView12({
2606
- state: state2,
2607
- parent: root
2608
- })
2506
+ const newView = new EditorView12({
2507
+ parent: rootRef.current,
2508
+ state
2609
2509
  });
2510
+ setView(newView);
2610
2511
  return () => {
2611
- view?.destroy();
2612
- setEditor(void 0);
2512
+ newView?.destroy();
2513
+ setView(null);
2613
2514
  };
2614
2515
  }, [
2615
- root,
2516
+ rootRef,
2616
2517
  model,
2617
2518
  readonly,
2618
2519
  editorMode,
@@ -2621,12 +2522,8 @@ var BaseTextEditor = /* @__PURE__ */ forwardRef(({ model, readonly, comments: co
2621
2522
  const handleKeyUp = useCallback((event) => {
2622
2523
  const { key, altKey, shiftKey, metaKey, ctrlKey } = event;
2623
2524
  switch (key) {
2624
- case "Enter": {
2625
- view?.contentDOM.focus();
2626
- break;
2627
- }
2628
2525
  case "Escape": {
2629
- editorMode === "vim" && (altKey || shiftKey || metaKey || ctrlKey) && root?.focus();
2526
+ editorMode === "vim" && (altKey || shiftKey || metaKey || ctrlKey) && rootRef.current?.focus();
2630
2527
  break;
2631
2528
  }
2632
2529
  }
@@ -2636,16 +2533,17 @@ var BaseTextEditor = /* @__PURE__ */ forwardRef(({ model, readonly, comments: co
2636
2533
  ]);
2637
2534
  return /* @__PURE__ */ React.createElement("div", {
2638
2535
  key: model.id,
2639
- ref: setRoot,
2536
+ role: "none",
2537
+ ref: rootRef,
2640
2538
  tabIndex: 0,
2641
- ...slots?.root,
2642
- ...editorMode !== "vim" && tabsterDOMAttribute,
2643
- onKeyUp: handleKeyUp
2539
+ onKeyUp: handleKeyUp,
2540
+ ...slots.root,
2541
+ ...editorMode !== "vim" && tabsterDOMAttribute
2644
2542
  });
2645
2543
  });
2646
- var TextEditor = /* @__PURE__ */ forwardRef(({ readonly, extensions = [], slots: _slots, ...props }, forwardedRef) => {
2544
+ var TextEditor = /* @__PURE__ */ forwardRef(({ readonly, placeholder: placeholder3, extensions = [], theme = textTheme, slots, ...props }, forwardedRef) => {
2647
2545
  const { themeMode } = useThemeContext();
2648
- const slots = defaultsDeep2({}, _slots, defaultTextSlots);
2546
+ const updatedSlots = defaultsDeep2({}, slots, defaultTextSlots);
2649
2547
  return /* @__PURE__ */ React.createElement(BaseTextEditor, {
2650
2548
  ref: forwardedRef,
2651
2549
  readonly,
@@ -2653,17 +2551,18 @@ var TextEditor = /* @__PURE__ */ forwardRef(({ readonly, extensions = [], slots:
2653
2551
  basicBundle({
2654
2552
  readonly,
2655
2553
  themeMode,
2656
- placeholder: slots?.editor?.placeholder
2554
+ placeholder: placeholder3
2657
2555
  }),
2658
2556
  ...extensions
2659
2557
  ],
2660
- slots,
2558
+ theme,
2559
+ slots: updatedSlots,
2661
2560
  ...props
2662
2561
  });
2663
2562
  });
2664
- var MarkdownEditor = /* @__PURE__ */ forwardRef(({ readonly, extensions = [], slots: _slots, ...props }, forwardedRef) => {
2563
+ var MarkdownEditor = /* @__PURE__ */ forwardRef(({ readonly, placeholder: placeholder3, extensions = [], theme = markdownTheme, slots, ...props }, forwardedRef) => {
2665
2564
  const { themeMode } = useThemeContext();
2666
- const slots = defaultsDeep2({}, _slots, defaultMarkdownSlots);
2565
+ const updatedSlots = defaultsDeep2({}, slots, defaultMarkdownSlots);
2667
2566
  return /* @__PURE__ */ React.createElement(BaseTextEditor, {
2668
2567
  ref: forwardedRef,
2669
2568
  readonly,
@@ -2671,30 +2570,25 @@ var MarkdownEditor = /* @__PURE__ */ forwardRef(({ readonly, extensions = [], sl
2671
2570
  markdownBundle({
2672
2571
  readonly,
2673
2572
  themeMode,
2674
- placeholder: slots?.editor?.placeholder
2573
+ placeholder: placeholder3
2675
2574
  }),
2676
2575
  ...extensions
2677
2576
  ],
2678
- slots,
2577
+ theme,
2578
+ slots: updatedSlots,
2679
2579
  ...props
2680
2580
  });
2681
2581
  });
2682
2582
  var defaultSlots = {
2683
2583
  root: {
2684
- className: mx3("p-2", inputSurface)
2584
+ className: mx3("p-2 overflow-y-auto", inputSurface)
2685
2585
  }
2686
2586
  };
2687
2587
  var defaultTextSlots = {
2688
- ...defaultSlots,
2689
- editor: {
2690
- theme: textTheme
2691
- }
2588
+ ...defaultSlots
2692
2589
  };
2693
2590
  var defaultMarkdownSlots = {
2694
- ...defaultSlots,
2695
- editor: {
2696
- theme: markdownTheme
2697
- }
2591
+ ...defaultSlots
2698
2592
  };
2699
2593
 
2700
2594
  // packages/ui/react-ui-editor/src/hooks/automerge.ts
@@ -2817,15 +2711,13 @@ import * as decoding from "lib0/decoding";
2817
2711
  import * as encoding from "lib0/encoding";
2818
2712
  import { Observable } from "lib0/observable";
2819
2713
  import * as YP from "y-protocols/awareness";
2820
- import * as Y2 from "yjs";
2821
2714
  import { invariant as invariant4 } from "@dxos/invariant";
2822
2715
  import { log as log4 } from "@dxos/log";
2823
- import { arrayToString as arrayToString2, stringToArray as stringToArray2 } from "@dxos/util";
2824
2716
  var __dxlog_file5 = "/home/runner/work/dxos/dxos/packages/ui/react-ui-editor/src/hooks/yjs.ts";
2825
2717
  var createYjsModel = ({ identity, space, text }) => {
2826
2718
  invariant4(text?.doc && text?.content, void 0, {
2827
2719
  F: __dxlog_file5,
2828
- L: 23,
2720
+ L: 21,
2829
2721
  S: void 0,
2830
2722
  A: [
2831
2723
  "text?.doc && text?.content",
@@ -2841,34 +2733,6 @@ var createYjsModel = ({ identity, space, text }) => {
2841
2733
  id: text.doc.guid,
2842
2734
  content: text.content,
2843
2735
  text: () => text.content.toString(),
2844
- // https://github.com/yjs/yjs?tab=readme-ov-file#relative-positions
2845
- // TODO(dmaretskyi): Refactor as cursor-converter.
2846
- getCursorFromRange: (range) => {
2847
- const from = Y2.encodeRelativePosition(Y2.createRelativePositionFromTypeIndex(text.content, range.from));
2848
- const to = Y2.encodeRelativePosition(Y2.createRelativePositionFromTypeIndex(text.content, range.to, -1));
2849
- return [
2850
- arrayToString2(from),
2851
- arrayToString2(to)
2852
- ].join(":");
2853
- },
2854
- getRangeFromCursor: (cursor) => {
2855
- invariant4(text.doc, void 0, {
2856
- F: __dxlog_file5,
2857
- L: 40,
2858
- S: void 0,
2859
- A: [
2860
- "text.doc",
2861
- ""
2862
- ]
2863
- });
2864
- const parts = cursor.split(":");
2865
- const from = Y2.createAbsolutePositionFromRelativePosition(Y2.decodeRelativePosition(stringToArray2(parts[0])), text.doc);
2866
- const to = Y2.createAbsolutePositionFromRelativePosition(Y2.decodeRelativePosition(stringToArray2(parts[1])), text.doc);
2867
- return from && to ? {
2868
- from: from.index,
2869
- to: to.index
2870
- } : void 0;
2871
- },
2872
2736
  extension: [
2873
2737
  modelState.init(() => model),
2874
2738
  yjs(text.content, provider?.awareness)
@@ -2918,7 +2782,7 @@ var YAwarenessProvider = class extends Observable {
2918
2782
  origin
2919
2783
  }, {
2920
2784
  F: __dxlog_file5,
2921
- L: 122,
2785
+ L: 100,
2922
2786
  S: this,
2923
2787
  C: (f, a) => f(...a)
2924
2788
  });
@@ -2931,7 +2795,7 @@ var YAwarenessProvider = class extends Observable {
2931
2795
  _handleSpaceMessage({ payload }) {
2932
2796
  log4("space message", payload, {
2933
2797
  F: __dxlog_file5,
2934
- L: 131,
2798
+ L: 109,
2935
2799
  S: this,
2936
2800
  C: (f, a) => f(...a)
2937
2801
  });
@@ -2981,7 +2845,7 @@ var modelState = StateField5.define({
2981
2845
  });
2982
2846
  var useTextModel = (props) => {
2983
2847
  const { identity, space, text } = props;
2984
- const [model, setModel] = useState2(() => createModel(props));
2848
+ const [model, setModel] = useState2();
2985
2849
  useEffect2(() => setModel(createModel(props)), [
2986
2850
  identity,
2987
2851
  space,
@@ -3040,6 +2904,7 @@ var createAutomergeModel = ({ space, identity, text }) => {
3040
2904
  export {
3041
2905
  AwarenessProvider,
3042
2906
  BaseTextEditor,
2907
+ Cursor,
3043
2908
  Doc,
3044
2909
  EditorModes,
3045
2910
  MarkdownEditor,
@@ -3055,11 +2920,13 @@ export {
3055
2920
  awareness,
3056
2921
  basicBundle,
3057
2922
  blast,
2923
+ callbackWrapper,
3058
2924
  code2 as code,
3059
2925
  codeTheme,
3060
2926
  comments,
3061
2927
  createAutomergeModel,
3062
2928
  createYjsModel,
2929
+ cursorConverter,
3063
2930
  defaultMarkdownSlots,
3064
2931
  defaultOptions,
3065
2932
  defaultSlots,