@dxos/react-ui-editor 0.8.2-main.f081794 → 0.8.2-main.fbd8ed0

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 (131) hide show
  1. package/dist/lib/browser/index.mjs +1664 -1359
  2. package/dist/lib/browser/index.mjs.map +4 -4
  3. package/dist/lib/browser/meta.json +1 -1
  4. package/dist/lib/browser/testing/index.mjs.map +2 -2
  5. package/dist/lib/node/index.cjs +2122 -1819
  6. package/dist/lib/node/index.cjs.map +4 -4
  7. package/dist/lib/node/meta.json +1 -1
  8. package/dist/lib/node/testing/index.cjs.map +2 -2
  9. package/dist/lib/node-esm/index.mjs +1664 -1359
  10. package/dist/lib/node-esm/index.mjs.map +4 -4
  11. package/dist/lib/node-esm/meta.json +1 -1
  12. package/dist/lib/node-esm/testing/index.mjs.map +2 -2
  13. package/dist/types/src/components/EditorToolbar/EditorToolbar.d.ts +1 -1
  14. package/dist/types/src/components/EditorToolbar/EditorToolbar.d.ts.map +1 -1
  15. package/dist/types/src/{stories/InputMode.stories.d.ts → components/EditorToolbar/EditorToolbar.stories.d.ts} +3 -7
  16. package/dist/types/src/components/EditorToolbar/EditorToolbar.stories.d.ts.map +1 -0
  17. package/dist/types/src/components/EditorToolbar/blocks.d.ts +4 -3
  18. package/dist/types/src/components/EditorToolbar/blocks.d.ts.map +1 -1
  19. package/dist/types/src/components/EditorToolbar/comment.d.ts +4 -3
  20. package/dist/types/src/components/EditorToolbar/comment.d.ts.map +1 -1
  21. package/dist/types/src/components/EditorToolbar/formatting.d.ts +4 -3
  22. package/dist/types/src/components/EditorToolbar/formatting.d.ts.map +1 -1
  23. package/dist/types/src/components/EditorToolbar/headings.d.ts +4 -3
  24. package/dist/types/src/components/EditorToolbar/headings.d.ts.map +1 -1
  25. package/dist/types/src/components/EditorToolbar/image.d.ts +16 -0
  26. package/dist/types/src/components/EditorToolbar/image.d.ts.map +1 -0
  27. package/dist/types/src/components/EditorToolbar/lists.d.ts +4 -3
  28. package/dist/types/src/components/EditorToolbar/lists.d.ts.map +1 -1
  29. package/dist/types/src/components/EditorToolbar/search.d.ts +17 -0
  30. package/dist/types/src/components/EditorToolbar/search.d.ts.map +1 -0
  31. package/dist/types/src/components/EditorToolbar/util.d.ts +11 -17
  32. package/dist/types/src/components/EditorToolbar/util.d.ts.map +1 -1
  33. package/dist/types/src/components/EditorToolbar/view-mode.d.ts +4 -3
  34. package/dist/types/src/components/EditorToolbar/view-mode.d.ts.map +1 -1
  35. package/dist/types/src/defaults.d.ts.map +1 -1
  36. package/dist/types/src/extensions/annotations.d.ts.map +1 -1
  37. package/dist/types/src/extensions/autocomplete.d.ts.map +1 -1
  38. package/dist/types/src/extensions/automerge/automerge.d.ts.map +1 -1
  39. package/dist/types/src/extensions/automerge/automerge.stories.d.ts.map +1 -1
  40. package/dist/types/src/extensions/automerge/cursor.d.ts.map +1 -1
  41. package/dist/types/src/extensions/automerge/defs.d.ts +1 -1
  42. package/dist/types/src/extensions/automerge/defs.d.ts.map +1 -1
  43. package/dist/types/src/extensions/automerge/sync.d.ts.map +1 -1
  44. package/dist/types/src/extensions/automerge/update-automerge.d.ts +1 -1
  45. package/dist/types/src/extensions/automerge/update-automerge.d.ts.map +1 -1
  46. package/dist/types/src/extensions/automerge/update-codemirror.d.ts +1 -1
  47. package/dist/types/src/extensions/automerge/update-codemirror.d.ts.map +1 -1
  48. package/dist/types/src/extensions/awareness/awareness.d.ts.map +1 -1
  49. package/dist/types/src/extensions/blast.d.ts.map +1 -1
  50. package/dist/types/src/extensions/command/command.d.ts.map +1 -1
  51. package/dist/types/src/extensions/command/hint.d.ts.map +1 -1
  52. package/dist/types/src/extensions/command/menu.d.ts.map +1 -1
  53. package/dist/types/src/extensions/comments.d.ts.map +1 -1
  54. package/dist/types/src/extensions/debug.d.ts.map +1 -1
  55. package/dist/types/src/extensions/dnd.d.ts.map +1 -1
  56. package/dist/types/src/extensions/factories.d.ts.map +1 -1
  57. package/dist/types/src/extensions/folding.d.ts.map +1 -1
  58. package/dist/types/src/extensions/listener.d.ts.map +1 -1
  59. package/dist/types/src/extensions/markdown/bundle.d.ts.map +1 -1
  60. package/dist/types/src/extensions/markdown/changes.d.ts.map +1 -1
  61. package/dist/types/src/extensions/markdown/debug.d.ts.map +1 -1
  62. package/dist/types/src/extensions/markdown/decorate.d.ts +1 -0
  63. package/dist/types/src/extensions/markdown/decorate.d.ts.map +1 -1
  64. package/dist/types/src/extensions/markdown/formatting.d.ts +1 -1
  65. package/dist/types/src/extensions/markdown/formatting.d.ts.map +1 -1
  66. package/dist/types/src/extensions/markdown/highlight.d.ts.map +1 -1
  67. package/dist/types/src/extensions/markdown/image.d.ts.map +1 -1
  68. package/dist/types/src/extensions/markdown/index.d.ts +1 -0
  69. package/dist/types/src/extensions/markdown/index.d.ts.map +1 -1
  70. package/dist/types/src/extensions/markdown/link.d.ts.map +1 -1
  71. package/dist/types/src/extensions/markdown/outliner.d.ts +12 -0
  72. package/dist/types/src/extensions/markdown/outliner.d.ts.map +1 -0
  73. package/dist/types/src/extensions/markdown/table.d.ts.map +1 -1
  74. package/dist/types/src/extensions/mention.d.ts.map +1 -1
  75. package/dist/types/src/extensions/modes.d.ts +5 -5
  76. package/dist/types/src/extensions/modes.d.ts.map +1 -1
  77. package/dist/types/src/extensions/preview/preview.d.ts.map +1 -1
  78. package/dist/types/src/extensions/selection.d.ts.map +1 -1
  79. package/dist/types/src/extensions/typewriter.d.ts.map +1 -1
  80. package/dist/types/src/hooks/index.d.ts +0 -1
  81. package/dist/types/src/hooks/index.d.ts.map +1 -1
  82. package/dist/types/src/hooks/useTextEditor.d.ts.map +1 -1
  83. package/dist/types/src/stories/TextEditorBasic.stories.d.ts +3 -0
  84. package/dist/types/src/stories/TextEditorBasic.stories.d.ts.map +1 -1
  85. package/dist/types/src/stories/story-utils.d.ts.map +1 -1
  86. package/dist/types/src/styles/theme.d.ts.map +1 -1
  87. package/dist/types/src/testing/RefPopover.d.ts.map +1 -1
  88. package/dist/types/src/util/cursor.d.ts.map +1 -1
  89. package/dist/types/src/util/debug.d.ts.map +1 -1
  90. package/dist/types/src/util/dom.d.ts.map +1 -1
  91. package/dist/types/src/util/facet.d.ts.map +1 -1
  92. package/dist/types/src/util/react.d.ts.map +1 -1
  93. package/dist/types/tsconfig.tsbuildinfo +1 -1
  94. package/package.json +32 -28
  95. package/src/components/EditorToolbar/EditorToolbar.stories.tsx +90 -0
  96. package/src/components/EditorToolbar/EditorToolbar.tsx +31 -32
  97. package/src/components/EditorToolbar/blocks.ts +27 -6
  98. package/src/components/EditorToolbar/comment.ts +11 -4
  99. package/src/components/EditorToolbar/formatting.ts +34 -7
  100. package/src/components/EditorToolbar/headings.ts +9 -8
  101. package/src/components/EditorToolbar/image.ts +16 -0
  102. package/src/components/EditorToolbar/lists.ts +26 -7
  103. package/src/components/EditorToolbar/search.ts +19 -0
  104. package/src/components/EditorToolbar/util.ts +14 -14
  105. package/src/components/EditorToolbar/view-mode.ts +9 -8
  106. package/src/defaults.ts +1 -1
  107. package/src/extensions/automerge/automerge.stories.tsx +9 -7
  108. package/src/extensions/automerge/automerge.test.tsx +4 -4
  109. package/src/extensions/automerge/automerge.ts +2 -2
  110. package/src/extensions/automerge/defs.ts +1 -2
  111. package/src/extensions/automerge/sync.ts +4 -4
  112. package/src/extensions/automerge/update-automerge.ts +1 -1
  113. package/src/extensions/automerge/update-codemirror.ts +3 -4
  114. package/src/extensions/markdown/changes.ts +3 -2
  115. package/src/extensions/markdown/decorate.ts +8 -7
  116. package/src/extensions/markdown/formatting.ts +4 -4
  117. package/src/extensions/markdown/index.ts +1 -0
  118. package/src/extensions/markdown/outliner.ts +235 -0
  119. package/src/extensions/markdown/styles.ts +2 -2
  120. package/src/extensions/modes.ts +5 -6
  121. package/src/extensions/preview/preview.ts +1 -1
  122. package/src/hooks/index.ts +0 -1
  123. package/src/stories/TextEditorBasic.stories.tsx +44 -0
  124. package/src/stories/story-utils.tsx +7 -9
  125. package/src/styles/theme.ts +3 -0
  126. package/src/testing/RefPopover.tsx +4 -4
  127. package/dist/types/src/hooks/useActionHandler.d.ts +0 -4
  128. package/dist/types/src/hooks/useActionHandler.d.ts.map +0 -1
  129. package/dist/types/src/stories/InputMode.stories.d.ts.map +0 -1
  130. package/src/hooks/useActionHandler.ts +0 -12
  131. package/src/stories/InputMode.stories.tsx +0 -124
@@ -0,0 +1,235 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { syntaxTree } from '@codemirror/language';
6
+ import {
7
+ type ChangeSpec,
8
+ type Extension,
9
+ type Line,
10
+ StateField,
11
+ type Transaction,
12
+ EditorState,
13
+ type Range,
14
+ } from '@codemirror/state';
15
+ import { Decoration, type DecorationSet, EditorView } from '@codemirror/view';
16
+
17
+ import { log } from '@dxos/log';
18
+ import { mx } from '@dxos/react-ui-theme';
19
+
20
+ // TODO(burdon): Cut-and-paste.
21
+ // TODO(burdon): Toggle list/task mode (in gutter?)
22
+ // TODO(burdon): Convert to task object and insert link (menu button).
23
+ // TOOD(burdon): Continuation lines and rich formatting?
24
+
25
+ const indentLevel = 2;
26
+ const matchTaskMarker = /^\s*- (\[ \]|\[x\])? /;
27
+
28
+ /**
29
+ * Get the starting position of text on the given line.
30
+ */
31
+ const getLineInfo = (line: Line) => {
32
+ const match = line.text.match(matchTaskMarker);
33
+ const start = line.from + (match?.[0]?.length ?? 0);
34
+ return {
35
+ match, // TODO(burdon): Indicate task or list marker.
36
+ start,
37
+ };
38
+ };
39
+
40
+ /**
41
+ * Outliner extension.
42
+ * - Store outline as a standard markdown document with task and list markers.
43
+ * - Support continuation lines and rich formatting (with Shift+Enter).
44
+ * - Constrain editor to outline structure.
45
+ * - Support smart cut-and-paste.
46
+ * - Support extracted links.
47
+ * - Drag/drop lines or move them via shortcuts.
48
+ */
49
+ export const outliner = (): Extension => [
50
+ EditorState.transactionFilter.of((tr) => {
51
+ // Don't allow cursor before marker.
52
+ if (!tr.docChanged) {
53
+ const pos = tr.selection?.ranges[tr.selection?.mainIndex]?.from;
54
+ if (pos != null) {
55
+ const { match, start } = getLineInfo(tr.startState.doc.lineAt(pos));
56
+ if (match) {
57
+ if (pos < start) {
58
+ return [{ selection: { anchor: start, head: start } }];
59
+ }
60
+ }
61
+ }
62
+
63
+ return tr;
64
+ }
65
+
66
+ const changes: ChangeSpec[] = [];
67
+ tr.changes.iterChanges((fromA, toA, fromB, toB, insert) => {
68
+ // NOTE: Task markers are atomic so will be deleted when backspace is pressed.
69
+ // NOTE: CM inserts 2 or 6 spaces when deleting a list or task marker to create a continuation.
70
+ // - [ ] <- backspace here deletes the task marker.
71
+ // - [ ] <- backspace here inserts 6 spaces (creates continuation).
72
+ // - [ ] <- backspace here deletes the task marker.
73
+
74
+ const line = tr.startState.doc.lineAt(fromA);
75
+ const isTaskMarker = line.text.match(matchTaskMarker);
76
+ if (isTaskMarker) {
77
+ const { start } = getLineInfo(line);
78
+
79
+ // Detect and cancel replacement of task marker with continuation indent.
80
+ const replace = start === toA && toA - fromA === insert.length;
81
+ if (replace) {
82
+ log.info('delete line');
83
+ changes.push({ from: line.from - 1, to: toA });
84
+ return;
85
+ }
86
+
87
+ // Detect deletion of marker.
88
+ if (fromB === toB) {
89
+ if (toA === line.to) {
90
+ const line = tr.state.doc.lineAt(fromA);
91
+ if (line.text.match(/^\s*$/)) {
92
+ if (line.from === 0) {
93
+ // Don't delete first line.
94
+ log.info('skip');
95
+ changes.push({ from: 0, to: 0 });
96
+ return;
97
+ } else {
98
+ // Delete indent and marker.
99
+ log.info('delete line');
100
+ changes.push({ from: line.from - 1, to: toA });
101
+ return;
102
+ }
103
+ }
104
+ }
105
+ return;
106
+ }
107
+
108
+ // Check appropriate indentation relative to previous line.
109
+ if (insert.length === indentLevel) {
110
+ if (line.number === 1) {
111
+ log.info('skip');
112
+ changes.push({ from: 0, to: 0 });
113
+ return;
114
+ } else {
115
+ const getIndent = (text: string) => (text.match(/^\s*/)?.[0]?.length ?? 0) / indentLevel;
116
+ const currentIndent = getIndent(line.text);
117
+ const indentPrevious = getIndent(tr.state.doc.lineAt(fromA - 1).text);
118
+ if (currentIndent > indentPrevious) {
119
+ log.info('skip');
120
+ changes.push({ from: 0, to: 0 });
121
+ return;
122
+ }
123
+ }
124
+ }
125
+
126
+ // TODO(burdon): Detect pressing ENTER on empty line that is indented.
127
+ // Don't allow empty line.
128
+ // if (start === line.to && insert.toString() === '\n') {
129
+ // log.info('skip');
130
+ // changes.push({ from: 0, to: 0 });
131
+ // return;
132
+ // }
133
+
134
+ log.info('change', {
135
+ line: { from: line.from, to: line.to },
136
+ start,
137
+ a: [fromA, toA],
138
+ b: [fromB, toB],
139
+ insert: { text: insert.toString(), length: insert.length },
140
+ });
141
+ }
142
+ });
143
+
144
+ if (changes.length > 0) {
145
+ return [{ changes }];
146
+ }
147
+
148
+ return tr;
149
+ }),
150
+
151
+ StateField.define<DecorationSet>({
152
+ create: (state) => {
153
+ return Decoration.set(buildDecorations(0, state.doc.length, state));
154
+ },
155
+ update: (value: DecorationSet, tr: Transaction) => {
156
+ const from = 0;
157
+ const to = tr.state.doc.length;
158
+ return value.map(tr.changes).update({
159
+ filterFrom: 0,
160
+ filterTo: tr.state.doc.length,
161
+ filter: () => false,
162
+ add: buildDecorations(from, to, tr.state),
163
+ });
164
+ },
165
+ provide: (field) => EditorView.decorations.from(field),
166
+ }),
167
+
168
+ // TODO(burdon): Increase indent padding by configuring decorate extension.
169
+ // TODO(burdon): Hover to select entire group.
170
+ EditorView.theme({
171
+ '.cm-list-item-start': {
172
+ borderTop: '1px solid var(--dx-separator)',
173
+ borderLeft: '1px solid var(--dx-separator)',
174
+ borderRight: '1px solid var(--dx-separator)',
175
+ borderTopLeftRadius: '4px',
176
+ borderTopRightRadius: '4px',
177
+ paddingTop: '4px',
178
+ marginTop: '8px',
179
+ },
180
+ '.cm-list-item-end': {
181
+ borderLeft: '1px solid var(--dx-separator)',
182
+ borderRight: '1px solid var(--dx-separator)',
183
+ borderBottom: '1px solid var(--dx-separator)',
184
+ borderBottomLeftRadius: '4px',
185
+ borderBottomRightRadius: '4px',
186
+ paddingBottom: '4px',
187
+ marginBottom: '8px',
188
+ },
189
+ '.cm-list-item-continuation': {
190
+ borderLeft: '1px solid var(--dx-separator)',
191
+ borderRight: '1px solid var(--dx-separator)',
192
+
193
+ // TODO(burdon): Should match parent indentation.
194
+ paddingLeft: '24px',
195
+ },
196
+
197
+ // TODO(burdon): Set via options to decorate extension.
198
+ '.cm-list-item-continuation.cm-codeblock-start': {
199
+ borderRadius: '0',
200
+ },
201
+ }),
202
+ ];
203
+
204
+ /**
205
+ * Add line decorations.
206
+ */
207
+ const buildDecorations = (from: number, to: number, state: EditorState) => {
208
+ const decorations: Range<Decoration>[] = [];
209
+ syntaxTree(state).iterate({
210
+ enter: (node) => {
211
+ if (node.name === 'ListItem') {
212
+ const sub = node.node.getChild('BulletList');
213
+ const lineStart = state.doc.lineAt(node.from);
214
+ const lineEnd = sub ? state.doc.lineAt(state.doc.lineAt(sub.from).from - 1) : state.doc.lineAt(node.to);
215
+
216
+ decorations.push(
217
+ Decoration.line({
218
+ class: mx('cm-list-item-start', lineStart.number === lineEnd.number && 'cm-list-item-end'),
219
+ }).range(lineStart.from, lineStart.from),
220
+ );
221
+
222
+ for (let i = lineStart.from + 1; i < lineEnd.from; i++) {
223
+ decorations.push(Decoration.line({ class: mx('cm-list-item-continuation') }).range(i, i));
224
+ }
225
+
226
+ // TODO(burdon): Need to sort.
227
+ if (lineStart.number !== lineEnd.number) {
228
+ decorations.push(Decoration.line({ class: mx('cm-list-item-end') }).range(lineEnd.from, lineEnd.from));
229
+ }
230
+ }
231
+ },
232
+ });
233
+
234
+ return decorations;
235
+ };
@@ -59,11 +59,11 @@ export const formattingStyles = EditorView.theme({
59
59
  background: 'var(--dx-cmCodeblock)',
60
60
  paddingInline: '1rem !important',
61
61
  },
62
- '& .cm-codeblock-first': {
62
+ '& .cm-codeblock-start': {
63
63
  borderTopLeftRadius: '.25rem',
64
64
  borderTopRightRadius: '.25rem',
65
65
  },
66
- '& .cm-codeblock-last': {
66
+ '& .cm-codeblock-end': {
67
67
  borderBottomLeftRadius: '.25rem',
68
68
  borderBottomRightRadius: '.25rem',
69
69
  },
@@ -6,18 +6,17 @@ import { type Extension } from '@codemirror/state';
6
6
  import { keymap } from '@codemirror/view';
7
7
  import { vim } from '@replit/codemirror-vim';
8
8
  import { vscodeKeymap } from '@replit/codemirror-vscode-keymap';
9
-
10
- import { S } from '@dxos/echo-schema';
9
+ import { Schema } from 'effect';
11
10
 
12
11
  import { singleValueFacet } from '../util';
13
12
 
14
13
  export const EditorViewModes = ['preview', 'readonly', 'source'] as const;
15
- export const EditorViewMode = S.Union(...EditorViewModes.map((mode) => S.Literal(mode)));
16
- export type EditorViewMode = S.Schema.Type<typeof EditorViewMode>;
14
+ export const EditorViewMode = Schema.Union(...EditorViewModes.map((mode) => Schema.Literal(mode)));
15
+ export type EditorViewMode = Schema.Schema.Type<typeof EditorViewMode>;
17
16
 
18
17
  export const EditorInputModes = ['default', 'vim', 'vscode'] as const;
19
- export const EditorInputMode = S.Union(...EditorInputModes.map((mode) => S.Literal(mode)));
20
- export type EditorInputMode = S.Schema.Type<typeof EditorInputMode>;
18
+ export const EditorInputMode = Schema.Union(...EditorInputModes.map((mode) => Schema.Literal(mode)));
19
+ export type EditorInputMode = Schema.Schema.Type<typeof EditorInputMode>;
21
20
 
22
21
  export type EditorInputConfig = {
23
22
  type?: string;
@@ -186,7 +186,7 @@ class PreviewInlineWidget extends WidgetType {
186
186
 
187
187
  override toDOM(view: EditorView) {
188
188
  const root = document.createElement('dx-ref-tag');
189
- root.setAttribute('label', this._link.label);
189
+ root.textContent = this._link.label;
190
190
  root.setAttribute('ref', this._link.ref);
191
191
  return root;
192
192
  }
@@ -2,5 +2,4 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
- export * from './useActionHandler';
6
5
  export * from './useTextEditor';
@@ -38,6 +38,7 @@ import {
38
38
  table,
39
39
  autocomplete,
40
40
  mention,
41
+ outliner,
41
42
  } from '../extensions';
42
43
 
43
44
  const meta: Meta<typeof DefaultStory> = {
@@ -188,20 +189,63 @@ export const Lists = {
188
189
  ),
189
190
  };
190
191
 
192
+ //
193
+ // Bullet List
194
+ //
195
+
191
196
  export const BulletList = {
192
197
  render: () => <DefaultStory text={str(content.bullets, content.footer)} extensions={[decorateMarkdown()]} />,
193
198
  };
194
199
 
200
+ //
201
+ // Ordered List
202
+ //
203
+
195
204
  export const OrderedList = {
196
205
  render: () => <DefaultStory text={str(content.numbered, content.footer)} extensions={[decorateMarkdown()]} />,
197
206
  };
198
207
 
208
+ //
209
+ // Task List
210
+ //
211
+
199
212
  export const TaskList = {
200
213
  render: () => (
201
214
  <DefaultStory text={str(content.tasks, content.footer)} extensions={[decorateMarkdown()]} debug='raw+tree' />
202
215
  ),
203
216
  };
204
217
 
218
+ //
219
+ // Outliner
220
+ //
221
+
222
+ export const Outliner = {
223
+ render: () => (
224
+ <DefaultStory
225
+ // text={str(...content.tasks.split('\n').filter((line) => line.trim().startsWith('-')))}
226
+ text={str(
227
+ //
228
+ '- [ ] A',
229
+ '- [ ] B',
230
+ // Continuation lines.
231
+ ' ## Example',
232
+ ' Continuation line belonging to B.',
233
+ ' ```ts',
234
+ ' const x = 100',
235
+ ' ```',
236
+ ' - [ ] C',
237
+ ' - D Items can have links [like this](https://example.com).',
238
+ )}
239
+ extensions={[decorateMarkdown({ listPaddingLeft: 8 }), outliner()]}
240
+ debug='raw+tree'
241
+ />
242
+ ),
243
+ };
244
+
245
+ //
246
+ // Table
247
+ //
248
+
205
249
  export const Table = {
206
250
  render: () => <DefaultStory text={str(content.table, content.footer)} extensions={[decorateMarkdown(), table()]} />,
207
251
  };
@@ -17,21 +17,19 @@ import { mx } from '@dxos/react-ui-theme';
17
17
 
18
18
  import { editorContent, editorGutter } from '../defaults';
19
19
  import {
20
- type EditorSelectionState,
21
20
  type DebugNode,
22
- decorateMarkdown,
23
- formattingKeymap,
24
- linkTooltip,
25
- image,
26
- table,
27
- folding,
28
- } from '../extensions';
29
- import {
21
+ type EditorSelectionState,
30
22
  createDataExtensions,
31
23
  createBasicExtensions,
32
24
  createMarkdownExtensions,
33
25
  createThemeExtensions,
26
+ decorateMarkdown,
34
27
  debugTree,
28
+ folding,
29
+ formattingKeymap,
30
+ image,
31
+ linkTooltip,
32
+ table,
35
33
  } from '../extensions';
36
34
  import { useTextEditor, type UseTextEditorProps } from '../hooks';
37
35
  import { createRenderer } from '../util';
@@ -119,6 +119,9 @@ export const defaultTheme: ThemeStyles = {
119
119
  '.cm-selectionBackground': {
120
120
  background: 'var(--dx-cmSelection)',
121
121
  },
122
+ '&.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground': {
123
+ background: 'var(--dx-cmFocusedSelection)',
124
+ },
122
125
 
123
126
  /**
124
127
  * Search.
@@ -3,10 +3,10 @@
3
3
  //
4
4
 
5
5
  import { createContext } from '@radix-ui/react-context';
6
- import React, { type PropsWithChildren, useRef, useState, useEffect, useCallback } from 'react';
6
+ import React, { type PropsWithChildren, useRef, useState, useEffect, useCallback, type RefObject } from 'react';
7
7
 
8
8
  import { addEventListener } from '@dxos/async';
9
- import { type DxRefTagActivate } from '@dxos/lit-ui';
9
+ import { type DxRefTag, type DxRefTagActivate } from '@dxos/lit-ui';
10
10
  import { Popover } from '@dxos/react-ui';
11
11
 
12
12
  import { type PreviewLinkRef, type PreviewLinkTarget, type PreviewLookup } from '../extensions';
@@ -21,7 +21,7 @@ const [RefPopoverContextProvider, useRefPopover] = createContext<RefPopoverValue
21
21
  type RefPopoverProviderProps = PropsWithChildren<{ onLookup?: PreviewLookup }>;
22
22
 
23
23
  const RefPopoverProvider = ({ children, onLookup }: RefPopoverProviderProps) => {
24
- const trigger = useRef<HTMLButtonElement | null>(null);
24
+ const trigger = useRef<DxRefTag | null>(null);
25
25
  const [value, setValue] = useState<RefPopoverValue>({});
26
26
  const [rootRef, setRootRef] = useState<HTMLDivElement | null>(null);
27
27
  const [open, setOpen] = useState(false);
@@ -56,7 +56,7 @@ const RefPopoverProvider = ({ children, onLookup }: RefPopoverProviderProps) =>
56
56
  return (
57
57
  <RefPopoverContextProvider pending={value.pending} link={value.link} target={value.target}>
58
58
  <Popover.Root open={open} onOpenChange={setOpen}>
59
- <Popover.VirtualTrigger virtualRef={trigger} />
59
+ <Popover.VirtualTrigger virtualRef={trigger as unknown as RefObject<HTMLButtonElement>} />
60
60
  <div role='none' className='contents' ref={setRootRef}>
61
61
  {children}
62
62
  </div>
@@ -1,4 +0,0 @@
1
- import { type EditorView } from '@codemirror/view';
2
- import { type EditorAction } from '../extensions';
3
- export declare const useActionHandler: (view?: EditorView | null) => (action: EditorAction) => void | null | undefined;
4
- //# sourceMappingURL=useActionHandler.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"useActionHandler.d.ts","sourceRoot":"","sources":["../../../../src/hooks/useActionHandler.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,KAAK,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAGnD,OAAO,EAAE,KAAK,YAAY,EAAwB,MAAM,eAAe,CAAC;AAExE,eAAO,MAAM,gBAAgB,UAAW,UAAU,GAAG,IAAI,cAC3B,YAAY,4BACzC,CAAC"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"InputMode.stories.d.ts","sourceRoot":"","sources":["../../../../src/stories/InputMode.stories.tsx"],"names":[],"mappings":"AAIA,OAAO,aAAa,CAAC;AAErB,OAAO,KAAmB,MAAM,OAAO,CAAC;AAiBxC,OAAO,EAAmC,KAAK,kBAAkB,EAAE,MAAM,UAAU,CAAC;AAGpF,KAAK,UAAU,GAAG;IAAE,WAAW,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,kBAAkB,CAAC;AAqEpF,eAAO,MAAM,OAAO;;CAanB,CAAC;AAEF,eAAO,MAAM,QAAQ;;;;;;CAMpB,CAAC;;;iEAxFwE,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA0FpF,wBAKE"}
@@ -1,12 +0,0 @@
1
- //
2
- // Copyright 2024 DXOS.org
3
- //
4
-
5
- import { type EditorView } from '@codemirror/view';
6
- import { useCallback } from 'react';
7
-
8
- import { type EditorAction, processEditorPayload } from '../extensions';
9
-
10
- export const useActionHandler = (view?: EditorView | null) => {
11
- return useCallback((action: EditorAction) => view && processEditorPayload(view, action.properties), [view]);
12
- };
@@ -1,124 +0,0 @@
1
- //
2
- // Copyright 2024 DXOS.org
3
- //
4
-
5
- import '@dxos-theme';
6
-
7
- import React, { useState } from 'react';
8
-
9
- import { Toolbar as NaturalToolbar, Select, useThemeContext } from '@dxos/react-ui';
10
- import { attentionSurface, mx } from '@dxos/react-ui-theme';
11
- import { withLayout, withTheme } from '@dxos/storybook-utils';
12
-
13
- import { EditorToolbar, useEditorToolbarState } from '../components';
14
- import {
15
- type EditorInputMode,
16
- decorateMarkdown,
17
- createMarkdownExtensions,
18
- formattingKeymap,
19
- useFormattingState,
20
- createBasicExtensions,
21
- createThemeExtensions,
22
- InputModeExtensions,
23
- } from '../extensions';
24
- import { useActionHandler, useTextEditor, type UseTextEditorProps } from '../hooks';
25
- import translations from '../translations';
26
-
27
- type StoryProps = { placeholder?: string; readOnly?: boolean } & UseTextEditorProps;
28
-
29
- const DefaultStory = ({ autoFocus, initialValue, placeholder, readOnly }: StoryProps) => {
30
- const { themeMode } = useThemeContext();
31
- const toolbarState = useEditorToolbarState({ viewMode: 'source' });
32
- const trackFormatting = useFormattingState(toolbarState);
33
- const [editorInputMode, _setEditorInputMode] = useState<EditorInputMode>('default');
34
- const { parentRef, view } = useTextEditor(
35
- () => ({
36
- autoFocus,
37
- initialValue,
38
- moveToEndOfLine: true,
39
- extensions: [
40
- editorInputMode ? InputModeExtensions[editorInputMode] : [],
41
- createBasicExtensions({ placeholder, lineWrapping: true, readOnly }),
42
- createMarkdownExtensions({ themeMode }),
43
- createThemeExtensions({ themeMode, syntaxHighlighting: true }),
44
- decorateMarkdown(),
45
- formattingKeymap(),
46
- trackFormatting,
47
- ],
48
- }),
49
- [editorInputMode, themeMode, placeholder, readOnly],
50
- );
51
-
52
- const handleAction = useActionHandler(view);
53
-
54
- // TODO(marijn): This doesn't update the state on view changes.
55
- // Also not sure if view is even guaranteed to exist at this point.
56
- return (
57
- <div role='none' className={mx('fixed inset-0 flex flex-col')}>
58
- {toolbarState && <EditorToolbar onAction={handleAction} state={toolbarState} />}
59
- <div role='none' className='grow overflow-hidden'>
60
- <div className={attentionSurface} ref={parentRef} />
61
- </div>
62
- </div>
63
- );
64
- };
65
-
66
- const _EditorInputModeToolbar = ({
67
- editorInputMode,
68
- setEditorInputMode,
69
- }: {
70
- editorInputMode: EditorInputMode;
71
- setEditorInputMode: (mode: EditorInputMode) => void;
72
- }) => {
73
- return (
74
- <Select.Root value={editorInputMode} onValueChange={(value) => setEditorInputMode(value as EditorInputMode)}>
75
- <NaturalToolbar.Button asChild>
76
- <Select.TriggerButton variant='ghost'>{editorInputMode}</Select.TriggerButton>
77
- </NaturalToolbar.Button>
78
- <Select.Portal>
79
- <Select.Content>
80
- <Select.ScrollUpButton />
81
- <Select.Viewport>
82
- {['default', 'vim'].map((mode) => (
83
- <Select.Option key={mode} value={mode}>
84
- {mode}
85
- </Select.Option>
86
- ))}
87
- </Select.Viewport>
88
- <Select.ScrollDownButton />
89
- <Select.Arrow />
90
- </Select.Content>
91
- </Select.Portal>
92
- </Select.Root>
93
- );
94
- };
95
-
96
- export const Default = {
97
- render: () => {
98
- const { themeMode } = useThemeContext();
99
- const { parentRef } = useTextEditor({
100
- extensions: [
101
- //
102
- createBasicExtensions({ placeholder: 'Enter text...' }),
103
- createThemeExtensions({ themeMode }),
104
- ],
105
- });
106
-
107
- return <div ref={parentRef} className={attentionSurface} />;
108
- },
109
- };
110
-
111
- export const Markdown = {
112
- args: {
113
- autoFocus: true,
114
- placeholder: 'Text...',
115
- initialValue: '# Demo\n\nThis is a document.\n\n',
116
- },
117
- };
118
-
119
- export default {
120
- title: 'ui/react-ui-editor/InputMode',
121
- render: DefaultStory,
122
- decorators: [withTheme, withLayout({ fullscreen: true, tooltips: true })],
123
- parameters: { translations, layout: 'fullscreen' },
124
- };