@dxos/react-ui-editor 0.4.5 → 0.4.6-main.20d1ec4

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 (48) hide show
  1. package/dist/lib/browser/index.mjs +156 -137
  2. package/dist/lib/browser/index.mjs.map +3 -3
  3. package/dist/lib/browser/meta.json +1 -1
  4. package/dist/types/src/components/TextEditor/TextEditor.d.ts +2 -0
  5. package/dist/types/src/components/TextEditor/TextEditor.d.ts.map +1 -1
  6. package/dist/types/src/components/TextEditor/automerge.stories.d.ts +3 -5
  7. package/dist/types/src/components/TextEditor/automerge.stories.d.ts.map +1 -1
  8. package/dist/types/src/components/TextEditor/hooks.stories.d.ts.map +1 -1
  9. package/dist/types/src/components/Toolbar/Toolbar.d.ts.map +1 -1
  10. package/dist/types/src/components/Toolbar/Toolbar.stories.d.ts.map +1 -1
  11. package/dist/types/src/extensions/automerge/automerge.d.ts +2 -7
  12. package/dist/types/src/extensions/automerge/automerge.d.ts.map +1 -1
  13. package/dist/types/src/extensions/automerge/cursor.d.ts +2 -3
  14. package/dist/types/src/extensions/automerge/cursor.d.ts.map +1 -1
  15. package/dist/types/src/extensions/automerge/defs.d.ts +9 -2
  16. package/dist/types/src/extensions/automerge/defs.d.ts.map +1 -1
  17. package/dist/types/src/extensions/markdown/decorate.d.ts.map +1 -1
  18. package/dist/types/src/hooks/defs.d.ts +1 -0
  19. package/dist/types/src/hooks/defs.d.ts.map +1 -1
  20. package/dist/types/src/hooks/useEditorView.d.ts +1 -0
  21. package/dist/types/src/hooks/useEditorView.d.ts.map +1 -1
  22. package/dist/types/src/hooks/useTextEditor.d.ts +3 -3
  23. package/dist/types/src/hooks/useTextEditor.d.ts.map +1 -1
  24. package/dist/types/src/hooks/useTextModel.d.ts +12 -4
  25. package/dist/types/src/hooks/useTextModel.d.ts.map +1 -1
  26. package/dist/types/src/index.d.ts +1 -1
  27. package/dist/types/src/index.d.ts.map +1 -1
  28. package/dist/types/src/styles/layout.d.ts +2 -3
  29. package/dist/types/src/styles/layout.d.ts.map +1 -1
  30. package/dist/types/src/themes/default.d.ts.map +1 -1
  31. package/package.json +26 -25
  32. package/src/components/TextEditor/MarkdownEditor.stories.tsx +1 -1
  33. package/src/components/TextEditor/TextEditor.tsx +36 -18
  34. package/src/components/TextEditor/automerge.stories.tsx +15 -16
  35. package/src/components/TextEditor/hooks.stories.tsx +11 -8
  36. package/src/components/Toolbar/Toolbar.stories.tsx +9 -6
  37. package/src/components/Toolbar/Toolbar.tsx +22 -2
  38. package/src/extensions/automerge/automerge.ts +10 -15
  39. package/src/extensions/automerge/cursor.ts +2 -3
  40. package/src/extensions/automerge/defs.ts +15 -2
  41. package/src/extensions/markdown/decorate.ts +13 -49
  42. package/src/hooks/defs.ts +2 -0
  43. package/src/hooks/useEditorView.ts +2 -0
  44. package/src/hooks/useTextEditor.ts +8 -12
  45. package/src/hooks/useTextModel.ts +34 -5
  46. package/src/index.ts +1 -7
  47. package/src/styles/layout.ts +2 -5
  48. package/src/themes/default.ts +14 -3
@@ -10,23 +10,6 @@ import { mx } from '@dxos/react-ui-theme';
10
10
 
11
11
  import { getToken } from '../../styles';
12
12
 
13
- /**
14
- * Widget for first/last line of code block (read-only).
15
- */
16
- // TODO(burdon): Add copy-to-clipboard button.
17
- class FencedCodeBoundary extends WidgetType {
18
- constructor(private readonly _className: string) {
19
- super();
20
- }
21
-
22
- override toDOM() {
23
- const el = document.createElement('span');
24
- el.innerHTML = ' ';
25
- el.className = mx('cm-code cm-codeblock', this._className);
26
- return el;
27
- }
28
- }
29
-
30
13
  class HorizontalRuleWidget extends WidgetType {
31
14
  override toDOM() {
32
15
  const el = document.createElement('span');
@@ -91,8 +74,9 @@ class CheckboxWidget extends WidgetType {
91
74
  }
92
75
 
93
76
  const hide = Decoration.replace({});
94
- const fencedCodeTop = Decoration.replace({ widget: new FencedCodeBoundary('cm-codeblock-end cm-codeblock-first') });
95
- const fencedCodeBottom = Decoration.replace({ widget: new FencedCodeBoundary('cm-codeblock-end cm-codeblock-last') });
77
+ const fencedCodeLine = Decoration.line({ class: mx('cm-code cm-codeblock-line') });
78
+ const fencedCodeLineFirst = Decoration.line({ class: mx('cm-code cm-codeblock-line', 'cm-codeblock-first') });
79
+ const fencedCodeLineLast = Decoration.line({ class: mx('cm-code cm-codeblock-line', 'cm-codeblock-last') });
96
80
  const horizontalRule = Decoration.replace({ widget: new HorizontalRuleWidget() });
97
81
  const checkedTask = Decoration.replace({ widget: new CheckboxWidget(true) });
98
82
  const uncheckedTask = Decoration.replace({ widget: new CheckboxWidget(false) });
@@ -130,16 +114,13 @@ const buildDecorations = (view: EditorView, options: DecorateOptions): Decoratio
130
114
  }
131
115
  const first = block.from <= node.from;
132
116
  const last = block.to >= node.to;
117
+ builder.add(
118
+ block.from,
119
+ block.from,
120
+ first ? fencedCodeLineFirst : last ? fencedCodeLineLast : fencedCodeLine,
121
+ );
133
122
  if (!editing && (first || last)) {
134
- builder.add(block.from, block.to, first ? fencedCodeTop : fencedCodeBottom);
135
- } else {
136
- builder.add(
137
- block.from,
138
- block.from,
139
- Decoration.line({
140
- class: mx('cm-code cm-codeblock-line', first && 'cm-codeblock-first', last && 'cm-codeblock-last'),
141
- }),
142
- );
123
+ builder.add(block.from, block.to, hide);
143
124
  }
144
125
  }
145
126
  return false;
@@ -252,18 +233,13 @@ const formattingStyles = EditorView.baseTheme({
252
233
  },
253
234
  },
254
235
  '& .cm-codeblock-first': {
255
- '&::after': {
256
- borderTopLeftRadius: '.5rem',
257
- borderTopRightRadius: '.5rem',
258
- },
236
+ borderTopLeftRadius: '.5rem',
237
+ borderTopRightRadius: '.5rem',
259
238
  },
260
239
  '& .cm-codeblock-last': {
261
- '&::after': {
262
- borderBottomLeftRadius: '.5rem',
263
- borderBottomRightRadius: '.5rem',
264
- },
240
+ borderBottomLeftRadius: '.5rem',
241
+ borderBottomRightRadius: '.5rem',
265
242
  },
266
-
267
243
  '&light .cm-codeblock-line, &light .cm-activeLine.cm-codeblock-line': {
268
244
  background: getToken('extend.semanticColors.input.light'),
269
245
  mixBlendMode: 'darken',
@@ -272,18 +248,6 @@ const formattingStyles = EditorView.baseTheme({
272
248
  background: getToken('extend.semanticColors.input.dark'),
273
249
  mixBlendMode: 'lighten',
274
250
  },
275
- '&light .cm-codeblock-first, &light .cm-codeblock-last': {
276
- mixBlendMode: 'darken',
277
- '&::after': {
278
- background: getToken('extend.semanticColors.input.light'),
279
- },
280
- },
281
- '&dark .cm-codeblock-first, &dark .cm-codeblock-last': {
282
- mixBlendMode: 'lighten',
283
- '&::after': {
284
- background: getToken('extend.semanticColors.input.dark'),
285
- },
286
- },
287
251
  '& .cm-hr': {
288
252
  display: 'inline-block',
289
253
  width: '100%',
package/src/hooks/defs.ts CHANGED
@@ -35,7 +35,9 @@ export type EditorModel = {
35
35
 
36
36
  /**
37
37
  * State field makes the model available to other extensions.
38
+ * @deprecated
38
39
  */
40
+ // TODO(burdon): Remove.
39
41
  export const modelState = StateField.define<EditorModel | undefined>({
40
42
  create: () => undefined,
41
43
  update: (model) => model,
@@ -7,6 +7,7 @@ import { type MutableRefObject, useRef, useState } from 'react';
7
7
 
8
8
  /**
9
9
  * Hook for accessing the editor view.
10
+ * @deprecated
10
11
  *
11
12
  * ```tsx
12
13
  * const Test = () => {
@@ -20,6 +21,7 @@ import { type MutableRefObject, useRef, useState } from 'react';
20
21
  * };
21
22
  * ```
22
23
  */
24
+ // TODO(burdon): Replace with useTextEditor.
23
25
  export const useEditorView = (id: string): [MutableRefObject<EditorView | null>, boolean] => {
24
26
  const editorRef = useRef<EditorView | null>(null);
25
27
  const [prev, setPrev] = useState<[EditorView | null, string]>([null, '']);
@@ -10,13 +10,13 @@ import {
10
10
  type StateEffect,
11
11
  } from '@codemirror/state';
12
12
  import { EditorView } from '@codemirror/view';
13
- import { type RefObject, useEffect, useRef, useState } from 'react';
13
+ import { type DependencyList, type RefObject, useEffect, useRef, useState } from 'react';
14
14
 
15
15
  import { log } from '@dxos/log';
16
16
  import { type ThemeMode } from '@dxos/react-ui';
17
17
  import { isNotFalsy } from '@dxos/util';
18
18
 
19
- import { type ThemeStyles } from '..//styles';
19
+ import { type ThemeStyles } from '../styles';
20
20
  import { defaultTheme } from '../themes';
21
21
  import { logChanges } from '../util';
22
22
 
@@ -26,6 +26,7 @@ export type UseTextEditorOptions = {
26
26
  debug?: boolean;
27
27
  } & EditorStateConfig;
28
28
 
29
+ // TODO(burdon): Return tuple?
29
30
  export type UseTextEditor = {
30
31
  parentRef: RefObject<HTMLDivElement>;
31
32
  view?: EditorView;
@@ -35,14 +36,10 @@ export type UseTextEditor = {
35
36
  * Hook for creating editor.
36
37
  */
37
38
  // TODO(wittjosiah): Does not work in strict mode.
38
- export const useTextEditor = ({
39
- autoFocus,
40
- scrollTo,
41
- debug,
42
- doc,
43
- selection,
44
- extensions,
45
- }: UseTextEditorOptions = {}): UseTextEditor => {
39
+ export const useTextEditor = (
40
+ { autoFocus, scrollTo, debug, doc, selection, extensions }: UseTextEditorOptions = {},
41
+ deps?: DependencyList,
42
+ ): UseTextEditor => {
46
43
  const onUpdate = useRef<() => void>();
47
44
  const [view, setView] = useState<EditorView>();
48
45
  const parentRef = useRef<HTMLDivElement>(null);
@@ -87,8 +84,7 @@ export const useTextEditor = ({
87
84
  return () => {
88
85
  view?.destroy();
89
86
  };
90
- // TODO(wittjosiah): Does `parentRef` ever change? Only `.current` changes?
91
- }, [parentRef, extensions]);
87
+ }, deps);
92
88
 
93
89
  useEffect(() => {
94
90
  if (view) {
@@ -2,6 +2,7 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
+ import { type Extension } from '@codemirror/state';
5
6
  import get from 'lodash.get';
6
7
  import { type Dispatch, type SetStateAction, useState, useMemo } from 'react';
7
8
 
@@ -14,13 +15,42 @@ import { type Identity } from '@dxos/react-client/halo';
14
15
  import { SpaceAwarenessProvider } from './awareness-provider';
15
16
  import { type EditorModel, modelState } from './defs';
16
17
  import { automerge, awareness } from '../extensions';
18
+ import { type DocAccessor } from '../extensions/automerge/defs';
17
19
  import { cursorColor } from '../styles';
18
20
 
19
- // TODO(burdon): Remove space/identity dependency. Define interface for the framework re content and presence.
20
- export type UseTextModelProps = {
21
- identity?: Identity | null;
21
+ export type useTextExtensionsProps = {
22
+ id: string;
23
+ text: DocAccessor;
22
24
  space?: Space;
25
+ identity?: Identity;
26
+ };
27
+
28
+ // TODO(burdon): Factor out automerge defs and extension (not hook).
29
+ export const useTextExtensions = ({ id, text, space, identity }: useTextExtensionsProps): Extension[] => {
30
+ const extensions: Extension[] = [automerge(text)];
31
+
32
+ if (space && identity) {
33
+ const awarenessProvider = new SpaceAwarenessProvider({
34
+ space,
35
+ channel: `awareness.${id}`,
36
+ peerId: identity.identityKey.toHex(),
37
+ info: {
38
+ displayName: identity.profile?.displayName ?? generateName(identity.identityKey.toHex()),
39
+ color: cursorColor.color,
40
+ lightColor: cursorColor.light,
41
+ },
42
+ });
43
+
44
+ extensions.push(awareness(awarenessProvider));
45
+ }
46
+
47
+ return extensions;
48
+ };
49
+
50
+ export type UseTextModelProps = {
23
51
  text?: TextObject;
52
+ space?: Space;
53
+ identity?: Identity | null;
24
54
  };
25
55
 
26
56
  /**
@@ -32,8 +62,7 @@ export const useTextModel = (props: UseTextModelProps): EditorModel | undefined
32
62
 
33
63
  /**
34
64
  * For use primarily in stories & tests so the dependence on TextObject can be avoided.
35
- * @param id
36
- * @param defaultContent
65
+ * @deprecated
37
66
  */
38
67
  export const useInMemoryTextModel = ({
39
68
  id,
package/src/index.ts CHANGED
@@ -12,13 +12,7 @@ export { Doc, YText, YXmlFragment } from '@dxos/text-model';
12
12
  export * from './components';
13
13
  export * from './extensions';
14
14
  export * from './hooks';
15
- export {
16
- getToken,
17
- editorWithToolbarLayout,
18
- editorFillLayoutEditor,
19
- editorFillLayoutRoot,
20
- editorHalfViewportOverscrollContent,
21
- } from './styles';
15
+ export { getToken, editorWithToolbarLayout, editorFillLayoutRoot, editorFillLayoutEditor } from './styles';
22
16
  export * from './themes';
23
17
  export * from './util';
24
18
  export { translations };
@@ -5,8 +5,5 @@
5
5
  export const editorWithToolbarLayout =
6
6
  'grid grid-cols-1 grid-rows-[min-content_1fr] data-[toolbar=disabled]:grid-rows-[1fr] justify-center content-start overflow-hidden';
7
7
 
8
- export const editorFillLayoutRoot = 'bs-full overflow-hidden grid';
9
- export const editorFillLayoutEditor =
10
- 'bs-full overflow-hidden [&>.cm-scroller]:bs-full [&>.cm-scroller]:overflow-y-auto';
11
-
12
- export const editorHalfViewportOverscrollContent = 'after:block after:min-bs-[50dvh] after:pointer-events-none';
8
+ export const editorFillLayoutRoot = 'bs-full relative';
9
+ export const editorFillLayoutEditor = '!absolute inset-0';
@@ -50,14 +50,18 @@ export const defaultTheme: ThemeStyles = {
50
50
  outline: 'none',
51
51
  },
52
52
 
53
+ // NOTE: See https://codemirror.net/docs/guide (DOM Structure).
53
54
  '.cm-scroller': {
54
- // overflow: 'visible',
55
+ // TODO(burdon): Reconcile with docs: https://codemirror.net/docs/guide
56
+ // Inside of that is the scroller element. If the editor has its own scrollbar, this one should be styled with overflow: auto. But it doesn't have to—the editor also supports growing to accomodate its content, or growing up to a certain max-height and then scrolling.
57
+ overflowY: 'auto',
55
58
  fontFamily: get(tokens, 'fontFamily.mono', []).join(','),
56
- lineHeight: 1.4,
59
+ lineHeight: 1.5,
57
60
  },
58
61
 
59
62
  '.cm-content': {
60
- padding: 0,
63
+ // TODO(burdon): Is it possible to remove the padding value from CM's default theme?
64
+ // padding: 'unset',
61
65
  // NOTE: Base font size (otherwise defined by HTML tag, which might be different for storybook).
62
66
  fontSize: '16px',
63
67
  },
@@ -96,6 +100,13 @@ export const defaultTheme: ThemeStyles = {
96
100
  background: 'transparent',
97
101
  },
98
102
 
103
+ //
104
+ // gutter
105
+ //
106
+ '.cm-gutterElement': {
107
+ width: '48px',
108
+ },
109
+
99
110
  //
100
111
  // Selection
101
112
  //