@dxos/react-ui-editor 0.8.4-main.ef1bc66f44 → 0.8.4-main.effb148878

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 (149) hide show
  1. package/LICENSE +102 -5
  2. package/README.md +1 -1
  3. package/dist/lib/browser/index.mjs +796 -755
  4. package/dist/lib/browser/index.mjs.map +4 -4
  5. package/dist/lib/browser/meta.json +1 -1
  6. package/dist/lib/browser/translations.mjs +39 -0
  7. package/dist/lib/browser/translations.mjs.map +7 -0
  8. package/dist/lib/node-esm/index.mjs +796 -755
  9. package/dist/lib/node-esm/index.mjs.map +4 -4
  10. package/dist/lib/node-esm/meta.json +1 -1
  11. package/dist/lib/node-esm/translations.mjs +41 -0
  12. package/dist/lib/node-esm/translations.mjs.map +7 -0
  13. package/dist/types/src/components/Editor/Editor.d.ts +36 -25
  14. package/dist/types/src/components/Editor/Editor.d.ts.map +1 -1
  15. package/dist/types/src/components/Editor/Editor.stories.d.ts +4 -4
  16. package/dist/types/src/components/Editor/Editor.stories.d.ts.map +1 -1
  17. package/dist/types/src/components/{EditorContent/EditorContent.d.ts → Editor/EditorView.d.ts} +5 -5
  18. package/dist/types/src/components/Editor/EditorView.d.ts.map +1 -0
  19. package/dist/types/src/components/Editor/controller.d.ts.map +1 -0
  20. package/dist/types/src/components/EditorMenuProvider/EditorMenuProvider.d.ts +1 -3
  21. package/dist/types/src/components/EditorMenuProvider/EditorMenuProvider.d.ts.map +1 -1
  22. package/dist/types/src/components/EditorMenuProvider/menu-presets.d.ts.map +1 -1
  23. package/dist/types/src/components/EditorMenuProvider/menu.d.ts.map +1 -1
  24. package/dist/types/src/components/EditorMenuProvider/popover.d.ts +2 -1
  25. package/dist/types/src/components/EditorMenuProvider/popover.d.ts.map +1 -1
  26. package/dist/types/src/components/EditorMenuProvider/useEditorMenu.d.ts.map +1 -1
  27. package/dist/types/src/components/EditorPreviewProvider/EditorPreviewProvider.d.ts.map +1 -1
  28. package/dist/types/src/components/EditorToolbar/EditorToolbar.d.ts +2 -2
  29. package/dist/types/src/components/EditorToolbar/EditorToolbar.d.ts.map +1 -1
  30. package/dist/types/src/components/EditorToolbar/blocks.d.ts +4 -18
  31. package/dist/types/src/components/EditorToolbar/blocks.d.ts.map +1 -1
  32. package/dist/types/src/components/EditorToolbar/formatting.d.ts +4 -18
  33. package/dist/types/src/components/EditorToolbar/formatting.d.ts.map +1 -1
  34. package/dist/types/src/components/EditorToolbar/headings.d.ts +4 -18
  35. package/dist/types/src/components/EditorToolbar/headings.d.ts.map +1 -1
  36. package/dist/types/src/components/EditorToolbar/image.d.ts +3 -8
  37. package/dist/types/src/components/EditorToolbar/image.d.ts.map +1 -1
  38. package/dist/types/src/components/EditorToolbar/index.d.ts +1 -2
  39. package/dist/types/src/components/EditorToolbar/index.d.ts.map +1 -1
  40. package/dist/types/src/components/EditorToolbar/lists.d.ts +6 -0
  41. package/dist/types/src/components/EditorToolbar/lists.d.ts.map +1 -0
  42. package/dist/types/src/components/EditorToolbar/search.d.ts +3 -8
  43. package/dist/types/src/components/EditorToolbar/search.d.ts.map +1 -1
  44. package/dist/types/src/components/EditorToolbar/types.d.ts +6 -0
  45. package/dist/types/src/components/EditorToolbar/types.d.ts.map +1 -0
  46. package/dist/types/src/components/EditorToolbar/view-mode.d.ts +5 -19
  47. package/dist/types/src/components/EditorToolbar/view-mode.d.ts.map +1 -1
  48. package/dist/types/src/components/index.d.ts +0 -2
  49. package/dist/types/src/components/index.d.ts.map +1 -1
  50. package/dist/types/src/extensions/Assistant.stories.d.ts +10 -0
  51. package/dist/types/src/extensions/Assistant.stories.d.ts.map +1 -0
  52. package/dist/types/src/extensions/assistant-extension.d.ts +24 -0
  53. package/dist/types/src/extensions/assistant-extension.d.ts.map +1 -0
  54. package/dist/types/src/extensions/index.d.ts +2 -0
  55. package/dist/types/src/extensions/index.d.ts.map +1 -0
  56. package/dist/types/src/hooks/index.d.ts +1 -0
  57. package/dist/types/src/hooks/index.d.ts.map +1 -1
  58. package/dist/types/src/hooks/useBasicMarkdownExtensions.d.ts +25 -0
  59. package/dist/types/src/hooks/useBasicMarkdownExtensions.d.ts.map +1 -0
  60. package/dist/types/src/hooks/useTextEditor.d.ts.map +1 -1
  61. package/dist/types/src/index.d.ts +1 -2
  62. package/dist/types/src/index.d.ts.map +1 -1
  63. package/dist/types/src/stories/Automerge.stories.d.ts +25 -24
  64. package/dist/types/src/stories/Automerge.stories.d.ts.map +1 -1
  65. package/dist/types/src/stories/Comments.stories.d.ts +2 -2
  66. package/dist/types/src/stories/Comments.stories.d.ts.map +1 -1
  67. package/dist/types/src/stories/EditorToolbar.stories.d.ts +28 -26
  68. package/dist/types/src/stories/EditorToolbar.stories.d.ts.map +1 -1
  69. package/dist/types/src/stories/Experimental.stories.d.ts +3 -3
  70. package/dist/types/src/stories/Experimental.stories.d.ts.map +1 -1
  71. package/dist/types/src/stories/Markdown.stories.d.ts +2 -2
  72. package/dist/types/src/stories/Markdown.stories.d.ts.map +1 -1
  73. package/dist/types/src/stories/Outliner.stories.d.ts +2 -2
  74. package/dist/types/src/stories/Outliner.stories.d.ts.map +1 -1
  75. package/dist/types/src/stories/Popover.stories.d.ts +2 -2
  76. package/dist/types/src/stories/Popover.stories.d.ts.map +1 -1
  77. package/dist/types/src/stories/Preview.stories.d.ts +2 -2
  78. package/dist/types/src/stories/Preview.stories.d.ts.map +1 -1
  79. package/dist/types/src/stories/Tags.stories.d.ts.map +1 -1
  80. package/dist/types/src/stories/TextEditor.stories.d.ts +2 -2
  81. package/dist/types/src/stories/TextEditor.stories.d.ts.map +1 -1
  82. package/dist/types/src/stories/Theme.stories.d.ts.map +1 -1
  83. package/dist/types/src/stories/components/EditorStory.d.ts +4 -4
  84. package/dist/types/src/stories/components/EditorStory.d.ts.map +1 -1
  85. package/dist/types/src/stories/components/util.d.ts +3 -2
  86. package/dist/types/src/stories/components/util.d.ts.map +1 -1
  87. package/dist/types/src/translations.d.ts +24 -24
  88. package/dist/types/src/translations.d.ts.map +1 -1
  89. package/dist/types/src/util/react.d.ts +2 -5
  90. package/dist/types/src/util/react.d.ts.map +1 -1
  91. package/dist/types/tsconfig.tsbuildinfo +1 -1
  92. package/package.json +77 -68
  93. package/src/components/Editor/Editor.stories.tsx +15 -21
  94. package/src/components/Editor/Editor.tsx +54 -53
  95. package/src/components/Editor/EditorView.tsx +102 -0
  96. package/src/components/EditorMenuProvider/EditorMenuProvider.tsx +19 -24
  97. package/src/components/EditorMenuProvider/menu-presets.ts +1 -0
  98. package/src/components/EditorMenuProvider/popover.ts +3 -1
  99. package/src/components/EditorMenuProvider/useEditorMenu.ts +8 -1
  100. package/src/components/EditorPreviewProvider/EditorPreviewProvider.tsx +1 -1
  101. package/src/components/EditorToolbar/EditorToolbar.tsx +31 -65
  102. package/src/components/EditorToolbar/blocks.ts +54 -46
  103. package/src/components/EditorToolbar/formatting.ts +44 -45
  104. package/src/components/EditorToolbar/headings.ts +44 -50
  105. package/src/components/EditorToolbar/image.ts +16 -21
  106. package/src/components/EditorToolbar/index.ts +2 -3
  107. package/src/components/EditorToolbar/lists.ts +58 -0
  108. package/src/components/EditorToolbar/search.ts +16 -21
  109. package/src/components/EditorToolbar/types.ts +8 -0
  110. package/src/components/EditorToolbar/view-mode.ts +37 -43
  111. package/src/components/index.ts +0 -3
  112. package/src/extensions/Assistant.stories.tsx +112 -0
  113. package/src/extensions/assistant-extension.tsx +223 -0
  114. package/src/extensions/index.ts +5 -0
  115. package/src/hooks/index.ts +1 -0
  116. package/src/hooks/useBasicMarkdownExtensions.ts +55 -0
  117. package/src/index.ts +1 -4
  118. package/src/stories/Automerge.stories.tsx +18 -16
  119. package/src/stories/Comments.stories.tsx +6 -6
  120. package/src/stories/EditorToolbar.stories.tsx +37 -65
  121. package/src/stories/Experimental.stories.tsx +12 -12
  122. package/src/stories/Markdown.stories.tsx +2 -2
  123. package/src/stories/Outliner.stories.tsx +4 -5
  124. package/src/stories/Popover.stories.tsx +10 -11
  125. package/src/stories/Preview.stories.tsx +51 -43
  126. package/src/stories/Tags.stories.tsx +5 -5
  127. package/src/stories/TextEditor.stories.tsx +2 -2
  128. package/src/stories/Theme.stories.tsx +4 -4
  129. package/src/stories/components/EditorStory.tsx +19 -12
  130. package/src/stories/components/util.tsx +49 -50
  131. package/src/translations.ts +29 -24
  132. package/src/util/react.tsx +4 -13
  133. package/dist/types/src/components/EditorContent/EditorContent.d.ts.map +0 -1
  134. package/dist/types/src/components/EditorContent/controller.d.ts.map +0 -1
  135. package/dist/types/src/components/EditorContent/index.d.ts +0 -3
  136. package/dist/types/src/components/EditorContent/index.d.ts.map +0 -1
  137. package/dist/types/src/components/EditorToolbar/actions.d.ts +0 -24
  138. package/dist/types/src/components/EditorToolbar/actions.d.ts.map +0 -1
  139. package/dist/types/src/components/EditorToolbar/useEditorToolbar.d.ts +0 -11
  140. package/dist/types/src/components/EditorToolbar/useEditorToolbar.d.ts.map +0 -1
  141. package/dist/types/src/stories/CommandDialog.stories.d.ts +0 -14
  142. package/dist/types/src/stories/CommandDialog.stories.d.ts.map +0 -1
  143. package/src/components/EditorContent/EditorContent.tsx +0 -83
  144. package/src/components/EditorContent/index.ts +0 -6
  145. package/src/components/EditorToolbar/actions.ts +0 -87
  146. package/src/components/EditorToolbar/useEditorToolbar.ts +0 -20
  147. package/src/stories/CommandDialog.stories.tsx +0 -81
  148. /package/dist/types/src/components/{EditorContent → Editor}/controller.d.ts +0 -0
  149. /package/src/components/{EditorContent → Editor}/controller.ts +0 -0
@@ -1,307 +1,11 @@
1
- // src/translations.ts
2
- var translationKey = "@dxos/react-ui-editor";
3
- var translations = [
4
- {
5
- "en-US": {
6
- [translationKey]: {
7
- "strong label": "Bold",
8
- "emphasis label": "Italics",
9
- "strikethrough label": "Strikethrough",
10
- "code label": "Code",
11
- "link label": "Link",
12
- "list-bullet label": "Bullet list",
13
- "list-ordered label": "Numbered list",
14
- "list-task label": "Task list",
15
- "blockquote label": "Block quote",
16
- "codeblock label": "Code block",
17
- "comment label": "Create comment",
18
- "selection overlaps existing comment label": "Selection overlaps existing comment",
19
- "select text to comment label": "Select text to comment",
20
- "image label": "Insert image",
21
- "table label": "Create table",
22
- "heading label": "Heading level",
23
- "heading level label_zero": "Paragraph",
24
- "heading level label_one": "Heading level {{count}}",
25
- "heading level label_other": "Heading level {{count}}",
26
- "search label": "Search",
27
- "view mode label": "Editor view",
28
- "preview mode label": "Markdown",
29
- "readonly mode label": "Read only",
30
- "source mode label": "Plain text"
31
- }
32
- }
33
- }
34
- ];
35
-
36
1
  // src/components/Editor/Editor.tsx
2
+ import { Atom as Atom2 } from "@effect-atom/atom-react";
37
3
  import { createContext } from "@radix-ui/react-context";
38
4
  import React4, { forwardRef as forwardRef2, useCallback as useCallback4, useImperativeHandle as useImperativeHandle2, useMemo as useMemo5, useState as useState4 } from "react";
39
5
  import { invariant as invariant3 } from "@dxos/invariant";
40
6
  import { mx as mx2 } from "@dxos/ui-theme";
41
7
  import { isNonNullable as isNonNullable2 } from "@dxos/util";
42
8
 
43
- // src/components/EditorContent/controller.ts
44
- var noopController = {
45
- get view() {
46
- return null;
47
- },
48
- getText: () => "",
49
- setText: () => {
50
- },
51
- focus: () => {
52
- }
53
- };
54
- var createEditorController = (view) => {
55
- return {
56
- get view() {
57
- return view;
58
- },
59
- getText: () => {
60
- return view?.state.doc.toString() ?? "";
61
- },
62
- setText: (text, focus) => {
63
- view?.dispatch({
64
- changes: {
65
- from: 0,
66
- to: view?.state.doc.length ?? 0,
67
- insert: text
68
- },
69
- selection: {
70
- anchor: text.length,
71
- head: text.length
72
- }
73
- });
74
- if (focus) {
75
- view?.focus();
76
- }
77
- },
78
- focus: () => view?.focus()
79
- };
80
- };
81
-
82
- // src/components/EditorContent/EditorContent.tsx
83
- import { Transaction } from "@codemirror/state";
84
- import { EditorView as EditorView2 } from "@codemirror/view";
85
- import React, { forwardRef, useEffect as useEffect2, useImperativeHandle } from "react";
86
- import { initialSync } from "@dxos/ui-editor";
87
- import { mx } from "@dxos/ui-theme";
88
-
89
- // src/hooks/useTextEditor.ts
90
- import { EditorState } from "@codemirror/state";
91
- import { EditorView } from "@codemirror/view";
92
- import { useCallback, useEffect, useMemo, useRef, useState } from "react";
93
- import { log } from "@dxos/log";
94
- import { createEditorStateTransaction, debugDispatcher, documentId, modalStateField } from "@dxos/ui-editor";
95
- import { getProviderValue, isTruthy } from "@dxos/util";
96
- var __dxlog_file = "/__w/dxos/dxos/packages/ui/react-ui-editor/src/hooks/useTextEditor.ts";
97
- var instanceCount = 0;
98
- var useTextEditor = (props = {}, deps = []) => {
99
- const { id, doc, initialValue, extensions, autoFocus, scrollTo, selection, selectionEnd, debug } = useMemo(() => getProviderValue(props), deps ?? []);
100
- const [instanceId] = useState(() => `text-editor-${++instanceCount}`);
101
- const [view, setView] = useState(null);
102
- const parentRef = useRef(null);
103
- useEffect(() => {
104
- let view2 = null;
105
- if (parentRef.current) {
106
- log("create", {
107
- id,
108
- instanceId,
109
- doc: initialValue?.length ?? 0
110
- }, {
111
- F: __dxlog_file,
112
- L: 75,
113
- S: void 0,
114
- C: (f, a) => f(...a)
115
- });
116
- let initialSelection;
117
- if (selection?.anchor && initialValue?.length) {
118
- if (selection.anchor <= initialValue.length && (selection?.head ?? 0) <= initialValue.length) {
119
- initialSelection = selection;
120
- }
121
- } else if (selectionEnd && selection === void 0) {
122
- const index = initialValue?.indexOf("\n");
123
- const anchor = !index || index === -1 ? 0 : index;
124
- initialSelection = {
125
- anchor
126
- };
127
- }
128
- const state = EditorState.create({
129
- doc: doc ?? initialValue,
130
- // selection: initialSelection,
131
- extensions: [
132
- id && documentId.of(id),
133
- extensions,
134
- // NOTE: This doesn't catch errors in keymap functions.
135
- EditorView.exceptionSink.of((err) => {
136
- log.catch(err, void 0, {
137
- F: __dxlog_file,
138
- L: 97,
139
- S: void 0,
140
- C: (f, a) => f(...a)
141
- });
142
- })
143
- ].filter(isTruthy)
144
- });
145
- view2 = new EditorView({
146
- parent: parentRef.current,
147
- state,
148
- scrollTo: scrollTo ? EditorView.scrollIntoView(scrollTo, {
149
- yMargin: 96
150
- }) : void 0,
151
- dispatchTransactions: debug ? debugDispatcher : void 0
152
- });
153
- if (selectionEnd && !initialSelection) {
154
- const { to } = view2.state.doc.lineAt(0);
155
- if (to) {
156
- view2.dispatch({
157
- selection: {
158
- anchor: to
159
- }
160
- });
161
- }
162
- }
163
- setView(view2);
164
- }
165
- return () => {
166
- log("destroy", {
167
- id
168
- }, {
169
- F: __dxlog_file,
170
- L: 122,
171
- S: void 0,
172
- C: (f, a) => f(...a)
173
- });
174
- view2?.destroy();
175
- };
176
- }, deps);
177
- useEffect(() => {
178
- if (view) {
179
- if (scrollTo || selection) {
180
- if (selection && selection.anchor > view.state.doc.length) {
181
- log.warn("invalid selection", {
182
- length: view.state.doc.length,
183
- scrollTo,
184
- selection
185
- }, {
186
- F: __dxlog_file,
187
- L: 131,
188
- S: void 0,
189
- C: (f, a) => f(...a)
190
- });
191
- return;
192
- }
193
- view.dispatch(createEditorStateTransaction({
194
- scrollTo,
195
- selection
196
- }));
197
- }
198
- }
199
- }, [
200
- view,
201
- scrollTo,
202
- selection
203
- ]);
204
- useEffect(() => {
205
- if (view && autoFocus) {
206
- view.focus();
207
- }
208
- }, [
209
- autoFocus,
210
- view
211
- ]);
212
- const handleKeyDown = useCallback((event) => {
213
- const { key, target, currentTarget } = event;
214
- switch (key) {
215
- case "Escape": {
216
- const modal = view?.state.field(modalStateField, false);
217
- if (modal) {
218
- return;
219
- }
220
- const element = view?.contentDOM.closest('[tabindex="0"]');
221
- element?.focus();
222
- break;
223
- }
224
- case "Enter": {
225
- event.preventDefault();
226
- if (target === currentTarget) {
227
- view?.focus();
228
- }
229
- break;
230
- }
231
- }
232
- }, [
233
- view
234
- ]);
235
- return {
236
- parentRef,
237
- view,
238
- focusAttributes: {
239
- tabIndex: 0,
240
- onKeyDown: handleKeyDown
241
- }
242
- };
243
- };
244
-
245
- // src/components/EditorContent/EditorContent.tsx
246
- var EditorContent = /* @__PURE__ */ forwardRef(({ classNames, id, extensions, selectionEnd, focusable = true, value, onChange, ...props }, forwardedRef) => {
247
- const { parentRef, focusAttributes, view } = useTextEditor(() => ({
248
- id,
249
- initialValue: value,
250
- selectionEnd,
251
- extensions: [
252
- extensions ?? [],
253
- EditorView2.updateListener.of(({ view: view2, docChanged, transactions }) => {
254
- const isInitialSync = transactions.some((tr) => tr.annotation(Transaction.userEvent) === initialSync.value);
255
- if (!isInitialSync && docChanged) {
256
- onChange?.(view2.state.doc.toString());
257
- }
258
- })
259
- ],
260
- ...props
261
- }), [
262
- id,
263
- extensions,
264
- selectionEnd,
265
- onChange
266
- ]);
267
- useImperativeHandle(forwardedRef, () => {
268
- return createEditorController(view);
269
- }, [
270
- id,
271
- view
272
- ]);
273
- useEffect2(() => {
274
- requestAnimationFrame(() => {
275
- view?.dispatch({
276
- annotations: initialSync,
277
- changes: value ? [
278
- {
279
- from: 0,
280
- to: view?.state.doc.length ?? 0,
281
- insert: value ?? ""
282
- }
283
- ] : [],
284
- selection: selectionEnd ? {
285
- anchor: view?.state.doc.length ?? 0
286
- } : void 0
287
- });
288
- if (selectionEnd) {
289
- view?.focus();
290
- }
291
- });
292
- }, [
293
- view,
294
- value,
295
- selectionEnd
296
- ]);
297
- return /* @__PURE__ */ React.createElement("div", {
298
- role: "none",
299
- className: mx("is-full outline-none focus:border-accentSurface focus-within:border-neutralFocusIndicator", classNames),
300
- ref: parentRef,
301
- ...focusable ? focusAttributes : {}
302
- });
303
- });
304
-
305
9
  // src/components/EditorMenuProvider/menu.ts
306
10
  import { insertAtCursor } from "@dxos/ui-editor";
307
11
  var getMenuItem = (groups, id) => {
@@ -338,9 +42,9 @@ import { insertAtLineStart } from "@dxos/ui-editor";
338
42
 
339
43
  // src/components/EditorMenuProvider/popover.ts
340
44
  import { Prec, RangeSetBuilder, StateEffect, StateField } from "@codemirror/state";
341
- import { Decoration, EditorView as EditorView3, ViewPlugin, keymap } from "@codemirror/view";
342
- import { modalStateField as modalStateField2, placeholder } from "@dxos/ui-editor";
343
- import { isNonNullable, isTruthy as isTruthy2 } from "@dxos/util";
45
+ import { Decoration, EditorView, ViewPlugin, keymap } from "@codemirror/view";
46
+ import { modalStateField, placeholder } from "@dxos/ui-editor";
47
+ import { isNonNullable, isTruthy } from "@dxos/util";
344
48
  var DELIMITERS = [
345
49
  " ",
346
50
  ":"
@@ -351,15 +55,16 @@ var popover = (options = {}) => {
351
55
  popoverStateField,
352
56
  popoverTriggerListener(options),
353
57
  popoverAnchorDecoration(options),
354
- modalStateField2,
58
+ modalStateField,
355
59
  options.trigger && placeholder({
356
60
  // TODO(burdon): Translations.
357
61
  content: `Press '${Array.isArray(options.trigger) ? options.trigger[0] : options.trigger}' for commands`,
62
+ focusOnly: true,
358
63
  ...options.placeholder
359
64
  })
360
- ].filter(isTruthy2);
65
+ ].filter(isTruthy);
361
66
  };
362
- var popoverTriggerListener = (options) => EditorView3.updateListener.of(({ view, docChanged }) => {
67
+ var popoverTriggerListener = (options) => EditorView.updateListener.of(({ view, docChanged }) => {
363
68
  const { range: activeRange, trigger } = view.state.field(popoverStateField) ?? {};
364
69
  if (!activeRange) {
365
70
  return;
@@ -504,7 +209,7 @@ var popoverKeymap = (options) => {
504
209
  return false;
505
210
  }
506
211
  }
507
- ].filter(isTruthy2));
212
+ ].filter(isTruthy));
508
213
  };
509
214
  var popoverAnchorDecoration = (options) => {
510
215
  return ViewPlugin.fromClass(class {
@@ -699,36 +404,28 @@ var linkSlashCommands = {
699
404
 
700
405
  // src/components/EditorMenuProvider/EditorMenuProvider.tsx
701
406
  import { useControllableState } from "@radix-ui/react-use-controllable-state";
702
- import React2, { Fragment, useCallback as useCallback2, useEffect as useEffect3, useRef as useRef2, useState as useState2 } from "react";
407
+ import React, { Fragment, useCallback, useEffect, useRef, useState } from "react";
703
408
  import { addEventListener } from "@dxos/async";
704
409
  import { invariant } from "@dxos/invariant";
705
- import { DX_ANCHOR_ACTIVATE, Icon, Popover, toLocalizedString, useDynamicRef, useThemeContext, useTranslation } from "@dxos/react-ui";
706
- var __dxlog_file2 = "/__w/dxos/dxos/packages/ui/react-ui-editor/src/components/EditorMenuProvider/EditorMenuProvider.tsx";
410
+ import { DX_ANCHOR_ACTIVATE, Icon, Popover, ScrollArea, toLocalizedString, useDynamicRef, useThemeContext, useTranslation } from "@dxos/react-ui";
411
+ var __dxlog_file = "/__w/dxos/dxos/packages/ui/react-ui-editor/src/components/EditorMenuProvider/EditorMenuProvider.tsx";
707
412
  var EditorMenuProvider = ({ children, view, groups, currentItem, open: openProp, defaultOpen, numItems = 8, onOpenChange, onActivate, onSelect, onCancel }) => {
708
413
  const { tx } = useThemeContext();
709
- const triggerRef = useRef2(null);
414
+ const triggerRef = useRef(null);
710
415
  const viewRef = useDynamicRef(view);
711
416
  const [open, setOpen] = useControllableState({
712
417
  prop: openProp,
713
418
  defaultProp: defaultOpen,
714
419
  onChange: (open2) => {
715
- invariant(viewRef.current, void 0, {
716
- F: __dxlog_file2,
717
- L: 65,
718
- S: void 0,
719
- A: [
720
- "viewRef.current",
721
- ""
722
- ]
723
- });
420
+ invariant(viewRef.current, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file, L: 21, S: void 0, A: ["viewRef.current", ""] });
724
421
  onOpenChange?.({
725
422
  view: viewRef.current,
726
423
  open: open2
727
424
  });
728
425
  }
729
426
  });
730
- const [root, setRoot] = useState2(null);
731
- useEffect3(() => {
427
+ const [root, setRoot] = useState(null);
428
+ useEffect(() => {
732
429
  if (!root) {
733
430
  return;
734
431
  }
@@ -753,16 +450,8 @@ var EditorMenuProvider = ({ children, view, groups, currentItem, open: openProp,
753
450
  root,
754
451
  onActivate
755
452
  ]);
756
- const handleSelect = useCallback2((item) => {
757
- invariant(viewRef.current, void 0, {
758
- F: __dxlog_file2,
759
- L: 100,
760
- S: void 0,
761
- A: [
762
- "viewRef.current",
763
- ""
764
- ]
765
- });
453
+ const handleSelect = useCallback((item) => {
454
+ invariant(viewRef.current, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file, L: 56, S: void 0, A: ["viewRef.current", ""] });
766
455
  onSelect?.({
767
456
  view: viewRef.current,
768
457
  item
@@ -772,62 +461,63 @@ var EditorMenuProvider = ({ children, view, groups, currentItem, open: openProp,
772
461
  onSelect
773
462
  ]);
774
463
  const menuGroups = groups?.filter((group) => group.items.length > 0) ?? [];
775
- return /* @__PURE__ */ React2.createElement(Popover.Root, {
464
+ return /* @__PURE__ */ React.createElement(Popover.Root, {
776
465
  modal: false,
777
466
  open,
778
467
  onOpenChange: setOpen
779
- }, /* @__PURE__ */ React2.createElement(Popover.VirtualTrigger, {
468
+ }, /* @__PURE__ */ React.createElement(Popover.VirtualTrigger, {
780
469
  virtualRef: triggerRef
781
- }), /* @__PURE__ */ React2.createElement(Popover.Portal, null, /* @__PURE__ */ React2.createElement(Popover.Content, {
470
+ }), /* @__PURE__ */ React.createElement(Popover.Portal, null, /* @__PURE__ */ React.createElement(Popover.Content, {
782
471
  align: "start",
783
- classNames: tx("menu.content", "menu--exotic-unfocusable", {
784
- elevation: "positioned"
785
- }, [
786
- "overflow-y-auto",
472
+ classNames: [
473
+ "flex flex-col",
787
474
  !menuGroups.length && "hidden"
788
- ]),
475
+ ],
789
476
  style: {
790
477
  maxBlockSize: 36 * numItems + 10
791
478
  },
792
- /**
793
- * NOTE: We keep the focus in the editor, but Radix routes escape key.
794
- */
479
+ // NOTE: We keep the focus in the editor, but Radix routes escape key.
795
480
  onEscapeKeyDown: () => {
796
- onCancel?.({
797
- view
798
- });
481
+ const currentView = viewRef.current;
482
+ if (currentView) {
483
+ onCancel?.({
484
+ view: currentView
485
+ });
486
+ }
799
487
  },
800
488
  onOpenAutoFocus: (event) => event.preventDefault()
801
- }, /* @__PURE__ */ React2.createElement(Popover.Viewport, {
802
- classNames: tx("menu.viewport", "menu__viewport--exotic-unfocusable", {})
803
- }, /* @__PURE__ */ React2.createElement(Menu, {
489
+ }, /* @__PURE__ */ React.createElement(Popover.Viewport, {
490
+ asChild: true,
491
+ classNames: "dx-container"
492
+ }, /* @__PURE__ */ React.createElement(ScrollArea.Root, {
493
+ thin: true
494
+ }, /* @__PURE__ */ React.createElement(ScrollArea.Viewport, null, /* @__PURE__ */ React.createElement(Menu, {
804
495
  groups: menuGroups,
805
496
  currentItem,
806
497
  onSelect: handleSelect
807
- })), /* @__PURE__ */ React2.createElement(Popover.Arrow, null))), /* @__PURE__ */ React2.createElement("div", {
808
- ref: setRoot,
809
- role: "none",
810
- className: "contents"
498
+ })))), /* @__PURE__ */ React.createElement(Popover.Arrow, null))), /* @__PURE__ */ React.createElement("div", {
499
+ className: "contents",
500
+ ref: setRoot
811
501
  }, children));
812
502
  };
813
503
  var Menu = ({ groups, currentItem, onSelect }) => {
814
504
  const { tx } = useThemeContext();
815
- return /* @__PURE__ */ React2.createElement("ul", null, groups.map((group, index) => /* @__PURE__ */ React2.createElement(Fragment, {
505
+ return /* @__PURE__ */ React.createElement("ul", null, groups.map((group, index) => /* @__PURE__ */ React.createElement(Fragment, {
816
506
  key: group.id
817
- }, /* @__PURE__ */ React2.createElement(MenuGroup, {
507
+ }, /* @__PURE__ */ React.createElement(MenuGroup, {
818
508
  group,
819
509
  currentItem,
820
510
  onSelect
821
- }), index < groups.length - 1 && /* @__PURE__ */ React2.createElement("div", {
822
- className: tx("menu.separator", "menu__item", {})
511
+ }), index < groups.length - 1 && /* @__PURE__ */ React.createElement("div", {
512
+ className: tx("menu.separator", {})
823
513
  }))));
824
514
  };
825
515
  var MenuGroup = ({ group, currentItem, onSelect }) => {
826
516
  const { tx } = useThemeContext();
827
517
  const { t } = useTranslation();
828
- return /* @__PURE__ */ React2.createElement(React2.Fragment, null, group.label && /* @__PURE__ */ React2.createElement("div", {
829
- className: tx("menu.groupLabel", "menu__group__label", {})
830
- }, /* @__PURE__ */ React2.createElement("span", null, toLocalizedString(group.label, t))), group.items.map((item) => /* @__PURE__ */ React2.createElement(MenuItem, {
518
+ return /* @__PURE__ */ React.createElement(React.Fragment, null, group.label && /* @__PURE__ */ React.createElement("div", {
519
+ className: tx("menu.groupLabel", {})
520
+ }, /* @__PURE__ */ React.createElement("span", null, toLocalizedString(group.label, t))), group.items.map((item) => /* @__PURE__ */ React.createElement(MenuItem, {
831
521
  key: item.id,
832
522
  item,
833
523
  current: currentItem === item.id,
@@ -837,8 +527,8 @@ var MenuGroup = ({ group, currentItem, onSelect }) => {
837
527
  var MenuItem = ({ item, current, onSelect }) => {
838
528
  const { tx } = useThemeContext();
839
529
  const { t } = useTranslation();
840
- const listRef = useRef2(null);
841
- useEffect3(() => {
530
+ const listRef = useRef(null);
531
+ useEffect(() => {
842
532
  if (current && listRef.current) {
843
533
  listRef.current.scrollIntoView({
844
534
  behavior: "smooth",
@@ -848,56 +538,47 @@ var MenuItem = ({ item, current, onSelect }) => {
848
538
  }, [
849
539
  current
850
540
  ]);
851
- const handleSelect = useCallback2(() => onSelect?.(item), [
541
+ const handleSelect = useCallback(() => onSelect?.(item), [
852
542
  item,
853
543
  onSelect
854
544
  ]);
855
- return /* @__PURE__ */ React2.createElement("li", {
545
+ return /* @__PURE__ */ React.createElement("li", {
856
546
  ref: listRef,
857
- className: tx("menu.item", "menu__item--exotic-unfocusable", {}, [
858
- current && "bg-hoverSurface"
547
+ className: tx("menu.item", {}, [
548
+ current && "bg-hover-surface"
859
549
  ]),
860
550
  onClick: handleSelect
861
- }, item.icon && /* @__PURE__ */ React2.createElement(Icon, {
862
- icon: item.icon,
863
- size: 5
864
- }), /* @__PURE__ */ React2.createElement("span", {
551
+ }, item.icon && /* @__PURE__ */ React.createElement(Icon, {
552
+ icon: item.icon
553
+ }), /* @__PURE__ */ React.createElement("span", {
865
554
  className: "grow truncate"
866
555
  }, toLocalizedString(item.label, t)));
867
556
  };
868
557
 
869
558
  // src/components/EditorMenuProvider/useEditorMenu.ts
870
- import { useCallback as useCallback3, useMemo as useMemo2, useRef as useRef3, useState as useState3 } from "react";
559
+ import { useCallback as useCallback2, useMemo, useRef as useRef2, useState as useState2 } from "react";
871
560
  import { invariant as invariant2 } from "@dxos/invariant";
872
561
  import { modalStateEffect } from "@dxos/ui-editor";
873
- var __dxlog_file3 = "/__w/dxos/dxos/packages/ui/react-ui-editor/src/components/EditorMenuProvider/useEditorMenu.ts";
562
+ var __dxlog_file2 = "/__w/dxos/dxos/packages/ui/react-ui-editor/src/components/EditorMenuProvider/useEditorMenu.ts";
874
563
  var useEditorMenu = ({ trigger, triggerKey, placeholder: placeholder2, filter = true, getMenu }) => {
875
- const groupsRef = useRef3([]);
876
- const currentRef = useRef3(null);
877
- const [currentItem, setCurrentItem] = useState3();
878
- const [open, setOpen] = useState3(false);
879
- const [_, refresh] = useState3({});
880
- const getMenuOptions = useCallback3(async ({ text, trigger: trigger2, ...props }) => {
564
+ const groupsRef = useRef2([]);
565
+ const currentRef = useRef2(null);
566
+ const [currentItem, setCurrentItem] = useState2();
567
+ const [open, setOpen] = useState2(false);
568
+ const [_, refresh] = useState2({});
569
+ const getMenuOptions = useCallback2(async ({ text, trigger: trigger2, ...props }) => {
881
570
  const groups = await getMenu?.({
882
571
  text,
883
572
  trigger: trigger2,
884
573
  ...props
885
574
  }) ?? [];
886
- return filter ? filterMenuGroups(groups, (item) => text ? item.label.toLowerCase().startsWith(text.toLowerCase()) : true) : groups;
575
+ return filter && trigger2 !== "@" ? filterMenuGroups(groups, (item) => text ? item.label.toLowerCase().startsWith(text.toLowerCase()) : true) : groups;
887
576
  }, [
888
577
  getMenu,
889
578
  filter
890
579
  ]);
891
- const handleOpenChange = useCallback3(async ({ view, open: open2 }) => {
892
- invariant2(view, void 0, {
893
- F: __dxlog_file3,
894
- L: 76,
895
- S: void 0,
896
- A: [
897
- "view",
898
- ""
899
- ]
900
- });
580
+ const handleOpenChange = useCallback2(async ({ view, open: open2 }) => {
581
+ invariant2(view, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file2, L: 40, S: void 0, A: ["view", ""] });
901
582
  setOpen(open2);
902
583
  if (!open2) {
903
584
  setCurrentItem(void 0);
@@ -917,7 +598,7 @@ var useEditorMenu = ({ trigger, triggerKey, placeholder: placeholder2, filter =
917
598
  }, [
918
599
  getMenuOptions
919
600
  ]);
920
- const handleActivate = useCallback3(async ({ view, trigger: trigger2 }) => {
601
+ const handleActivate = useCallback2(async ({ view, trigger: trigger2 }) => {
921
602
  const item = getMenuItem(groupsRef.current, currentItem);
922
603
  if (item) {
923
604
  currentRef.current = item;
@@ -933,13 +614,24 @@ var useEditorMenu = ({ trigger, triggerKey, placeholder: placeholder2, filter =
933
614
  open,
934
615
  handleOpenChange
935
616
  ]);
936
- const handleSelect = useCallback3(({ view, item }) => {
617
+ const handleSelect = useCallback2(({ view, item }) => {
618
+ const { range } = view.state.field(popoverStateField) ?? {};
619
+ if (range) {
620
+ view.dispatch({
621
+ changes: {
622
+ from: range.from,
623
+ to: range.to,
624
+ insert: ""
625
+ }
626
+ });
627
+ }
937
628
  void item.onSelect?.({
938
629
  view,
939
630
  head: view.state.selection.main.head
940
631
  });
632
+ view.focus();
941
633
  }, []);
942
- const handleCancel = useCallback3(({ view }) => {
634
+ const handleCancel = useCallback2(({ view }) => {
943
635
  const { range, trigger: trigger2 } = view.state.field(popoverStateField) ?? {};
944
636
  if (range && trigger2) {
945
637
  view.dispatch({
@@ -951,7 +643,7 @@ var useEditorMenu = ({ trigger, triggerKey, placeholder: placeholder2, filter =
951
643
  }
952
644
  }, []);
953
645
  const serializedTrigger = Array.isArray(trigger) ? trigger.join(",") : trigger;
954
- const extension = useMemo2(() => {
646
+ const extension = useMemo(() => {
955
647
  return popover({
956
648
  trigger,
957
649
  triggerKey,
@@ -1017,150 +709,70 @@ var useEditorMenu = ({ trigger, triggerKey, placeholder: placeholder2, filter =
1017
709
 
1018
710
  // src/components/EditorToolbar/EditorToolbar.tsx
1019
711
  import { Atom } from "@effect-atom/atom-react";
1020
- import React3, { memo, useMemo as useMemo3 } from "react";
712
+ import React2, { memo, useMemo as useMemo2 } from "react";
1021
713
  import { ElevationProvider } from "@dxos/react-ui";
1022
- import { MenuProvider, ToolbarMenu, createGapSeparator, useMenuActions } from "@dxos/react-ui-menu";
714
+ import { Menu as Menu2, MenuBuilder, useMenuActions } from "@dxos/react-ui-menu";
1023
715
 
1024
- // src/components/EditorToolbar/actions.ts
1025
- import { createMenuAction, createMenuItemGroup } from "@dxos/react-ui-menu";
1026
- import { List, addList, removeList } from "@dxos/ui-editor";
1027
- var listStyles = {
1028
- bullet: "ph--list-bullets--regular",
1029
- ordered: "ph--list-numbers--regular",
1030
- task: "ph--list-checks--regular"
716
+ // src/components/EditorToolbar/blocks.ts
717
+ import { addBlockquote, addCodeblock, insertTable, removeBlockquote, removeCodeblock } from "@dxos/ui-editor";
718
+ import { translationKey } from "#translations";
719
+ var blockTypes = {
720
+ blockquote: "ph--quotes--regular",
721
+ codeblock: "ph--code-block--regular",
722
+ table: "ph--table--regular"
1031
723
  };
1032
- var createLists = (state, getView) => {
1033
- const value = state.listStyle ?? "";
1034
- const listGroupAction = createListGroupAction(value);
1035
- const listActionsMap = createListActions(value, getView);
1036
- return {
1037
- nodes: [
1038
- listGroupAction,
1039
- ...listActionsMap
1040
- ],
1041
- edges: [
724
+ var addBlocks = (state, getView) => (builder) => {
725
+ const value = state?.blockQuote ? "blockquote" : state.blockType ?? "";
726
+ builder.group("block", {
727
+ label: [
728
+ "block.label",
1042
729
  {
1043
- source: "root",
1044
- target: "list"
1045
- },
1046
- ...listActionsMap.map(({ id }) => ({
1047
- source: listGroupAction.id,
1048
- target: id
1049
- }))
1050
- ]
1051
- };
1052
- };
1053
- var createEditorAction = (id, props, invoke) => {
1054
- const { label = [
1055
- `${id} label`,
1056
- {
1057
- ns: translationKey
1058
- }
1059
- ], ...rest } = props;
1060
- return createMenuAction(id, invoke, {
1061
- label,
1062
- ...rest
1063
- });
1064
- };
1065
- var createEditorActionGroup = (id, props, icon) => {
1066
- const { label = [
1067
- `${id} label`,
1068
- {
1069
- ns: translationKey
1070
- }
1071
- ], ...rest } = props;
1072
- return createMenuItemGroup(id, {
1073
- label,
1074
- icon,
730
+ ns: translationKey
731
+ }
732
+ ],
1075
733
  iconOnly: true,
1076
- ...rest
1077
- });
1078
- };
1079
- var createListGroupAction = (value) => createEditorActionGroup("list", {
1080
- variant: "toggleGroup",
1081
- selectCardinality: "single",
1082
- value
1083
- });
1084
- var createListActions = (value, getView) => Object.entries(listStyles).map(([listStyle, icon]) => {
1085
- const checked = value === listStyle;
1086
- return createEditorAction(`list-${listStyle}`, {
1087
- checked,
1088
- icon
1089
- }, () => {
1090
- const view = getView();
1091
- if (!view) {
1092
- return;
1093
- }
1094
- const listType = listStyle === "ordered" ? List.Ordered : listStyle === "bullet" ? List.Bullet : List.Task;
1095
- if (checked) {
1096
- removeList(listType)(view);
1097
- } else {
1098
- addList(listType)(view);
1099
- }
1100
- });
1101
- });
1102
-
1103
- // src/components/EditorToolbar/blocks.ts
1104
- import { addBlockquote, addCodeblock, insertTable, removeBlockquote, removeCodeblock } from "@dxos/ui-editor";
1105
- var createBlockGroupAction = (value) => createEditorActionGroup("block", {
1106
- variant: "toggleGroup",
1107
- selectCardinality: "single",
1108
- value
1109
- });
1110
- var createBlockActions = (value, getView, blankLine) => Object.entries({
1111
- blockquote: "ph--quotes--regular",
1112
- codeblock: "ph--code-block--regular",
1113
- table: "ph--table--regular"
1114
- }).map(([type, icon]) => {
1115
- const checked = type === value;
1116
- return createEditorAction(type, {
1117
- checked,
1118
- ...type === "table" && {
1119
- disabled: !!blankLine
1120
- },
1121
- icon
1122
- }, () => {
1123
- const view = getView();
1124
- if (!view) {
1125
- return;
1126
- }
1127
- switch (type) {
1128
- case "blockquote":
1129
- checked ? removeBlockquote(view) : addBlockquote(view);
1130
- break;
1131
- case "codeblock":
1132
- checked ? removeCodeblock(view) : addCodeblock(view);
1133
- break;
1134
- case "table":
1135
- insertTable(view);
1136
- break;
734
+ variant: "toggleGroup",
735
+ selectCardinality: "single",
736
+ value
737
+ }, (group) => {
738
+ for (const [type, icon] of Object.entries(blockTypes)) {
739
+ const checked = type === value;
740
+ group.action(type, {
741
+ label: [
742
+ `block.${type}.label`,
743
+ {
744
+ ns: translationKey
745
+ }
746
+ ],
747
+ checked,
748
+ ...type === "table" && {
749
+ disabled: !!state.blankLine
750
+ },
751
+ icon
752
+ }, () => {
753
+ const view = getView();
754
+ if (!view) {
755
+ return;
756
+ }
757
+ switch (type) {
758
+ case "blockquote":
759
+ checked ? removeBlockquote(view) : addBlockquote(view);
760
+ break;
761
+ case "codeblock":
762
+ checked ? removeCodeblock(view) : addCodeblock(view);
763
+ break;
764
+ case "table":
765
+ insertTable(view);
766
+ break;
767
+ }
768
+ });
1137
769
  }
1138
770
  });
1139
- });
1140
- var createBlocks = (state, getView) => {
1141
- const value = state?.blockQuote ? "blockquote" : state.blockType ?? "";
1142
- const blockGroupAction = createBlockGroupAction(value);
1143
- const blockActions = createBlockActions(value, getView, state.blankLine);
1144
- return {
1145
- nodes: [
1146
- blockGroupAction,
1147
- ...blockActions
1148
- ],
1149
- edges: [
1150
- {
1151
- source: "root",
1152
- target: "block"
1153
- },
1154
- ...blockActions.map(({ id }) => ({
1155
- source: blockGroupAction.id,
1156
- target: id
1157
- }))
1158
- ]
1159
- };
1160
771
  };
1161
772
 
1162
773
  // src/components/EditorToolbar/formatting.ts
1163
774
  import { Inline, addLink, removeLink, setStyle } from "@dxos/ui-editor";
775
+ import { translationKey as translationKey2 } from "#translations";
1164
776
  var formats = {
1165
777
  strong: "ph--text-b--regular",
1166
778
  emphasis: "ph--text-italic--regular",
@@ -1168,205 +780,231 @@ var formats = {
1168
780
  code: "ph--code--regular",
1169
781
  link: "ph--link--regular"
1170
782
  };
1171
- var createFormattingGroup = (formatting) => createEditorActionGroup("formatting", {
1172
- variant: "toggleGroup",
1173
- selectCardinality: "multiple",
1174
- value: Object.keys(formats).filter((key) => !!formatting[key])
1175
- });
1176
- var createFormattingActions = (formatting, getView) => Object.entries(formats).map(([type, icon]) => {
1177
- const checked = !!formatting[type];
1178
- return createEditorAction(type, {
1179
- checked,
1180
- icon
1181
- }, () => {
1182
- const view = getView();
1183
- if (!view) {
1184
- return;
1185
- }
1186
- if (type === "link") {
1187
- checked ? removeLink(view) : addLink()(view);
1188
- return;
783
+ var addFormatting = (state, getView) => (builder) => {
784
+ const formatting = state;
785
+ builder.group("formatting", {
786
+ label: [
787
+ "formatting.label",
788
+ {
789
+ ns: translationKey2
790
+ }
791
+ ],
792
+ iconOnly: true,
793
+ variant: "toggleGroup",
794
+ selectCardinality: "multiple",
795
+ value: Object.keys(formats).filter((key) => !!formatting[key])
796
+ }, (group) => {
797
+ for (const [type, icon] of Object.entries(formats)) {
798
+ const checked = !!formatting[type];
799
+ group.action(type, {
800
+ label: [
801
+ `formatting.${type}.label`,
802
+ {
803
+ ns: translationKey2
804
+ }
805
+ ],
806
+ checked,
807
+ icon
808
+ }, () => {
809
+ const view = getView();
810
+ if (!view) {
811
+ return;
812
+ }
813
+ if (type === "link") {
814
+ checked ? removeLink(view) : addLink()(view);
815
+ return;
816
+ }
817
+ setStyle(type === "strong" ? Inline.Strong : type === "emphasis" ? Inline.Emphasis : type === "strikethrough" ? Inline.Strikethrough : Inline.Code, !checked)(view);
818
+ });
1189
819
  }
1190
- const inlineType = type === "strong" ? Inline.Strong : type === "emphasis" ? Inline.Emphasis : type === "strikethrough" ? Inline.Strikethrough : Inline.Code;
1191
- setStyle(inlineType, !checked)(view);
1192
820
  });
1193
- });
1194
- var createFormatting = (state, getView) => {
1195
- const formattingGroupAction = createFormattingGroup(state);
1196
- const formattingActions = createFormattingActions(state, getView);
1197
- return {
1198
- nodes: [
1199
- formattingGroupAction,
1200
- ...formattingActions
1201
- ],
1202
- edges: [
1203
- {
1204
- source: "root",
1205
- target: "formatting"
1206
- },
1207
- ...formattingActions.map(({ id }) => ({
1208
- source: formattingGroupAction.id,
1209
- target: id
1210
- }))
1211
- ]
1212
- };
1213
821
  };
1214
822
 
1215
823
  // src/components/EditorToolbar/headings.ts
1216
824
  import { setHeading } from "@dxos/ui-editor";
1217
- var createHeadingGroupAction = (value) => createEditorActionGroup("heading", {
1218
- variant: "dropdownMenu",
1219
- applyActive: true,
1220
- selectCardinality: "single",
1221
- // TODO(wittjosiah): Remove? Not sure this does anything.
1222
- value
1223
- }, "ph--text-h--regular");
1224
- var createHeadingActions = (currentLevel, getView) => Object.entries({
1225
- "0": "ph--paragraph--regular",
1226
- "1": "ph--text-h-one--regular",
1227
- "2": "ph--text-h-two--regular",
1228
- "3": "ph--text-h-three--regular",
1229
- "4": "ph--text-h-four--regular",
1230
- "5": "ph--text-h-five--regular",
1231
- "6": "ph--text-h-six--regular"
1232
- }).map(([levelStr, icon]) => {
1233
- const level = parseInt(levelStr);
1234
- return createEditorAction(`heading--${levelStr}`, {
1235
- label: [
1236
- "heading level label",
1237
- {
1238
- count: level,
1239
- ns: translationKey
1240
- }
1241
- ],
1242
- icon,
1243
- checked: levelStr === currentLevel
1244
- }, () => setHeading(level)(getView()));
1245
- });
825
+ import { translationKey as translationKey3 } from "#translations";
826
+ var headingIcons = {
827
+ 0: "ph--paragraph--regular",
828
+ 1: "ph--text-h-one--regular",
829
+ 2: "ph--text-h-two--regular",
830
+ 3: "ph--text-h-three--regular",
831
+ 4: "ph--text-h-four--regular",
832
+ 5: "ph--text-h-five--regular",
833
+ 6: "ph--text-h-six--regular"
834
+ };
1246
835
  var computeHeadingValue = (state) => {
1247
836
  const blockType = state ? state.blockType : "paragraph";
1248
837
  const heading = blockType && /heading(\d)/.exec(blockType);
1249
838
  return heading ? heading[1] : blockType === "paragraph" || !blockType ? "0" : "";
1250
839
  };
1251
- var createHeadings = (state, getView) => {
840
+ var addHeadings = (state, getView) => (builder) => {
1252
841
  const headingValue = computeHeadingValue(state);
1253
- const headingGroupAction = createHeadingGroupAction(headingValue);
1254
- const headingActions = createHeadingActions(headingValue, getView);
1255
- return {
1256
- nodes: [
1257
- headingGroupAction,
1258
- ...headingActions
1259
- ],
1260
- edges: [
842
+ builder.group("heading", {
843
+ label: [
844
+ "heading.label",
1261
845
  {
1262
- source: "root",
1263
- target: "heading"
1264
- },
1265
- ...headingActions.map(({ id }) => ({
1266
- source: headingGroupAction.id,
1267
- target: id
1268
- }))
1269
- ]
1270
- };
846
+ ns: translationKey3
847
+ }
848
+ ],
849
+ icon: "ph--text-h--regular",
850
+ iconOnly: true,
851
+ variant: "dropdownMenu",
852
+ applyActive: true,
853
+ selectCardinality: "single",
854
+ // TODO(wittjosiah): Remove? Not sure this does anything.
855
+ value: headingValue
856
+ }, (group) => {
857
+ for (const [levelStr, icon] of Object.entries(headingIcons)) {
858
+ const level = parseInt(levelStr);
859
+ group.action(`heading--${levelStr}`, {
860
+ label: [
861
+ "heading-level.label",
862
+ {
863
+ count: level,
864
+ ns: translationKey3
865
+ }
866
+ ],
867
+ icon,
868
+ checked: levelStr === headingValue
869
+ }, () => setHeading(level)(getView()));
870
+ }
871
+ });
1271
872
  };
1272
873
 
1273
874
  // src/components/EditorToolbar/image.ts
1274
- var createImageUploadAction = (onImageUpload) => createEditorAction("image", {
1275
- testId: "editor.toolbar.image",
1276
- icon: "ph--image-square--regular"
1277
- }, onImageUpload);
1278
- var createImageUpload = (onImageUpload) => ({
1279
- nodes: [
1280
- createImageUploadAction(onImageUpload)
1281
- ],
1282
- edges: [
1283
- {
1284
- source: "root",
1285
- target: "image"
875
+ import { translationKey as translationKey4 } from "#translations";
876
+ var addImageUpload = (onImageUpload) => (builder) => {
877
+ builder.action("image", {
878
+ label: [
879
+ "image.label",
880
+ {
881
+ ns: translationKey4
882
+ }
883
+ ],
884
+ testId: "editor.toolbar.image",
885
+ icon: "ph--image-square--regular"
886
+ }, onImageUpload);
887
+ };
888
+
889
+ // src/components/EditorToolbar/lists.ts
890
+ import { List, addList, removeList } from "@dxos/ui-editor";
891
+ import { translationKey as translationKey5 } from "#translations";
892
+ var listStyles = {
893
+ bullet: "ph--list-bullets--regular",
894
+ ordered: "ph--list-numbers--regular",
895
+ task: "ph--list-checks--regular"
896
+ };
897
+ var addLists = (state, getView) => (builder) => {
898
+ const value = state.listStyle ?? "";
899
+ builder.group("list", {
900
+ label: [
901
+ "list.label",
902
+ {
903
+ ns: translationKey5
904
+ }
905
+ ],
906
+ iconOnly: true,
907
+ variant: "toggleGroup",
908
+ selectCardinality: "single",
909
+ value
910
+ }, (group) => {
911
+ for (const [listStyle, icon] of Object.entries(listStyles)) {
912
+ const checked = value === listStyle;
913
+ group.action(`list-${listStyle}`, {
914
+ label: [
915
+ `list.${listStyle}.label`,
916
+ {
917
+ ns: translationKey5
918
+ }
919
+ ],
920
+ checked,
921
+ icon
922
+ }, () => {
923
+ const view = getView();
924
+ if (!view) {
925
+ return;
926
+ }
927
+ const listType = listStyle === "ordered" ? List.Ordered : listStyle === "bullet" ? List.Bullet : List.Task;
928
+ if (checked) {
929
+ removeList(listType)(view);
930
+ } else {
931
+ addList(listType)(view);
932
+ }
933
+ });
1286
934
  }
1287
- ]
1288
- });
935
+ });
936
+ };
1289
937
 
1290
938
  // src/components/EditorToolbar/search.ts
1291
939
  import { openSearchPanel } from "@codemirror/search";
1292
- var createSearchAction = (getView) => createEditorAction("search", {
1293
- testId: "editor.toolbar.search",
1294
- icon: "ph--magnifying-glass--regular"
1295
- }, () => openSearchPanel(getView()));
1296
- var createSearch = (getView) => ({
1297
- nodes: [
1298
- createSearchAction(getView)
1299
- ],
1300
- edges: [
1301
- {
1302
- source: "root",
1303
- target: "search"
1304
- }
1305
- ]
1306
- });
940
+ import { translationKey as translationKey6 } from "#translations";
941
+ var addSearch = (getView) => (builder) => {
942
+ builder.action("search", {
943
+ label: [
944
+ "search.label",
945
+ {
946
+ ns: translationKey6
947
+ }
948
+ ],
949
+ testId: "editor.toolbar.search",
950
+ icon: "ph--magnifying-glass--regular"
951
+ }, () => openSearchPanel(getView()));
952
+ };
1307
953
 
1308
954
  // src/components/EditorToolbar/view-mode.ts
1309
- var createViewModeGroupAction = (value) => createEditorActionGroup("viewMode", {
1310
- variant: "dropdownMenu",
1311
- applyActive: true,
1312
- selectCardinality: "single",
1313
- value
1314
- }, "ph--eye--regular");
1315
- var createViewModeActions = (value, onViewModeChange) => Object.entries({
955
+ import { translationKey as translationKey7 } from "#translations";
956
+ var viewModes = {
1316
957
  preview: "ph--eye--regular",
1317
958
  source: "ph--pencil-simple--regular",
1318
959
  readonly: "ph--pencil-slash--regular"
1319
- }).map(([viewMode, icon]) => {
1320
- const checked = viewMode === value;
1321
- return createEditorAction(`view-mode--${viewMode}`, {
960
+ };
961
+ var addViewMode = (state, onViewModeChange) => (builder) => {
962
+ const value = state.viewMode ?? "source";
963
+ builder.group("viewMode", {
1322
964
  label: [
1323
- `${viewMode} mode label`,
965
+ "view-mode.label",
1324
966
  {
1325
- ns: translationKey
967
+ ns: translationKey7
1326
968
  }
1327
969
  ],
1328
- checked,
1329
- icon
1330
- }, () => onViewModeChange(viewMode));
1331
- });
1332
- var createViewMode = (state, onViewModeChange) => {
1333
- const value = state.viewMode ?? "source";
1334
- const viewModeGroupAction = createViewModeGroupAction(value);
1335
- const viewModeActions = createViewModeActions(value, onViewModeChange);
1336
- return {
1337
- nodes: [
1338
- viewModeGroupAction,
1339
- ...viewModeActions
1340
- ],
1341
- edges: [
1342
- {
1343
- source: "root",
1344
- target: "viewMode"
1345
- },
1346
- ...viewModeActions.map(({ id }) => ({
1347
- source: viewModeGroupAction.id,
1348
- target: id
1349
- }))
1350
- ]
1351
- };
970
+ icon: "ph--eye--regular",
971
+ iconOnly: true,
972
+ variant: "dropdownMenu",
973
+ applyActive: true,
974
+ selectCardinality: "single",
975
+ value
976
+ }, (group) => {
977
+ for (const [viewMode, icon] of Object.entries(viewModes)) {
978
+ const checked = viewMode === value;
979
+ group.action(`view-mode--${viewMode}`, {
980
+ label: [
981
+ `view-mode.${viewMode}.label`,
982
+ {
983
+ ns: translationKey7
984
+ }
985
+ ],
986
+ checked,
987
+ icon
988
+ }, () => onViewModeChange(viewMode));
989
+ }
990
+ });
1352
991
  };
1353
992
 
1354
993
  // src/components/EditorToolbar/EditorToolbar.tsx
1355
994
  var EditorToolbar = /* @__PURE__ */ memo(({ classNames, role, attendableId, onAction, ...props }) => {
1356
- const menuProps = useEditorToolbarActionGraph(props);
1357
- return /* @__PURE__ */ React3.createElement(ElevationProvider, {
995
+ const menuActions = useMarkdownMenuActions(props);
996
+ return /* @__PURE__ */ React2.createElement(ElevationProvider, {
1358
997
  elevation: role === "section" ? "positioned" : "base"
1359
- }, /* @__PURE__ */ React3.createElement(MenuProvider, {
1360
- ...menuProps,
998
+ }, /* @__PURE__ */ React2.createElement(Menu2.Root, {
999
+ ...menuActions,
1361
1000
  attendableId,
1362
1001
  onAction
1363
- }, /* @__PURE__ */ React3.createElement(ToolbarMenu, {
1364
- classNames,
1365
- textBlockWidth: true
1002
+ }, /* @__PURE__ */ React2.createElement(Menu2.Toolbar, {
1003
+ classNames
1366
1004
  })));
1367
1005
  });
1368
- var useEditorToolbarActionGraph = ({ state, getView, customActions, ...features }) => {
1369
- const menuCreator = useMemo3(() => createToolbarActions({
1006
+ var useMarkdownMenuActions = ({ state, getView, customActions, ...features }) => {
1007
+ const menuCreator = useMemo2(() => createMarkdownActions({
1370
1008
  state,
1371
1009
  getView,
1372
1010
  customActions,
@@ -1385,63 +1023,300 @@ var useEditorToolbarActionGraph = ({ state, getView, customActions, ...features
1385
1023
  ]);
1386
1024
  return useMenuActions(menuCreator);
1387
1025
  };
1388
- var createToolbarActions = ({ state, getView, customActions, ...features }) => {
1026
+ var createMarkdownActions = ({ state, getView, customActions, ...features }) => {
1389
1027
  return Atom.make((get) => {
1390
- const graph = {
1391
- nodes: [],
1392
- edges: []
1393
- };
1394
- const addSubGraph = (graph2, subGraph) => {
1395
- graph2.nodes.push(...subGraph.nodes);
1396
- graph2.edges.push(...subGraph.edges);
1397
- };
1398
1028
  const stateSnapshot = get(state);
1399
- if (features?.showHeadings ?? true) {
1400
- addSubGraph(graph, createHeadings(stateSnapshot, getView));
1401
- }
1402
- if (features?.showFormatting ?? true) {
1403
- addSubGraph(graph, createFormatting(stateSnapshot, getView));
1404
- }
1405
- if (features?.showLists ?? true) {
1406
- addSubGraph(graph, createLists(stateSnapshot, getView));
1407
- }
1408
- if (features?.showBlocks ?? true) {
1409
- addSubGraph(graph, createBlocks(stateSnapshot, getView));
1029
+ return MenuBuilder.make().subgraph(features?.showHeadings !== false && addHeadings(stateSnapshot, getView)).subgraph(features?.showFormatting !== false && addFormatting(stateSnapshot, getView)).subgraph(features?.showLists !== false && addLists(stateSnapshot, getView)).subgraph(features?.showBlocks !== false && addBlocks(stateSnapshot, getView)).subgraph(features?.onImageUpload && addImageUpload(features.onImageUpload)).separator("gap").subgraph(customActions && get(customActions)).subgraph(features?.showSearch !== false && addSearch(getView)).subgraph(features?.onViewModeChange && addViewMode(stateSnapshot, features.onViewModeChange)).build();
1030
+ });
1031
+ };
1032
+
1033
+ // src/components/Editor/EditorView.tsx
1034
+ import { Transaction } from "@codemirror/state";
1035
+ import { EditorView as NaturalEditorView } from "@codemirror/view";
1036
+ import React3, { forwardRef, useEffect as useEffect3, useImperativeHandle, useRef as useRef4 } from "react";
1037
+ import { initialSync } from "@dxos/ui-editor";
1038
+ import { mx } from "@dxos/ui-theme";
1039
+
1040
+ // src/hooks/useBasicMarkdownExtensions.ts
1041
+ import { useMemo as useMemo3 } from "react";
1042
+ import { useThemeContext as useThemeContext2 } from "@dxos/react-ui";
1043
+ import { createBasicExtensions, createMarkdownExtensions, createThemeExtensions, decorateMarkdown } from "@dxos/ui-editor";
1044
+ var useBasicMarkdownExtensions = ({ placeholder: placeholder2, decorate = true, extensions } = {}) => {
1045
+ const { themeMode } = useThemeContext2();
1046
+ return useMemo3(() => [
1047
+ createBasicExtensions({
1048
+ placeholder: placeholder2
1049
+ }),
1050
+ createThemeExtensions({
1051
+ syntaxHighlighting: true,
1052
+ themeMode
1053
+ }),
1054
+ createMarkdownExtensions(),
1055
+ ...decorate ? [
1056
+ decorateMarkdown()
1057
+ ] : [],
1058
+ ...extensions ?? []
1059
+ ], [
1060
+ placeholder2,
1061
+ themeMode,
1062
+ decorate,
1063
+ extensions
1064
+ ]);
1065
+ };
1066
+
1067
+ // src/hooks/useTextEditor.ts
1068
+ import { EditorState } from "@codemirror/state";
1069
+ import { EditorView as EditorView2 } from "@codemirror/view";
1070
+ import { useCallback as useCallback3, useEffect as useEffect2, useMemo as useMemo4, useRef as useRef3, useState as useState3 } from "react";
1071
+ import { log } from "@dxos/log";
1072
+ import { createEditorStateTransaction, debugDispatcher, documentId, modalStateField as modalStateField2 } from "@dxos/ui-editor";
1073
+ import { getProviderValue, isTruthy as isTruthy2 } from "@dxos/util";
1074
+ var __dxlog_file3 = "/__w/dxos/dxos/packages/ui/react-ui-editor/src/hooks/useTextEditor.ts";
1075
+ var instanceCount = 0;
1076
+ var useTextEditor = (props = {}, deps = []) => {
1077
+ const { id, doc, initialValue, extensions, autoFocus, scrollTo, selection, selectionEnd, debug } = useMemo4(() => getProviderValue(props), deps ?? []);
1078
+ const [instanceId] = useState3(() => `text-editor-${++instanceCount}`);
1079
+ const [view, setView] = useState3(null);
1080
+ const parentRef = useRef3(null);
1081
+ useEffect2(() => {
1082
+ let view2 = null;
1083
+ if (parentRef.current) {
1084
+ log("create", {
1085
+ id,
1086
+ instanceId,
1087
+ doc: initialValue?.length ?? 0
1088
+ }, { "~LogMeta": "~LogMeta", F: __dxlog_file3, L: 22, S: void 0 });
1089
+ let initialSelection;
1090
+ if (selection?.anchor && initialValue?.length) {
1091
+ if (selection.anchor <= initialValue.length && (selection?.head ?? 0) <= initialValue.length) {
1092
+ initialSelection = selection;
1093
+ }
1094
+ } else if (selectionEnd && selection === void 0) {
1095
+ const index = initialValue?.indexOf("\n");
1096
+ const anchor = !index || index === -1 ? 0 : index;
1097
+ initialSelection = {
1098
+ anchor
1099
+ };
1100
+ }
1101
+ const state = EditorState.create({
1102
+ doc: doc ?? initialValue,
1103
+ // selection: initialSelection,
1104
+ extensions: [
1105
+ id && documentId.of(id),
1106
+ extensions,
1107
+ // NOTE: This doesn't catch errors in keymap functions.
1108
+ EditorView2.exceptionSink.of((err) => {
1109
+ log.catch(err, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file3, L: 48, S: void 0 });
1110
+ })
1111
+ ].filter(isTruthy2)
1112
+ });
1113
+ view2 = new EditorView2({
1114
+ parent: parentRef.current,
1115
+ state,
1116
+ scrollTo: scrollTo ? EditorView2.scrollIntoView(scrollTo, {
1117
+ yMargin: 96
1118
+ }) : void 0,
1119
+ dispatchTransactions: debug ? debugDispatcher : void 0
1120
+ });
1121
+ if (selectionEnd && !initialSelection) {
1122
+ const { to } = view2.state.doc.lineAt(0);
1123
+ if (to) {
1124
+ view2.dispatch({
1125
+ selection: {
1126
+ anchor: to
1127
+ }
1128
+ });
1129
+ }
1130
+ }
1131
+ setView(view2);
1410
1132
  }
1411
- if (features?.onImageUpload) {
1412
- addSubGraph(graph, createImageUpload(features.onImageUpload));
1133
+ return () => {
1134
+ log("destroy", {
1135
+ id
1136
+ }, { "~LogMeta": "~LogMeta", F: __dxlog_file3, L: 75, S: void 0 });
1137
+ view2?.destroy();
1138
+ };
1139
+ }, deps);
1140
+ useEffect2(() => {
1141
+ if (view) {
1142
+ if (scrollTo || selection) {
1143
+ if (selection && selection.anchor > view.state.doc.length) {
1144
+ log.warn("invalid selection", {
1145
+ length: view.state.doc.length,
1146
+ scrollTo,
1147
+ selection
1148
+ }, { "~LogMeta": "~LogMeta", F: __dxlog_file3, L: 85, S: void 0 });
1149
+ return;
1150
+ }
1151
+ view.dispatch(createEditorStateTransaction({
1152
+ scrollTo,
1153
+ selection
1154
+ }));
1155
+ }
1413
1156
  }
1414
- addSubGraph(graph, createGapSeparator());
1415
- if (customActions) {
1416
- addSubGraph(graph, get(customActions));
1157
+ }, [
1158
+ view,
1159
+ scrollTo,
1160
+ selection
1161
+ ]);
1162
+ useEffect2(() => {
1163
+ if (view && autoFocus) {
1164
+ view.focus();
1417
1165
  }
1418
- if (features?.showSearch ?? true) {
1419
- addSubGraph(graph, createSearch(getView));
1166
+ }, [
1167
+ autoFocus,
1168
+ view
1169
+ ]);
1170
+ const handleKeyDown = useCallback3((event) => {
1171
+ const { key, target, currentTarget } = event;
1172
+ switch (key) {
1173
+ case "Escape": {
1174
+ const modal = view?.state.field(modalStateField2, false);
1175
+ if (modal) {
1176
+ return;
1177
+ }
1178
+ const element = view?.contentDOM.closest('[tabindex="0"]');
1179
+ element?.focus();
1180
+ break;
1181
+ }
1182
+ case "Enter": {
1183
+ event.preventDefault();
1184
+ if (target === currentTarget) {
1185
+ view?.focus();
1186
+ }
1187
+ break;
1188
+ }
1420
1189
  }
1421
- if (features?.onViewModeChange) {
1422
- addSubGraph(graph, createViewMode(stateSnapshot, features.onViewModeChange));
1190
+ }, [
1191
+ view
1192
+ ]);
1193
+ return {
1194
+ parentRef,
1195
+ view,
1196
+ focusAttributes: {
1197
+ tabIndex: 0,
1198
+ onKeyDown: handleKeyDown
1423
1199
  }
1424
- return graph;
1425
- });
1200
+ };
1426
1201
  };
1427
1202
 
1428
- // src/components/EditorToolbar/useEditorToolbar.ts
1429
- import { Atom as Atom2 } from "@effect-atom/atom-react";
1430
- import { useMemo as useMemo4 } from "react";
1431
- var useEditorToolbar = (initialState = {}) => {
1432
- return useMemo4(() => Atom2.make(initialState), []);
1203
+ // src/components/Editor/controller.ts
1204
+ var noopController = {
1205
+ get view() {
1206
+ return null;
1207
+ },
1208
+ getText: () => "",
1209
+ setText: () => {
1210
+ },
1211
+ focus: () => {
1212
+ }
1213
+ };
1214
+ var createEditorController = (view) => {
1215
+ return {
1216
+ get view() {
1217
+ return view;
1218
+ },
1219
+ getText: () => {
1220
+ return view?.state.doc.toString() ?? "";
1221
+ },
1222
+ setText: (text, focus) => {
1223
+ view?.dispatch({
1224
+ changes: {
1225
+ from: 0,
1226
+ to: view?.state.doc.length ?? 0,
1227
+ insert: text
1228
+ },
1229
+ selection: {
1230
+ anchor: text.length,
1231
+ head: text.length
1232
+ }
1233
+ });
1234
+ if (focus) {
1235
+ view?.focus();
1236
+ }
1237
+ },
1238
+ focus: () => view?.focus()
1239
+ };
1433
1240
  };
1434
1241
 
1242
+ // src/components/Editor/EditorView.tsx
1243
+ var EditorView3 = /* @__PURE__ */ forwardRef(({ classNames, id, extensions, selectionEnd, focusable = true, value, onChange, ...props }, forwardedRef) => {
1244
+ const onChangeRef = useRef4(onChange);
1245
+ onChangeRef.current = onChange;
1246
+ const { parentRef, focusAttributes, view } = useTextEditor(() => ({
1247
+ id,
1248
+ initialValue: value,
1249
+ selectionEnd,
1250
+ extensions: [
1251
+ extensions ?? [],
1252
+ NaturalEditorView.updateListener.of(({ view: view2, docChanged, transactions }) => {
1253
+ const isInitialSync = transactions.some((tr) => tr.annotation(Transaction.userEvent) === initialSync.value);
1254
+ if (!isInitialSync && docChanged) {
1255
+ onChangeRef.current?.(view2.state.doc.toString());
1256
+ }
1257
+ })
1258
+ ],
1259
+ ...props
1260
+ }), [
1261
+ id,
1262
+ extensions,
1263
+ selectionEnd
1264
+ ]);
1265
+ useImperativeHandle(forwardedRef, () => {
1266
+ return createEditorController(view);
1267
+ }, [
1268
+ id,
1269
+ view
1270
+ ]);
1271
+ useEffect3(() => {
1272
+ if (!view) {
1273
+ return;
1274
+ }
1275
+ const next = value ?? "";
1276
+ if (view.state.doc.toString() === next) {
1277
+ return;
1278
+ }
1279
+ requestAnimationFrame(() => {
1280
+ if (view.state.doc.toString() === next) {
1281
+ return;
1282
+ }
1283
+ view.dispatch({
1284
+ annotations: initialSync,
1285
+ changes: [
1286
+ {
1287
+ from: 0,
1288
+ to: view.state.doc.length,
1289
+ insert: next
1290
+ }
1291
+ ],
1292
+ selection: selectionEnd ? {
1293
+ anchor: next.length
1294
+ } : void 0
1295
+ });
1296
+ if (selectionEnd) {
1297
+ view.focus();
1298
+ }
1299
+ });
1300
+ }, [
1301
+ view,
1302
+ value,
1303
+ selectionEnd
1304
+ ]);
1305
+ return /* @__PURE__ */ React3.createElement("div", {
1306
+ className: mx("w-full outline-hidden focus:border-accent-surface focus-within:border-focus-ring-subtle", classNames),
1307
+ ...focusable ? focusAttributes : {},
1308
+ ref: parentRef
1309
+ });
1310
+ });
1311
+
1435
1312
  // src/components/Editor/Editor.tsx
1436
1313
  var __dxlog_file4 = "/__w/dxos/dxos/packages/ui/react-ui-editor/src/components/Editor/Editor.tsx";
1437
1314
  var [EditorContextProvider, useEditorContext] = createContext("Editor");
1438
- var EditorRoot = /* @__PURE__ */ forwardRef2(({ children, extensions: extensionsProp, viewMode, ...props }, forwardedRef) => {
1439
- const state = useEditorToolbar({
1315
+ var EditorRoot = /* @__PURE__ */ forwardRef2(({ children, extensions: extensionsProp, viewMode, numItems, ...props }, forwardedRef) => {
1316
+ const state = useMemo5(() => Atom2.make({
1317
+ viewMode
1318
+ }), [
1440
1319
  viewMode
1441
- });
1442
- const [controller, setController] = useState4();
1443
- useImperativeHandle2(forwardedRef, () => controller ?? noopController, [
1444
- controller
1445
1320
  ]);
1446
1321
  const { groupsRef, extension, ...menuProps } = useEditorMenu(props);
1447
1322
  const extensions = useMemo5(() => [
@@ -1451,6 +1326,10 @@ var EditorRoot = /* @__PURE__ */ forwardRef2(({ children, extensions: extensions
1451
1326
  extension,
1452
1327
  extensionsProp
1453
1328
  ]);
1329
+ const [controller, setController] = useState4(noopController);
1330
+ useImperativeHandle2(forwardedRef, () => controller, [
1331
+ controller
1332
+ ]);
1454
1333
  return /* @__PURE__ */ React4.createElement(EditorContextProvider, {
1455
1334
  controller,
1456
1335
  setController,
@@ -1459,21 +1338,21 @@ var EditorRoot = /* @__PURE__ */ forwardRef2(({ children, extensions: extensions
1459
1338
  }, /* @__PURE__ */ React4.createElement(EditorMenuProvider, {
1460
1339
  view: controller?.view,
1461
1340
  groups: groupsRef.current,
1341
+ numItems,
1462
1342
  ...menuProps
1463
1343
  }, children));
1464
1344
  });
1465
1345
  EditorRoot.displayName = "Editor.Root";
1466
- var EDITOR_VIEWPORT_NAME = "Editor.Viewport";
1467
- var EditorViewport = ({ classNames, children }) => {
1346
+ var EDITOR_CONTENT_NAME = "Editor.Content";
1347
+ var EditorContent = ({ classNames, children }) => {
1468
1348
  return /* @__PURE__ */ React4.createElement("div", {
1469
- role: "none",
1470
- className: mx2("grid grid-rows-[min-content_1fr] bs-full overflow-hidden", classNames)
1349
+ className: mx2("grid grid-rows-[min-content_1fr] h-full overflow-hidden", classNames)
1471
1350
  }, children);
1472
1351
  };
1473
- EditorViewport.displayName = EDITOR_VIEWPORT_NAME;
1474
- var EDITOR_CONTENT_NAME = "Editor.Content";
1475
- var EditorContent2 = ({ extensions: providedExtensions, ...props }) => {
1476
- const { extensions: additionalExtensions = [], setController } = useEditorContext(EDITOR_CONTENT_NAME);
1352
+ EditorContent.displayName = EDITOR_CONTENT_NAME;
1353
+ var EDITOR_VIEW_NAME = "Editor.View";
1354
+ var EditorView4 = ({ extensions: providedExtensions, ...props }) => {
1355
+ const { extensions: additionalExtensions = [], setController } = useEditorContext(EDITOR_VIEW_NAME);
1477
1356
  const extensions = useMemo5(() => [
1478
1357
  additionalExtensions,
1479
1358
  providedExtensions
@@ -1481,26 +1360,18 @@ var EditorContent2 = ({ extensions: providedExtensions, ...props }) => {
1481
1360
  providedExtensions,
1482
1361
  additionalExtensions
1483
1362
  ]);
1484
- return /* @__PURE__ */ React4.createElement(EditorContent, {
1363
+ return /* @__PURE__ */ React4.createElement(EditorView3, {
1485
1364
  ...props,
1486
1365
  extensions,
1487
1366
  ref: setController
1488
1367
  });
1489
1368
  };
1490
- EditorContent2.displayName = EDITOR_CONTENT_NAME;
1369
+ EditorView4.displayName = EDITOR_VIEW_NAME;
1491
1370
  var EDITOR_TOOLBAR_NAME = "Editor.Toolbar";
1492
1371
  var EditorToolbar2 = (props) => {
1493
1372
  const { controller, state } = useEditorContext(EDITOR_TOOLBAR_NAME);
1494
1373
  const getView = useCallback4(() => {
1495
- invariant3(controller?.view, void 0, {
1496
- F: __dxlog_file4,
1497
- L: 146,
1498
- S: void 0,
1499
- A: [
1500
- "controller?.view",
1501
- ""
1502
- ]
1503
- });
1374
+ invariant3(controller?.view, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file4, L: 99, S: void 0, A: ["controller?.view", ""] });
1504
1375
  return controller?.view;
1505
1376
  }, [
1506
1377
  controller
@@ -1514,19 +1385,19 @@ var EditorToolbar2 = (props) => {
1514
1385
  EditorToolbar2.displayName = EDITOR_TOOLBAR_NAME;
1515
1386
  var Editor = {
1516
1387
  Root: EditorRoot,
1517
- Viewport: EditorViewport,
1518
- Content: EditorContent2,
1519
- Toolbar: EditorToolbar2
1388
+ Toolbar: EditorToolbar2,
1389
+ Content: EditorContent,
1390
+ View: EditorView4
1520
1391
  };
1521
1392
 
1522
1393
  // src/components/EditorPreviewProvider/EditorPreviewProvider.tsx
1523
1394
  import { createContext as createContext2 } from "@radix-ui/react-context";
1524
- import React5, { useCallback as useCallback5, useEffect as useEffect4, useRef as useRef4, useState as useState5 } from "react";
1395
+ import React5, { useCallback as useCallback5, useEffect as useEffect4, useRef as useRef5, useState as useState5 } from "react";
1525
1396
  import { addEventListener as addEventListener2 } from "@dxos/async";
1526
1397
  import { DX_ANCHOR_ACTIVATE as DX_ANCHOR_ACTIVATE2, Popover as Popover2 } from "@dxos/react-ui";
1527
1398
  var [EditorPreviewContextProvider, useEditorPreview] = createContext2("PreviewPopover", {});
1528
1399
  var EditorPreviewProvider = ({ children, onLookup }) => {
1529
- const triggerRef = useRef4(null);
1400
+ const triggerRef = useRef5(null);
1530
1401
  const [value, setValue] = useState5({});
1531
1402
  const [open, setOpen] = useState5(false);
1532
1403
  const handleActivate = useCallback5((event) => {
@@ -1575,17 +1446,187 @@ var EditorPreviewProvider = ({ children, onLookup }) => {
1575
1446
  }, /* @__PURE__ */ React5.createElement(Popover2.VirtualTrigger, {
1576
1447
  virtualRef: triggerRef
1577
1448
  }), /* @__PURE__ */ React5.createElement("div", {
1578
- role: "none",
1579
1449
  className: "contents",
1580
1450
  ref: setRoot
1581
1451
  }, children)));
1582
1452
  };
1453
+
1454
+ // src/extensions/assistant-extension.tsx
1455
+ import { forEachDiagnostic, linter, setDiagnostics } from "@codemirror/lint";
1456
+ import { ChangeSet } from "@codemirror/state";
1457
+ import { EditorView as EditorView5 } from "@codemirror/view";
1458
+ import { log as log2 } from "@dxos/log";
1459
+ import { safeParseJson, trim } from "@dxos/util";
1460
+ var __dxlog_file5 = "/__w/dxos/dxos/packages/ui/react-ui-editor/src/extensions/assistant-extension.tsx";
1461
+ var DEFAULT_INSTRUCTIONS = trim`
1462
+ Proofread the input text below.
1463
+ Identify typos and grammatical errors.
1464
+ Return ONLY a valid JSON array of objects with fields: "original" (string), "replacement" (string), "context" (string, 3-5 words around match).
1465
+ --
1466
+ `;
1467
+ var underline = (color) => {
1468
+ const svg = trim`
1469
+ <svg xmlns="http://www.w3.org/2000/svg" width="6" height="3">
1470
+ <path d="m0 3 l2 -2 l1 0 l2 2 l1 0" stroke="${color}" fill="none" stroke-width="1"/>
1471
+ </svg>
1472
+ `;
1473
+ return `url('data:image/svg+xml;base64,${btoa(svg)}') !important`;
1474
+ };
1475
+ var assistant = (options) => {
1476
+ const styles = getComputedStyle(document.documentElement);
1477
+ const style = {
1478
+ info: styles.getPropertyValue("--color-green-500").trim(),
1479
+ warning: styles.getPropertyValue("--color-orange-500").trim(),
1480
+ error: styles.getPropertyValue("--color-rose-500").trim()
1481
+ };
1482
+ return [
1483
+ assistantLinter(options),
1484
+ EditorView5.baseTheme({
1485
+ ".cm-lintRange-info": {
1486
+ backgroundImage: underline(style.info)
1487
+ },
1488
+ ".cm-lintRange-warning": {
1489
+ backgroundImage: underline(style.warning)
1490
+ },
1491
+ ".cm-lintRange-error": {
1492
+ backgroundImage: underline(style.error)
1493
+ },
1494
+ ".cm-panels-bottom": {
1495
+ borderTop: "1px solid var(--color-separator) !important"
1496
+ },
1497
+ ".cm-panel-lint .cm-panel": {
1498
+ outline: "none !important"
1499
+ },
1500
+ /** @apply dx-button */
1501
+ ".cm-panel button": {
1502
+ color: "var(--color-base-foreground) !important"
1503
+ },
1504
+ ".cm-panel.cm-panel-lint ul": {
1505
+ color: "var(--color-base-foreground) !important",
1506
+ backgroundColor: "var(--color-base-surface) !important",
1507
+ marginRight: "2rem !important"
1508
+ },
1509
+ ".cm-panel.cm-panel-lint ul [aria-selected]": {
1510
+ color: "var(--color-base-foreground) !important",
1511
+ backgroundColor: "var(--color-base-surface) !important"
1512
+ },
1513
+ ".cm-panel.cm-panel-lint ul li": {
1514
+ display: "grid",
1515
+ gridTemplateColumns: "1fr auto",
1516
+ alignItems: "center"
1517
+ },
1518
+ ".cm-panel.cm-panel-lint ul li .cm-diagnosticText": {
1519
+ paddingRight: "8px !important"
1520
+ },
1521
+ ".cm-panel.cm-panel-lint ul li button.cm-diagnosticAction": {
1522
+ margin: "none !important"
1523
+ },
1524
+ ".cm-diagnostic": {
1525
+ padding: "0px 8px !important",
1526
+ whiteSpace: "pre-wrap !important"
1527
+ },
1528
+ ".cm-diagnostic-info": {
1529
+ border: "none !important"
1530
+ }
1531
+ })
1532
+ ];
1533
+ };
1534
+ var isSuggestion = (value) => typeof value === "object" && value !== null && typeof value.original === "string" && typeof value.replacement === "string" && typeof value.context === "string";
1535
+ var findSuggestionIndex = (content, suggestion) => {
1536
+ const firstIdx = content.indexOf(suggestion.original);
1537
+ if (firstIdx === -1) {
1538
+ return -1;
1539
+ }
1540
+ const secondIdx = content.indexOf(suggestion.original, firstIdx + 1);
1541
+ if (secondIdx === -1) {
1542
+ return firstIdx;
1543
+ }
1544
+ const contextIdx = content.indexOf(suggestion.context);
1545
+ if (contextIdx !== -1) {
1546
+ const contextEnd = contextIdx + suggestion.context.length;
1547
+ if (secondIdx >= contextIdx && secondIdx <= contextEnd) {
1548
+ return secondIdx;
1549
+ }
1550
+ }
1551
+ return firstIdx;
1552
+ };
1553
+ var replaceTextAndDropLintAtRange = (view, from, to, insert) => {
1554
+ const kept = [];
1555
+ forEachDiagnostic(view.state, (diagnostic, diagnosticFrom, diagnosticTo) => {
1556
+ if (diagnosticFrom < to && diagnosticTo > from) {
1557
+ return;
1558
+ }
1559
+ kept.push({
1560
+ ...diagnostic,
1561
+ from: diagnosticFrom,
1562
+ to: diagnosticTo
1563
+ });
1564
+ });
1565
+ const changeSet = ChangeSet.of({
1566
+ from,
1567
+ to,
1568
+ insert
1569
+ }, view.state.doc.length);
1570
+ const next = kept.map((d) => ({
1571
+ ...d,
1572
+ from: changeSet.mapPos(d.from, 1),
1573
+ to: changeSet.mapPos(d.to, -1)
1574
+ }));
1575
+ view.dispatch({
1576
+ changes: {
1577
+ from,
1578
+ to,
1579
+ insert
1580
+ },
1581
+ ...setDiagnostics(view.state, next)
1582
+ });
1583
+ };
1584
+ var assistantLinter = ({ generate, instructions = DEFAULT_INSTRUCTIONS, autoPanel = true, delay = 2e3 }) => linter(async (view) => {
1585
+ try {
1586
+ const content = view.state.doc.toString();
1587
+ const result = await generate({
1588
+ instructions,
1589
+ content
1590
+ });
1591
+ const [match] = result.match(/\[.*\]/s) ?? [];
1592
+ const parsed = match ? safeParseJson(match, []) : [];
1593
+ const suggestions = Array.isArray(parsed) ? parsed.filter(isSuggestion) : [];
1594
+ if (suggestions && suggestions.length > 0) {
1595
+ const diagnostics = [];
1596
+ for (const suggestion of suggestions) {
1597
+ const idx = findSuggestionIndex(content, suggestion);
1598
+ if (idx !== -1) {
1599
+ diagnostics.push({
1600
+ from: idx,
1601
+ to: idx + suggestion.original.length,
1602
+ severity: "info",
1603
+ message: `Suggestion: ${suggestion.replacement}`,
1604
+ actions: [
1605
+ {
1606
+ name: "Apply",
1607
+ apply: (view2, from, to) => {
1608
+ replaceTextAndDropLintAtRange(view2, from, to, suggestion.replacement);
1609
+ }
1610
+ }
1611
+ ]
1612
+ });
1613
+ }
1614
+ }
1615
+ return diagnostics;
1616
+ }
1617
+ } catch (err) {
1618
+ log2.catch(err, void 0, { "~LogMeta": "~LogMeta", F: __dxlog_file5, L: 169, S: void 0 });
1619
+ }
1620
+ return [];
1621
+ }, {
1622
+ delay,
1623
+ autoPanel
1624
+ });
1583
1625
  export {
1584
1626
  Editor,
1585
- EditorContent,
1586
1627
  EditorMenuProvider,
1587
1628
  EditorPreviewProvider,
1588
- EditorToolbar,
1629
+ assistant,
1589
1630
  createEditorController,
1590
1631
  createMenuGroup,
1591
1632
  filterMenuGroups,
@@ -1597,10 +1638,10 @@ export {
1597
1638
  popover,
1598
1639
  popoverRangeEffect,
1599
1640
  popoverStateField,
1600
- translations,
1641
+ useBasicMarkdownExtensions,
1642
+ useEditorContext,
1601
1643
  useEditorMenu,
1602
1644
  useEditorPreview,
1603
- useEditorToolbar,
1604
1645
  useTextEditor
1605
1646
  };
1606
1647
  //# sourceMappingURL=index.mjs.map