@dxos/react-ui-editor 0.6.13 → 0.6.14-main.1366248

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 (133) hide show
  1. package/dist/lib/browser/index.mjs +772 -712
  2. package/dist/lib/browser/index.mjs.map +4 -4
  3. package/dist/lib/browser/meta.json +1 -1
  4. package/dist/lib/node/index.cjs +5667 -0
  5. package/dist/lib/node/index.cjs.map +7 -0
  6. package/dist/lib/node/meta.json +1 -0
  7. package/dist/lib/node-esm/index.mjs +5650 -0
  8. package/dist/lib/node-esm/index.mjs.map +7 -0
  9. package/dist/lib/node-esm/meta.json +1 -0
  10. package/dist/types/src/InputMode.stories.d.ts +11 -11
  11. package/dist/types/src/InputMode.stories.d.ts.map +1 -1
  12. package/dist/types/src/TextEditor.stories.d.ts +4 -1
  13. package/dist/types/src/TextEditor.stories.d.ts.map +1 -1
  14. package/dist/types/src/components/Toolbar/Toolbar.d.ts.map +1 -1
  15. package/dist/types/src/defaults.d.ts.map +1 -1
  16. package/dist/types/src/extensions/autocomplete.d.ts +2 -1
  17. package/dist/types/src/extensions/autocomplete.d.ts.map +1 -1
  18. package/dist/types/src/extensions/automerge/automerge.test.d.ts.map +1 -1
  19. package/dist/types/src/extensions/automerge/cursor.d.ts +1 -1
  20. package/dist/types/src/extensions/automerge/cursor.d.ts.map +1 -1
  21. package/dist/types/src/extensions/awareness/awareness.d.ts +2 -2
  22. package/dist/types/src/extensions/awareness/awareness.d.ts.map +1 -1
  23. package/dist/types/src/extensions/command/state.d.ts +2 -2
  24. package/dist/types/src/extensions/command/state.d.ts.map +1 -1
  25. package/dist/types/src/extensions/comments.d.ts +1 -1
  26. package/dist/types/src/extensions/comments.d.ts.map +1 -1
  27. package/dist/types/src/extensions/debug.d.ts +2 -2
  28. package/dist/types/src/extensions/debug.d.ts.map +1 -1
  29. package/dist/types/src/extensions/factories.d.ts +1 -0
  30. package/dist/types/src/extensions/factories.d.ts.map +1 -1
  31. package/dist/types/src/extensions/focus.d.ts +7 -0
  32. package/dist/types/src/extensions/focus.d.ts.map +1 -0
  33. package/dist/types/src/extensions/folding.d.ts.map +1 -1
  34. package/dist/types/src/extensions/index.d.ts +2 -4
  35. package/dist/types/src/extensions/index.d.ts.map +1 -1
  36. package/dist/types/src/extensions/listener.d.ts +2 -1
  37. package/dist/types/src/extensions/listener.d.ts.map +1 -1
  38. package/dist/types/src/extensions/markdown/decorate.d.ts.map +1 -1
  39. package/dist/types/src/extensions/markdown/formatting.test.d.ts.map +1 -1
  40. package/dist/types/src/extensions/markdown/highlight.d.ts.map +1 -1
  41. package/dist/types/src/extensions/markdown/image.d.ts +3 -6
  42. package/dist/types/src/extensions/markdown/image.d.ts.map +1 -1
  43. package/dist/types/src/extensions/markdown/link.d.ts +1 -1
  44. package/dist/types/src/extensions/markdown/link.d.ts.map +1 -1
  45. package/dist/types/src/extensions/markdown/styles.d.ts.map +1 -1
  46. package/dist/types/src/extensions/modes.d.ts +3 -4
  47. package/dist/types/src/extensions/modes.d.ts.map +1 -1
  48. package/dist/types/src/extensions/{state.d.ts → selection.d.ts} +8 -4
  49. package/dist/types/src/extensions/selection.d.ts.map +1 -0
  50. package/dist/types/src/hooks/useTextEditor.d.ts.map +1 -1
  51. package/dist/types/src/index.d.ts +1 -0
  52. package/dist/types/src/index.d.ts.map +1 -1
  53. package/dist/types/src/styles/markdown.d.ts +1 -2
  54. package/dist/types/src/styles/markdown.d.ts.map +1 -1
  55. package/dist/types/src/styles/theme.d.ts.map +1 -1
  56. package/dist/types/src/types.d.ts.map +1 -0
  57. package/dist/types/src/{extensions → util}/cursor.d.ts +9 -3
  58. package/dist/types/src/util/cursor.d.ts.map +1 -0
  59. package/dist/types/src/util/debug.d.ts +17 -0
  60. package/dist/types/src/util/debug.d.ts.map +1 -0
  61. package/dist/types/src/util/dom.d.ts.map +1 -0
  62. package/dist/types/src/util/facet.d.ts +3 -0
  63. package/dist/types/src/util/facet.d.ts.map +1 -0
  64. package/dist/types/src/util/index.d.ts +6 -0
  65. package/dist/types/src/util/index.d.ts.map +1 -0
  66. package/dist/types/src/{extensions/util → util}/react.d.ts +1 -1
  67. package/dist/types/src/util/react.d.ts.map +1 -0
  68. package/package.json +46 -41
  69. package/src/InputMode.stories.tsx +8 -8
  70. package/src/TextEditor.stories.tsx +100 -75
  71. package/src/components/Toolbar/Toolbar.tsx +8 -11
  72. package/src/defaults.ts +0 -2
  73. package/src/extensions/annotations.ts +1 -1
  74. package/src/extensions/autocomplete.ts +9 -8
  75. package/src/extensions/automerge/automerge.stories.tsx +2 -2
  76. package/src/extensions/automerge/{automerge.spec.tsx → automerge.test.tsx} +1 -0
  77. package/src/extensions/automerge/automerge.ts +2 -2
  78. package/src/extensions/automerge/cursor.ts +1 -1
  79. package/src/extensions/awareness/awareness.ts +3 -5
  80. package/src/extensions/command/hint.ts +1 -1
  81. package/src/extensions/command/state.ts +3 -4
  82. package/src/extensions/comments.ts +45 -47
  83. package/src/extensions/debug.ts +2 -2
  84. package/src/extensions/factories.ts +5 -1
  85. package/src/extensions/focus.ts +35 -0
  86. package/src/extensions/folding.tsx +7 -5
  87. package/src/extensions/index.ts +2 -4
  88. package/src/extensions/listener.ts +5 -2
  89. package/src/extensions/markdown/changes.test.ts +1 -3
  90. package/src/extensions/markdown/decorate.ts +50 -7
  91. package/src/extensions/markdown/formatting.test.ts +1 -3
  92. package/src/extensions/markdown/highlight.ts +0 -5
  93. package/src/extensions/markdown/image.ts +53 -42
  94. package/src/extensions/markdown/link.ts +3 -2
  95. package/src/extensions/markdown/parser.test.ts +1 -2
  96. package/src/extensions/markdown/styles.ts +10 -0
  97. package/src/extensions/markdown/table.ts +3 -3
  98. package/src/extensions/modes.ts +6 -7
  99. package/src/extensions/{state.ts → selection.ts} +20 -16
  100. package/src/hooks/useTextEditor.ts +36 -35
  101. package/src/index.ts +1 -0
  102. package/src/styles/markdown.ts +1 -3
  103. package/src/styles/theme.ts +3 -1
  104. package/src/{extensions → util}/cursor.ts +11 -8
  105. package/src/{util.ts → util/debug.ts} +25 -2
  106. package/src/util/facet.ts +13 -0
  107. package/src/{extensions/util → util}/index.ts +3 -2
  108. package/src/{extensions/util → util}/react.tsx +6 -1
  109. package/dist/types/src/extensions/automerge/automerge.spec.d.ts +0 -2
  110. package/dist/types/src/extensions/automerge/automerge.spec.d.ts.map +0 -1
  111. package/dist/types/src/extensions/cursor.d.ts.map +0 -1
  112. package/dist/types/src/extensions/doc.d.ts +0 -6
  113. package/dist/types/src/extensions/doc.d.ts.map +0 -1
  114. package/dist/types/src/extensions/state.d.ts.map +0 -1
  115. package/dist/types/src/extensions/types.d.ts.map +0 -1
  116. package/dist/types/src/extensions/util/dom.d.ts.map +0 -1
  117. package/dist/types/src/extensions/util/error.d.ts +0 -2
  118. package/dist/types/src/extensions/util/error.d.ts.map +0 -1
  119. package/dist/types/src/extensions/util/index.d.ts +0 -5
  120. package/dist/types/src/extensions/util/index.d.ts.map +0 -1
  121. package/dist/types/src/extensions/util/overlap.d.ts +0 -8
  122. package/dist/types/src/extensions/util/overlap.d.ts.map +0 -1
  123. package/dist/types/src/extensions/util/react.d.ts.map +0 -1
  124. package/dist/types/src/util.d.ts +0 -7
  125. package/dist/types/src/util.d.ts.map +0 -1
  126. package/src/extensions/automerge/automerge.test.ts +0 -13
  127. package/src/extensions/doc.ts +0 -17
  128. package/src/extensions/util/error.ts +0 -15
  129. package/src/extensions/util/overlap.ts +0 -12
  130. /package/dist/types/src/{extensions/types.d.ts → types.d.ts} +0 -0
  131. /package/dist/types/src/{extensions/util → util}/dom.d.ts +0 -0
  132. /package/src/{extensions/types.ts → types.ts} +0 -0
  133. /package/src/{extensions/util → util}/dom.ts +0 -0
@@ -25,9 +25,9 @@ export type TableOptions = {};
25
25
  * https://github.github.com/gfm/#tables-extension
26
26
  */
27
27
  export const table = (options: TableOptions = {}): Extension => {
28
- return StateField.define<RangeSet<any>>({
28
+ return StateField.define<RangeSet<Decoration>>({
29
29
  create: (state) => update(state, options),
30
- update: (_: RangeSet<any>, tr: Transaction) => update(tr.state, options),
30
+ update: (_: RangeSet<Decoration>, tr: Transaction) => update(tr.state, options),
31
31
  provide: (field) => EditorView.decorations.from(field),
32
32
  });
33
33
  };
@@ -40,7 +40,7 @@ type Table = {
40
40
  };
41
41
 
42
42
  const update = (state: EditorState, _options: TableOptions) => {
43
- const builder = new RangeSetBuilder();
43
+ const builder = new RangeSetBuilder<Decoration>();
44
44
  const cursor = state.selection.main.head;
45
45
 
46
46
  const tables: Table[] = [];
@@ -2,26 +2,25 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- import { type Extension, Facet } from '@codemirror/state';
5
+ 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
9
 
10
- export const focusEvent = 'focus.container';
10
+ import { singleValueFacet } from '../util';
11
11
 
12
12
  export const EditorViewModes = ['preview', 'readonly', 'source'] as const;
13
13
  export type EditorViewMode = (typeof EditorViewModes)[number];
14
+
14
15
  export const EditorInputModes = ['default', 'vim', 'vscode'] as const;
15
16
  export type EditorInputMode = (typeof EditorInputModes)[number];
16
17
 
17
18
  export type EditorInputConfig = {
18
- type: string;
19
+ type?: string;
19
20
  noTabster?: boolean;
20
21
  };
21
22
 
22
- export const editorInputMode = Facet.define<EditorInputConfig, EditorInputConfig>({
23
- combine: (modes) => modes[0] ?? {},
24
- });
23
+ export const editorInputMode = singleValueFacet<EditorInputConfig>({});
25
24
 
26
25
  export const InputModeExtensions: { [mode: string]: Extension } = {
27
26
  default: [],
@@ -39,7 +38,7 @@ export const InputModeExtensions: { [mode: string]: Extension } = {
39
38
  key: 'Alt-Escape',
40
39
  run: (view) => {
41
40
  // Focus container for tab navigation.
42
- view.dispatch({ userEvent: focusEvent });
41
+ view.dom.parentElement?.focus();
43
42
  return true;
44
43
  },
45
44
  },
@@ -9,9 +9,12 @@ import { debounce } from '@dxos/async';
9
9
  import { invariant } from '@dxos/invariant';
10
10
  import { isNotFalsy } from '@dxos/util';
11
11
 
12
- import { documentId } from './doc';
12
+ import { singleValueFacet } from '../util';
13
13
 
14
- const stateRestoreAnnotation = 'dxos.org/cm/state-restore';
14
+ /**
15
+ * Currently edited document id as FQ string.
16
+ */
17
+ export const documentId = singleValueFacet<string>();
15
18
 
16
19
  export type EditorSelection = {
17
20
  anchor: number;
@@ -23,13 +26,23 @@ export type EditorSelectionState = {
23
26
  selection?: EditorSelection;
24
27
  };
25
28
 
26
- export type EditorStateOptions = {
29
+ export type EditorStateStore = {
27
30
  setState: (id: string, state: EditorSelectionState) => void;
28
31
  getState: (id: string) => EditorSelectionState | undefined;
29
32
  };
30
33
 
31
- const keyPrefix = 'dxos.org/react-ui-editor/state';
32
- export const localStorageStateStoreAdapter: EditorStateOptions = {
34
+ const stateRestoreAnnotation = 'dxos.org/cm/state-restore';
35
+
36
+ export const createEditorStateTransaction = ({ scrollTo, selection }: EditorSelectionState): TransactionSpec => {
37
+ return {
38
+ selection,
39
+ scrollIntoView: !scrollTo,
40
+ effects: scrollTo ? EditorView.scrollIntoView(scrollTo, { yMargin: 96 }) : undefined,
41
+ annotations: Transaction.userEvent.of(stateRestoreAnnotation),
42
+ };
43
+ };
44
+
45
+ export const createEditorStateStore = (keyPrefix: string): EditorStateStore => ({
33
46
  getState: (id) => {
34
47
  invariant(id);
35
48
  const state = localStorage.getItem(`${keyPrefix}/${id}`);
@@ -40,21 +53,12 @@ export const localStorageStateStoreAdapter: EditorStateOptions = {
40
53
  invariant(id);
41
54
  localStorage.setItem(`${keyPrefix}/${id}`, JSON.stringify(state));
42
55
  },
43
- };
44
-
45
- export const createEditorStateTransaction = ({ scrollTo, selection }: EditorSelectionState): TransactionSpec => {
46
- return {
47
- selection,
48
- scrollIntoView: !scrollTo,
49
- effects: scrollTo ? EditorView.scrollIntoView(scrollTo, { yMargin: 96 }) : undefined,
50
- annotations: Transaction.userEvent.of(stateRestoreAnnotation),
51
- };
52
- };
56
+ });
53
57
 
54
58
  /**
55
59
  * Track scrolling and selection state to be restored when switching to document.
56
60
  */
57
- export const state = ({ getState, setState }: Partial<EditorStateOptions> = {}): Extension => {
61
+ export const selectionState = ({ getState, setState }: Partial<EditorStateStore> = {}): Extension => {
58
62
  const setStateDebounced = debounce(setState!, 1_000);
59
63
 
60
64
  return [
@@ -19,10 +19,11 @@ import {
19
19
  import { log } from '@dxos/log';
20
20
  import { getProviderValue, isNotFalsy, type MaybeProvider } from '@dxos/util';
21
21
 
22
- import { createEditorStateTransaction, documentId, editorInputMode, type EditorSelection } from '../extensions';
23
- import { logChanges } from '../util';
22
+ import { editorInputMode, type EditorSelection, documentId, createEditorStateTransaction } from '../extensions';
23
+ import { debugDispatcher } from '../util';
24
24
 
25
25
  export type UseTextEditor = {
26
+ // TODO(burdon): Rename.
26
27
  parentRef: RefObject<HTMLDivElement>;
27
28
  view?: EditorView;
28
29
  focusAttributes: ReturnType<typeof useFocusableGroup> & {
@@ -65,8 +66,6 @@ export const useTextEditor = (
65
66
 
66
67
  // NOTE: Increments by 2 in strict mode.
67
68
  const [instanceId] = useState(() => `text-editor-${++instanceCount}`);
68
- // Callback once view is created.
69
- const onUpdate = useRef<() => void>();
70
69
  const [view, setView] = useState<EditorView>();
71
70
  const parentRef = useRef<HTMLDivElement>(null);
72
71
 
@@ -87,44 +86,45 @@ export const useTextEditor = (
87
86
  }
88
87
 
89
88
  // https://codemirror.net/docs/ref/#state.EditorStateConfig
90
- // NOTE: Don't set selection here in case it is invalid (and crashes the state); dispatch below.
91
89
  const state = EditorState.create({
92
90
  doc: initialValue,
93
- selection: initialSelection,
91
+ // selection: initialSelection,
94
92
  extensions: [
95
93
  id && documentId.of(id),
96
- // NOTE: Doesn't catch errors in keymap functions.
94
+ extensions,
95
+ // NOTE: This doesn't catch errors in keymap functions.
97
96
  EditorView.exceptionSink.of((err) => {
98
97
  log.catch(err);
99
98
  }),
100
- extensions,
101
- EditorView.updateListener.of(() => {
102
- setTimeout(() => {
103
- onUpdate.current?.();
104
- });
105
- }),
99
+ // TODO(burdon): Factor out debug inspector.
100
+ // ViewPlugin.fromClass(
101
+ // class {
102
+ // constructor(_view: EditorView) {
103
+ // log('construct', { id });
104
+ // }
105
+ //
106
+ // destroy() {
107
+ // log('destroy', { id });
108
+ // }
109
+ // },
110
+ // ),
106
111
  ].filter(isNotFalsy),
107
112
  });
108
113
 
109
114
  // https://codemirror.net/docs/ref/#view.EditorViewConfig
110
115
  view = new EditorView({
111
116
  parent: parentRef.current,
112
- selection: initialSelection,
113
117
  state,
114
- // NOTE: Uncomment to debug/monitor all transactions.
115
- // https://codemirror.net/docs/ref/#view.EditorView.dispatch
116
- dispatchTransactions: (trs, view) => {
117
- if (debug) {
118
- logChanges(trs);
119
- }
120
- view.update(trs);
121
- },
118
+ scrollTo: scrollTo ? EditorView.scrollIntoView(scrollTo, { yMargin: 96 }) : undefined, // TODO(burdon): Const.
119
+ dispatchTransactions: debug ? debugDispatcher : undefined,
122
120
  });
123
121
 
124
- // Move to end of line after document loaded.
125
- if (!initialValue && moveToEndOfLine) {
122
+ // Move to end of line after document loaded (unless selection is specified).
123
+ if (moveToEndOfLine && !initialSelection) {
126
124
  const { to } = view.state.doc.lineAt(0);
127
- view.dispatch({ selection: { anchor: to } });
125
+ if (to) {
126
+ view.dispatch({ selection: { anchor: to } });
127
+ }
128
128
  }
129
129
 
130
130
  setView(view);
@@ -138,18 +138,16 @@ export const useTextEditor = (
138
138
 
139
139
  useEffect(() => {
140
140
  if (view) {
141
- // NOTE: Set selection after first update (since content may rerender on focus).
142
- onUpdate.current = () => {
143
- onUpdate.current = undefined;
144
- view.dispatch(createEditorStateTransaction({ scrollTo, selection }));
145
- };
141
+ if (scrollTo || selection) {
142
+ if (selection && selection.anchor > view.state.doc.length) {
143
+ log.warn('invalid selection', { length: view.state.doc.length, scrollTo, selection });
144
+ return;
145
+ }
146
146
 
147
- // Remove tabster attribute (rely on custom keymap).
148
- if (view.state.facet(editorInputMode).noTabster) {
149
- parentRef.current?.removeAttribute('data-tabster');
147
+ view.dispatch(createEditorStateTransaction({ scrollTo, selection }));
150
148
  }
151
149
  }
152
- }, [view, selection, scrollTo]);
150
+ }, [view, scrollTo, selection]);
153
151
 
154
152
  useEffect(() => {
155
153
  if (view && autoFocus) {
@@ -157,7 +155,10 @@ export const useTextEditor = (
157
155
  }
158
156
  }, [autoFocus, view]);
159
157
 
160
- const focusableGroup = useFocusableGroup({ tabBehavior: 'limited' });
158
+ const focusableGroup = useFocusableGroup({
159
+ tabBehavior: 'limited',
160
+ ignoreDefaultKeydown: { Escape: view?.state.facet(editorInputMode).noTabster },
161
+ });
161
162
 
162
163
  // Focus editor on Enter (e.g., when tabbing to this component).
163
164
  const handleKeyUp = useCallback<KeyboardEventHandler<HTMLDivElement>>(
package/src/index.ts CHANGED
@@ -14,6 +14,7 @@ export * from './components';
14
14
  export * from './defaults';
15
15
  export * from './extensions';
16
16
  export * from './hooks';
17
+ export * from './types';
17
18
  export * from './util';
18
19
 
19
20
  export { translations };
@@ -17,11 +17,9 @@ const headings: Record<HeadingLevel, string> = {
17
17
  };
18
18
 
19
19
  export const theme = {
20
- mark: 'opacity-50',
21
20
  code: 'font-mono !no-underline text-neutral-700 dark:text-neutral-300',
22
21
  codeMark: 'font-mono text-primary-500',
23
- // TODO(burdon): Replace with widget.
24
- blockquote: 'pl-1 mr-1 border-is-4 border-orange-500 dark:border-orange-500 dark:text-neutral-500',
22
+ mark: 'opacity-50',
25
23
  heading: (level: HeadingLevel) => {
26
24
  return mx(headings[level], 'dark:text-primary-400');
27
25
  },
@@ -71,9 +71,11 @@ export const defaultTheme: ThemeStyles = {
71
71
  /**
72
72
  * Gutters
73
73
  * NOTE: Gutters should have the same top margin as the content.
74
+ * NOTE: They can't be transparent since the content needs to scroll below.
74
75
  */
75
76
  '.cm-gutters': {
76
77
  background: 'var(--surface-bg)',
78
+ borderRight: 'none',
77
79
  },
78
80
  '.cm-gutter': {},
79
81
  '.cm-gutter.cm-lineNumbers .cm-gutterElement': {
@@ -95,7 +97,7 @@ export const defaultTheme: ThemeStyles = {
95
97
  paddingInline: 0,
96
98
  },
97
99
  '.cm-activeLine': {
98
- background: 'var(--dx-hoverSurface)',
100
+ background: 'var(--dx-cmActiveLine)',
99
101
  },
100
102
 
101
103
  /**
@@ -2,9 +2,17 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- import { type EditorState, Facet } from '@codemirror/state';
5
+ import { type EditorState } from '@codemirror/state';
6
6
 
7
- import { type Range } from './types';
7
+ import { singleValueFacet } from './facet';
8
+ import { type Range } from '../types';
9
+
10
+ /**
11
+ * Determines if two ranges overlap.
12
+ * A range is considered to overlap if there is any intersection
13
+ * between the two ranges, inclusive of their boundaries.
14
+ */
15
+ export const overlap = (a: Range, b: Range): boolean => a.from <= b.to && a.to >= b.from;
8
16
 
9
17
  /**
10
18
  * Converts indexes into the text document into stable peer-independent cursors.
@@ -27,15 +35,10 @@ const defaultCursorConverter: CursorConverter = {
27
35
  };
28
36
 
29
37
  export class Cursor {
30
- static readonly converter = Facet.define<CursorConverter, CursorConverter>({
31
- combine: (providers) => {
32
- return providers[0] ?? defaultCursorConverter;
33
- },
34
- });
38
+ static readonly converter = singleValueFacet(defaultCursorConverter);
35
39
 
36
40
  static readonly getCursorFromRange = (state: EditorState, range: Range) => {
37
41
  const cursorConverter = state.facet(Cursor.converter);
38
-
39
42
  const from = cursorConverter.toCursor(range.from);
40
43
  const to = cursorConverter.toCursor(range.to, -1);
41
44
  return [from, to].join(':');
@@ -2,14 +2,25 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- import type { Transaction } from '@codemirror/state';
5
+ import { type Transaction } from '@codemirror/state';
6
+ import { type EditorView } from '@codemirror/view';
6
7
 
7
8
  import { log } from '@dxos/log';
8
9
 
10
+ export const wrapWithCatch = (fn: (...args: any[]) => any) => {
11
+ return (...args: any[]) => {
12
+ try {
13
+ return fn(...args);
14
+ } catch (err) {
15
+ log.catch(err);
16
+ }
17
+ };
18
+ };
19
+
9
20
  /**
10
21
  * CodeMirror callbacks swallow errors so wrap handlers.
11
22
  */
12
- // TODO(burdon): EditorView.exceptionSink should catch errors.
23
+ // TODO(burdon): Reconcile with wrapWithCatch.
13
24
  export const callbackWrapper = <T extends Function>(fn: T): T =>
14
25
  ((...args: any[]) => {
15
26
  try {
@@ -19,6 +30,18 @@ export const callbackWrapper = <T extends Function>(fn: T): T =>
19
30
  }
20
31
  }) as unknown as T;
21
32
 
33
+ /**
34
+ * Log all changes before dispatching them to the view.
35
+ * https://codemirror.net/docs/ref/#view.EditorView.dispatch
36
+ */
37
+ export const debugDispatcher = (trs: readonly Transaction[], view: EditorView) => {
38
+ logChanges(trs);
39
+ view.update(trs);
40
+ };
41
+
42
+ /**
43
+ * Util to log transactions in update listener.
44
+ */
22
45
  export const logChanges = (trs: readonly Transaction[]) => {
23
46
  const changes = trs
24
47
  .flatMap((tr) => {
@@ -0,0 +1,13 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import { Facet } from '@codemirror/state';
6
+
7
+ export const singleValueFacet = <I, O = I>(defaultValue?: O) =>
8
+ Facet.define<I, O>({
9
+ // Called immediately.
10
+ combine: (providers) => {
11
+ return (providers[0] ?? defaultValue) as O;
12
+ },
13
+ });
@@ -2,7 +2,8 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
+ export * from './cursor';
6
+ export * from './debug';
5
7
  export * from './dom';
6
- export * from './error';
7
- export * from './overlap';
8
+ export * from './facet';
8
9
  export * from './react';
@@ -8,6 +8,8 @@ import { createRoot } from 'react-dom/client';
8
8
  import { ThemeProvider } from '@dxos/react-ui';
9
9
  import { defaultTx } from '@dxos/react-ui-theme';
10
10
 
11
+ // TODO(burdon): Factor out.
12
+
11
13
  export type ElementOptions = {
12
14
  className?: string;
13
15
  };
@@ -20,10 +22,13 @@ export const createElement = (tag: string, options?: ElementOptions, children?:
20
22
  if (children) {
21
23
  el.append(...(Array.isArray(children) ? children : [children]));
22
24
  }
25
+
23
26
  return el;
24
27
  };
25
28
 
26
- export const renderRoot = (root: HTMLElement, node: ReactNode): HTMLElement => {
29
+ // TODO(burdon): Remove react rendering; use DOM directly.
30
+ // NOTE: CM seems to remove/detach/overwrite portals that are attached to the DOM it control.s
31
+ export const renderRoot = <T extends Element>(root: T, node: ReactNode): T => {
27
32
  createRoot(root).render(<ThemeProvider tx={defaultTx}>{node}</ThemeProvider>);
28
33
  return root;
29
34
  };
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=automerge.spec.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"automerge.spec.d.ts","sourceRoot":"","sources":["../../../../../src/extensions/automerge/automerge.spec.tsx"],"names":[],"mappings":""}
@@ -1 +0,0 @@
1
- {"version":3,"file":"cursor.d.ts","sourceRoot":"","sources":["../../../../src/extensions/cursor.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,KAAK,WAAW,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAE5D,OAAO,EAAE,KAAK,KAAK,EAAE,MAAM,SAAS,CAAC;AAErC;;;;;;;;;GASG;AACH,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,GAAG,SAAS,GAAG,MAAM,CAAC;IAC/D,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC;CACpC;AAOD,qBAAa,MAAM;IACjB,MAAM,CAAC,QAAQ,CAAC,SAAS,0CAItB;IAEH,MAAM,CAAC,QAAQ,CAAC,kBAAkB,UAAW,WAAW,SAAS,KAAK,YAMpE;IAEF,MAAM,CAAC,QAAQ,CAAC,kBAAkB,UAAW,WAAW,UAAU,MAAM;;;kBAOtE;CACH"}
@@ -1,6 +0,0 @@
1
- import { Facet } from '@codemirror/state';
2
- /**
3
- * Currently edited document id as FQ string.
4
- */
5
- export declare const documentId: Facet<string, string>;
6
- //# sourceMappingURL=doc.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"doc.d.ts","sourceRoot":"","sources":["../../../../src/extensions/doc.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAI1C;;GAEG;AACH,eAAO,MAAM,UAAU,uBAKrB,CAAC"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"state.d.ts","sourceRoot":"","sources":["../../../../src/extensions/state.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,KAAK,SAAS,EAAe,KAAK,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAWtF,MAAM,MAAM,eAAe,GAAG;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG;IACjC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,eAAe,CAAC;CAC7B,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,QAAQ,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,oBAAoB,KAAK,IAAI,CAAC;IAC5D,QAAQ,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,oBAAoB,GAAG,SAAS,CAAC;CAC5D,CAAC;AAGF,eAAO,MAAM,6BAA6B,EAAE,kBAW3C,CAAC;AAEF,eAAO,MAAM,4BAA4B,4BAA6B,oBAAoB,KAAG,eAO5F,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,KAAK,4BAA4B,OAAO,CAAC,kBAAkB,CAAC,KAAQ,SAuChF,CAAC"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../src/extensions/types.ts"],"names":[],"mappings":"AAKA,MAAM,MAAM,KAAK,GAAG;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;CACZ,CAAC;AAIF,MAAM,MAAM,OAAO,GAAG;IACpB,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"dom.d.ts","sourceRoot":"","sources":["../../../../../src/extensions/util/dom.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,IAAI;IACnB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED,eAAO,MAAM,WAAW,SAAU,IAAI,QAAQ,OAAO;;;;;CAGpD,CAAC;AAIF,eAAO,MAAM,SAAS,SAAU,IAAI,QAAQ,MAAM,uBAKjD,CAAC;AAEF,eAAO,MAAM,cAAc,QAAS,IAAI,gBAQvC,CAAC"}
@@ -1,2 +0,0 @@
1
- export declare const wrapWithCatch: (fn: (...args: any[]) => any) => (...args: any[]) => any;
2
- //# sourceMappingURL=error.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"error.d.ts","sourceRoot":"","sources":["../../../../../src/extensions/util/error.ts"],"names":[],"mappings":"AAMA,eAAO,MAAM,aAAa,OAAQ,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,GAAG,eACtC,GAAG,EAAE,QAOvB,CAAC"}
@@ -1,5 +0,0 @@
1
- export * from './dom';
2
- export * from './error';
3
- export * from './overlap';
4
- export * from './react';
5
- //# sourceMappingURL=index.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/extensions/util/index.ts"],"names":[],"mappings":"AAIA,cAAc,OAAO,CAAC;AACtB,cAAc,SAAS,CAAC;AACxB,cAAc,WAAW,CAAC;AAC1B,cAAc,SAAS,CAAC"}
@@ -1,8 +0,0 @@
1
- import { type Range } from '../types';
2
- /**
3
- * Determines if two ranges overlap.
4
- * A range is considered to overlap if there is any intersection
5
- * between the two ranges, inclusive of their boundaries.
6
- */
7
- export declare const overlap: (a: Range, b: Range) => boolean;
8
- //# sourceMappingURL=overlap.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"overlap.d.ts","sourceRoot":"","sources":["../../../../../src/extensions/util/overlap.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,KAAK,KAAK,EAAE,MAAM,UAAU,CAAC;AAEtC;;;;GAIG;AACH,eAAO,MAAM,OAAO,MAAO,KAAK,KAAK,KAAK,KAAG,OAA2C,CAAC"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"react.d.ts","sourceRoot":"","sources":["../../../../../src/extensions/util/react.tsx"],"names":[],"mappings":"AAIA,OAAc,EAAE,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAM9C,MAAM,MAAM,cAAc,GAAG;IAC3B,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,eAAO,MAAM,aAAa,QAAS,MAAM,YAAY,cAAc,aAAa,SAAS,KAAG,WAS3F,CAAC;AAEF,eAAO,MAAM,UAAU,SAAU,WAAW,QAAQ,SAAS,KAAG,WAG/D,CAAC"}
@@ -1,7 +0,0 @@
1
- import type { Transaction } from '@codemirror/state';
2
- /**
3
- * CodeMirror callbacks swallow errors so wrap handlers.
4
- */
5
- export declare const callbackWrapper: <T extends Function>(fn: T) => T;
6
- export declare const logChanges: (trs: readonly Transaction[]) => void;
7
- //# sourceMappingURL=util.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"util.d.ts","sourceRoot":"","sources":["../../../src/util.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAIrD;;GAEG;AAEH,eAAO,MAAM,eAAe,GAAI,CAAC,SAAS,QAAQ,MAAM,CAAC,KAAG,CAOxC,CAAC;AAErB,eAAO,MAAM,UAAU,QAAS,SAAS,WAAW,EAAE,SAmBrD,CAAC"}
@@ -1,13 +0,0 @@
1
- //
2
- // Copyright 2024 DXOS.org
3
- //
4
-
5
- import { expect } from 'chai';
6
-
7
- import { describe, test } from '@dxos/test';
8
-
9
- describe('Test', () => {
10
- test('checks sanity', async () => {
11
- expect(true).to.be.true;
12
- });
13
- });
@@ -1,17 +0,0 @@
1
- //
2
- // Copyright 2024 DXOS.org
3
- //
4
-
5
- import { Facet } from '@codemirror/state';
6
-
7
- import { invariant } from '@dxos/invariant';
8
-
9
- /**
10
- * Currently edited document id as FQ string.
11
- */
12
- export const documentId = Facet.define<string, string>({
13
- combine: (providers) => {
14
- invariant(providers.length <= 1);
15
- return providers[0];
16
- },
17
- });
@@ -1,15 +0,0 @@
1
- //
2
- // Copyright 2024 DXOS.org
3
- //
4
-
5
- import { log } from '@dxos/log';
6
-
7
- export const wrapWithCatch = (fn: (...args: any[]) => any) => {
8
- return (...args: any[]) => {
9
- try {
10
- return fn(...args);
11
- } catch (err) {
12
- log.catch(err);
13
- }
14
- };
15
- };
@@ -1,12 +0,0 @@
1
- //
2
- // Copyright 2024 DXOS.org
3
- //
4
-
5
- import { type Range } from '../types';
6
-
7
- /**
8
- * Determines if two ranges overlap.
9
- * A range is considered to overlap if there is any intersection
10
- * between the two ranges, inclusive of their boundaries.
11
- */
12
- export const overlap = (a: Range, b: Range): boolean => a.from <= b.to && a.to >= b.from;
File without changes
File without changes