@dxos/react-ui-editor 0.6.14-main.2b6a0f3 → 0.6.14-main.69511f5

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 (101) hide show
  1. package/dist/lib/browser/index.mjs +650 -479
  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 +726 -545
  5. package/dist/lib/node/index.cjs.map +4 -4
  6. package/dist/lib/node/meta.json +1 -1
  7. package/dist/lib/node-esm/index.mjs +650 -478
  8. package/dist/lib/node-esm/index.mjs.map +4 -4
  9. package/dist/lib/node-esm/meta.json +1 -1
  10. package/dist/types/src/TextEditor.stories.d.ts.map +1 -1
  11. package/dist/types/src/extensions/automerge/cursor.d.ts +1 -1
  12. package/dist/types/src/extensions/automerge/cursor.d.ts.map +1 -1
  13. package/dist/types/src/extensions/comments.d.ts +1 -1
  14. package/dist/types/src/extensions/comments.d.ts.map +1 -1
  15. package/dist/types/src/extensions/factories.d.ts +1 -0
  16. package/dist/types/src/extensions/factories.d.ts.map +1 -1
  17. package/dist/types/src/extensions/focus.d.ts +7 -0
  18. package/dist/types/src/extensions/focus.d.ts.map +1 -0
  19. package/dist/types/src/extensions/index.d.ts +2 -0
  20. package/dist/types/src/extensions/index.d.ts.map +1 -1
  21. package/dist/types/src/extensions/markdown/image.d.ts +3 -6
  22. package/dist/types/src/extensions/markdown/image.d.ts.map +1 -1
  23. package/dist/types/src/extensions/selection.d.ts +24 -0
  24. package/dist/types/src/extensions/selection.d.ts.map +1 -0
  25. package/dist/types/src/hooks/useTextEditor.d.ts +1 -1
  26. package/dist/types/src/hooks/useTextEditor.d.ts.map +1 -1
  27. package/dist/types/src/index.d.ts +1 -1
  28. package/dist/types/src/types.d.ts.map +1 -0
  29. package/dist/types/src/{state → util}/cursor.d.ts +7 -1
  30. package/dist/types/src/util/cursor.d.ts.map +1 -0
  31. package/dist/types/src/{util.d.ts → util/debug.d.ts} +6 -2
  32. package/dist/types/src/util/debug.d.ts.map +1 -0
  33. package/dist/types/src/util/dom.d.ts.map +1 -0
  34. package/dist/types/src/{state/util.d.ts → util/facet.d.ts} +1 -1
  35. package/dist/types/src/util/facet.d.ts.map +1 -0
  36. package/dist/types/src/util/index.d.ts +6 -0
  37. package/dist/types/src/util/index.d.ts.map +1 -0
  38. package/dist/types/src/util/react.d.ts.map +1 -0
  39. package/package.json +28 -43
  40. package/src/TextEditor.stories.tsx +8 -6
  41. package/src/extensions/annotations.ts +1 -1
  42. package/src/extensions/automerge/automerge.ts +1 -1
  43. package/src/extensions/automerge/cursor.ts +1 -1
  44. package/src/extensions/awareness/awareness.ts +1 -1
  45. package/src/extensions/command/hint.ts +1 -1
  46. package/src/extensions/command/state.ts +1 -1
  47. package/src/extensions/comments.ts +3 -4
  48. package/src/extensions/factories.ts +5 -1
  49. package/src/extensions/focus.ts +35 -0
  50. package/src/extensions/folding.tsx +1 -1
  51. package/src/extensions/index.ts +2 -0
  52. package/src/extensions/markdown/decorate.ts +1 -1
  53. package/src/extensions/markdown/image.ts +53 -42
  54. package/src/extensions/modes.ts +1 -1
  55. package/src/{state/state.ts → extensions/selection.ts} +22 -22
  56. package/src/hooks/useTextEditor.ts +2 -3
  57. package/src/index.ts +1 -1
  58. package/src/{state → util}/cursor.ts +9 -3
  59. package/src/{util.ts → util/debug.ts} +15 -2
  60. package/src/{extensions/util → util}/index.ts +3 -2
  61. package/dist/lib/browser/chunk-CIQSMP7K.mjs +0 -148
  62. package/dist/lib/browser/chunk-CIQSMP7K.mjs.map +0 -7
  63. package/dist/lib/browser/state/index.mjs +0 -17
  64. package/dist/lib/browser/state/index.mjs.map +0 -7
  65. package/dist/lib/node/chunk-GZWIENFM.cjs +0 -169
  66. package/dist/lib/node/chunk-GZWIENFM.cjs.map +0 -7
  67. package/dist/lib/node/state/index.cjs +0 -39
  68. package/dist/lib/node/state/index.cjs.map +0 -7
  69. package/dist/lib/node-esm/chunk-GP5RCZ3X.mjs +0 -150
  70. package/dist/lib/node-esm/chunk-GP5RCZ3X.mjs.map +0 -7
  71. package/dist/lib/node-esm/state/index.mjs +0 -18
  72. package/dist/lib/node-esm/state/index.mjs.map +0 -7
  73. package/dist/types/src/extensions/util/dom.d.ts.map +0 -1
  74. package/dist/types/src/extensions/util/error.d.ts +0 -2
  75. package/dist/types/src/extensions/util/error.d.ts.map +0 -1
  76. package/dist/types/src/extensions/util/index.d.ts +0 -5
  77. package/dist/types/src/extensions/util/index.d.ts.map +0 -1
  78. package/dist/types/src/extensions/util/overlap.d.ts +0 -8
  79. package/dist/types/src/extensions/util/overlap.d.ts.map +0 -1
  80. package/dist/types/src/extensions/util/react.d.ts.map +0 -1
  81. package/dist/types/src/state/cursor.d.ts.map +0 -1
  82. package/dist/types/src/state/doc.d.ts +0 -5
  83. package/dist/types/src/state/doc.d.ts.map +0 -1
  84. package/dist/types/src/state/index.d.ts +0 -6
  85. package/dist/types/src/state/index.d.ts.map +0 -1
  86. package/dist/types/src/state/state.d.ts +0 -20
  87. package/dist/types/src/state/state.d.ts.map +0 -1
  88. package/dist/types/src/state/types.d.ts.map +0 -1
  89. package/dist/types/src/state/util.d.ts.map +0 -1
  90. package/dist/types/src/util.d.ts.map +0 -1
  91. package/src/extensions/util/error.ts +0 -15
  92. package/src/extensions/util/overlap.ts +0 -12
  93. package/src/state/doc.ts +0 -10
  94. package/src/state/index.ts +0 -11
  95. /package/dist/types/src/{state/types.d.ts → types.d.ts} +0 -0
  96. /package/dist/types/src/{extensions/util → util}/dom.d.ts +0 -0
  97. /package/dist/types/src/{extensions/util → util}/react.d.ts +0 -0
  98. /package/src/{state/types.ts → types.ts} +0 -0
  99. /package/src/{extensions/util → util}/dom.ts +0 -0
  100. /package/src/{state/util.ts → util/facet.ts} +0 -0
  101. /package/src/{extensions/util → util}/react.tsx +0 -0
@@ -0,0 +1,6 @@
1
+ export * from './cursor';
2
+ export * from './debug';
3
+ export * from './dom';
4
+ export * from './facet';
5
+ export * from './react';
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/util/index.ts"],"names":[],"mappings":"AAIA,cAAc,UAAU,CAAC;AACzB,cAAc,SAAS,CAAC;AACxB,cAAc,OAAO,CAAC;AACtB,cAAc,SAAS,CAAC;AACxB,cAAc,SAAS,CAAC"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"react.d.ts","sourceRoot":"","sources":["../../../../src/util/react.tsx"],"names":[],"mappings":"AAIA,OAAc,EAAE,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAQ9C,MAAM,MAAM,cAAc,GAAG;IAC3B,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,eAAO,MAAM,aAAa,QAAS,MAAM,YAAY,cAAc,aAAa,SAAS,KAAG,WAU3F,CAAC;AAIF,eAAO,MAAM,UAAU,GAAI,CAAC,SAAS,OAAO,QAAQ,CAAC,QAAQ,SAAS,KAAG,CAGxE,CAAC"}
package/package.json CHANGED
@@ -1,37 +1,22 @@
1
1
  {
2
2
  "name": "@dxos/react-ui-editor",
3
- "version": "0.6.14-main.2b6a0f3",
3
+ "version": "0.6.14-main.69511f5",
4
4
  "description": "Document editing experience within a DXOS shell.",
5
5
  "homepage": "https://dxos.org",
6
6
  "bugs": "https://github.com/dxos/dxos/issues",
7
7
  "license": "MIT",
8
8
  "author": "DXOS.org",
9
- "sideEffects": false,
9
+ "sideEffects": true,
10
10
  "exports": {
11
11
  ".": {
12
+ "types": "./dist/types/src/index.d.ts",
12
13
  "browser": "./dist/lib/browser/index.mjs",
13
- "node": {
14
- "require": "./dist/lib/node/index.cjs",
15
- "default": "./dist/lib/node-esm/index.mjs"
16
- },
17
- "types": "./dist/types/src/index.d.ts"
18
- },
19
- "./state": {
20
- "browser": "./dist/lib/browser/state/index.mjs",
21
- "node": {
22
- "require": "./dist/lib/node/state/index.cjs",
23
- "default": "./dist/lib/node-esm/state/index.mjs"
24
- },
25
- "types": "./dist/types/src/state/index.d.ts"
14
+ "node": "./dist/lib/node-esm/index.mjs"
26
15
  }
27
16
  },
28
17
  "types": "dist/types/src/index.d.ts",
29
18
  "typesVersions": {
30
- "*": {
31
- "state": [
32
- "dist/types/src/state/index.d.ts"
33
- ]
34
- }
19
+ "*": {}
35
20
  },
36
21
  "files": [
37
22
  "dist",
@@ -65,17 +50,17 @@
65
50
  "lodash.sortby": "^4.7.0",
66
51
  "react-dropzone": "^14.2.3",
67
52
  "style-mod": "^4.1.0",
68
- "@dxos/automerge": "0.6.14-main.2b6a0f3",
69
- "@dxos/context": "0.6.14-main.2b6a0f3",
70
- "@dxos/debug": "0.6.14-main.2b6a0f3",
71
- "@dxos/display-name": "0.6.14-main.2b6a0f3",
72
- "@dxos/async": "0.6.14-main.2b6a0f3",
73
- "@dxos/echo-schema": "0.6.14-main.2b6a0f3",
74
- "@dxos/invariant": "0.6.14-main.2b6a0f3",
75
- "@dxos/log": "0.6.14-main.2b6a0f3",
76
- "@dxos/protocols": "0.6.14-main.2b6a0f3",
77
- "@dxos/react-hooks": "0.6.14-main.2b6a0f3",
78
- "@dxos/util": "0.6.14-main.2b6a0f3"
53
+ "@dxos/async": "0.6.14-main.69511f5",
54
+ "@dxos/automerge": "0.6.14-main.69511f5",
55
+ "@dxos/context": "0.6.14-main.69511f5",
56
+ "@dxos/debug": "0.6.14-main.69511f5",
57
+ "@dxos/display-name": "0.6.14-main.69511f5",
58
+ "@dxos/invariant": "0.6.14-main.69511f5",
59
+ "@dxos/echo-schema": "0.6.14-main.69511f5",
60
+ "@dxos/protocols": "0.6.14-main.69511f5",
61
+ "@dxos/log": "0.6.14-main.69511f5",
62
+ "@dxos/react-hooks": "0.6.14-main.69511f5",
63
+ "@dxos/util": "0.6.14-main.69511f5"
79
64
  },
80
65
  "devDependencies": {
81
66
  "@phosphor-icons/react": "^2.1.5",
@@ -100,23 +85,23 @@
100
85
  "vite": "5.4.7",
101
86
  "vite-plugin-top-level-await": "^1.4.1",
102
87
  "vite-plugin-wasm": "^3.3.0",
103
- "@dxos/automerge": "0.6.14-main.2b6a0f3",
104
- "@dxos/echo-signals": "0.6.14-main.2b6a0f3",
105
- "@dxos/config": "0.6.14-main.2b6a0f3",
106
- "@dxos/keyboard": "0.6.14-main.2b6a0f3",
107
- "@dxos/random": "0.6.14-main.2b6a0f3",
108
- "@dxos/react-ui": "0.6.14-main.2b6a0f3",
109
- "@dxos/react-client": "0.6.14-main.2b6a0f3",
110
- "@dxos/react-ui-theme": "0.6.14-main.2b6a0f3",
111
- "@dxos/storybook-utils": "0.6.14-main.2b6a0f3"
88
+ "@dxos/automerge": "0.6.14-main.69511f5",
89
+ "@dxos/config": "0.6.14-main.69511f5",
90
+ "@dxos/echo-signals": "0.6.14-main.69511f5",
91
+ "@dxos/keyboard": "0.6.14-main.69511f5",
92
+ "@dxos/random": "0.6.14-main.69511f5",
93
+ "@dxos/react-ui": "0.6.14-main.69511f5",
94
+ "@dxos/react-client": "0.6.14-main.69511f5",
95
+ "@dxos/react-ui-theme": "0.6.14-main.69511f5",
96
+ "@dxos/storybook-utils": "0.6.14-main.69511f5"
112
97
  },
113
98
  "peerDependencies": {
114
99
  "@phosphor-icons/react": "^2.1.5",
115
100
  "react": "~18.2.0",
116
101
  "react-dom": "~18.2.0",
117
- "@dxos/react-client": "0.6.14-main.2b6a0f3",
118
- "@dxos/react-ui": "0.6.14-main.2b6a0f3",
119
- "@dxos/react-ui-theme": "0.6.14-main.2b6a0f3"
102
+ "@dxos/react-client": "0.6.14-main.69511f5",
103
+ "@dxos/react-ui": "0.6.14-main.69511f5",
104
+ "@dxos/react-ui-theme": "0.6.14-main.69511f5"
120
105
  },
121
106
  "publishConfig": {
122
107
  "access": "public"
@@ -12,7 +12,7 @@ import { type EditorView } from '@codemirror/view';
12
12
  import { ArrowSquareOut, X } from '@phosphor-icons/react';
13
13
  import { effect, useSignal } from '@preact/signals-react';
14
14
  import defaultsDeep from 'lodash.defaultsdeep';
15
- import React, { type FC, type KeyboardEvent, useEffect, useState } from 'react';
15
+ import React, { useEffect, useState, type FC, type KeyboardEvent } from 'react';
16
16
  import { createRoot } from 'react-dom/client';
17
17
 
18
18
  import { create, Expando } from '@dxos/echo-schema';
@@ -22,12 +22,11 @@ import { log } from '@dxos/log';
22
22
  import { faker } from '@dxos/random';
23
23
  import { createDocAccessor, createObject } from '@dxos/react-client/echo';
24
24
  import { Button, Input, useThemeContext } from '@dxos/react-ui';
25
- import { baseSurface, mx, getSize } from '@dxos/react-ui-theme';
25
+ import { baseSurface, getSize, mx } from '@dxos/react-ui-theme';
26
26
  import { withLayout, withTheme } from '@dxos/storybook-utils';
27
27
 
28
28
  import { editorContent, editorGutter, editorMonospace } from './defaults';
29
29
  import {
30
- InputModeExtensions,
31
30
  annotations,
32
31
  autocomplete,
33
32
  blast,
@@ -45,19 +44,22 @@ import {
45
44
  folding,
46
45
  formattingKeymap,
47
46
  image,
47
+ InputModeExtensions,
48
48
  linkTooltip,
49
49
  listener,
50
50
  mention,
51
+ selectionState,
51
52
  table,
52
53
  typewriter,
53
54
  type CommandAction,
54
55
  type CommentsOptions,
55
56
  type DebugNode,
57
+ type EditorSelectionState,
56
58
  } from './extensions';
57
- import { renderRoot } from './extensions/util';
58
59
  import { useTextEditor, type UseTextEditorProps } from './hooks';
59
- import { type Comment, type EditorSelectionState, state } from './state';
60
60
  import translations from './translations';
61
+ import { type Comment } from './types';
62
+ import { renderRoot } from './util';
61
63
 
62
64
  faker.seed(101);
63
65
 
@@ -416,7 +418,7 @@ export const Scrolling = {
416
418
  render: () => (
417
419
  <DefaultStory
418
420
  text={str('# Large Document', '', longText)}
419
- extensions={state({
421
+ extensions={selectionState({
420
422
  setState: (id, state) => global.set(id, state),
421
423
  getState: (id) => global.get(id),
422
424
  })}
@@ -7,7 +7,7 @@ import { Decoration, EditorView } from '@codemirror/view';
7
7
 
8
8
  import { isNotFalsy } from '@dxos/util';
9
9
 
10
- import { Cursor } from '../state';
10
+ import { Cursor } from '../util';
11
11
 
12
12
  type Annotation = {
13
13
  cursor: string;
@@ -13,7 +13,7 @@ import { type DocAccessor } from '@dxos/react-client/echo';
13
13
  import { cursorConverter } from './cursor';
14
14
  import { updateHeadsEffect, isReconcile, type State } from './defs';
15
15
  import { Syncer } from './sync';
16
- import { Cursor } from '../../state';
16
+ import { Cursor } from '../../util';
17
17
 
18
18
  export const automerge = (accessor: DocAccessor): Extension => {
19
19
  const syncState = StateField.define<State>({
@@ -5,7 +5,7 @@
5
5
  import { log } from '@dxos/log';
6
6
  import { type DocAccessor, fromCursor, toCursor } from '@dxos/react-client/echo';
7
7
 
8
- import { type CursorConverter } from '../../state';
8
+ import { type CursorConverter } from '../../util';
9
9
 
10
10
  export const cursorConverter = (accessor: DocAccessor): CursorConverter => ({
11
11
  toCursor: (pos, assoc) => {
@@ -16,7 +16,7 @@ import {
16
16
  import { Event } from '@dxos/async';
17
17
  import { Context } from '@dxos/context';
18
18
 
19
- import { singleValueFacet, Cursor, type CursorConverter } from '../../state';
19
+ import { singleValueFacet, Cursor, type CursorConverter } from '../../util';
20
20
 
21
21
  export interface AwarenessProvider {
22
22
  remoteStateChange: Event<void>;
@@ -7,7 +7,7 @@ import { Decoration, EditorView, ViewPlugin, type ViewUpdate, WidgetType } from
7
7
 
8
8
  import { type CommandOptions } from './command';
9
9
  import { commandState } from './state';
10
- import { clientRectsFor, flattenRect } from '../util';
10
+ import { clientRectsFor, flattenRect } from '../../util';
11
11
 
12
12
  class CommandHint extends WidgetType {
13
13
  constructor(readonly content: string | HTMLElement) {
@@ -13,7 +13,7 @@ import {
13
13
  } from '@codemirror/view';
14
14
 
15
15
  import { type CommandOptions } from './command';
16
- import { singleValueFacet } from '../../state';
16
+ import { singleValueFacet } from '../../util';
17
17
 
18
18
  type CommandState = {
19
19
  tooltip?: Tooltip | null;
@@ -28,10 +28,9 @@ import { debounce, type UnsubscribeCallback } from '@dxos/async';
28
28
  import { log } from '@dxos/log';
29
29
  import { nonNullable } from '@dxos/util';
30
30
 
31
- import { overlap } from './util';
32
- import { Cursor, documentId, singleValueFacet } from '../state';
33
- import { type Comment, type Range } from '../state';
34
- import { callbackWrapper } from '../util';
31
+ import { documentId } from './selection';
32
+ import { type Comment, type Range } from '../types';
33
+ import { Cursor, overlap, singleValueFacet, callbackWrapper } from '../util';
35
34
 
36
35
  //
37
36
  // State management.
@@ -31,7 +31,8 @@ import { type HuePalette, hueTokens } from '@dxos/react-ui-theme';
31
31
  import { hexToHue, isNotFalsy } from '@dxos/util';
32
32
 
33
33
  import { automerge } from './automerge';
34
- import { awareness, SpaceAwarenessProvider } from './awareness';
34
+ import { SpaceAwarenessProvider, awareness } from './awareness';
35
+ import { focus } from './focus';
35
36
  import { type ThemeStyles, defaultTheme } from '../styles';
36
37
 
37
38
  //
@@ -52,6 +53,7 @@ export type BasicExtensionsOptions = {
52
53
  dropCursor?: boolean;
53
54
  drawSelection?: boolean;
54
55
  editable?: boolean;
56
+ focus?: boolean;
55
57
  highlightActiveLine?: boolean;
56
58
  history?: boolean;
57
59
  indentWithTab?: boolean;
@@ -72,6 +74,7 @@ const defaultBasicOptions: BasicExtensionsOptions = {
72
74
  closeBrackets: true,
73
75
  drawSelection: true,
74
76
  editable: true,
77
+ focus: true,
75
78
  history: true,
76
79
  keymap: 'standard',
77
80
  lineWrapping: true,
@@ -98,6 +101,7 @@ export const createBasicExtensions = (_props?: BasicExtensionsOptions): Extensio
98
101
  props.closeBrackets && closeBrackets(),
99
102
  props.dropCursor && dropCursor(),
100
103
  props.drawSelection && drawSelection({ cursorBlinkRate: 1_200 }),
104
+ props.focus && focus,
101
105
  props.highlightActiveLine && highlightActiveLine(),
102
106
  props.history && history(),
103
107
  props.lineNumbers && lineNumbers(),
@@ -0,0 +1,35 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import { StateEffect, StateField } from '@codemirror/state';
6
+ import { EditorView } from '@codemirror/view';
7
+
8
+ const focusEffect = StateEffect.define<boolean>();
9
+
10
+ export const focusField = StateField.define<boolean>({
11
+ create: () => false,
12
+ update: (value, tr) => {
13
+ for (const effect of tr.effects) {
14
+ if (effect.is(focusEffect)) {
15
+ return effect.value;
16
+ }
17
+ }
18
+ return value;
19
+ },
20
+ });
21
+
22
+ /**
23
+ * Manage focus.
24
+ */
25
+ export const focus = [
26
+ focusField,
27
+ EditorView.domEventHandlers({
28
+ focus: (event, view) => {
29
+ setTimeout(() => view.dispatch({ effects: focusEffect.of(true) }));
30
+ },
31
+ blur: (event, view) => {
32
+ setTimeout(() => view.dispatch({ effects: focusEffect.of(false) }));
33
+ },
34
+ }),
35
+ ];
@@ -9,7 +9,7 @@ import React from 'react';
9
9
 
10
10
  import { Icon } from '@dxos/react-ui';
11
11
 
12
- import { createElement, renderRoot } from './util';
12
+ import { createElement, renderRoot } from '../util';
13
13
 
14
14
  export type FoldingOptions = {};
15
15
 
@@ -12,9 +12,11 @@ export * from './comments';
12
12
  export * from './debug';
13
13
  export * from './dnd';
14
14
  export * from './factories';
15
+ export * from './focus';
15
16
  export * from './folding';
16
17
  export * from './listener';
17
18
  export * from './markdown';
18
19
  export * from './mention';
19
20
  export * from './modes';
21
+ export * from './selection';
20
22
  export * from './typewriter';
@@ -15,7 +15,7 @@ import { image } from './image';
15
15
  import { formattingStyles, bulletListIndentationWidth, orderedListIndentationWidth } from './styles';
16
16
  import { table } from './table';
17
17
  import { theme, type HeadingLevel } from '../../styles';
18
- import { wrapWithCatch } from '../util';
18
+ import { wrapWithCatch } from '../../util';
19
19
 
20
20
  /**
21
21
  * Unicode characters.
@@ -3,43 +3,52 @@
3
3
  //
4
4
 
5
5
  import { syntaxTree } from '@codemirror/language';
6
- import { type EditorState, type Extension, StateField, type Transaction, type Range } from '@codemirror/state';
6
+ import { type EditorState, type Extension, type Range, StateField, type Transaction } from '@codemirror/state';
7
7
  import { Decoration, type DecorationSet, EditorView, WidgetType } from '@codemirror/view';
8
8
 
9
+ import { focusField } from '../focus';
10
+
9
11
  export type ImageOptions = {};
10
12
 
13
+ /**
14
+ * Create image decorations.
15
+ */
11
16
  export const image = (_options: ImageOptions = {}): Extension => {
12
- return StateField.define<DecorationSet>({
13
- create: (state) => {
14
- return Decoration.set(buildDecorations(0, state.doc.length, state));
15
- },
16
- update: (value: DecorationSet, tr: Transaction) => {
17
- if (!tr.docChanged && !tr.selection) {
18
- return value;
19
- }
17
+ return [
18
+ StateField.define<DecorationSet>({
19
+ create: (state) => {
20
+ // Process all images.
21
+ return Decoration.set(buildDecorations(0, state.doc.length, state));
22
+ },
23
+ update: (value: DecorationSet, tr: Transaction) => {
24
+ if (!tr.docChanged && !tr.selection) {
25
+ return value;
26
+ }
20
27
 
21
- // Find range of changes and cursor changes.
22
- const cursor = tr.state.selection.main.head;
23
- const oldCursor = tr.changes.mapPos(tr.startState.selection.main.head);
24
- let from = Math.min(cursor, oldCursor);
25
- let to = Math.max(cursor, oldCursor);
26
- tr.changes.iterChangedRanges((fromA, toA, fromB, toB) => {
27
- from = Math.min(from, fromB);
28
- to = Math.max(to, toB);
29
- });
30
-
31
- // Expand to cover lines.
32
- from = tr.state.doc.lineAt(from).from;
33
- to = tr.state.doc.lineAt(to).to;
34
- return value.map(tr.changes).update({
35
- filterFrom: from,
36
- filterTo: to,
37
- filter: () => false,
38
- add: buildDecorations(from, to, tr.state),
39
- });
40
- },
41
- provide: (field) => EditorView.decorations.from(field),
42
- });
28
+ // Find range of changes and cursor changes.
29
+ const cursor = tr.state.selection.main.head;
30
+ const oldCursor = tr.changes.mapPos(tr.startState.selection.main.head);
31
+ let from = Math.min(cursor, oldCursor);
32
+ let to = Math.max(cursor, oldCursor);
33
+ tr.changes.iterChangedRanges((fromA, toA, fromB, toB) => {
34
+ from = Math.min(from, fromB);
35
+ to = Math.max(to, toB);
36
+ });
37
+
38
+ // Expand to cover lines.
39
+ from = tr.state.doc.lineAt(from).from;
40
+ to = tr.state.doc.lineAt(to).to;
41
+
42
+ return value.map(tr.changes).update({
43
+ filterFrom: from,
44
+ filterTo: to,
45
+ filter: () => false,
46
+ add: buildDecorations(from, to, tr.state),
47
+ });
48
+ },
49
+ provide: (field) => EditorView.decorations.from(field),
50
+ }),
51
+ ];
43
52
  };
44
53
 
45
54
  const preloaded = new Set<string>();
@@ -55,15 +64,19 @@ const preloadImage = (url: string) => {
55
64
  const buildDecorations = (from: number, to: number, state: EditorState) => {
56
65
  const decorations: Range<Decoration>[] = [];
57
66
  const cursor = state.selection.main.head;
58
-
59
67
  syntaxTree(state).iterate({
60
68
  enter: (node) => {
61
69
  if (node.name === 'Image') {
62
70
  const urlNode = node.node.getChild('URL');
63
71
  if (urlNode) {
64
- const hide = state.readOnly || cursor < node.from || cursor > node.to;
72
+ const hide = state.readOnly || cursor < node.from || cursor > node.to || !state.field(focusField);
73
+
65
74
  const url = state.sliceDoc(urlNode.from, urlNode.to);
66
- // TODO(burdon): Doesn't load if scrolling with mouse.
75
+ // Some plugins might be using custom URLs; avoid attempts to render those URLs.
76
+ if (url.match(/^https?:\/\//) === null && url.match(/^file?:\/\//) === null) {
77
+ return;
78
+ }
79
+
67
80
  preloadImage(url);
68
81
  decorations.push(
69
82
  Decoration.replace({
@@ -94,14 +107,12 @@ class ImageWidget extends WidgetType {
94
107
  const img = document.createElement('img');
95
108
  img.setAttribute('src', this._url);
96
109
  img.setAttribute('class', 'cm-image');
97
- // Images are hidden until successfully loaded to avoid flickering effects.
98
- img.onload = () => img.classList.add('cm-loaded-image');
110
+ // If focused, hide image until successfully loaded to avoid flickering effects.
111
+ if (view.state.field(focusField)) {
112
+ img.onload = () => img.classList.add('cm-loaded-image');
113
+ } else {
114
+ img.classList.add('cm-loaded-image');
115
+ }
99
116
  return img;
100
117
  }
101
118
  }
102
-
103
- export type ImageUploadOptions = {
104
- onSelect: () => { url: string };
105
- };
106
-
107
- export const imageUpload = (options: ImageOptions = {}) => {};
@@ -7,7 +7,7 @@ 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
- import { singleValueFacet } from '../state';
10
+ import { singleValueFacet } from '../util';
11
11
 
12
12
  export const focusEvent = 'focus.container';
13
13
 
@@ -2,16 +2,19 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- import { type EditorState, type Extension, Transaction, type TransactionSpec } from '@codemirror/state';
5
+ import { type Extension, Transaction, type TransactionSpec } from '@codemirror/state';
6
6
  import { EditorView, keymap } from '@codemirror/view';
7
7
 
8
8
  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,25 +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 = (
46
- state: EditorState,
47
- { scrollTo, selection }: EditorSelectionState,
48
- ): TransactionSpec => {
49
- return {
50
- selection,
51
- scrollIntoView: !scrollTo,
52
- effects: scrollTo ? EditorView.scrollIntoView(scrollTo, { yMargin: 96 }) : undefined,
53
- annotations: Transaction.userEvent.of(stateRestoreAnnotation),
54
- };
55
- };
56
+ });
56
57
 
57
58
  /**
58
59
  * Track scrolling and selection state to be restored when switching to document.
59
60
  */
60
- // TODO(burdon): Rename.
61
- export const state = ({ getState, setState }: Partial<EditorStateOptions> = {}): Extension => {
61
+ export const selectionState = ({ getState, setState }: Partial<EditorStateStore> = {}): Extension => {
62
62
  const setStateDebounced = debounce(setState!, 1_000);
63
63
 
64
64
  return [
@@ -90,7 +90,7 @@ export const state = ({ getState, setState }: Partial<EditorStateOptions> = {}):
90
90
  run: (view) => {
91
91
  const state = getState(view.state.facet(documentId));
92
92
  if (state) {
93
- view.dispatch(createEditorStateTransaction(view.state, state));
93
+ view.dispatch(createEditorStateTransaction(state));
94
94
  }
95
95
  return true;
96
96
  },
@@ -19,8 +19,7 @@ import {
19
19
  import { log } from '@dxos/log';
20
20
  import { getProviderValue, isNotFalsy, type MaybeProvider } from '@dxos/util';
21
21
 
22
- import { editorInputMode } from '../extensions';
23
- import { type EditorSelection, documentId, createEditorStateTransaction } from '../state';
22
+ import { editorInputMode, type EditorSelection, documentId, createEditorStateTransaction } from '../extensions';
24
23
  import { debugDispatcher } from '../util';
25
24
 
26
25
  export type UseTextEditor = {
@@ -150,7 +149,7 @@ export const useTextEditor = (
150
149
  return;
151
150
  }
152
151
 
153
- view.dispatch(createEditorStateTransaction(view.state, { scrollTo, selection }));
152
+ view.dispatch(createEditorStateTransaction({ scrollTo, selection }));
154
153
  }
155
154
  }
156
155
  }, [view, scrollTo, selection]);
package/src/index.ts CHANGED
@@ -14,7 +14,7 @@ export * from './components';
14
14
  export * from './defaults';
15
15
  export * from './extensions';
16
16
  export * from './hooks';
17
- export * from './state';
17
+ export * from './types';
18
18
  export * from './util';
19
19
 
20
20
  export { translations };
@@ -4,8 +4,15 @@
4
4
 
5
5
  import { type EditorState } from '@codemirror/state';
6
6
 
7
- import { type Range } from './types';
8
- import { singleValueFacet } from './util';
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;
9
16
 
10
17
  /**
11
18
  * Converts indexes into the text document into stable peer-independent cursors.
@@ -32,7 +39,6 @@ export class Cursor {
32
39
 
33
40
  static readonly getCursorFromRange = (state: EditorState, range: Range) => {
34
41
  const cursorConverter = state.facet(Cursor.converter);
35
-
36
42
  const from = cursorConverter.toCursor(range.from);
37
43
  const to = cursorConverter.toCursor(range.to, -1);
38
44
  return [from, to].join(':');