@dxos/react-ui-editor 0.7.5-main.9d26e3a → 0.7.5-main.b19bfc8

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 (87) hide show
  1. package/dist/lib/browser/index.mjs +1125 -1137
  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 +1145 -1171
  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 +1125 -1137
  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/InputMode.stories.d.ts +3 -4
  11. package/dist/types/src/InputMode.stories.d.ts.map +1 -1
  12. package/dist/types/src/TextEditor.stories.d.ts +34 -35
  13. package/dist/types/src/TextEditor.stories.d.ts.map +1 -1
  14. package/dist/types/src/components/EditorToolbar/EditorToolbar.d.ts +3 -0
  15. package/dist/types/src/components/EditorToolbar/EditorToolbar.d.ts.map +1 -0
  16. package/dist/types/src/components/EditorToolbar/blocks.d.ts +18 -0
  17. package/dist/types/src/components/EditorToolbar/blocks.d.ts.map +1 -0
  18. package/dist/types/src/components/EditorToolbar/comment.d.ts +17 -0
  19. package/dist/types/src/components/EditorToolbar/comment.d.ts.map +1 -0
  20. package/dist/types/src/components/EditorToolbar/formatting.d.ts +18 -0
  21. package/dist/types/src/components/EditorToolbar/formatting.d.ts.map +1 -0
  22. package/dist/types/src/components/EditorToolbar/headings.d.ts +18 -0
  23. package/dist/types/src/components/EditorToolbar/headings.d.ts.map +1 -0
  24. package/dist/types/src/components/EditorToolbar/index.d.ts +3 -0
  25. package/dist/types/src/components/EditorToolbar/index.d.ts.map +1 -0
  26. package/dist/types/src/components/EditorToolbar/lists.d.ts +18 -0
  27. package/dist/types/src/components/EditorToolbar/lists.d.ts.map +1 -0
  28. package/dist/types/src/components/EditorToolbar/util.d.ts +58 -0
  29. package/dist/types/src/components/EditorToolbar/util.d.ts.map +1 -0
  30. package/dist/types/src/components/EditorToolbar/viewMode.d.ts +18 -0
  31. package/dist/types/src/components/EditorToolbar/viewMode.d.ts.map +1 -0
  32. package/dist/types/src/components/index.d.ts +1 -1
  33. package/dist/types/src/components/index.d.ts.map +1 -1
  34. package/dist/types/src/extensions/automerge/automerge.stories.d.ts +5 -6
  35. package/dist/types/src/extensions/automerge/automerge.stories.d.ts.map +1 -1
  36. package/dist/types/src/extensions/comments.d.ts +3 -4
  37. package/dist/types/src/extensions/comments.d.ts.map +1 -1
  38. package/dist/types/src/extensions/factories.d.ts.map +1 -1
  39. package/dist/types/src/extensions/markdown/editorAction.d.ts +12 -0
  40. package/dist/types/src/extensions/markdown/editorAction.d.ts.map +1 -0
  41. package/dist/types/src/extensions/markdown/formatting.d.ts +14 -12
  42. package/dist/types/src/extensions/markdown/formatting.d.ts.map +1 -1
  43. package/dist/types/src/extensions/markdown/index.d.ts +1 -1
  44. package/dist/types/src/extensions/markdown/index.d.ts.map +1 -1
  45. package/dist/types/src/extensions/markdown/styles.d.ts.map +1 -1
  46. package/dist/types/src/hooks/useActionHandler.d.ts +2 -2
  47. package/dist/types/src/hooks/useActionHandler.d.ts.map +1 -1
  48. package/dist/types/src/index.d.ts +1 -0
  49. package/dist/types/src/index.d.ts.map +1 -1
  50. package/dist/types/src/styles/stack-item-content-class-names.d.ts +3 -0
  51. package/dist/types/src/styles/stack-item-content-class-names.d.ts.map +1 -0
  52. package/dist/types/src/styles/theme.d.ts.map +1 -1
  53. package/dist/types/tsconfig.tsbuildinfo +1 -1
  54. package/package.json +31 -29
  55. package/src/InputMode.stories.tsx +7 -10
  56. package/src/components/EditorToolbar/EditorToolbar.tsx +106 -0
  57. package/src/components/EditorToolbar/blocks.ts +41 -0
  58. package/src/components/EditorToolbar/comment.ts +23 -0
  59. package/src/components/EditorToolbar/formatting.ts +41 -0
  60. package/src/components/EditorToolbar/headings.ts +59 -0
  61. package/src/components/EditorToolbar/index.ts +6 -0
  62. package/src/components/EditorToolbar/lists.ts +40 -0
  63. package/src/components/EditorToolbar/util.ts +65 -0
  64. package/src/components/EditorToolbar/viewMode.ts +48 -0
  65. package/src/components/index.ts +1 -1
  66. package/src/extensions/automerge/automerge.stories.tsx +2 -2
  67. package/src/extensions/comments.ts +12 -19
  68. package/src/extensions/factories.ts +11 -5
  69. package/src/extensions/markdown/decorate.ts +1 -1
  70. package/src/extensions/markdown/{action.ts → editorAction.ts} +22 -20
  71. package/src/extensions/markdown/formatting.test.ts +7 -6
  72. package/src/extensions/markdown/formatting.ts +20 -24
  73. package/src/extensions/markdown/index.ts +1 -1
  74. package/src/extensions/markdown/styles.ts +21 -0
  75. package/src/hooks/useActionHandler.ts +4 -4
  76. package/src/index.ts +4 -0
  77. package/src/styles/markdown.ts +1 -1
  78. package/src/styles/stack-item-content-class-names.ts +17 -0
  79. package/src/styles/theme.ts +2 -3
  80. package/dist/types/src/components/Toolbar/Toolbar.d.ts +0 -34
  81. package/dist/types/src/components/Toolbar/Toolbar.d.ts.map +0 -1
  82. package/dist/types/src/components/Toolbar/index.d.ts +0 -2
  83. package/dist/types/src/components/Toolbar/index.d.ts.map +0 -1
  84. package/dist/types/src/extensions/markdown/action.d.ts +0 -9
  85. package/dist/types/src/extensions/markdown/action.d.ts.map +0 -1
  86. package/src/components/Toolbar/Toolbar.tsx +0 -522
  87. package/src/components/Toolbar/index.ts +0 -5
@@ -0,0 +1,106 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import React, { useCallback } from 'react';
6
+
7
+ import { type NodeArg } from '@dxos/app-graph';
8
+ import { ElevationProvider } from '@dxos/react-ui';
9
+ import {
10
+ ToolbarMenu,
11
+ MenuProvider,
12
+ type MenuActionHandler,
13
+ useMenuActions,
14
+ createGapSeparator,
15
+ } from '@dxos/react-ui-menu';
16
+ import { textBlockWidth } from '@dxos/react-ui-theme';
17
+
18
+ import { createBlocks } from './blocks';
19
+ import { createComment } from './comment';
20
+ import { createFormatting } from './formatting';
21
+ import { createHeadings } from './headings';
22
+ import { createLists } from './lists';
23
+ import {
24
+ type EditorToolbarActionGraphProps,
25
+ type EditorToolbarFeatureFlags,
26
+ type EditorToolbarProps,
27
+ editorToolbarSearch,
28
+ } from './util';
29
+ import { createViewMode } from './viewMode';
30
+ import { stackItemContentToolbarClassNames } from '../../styles/stack-item-content-class-names';
31
+
32
+ const createToolbar = ({
33
+ state,
34
+ customActions,
35
+ ...features
36
+ }: EditorToolbarFeatureFlags & Pick<EditorToolbarActionGraphProps, 'state' | 'customActions'>): {
37
+ nodes: NodeArg<any>[];
38
+ edges: { source: string; target: string }[];
39
+ } => {
40
+ const nodes = [];
41
+ const edges = [];
42
+ if (features.headings ?? true) {
43
+ const headings = createHeadings(state);
44
+ nodes.push(...headings.nodes);
45
+ edges.push(...headings.edges);
46
+ }
47
+ if (features.formatting ?? true) {
48
+ const formatting = createFormatting(state);
49
+ nodes.push(...formatting.nodes);
50
+ edges.push(...formatting.edges);
51
+ }
52
+ if (features.lists ?? true) {
53
+ const lists = createLists(state);
54
+ nodes.push(...lists.nodes);
55
+ edges.push(...lists.edges);
56
+ }
57
+ if (features.blocks ?? true) {
58
+ const blocks = createBlocks(state);
59
+ nodes.push(...blocks.nodes);
60
+ edges.push(...blocks.edges);
61
+ }
62
+ if (customActions) {
63
+ const custom = customActions();
64
+ nodes.push(...custom.nodes);
65
+ edges.push(...custom.edges);
66
+ }
67
+ const editorToolbarGap = createGapSeparator();
68
+ nodes.push(...editorToolbarGap.nodes);
69
+ edges.push(...editorToolbarGap.edges);
70
+ if (features.comment ?? true) {
71
+ const comment = createComment(state);
72
+ nodes.push(...comment.nodes);
73
+ edges.push(...comment.edges);
74
+ }
75
+ if (features.search ?? true) {
76
+ nodes.push(editorToolbarSearch);
77
+ edges.push({ source: 'root', target: editorToolbarSearch.id });
78
+ }
79
+ if (features.viewMode ?? true) {
80
+ const viewMode = createViewMode(state);
81
+ nodes.push(...viewMode.nodes);
82
+ edges.push(...viewMode.edges);
83
+ }
84
+ return { nodes, edges };
85
+ };
86
+
87
+ const useEditorToolbarActionGraph = ({ onAction, ...props }: EditorToolbarProps) => {
88
+ const menuCreator = useCallback(() => createToolbar(props), [props]);
89
+
90
+ const { resolveGroupItems } = useMenuActions(menuCreator);
91
+
92
+ return { resolveGroupItems, onAction: onAction as MenuActionHandler };
93
+ };
94
+
95
+ export const EditorToolbar = ({ classNames, attendableId, role, ...props }: EditorToolbarProps) => {
96
+ const menuProps = useEditorToolbarActionGraph(props);
97
+ return (
98
+ <div role='none' className={stackItemContentToolbarClassNames(role)}>
99
+ <ElevationProvider elevation={role === 'section' ? 'positioned' : 'base'}>
100
+ <MenuProvider {...menuProps} attendableId={attendableId}>
101
+ <ToolbarMenu classNames={[textBlockWidth, '!bg-transparent', classNames]} />
102
+ </MenuProvider>
103
+ </ElevationProvider>
104
+ </div>
105
+ );
106
+ };
@@ -0,0 +1,41 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { type NodeArg } from '@dxos/app-graph';
6
+ import { type ToolbarMenuActionGroupProperties } from '@dxos/react-ui-menu';
7
+
8
+ import { createEditorAction, createEditorActionGroup, type EditorToolbarState } from './util';
9
+ import { type PayloadType } from '../../extensions';
10
+
11
+ const createBlockGroupAction = (value: string) =>
12
+ createEditorActionGroup('block', {
13
+ variant: 'toggleGroup',
14
+ selectCardinality: 'single',
15
+ value,
16
+ } as ToolbarMenuActionGroupProperties);
17
+
18
+ const createBlockActions = (value: string, blankLine?: boolean) =>
19
+ Object.entries({
20
+ blockquote: 'ph--quotes--regular',
21
+ codeblock: 'ph--code-block--regular',
22
+ table: 'ph--table--regular',
23
+ }).map(([type, icon]) => {
24
+ return createEditorAction(
25
+ { type: type as PayloadType, checked: type === value, ...(type === 'table' && { disabled: !!blankLine }) },
26
+ icon,
27
+ );
28
+ });
29
+
30
+ export const createBlocks = (state: EditorToolbarState) => {
31
+ const value = state?.blockQuote ? 'blockquote' : state.blockType ?? '';
32
+ const blockGroupAction = createBlockGroupAction(value);
33
+ const blockActions = createBlockActions(value, state.blankLine);
34
+ return {
35
+ nodes: [blockGroupAction as NodeArg<any>, ...blockActions],
36
+ edges: [
37
+ { source: 'root', target: 'block' },
38
+ ...blockActions.map(({ id }) => ({ source: blockGroupAction.id, target: id })),
39
+ ],
40
+ };
41
+ };
@@ -0,0 +1,23 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { type Label } from '@dxos/react-ui';
6
+
7
+ import { createEditorAction, type EditorToolbarState } from './util';
8
+ import { translationKey } from '../../translations';
9
+
10
+ const commentLabel = (comment?: boolean, selection?: boolean) =>
11
+ comment
12
+ ? 'selection overlaps existing comment label'
13
+ : selection === false
14
+ ? 'select text to comment label'
15
+ : 'comment label';
16
+
17
+ const createCommentAction = (label: Label) =>
18
+ createEditorAction({ type: 'comment', testId: 'editor.toolbar.comment' }, 'ph--chat-text--regular', label);
19
+
20
+ export const createComment = (state: EditorToolbarState) => ({
21
+ nodes: [createCommentAction([commentLabel(state.comment, state.selection), { ns: translationKey }])],
22
+ edges: [{ source: 'root', target: 'comment' }],
23
+ });
@@ -0,0 +1,41 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { type NodeArg } from '@dxos/app-graph';
6
+ import { type ToolbarMenuActionGroupProperties } from '@dxos/react-ui-menu';
7
+
8
+ import { createEditorAction, createEditorActionGroup, type EditorToolbarState } from './util';
9
+ import { type Formatting, type PayloadType } from '../../extensions';
10
+
11
+ const formats = {
12
+ strong: 'ph--text-b--regular',
13
+ emphasis: 'ph--text-italic--regular',
14
+ strikethrough: 'ph--text-strikethrough--regular',
15
+ code: 'ph--code--regular',
16
+ link: 'ph--link--regular',
17
+ };
18
+
19
+ const createFormattingGroup = (formatting: Formatting) =>
20
+ createEditorActionGroup('formatting', {
21
+ variant: 'toggleGroup',
22
+ selectCardinality: 'multiple',
23
+ value: Object.keys(formats).filter((key) => !!formatting[key as keyof Formatting]),
24
+ } as ToolbarMenuActionGroupProperties);
25
+
26
+ const createFormattingActions = (formatting: Formatting) =>
27
+ Object.entries(formats).map(([type, icon]) =>
28
+ createEditorAction({ type: type as PayloadType, checked: !!formatting[type as keyof Formatting] }, icon),
29
+ );
30
+
31
+ export const createFormatting = (state: EditorToolbarState) => {
32
+ const formattingGroupAction = createFormattingGroup(state);
33
+ const formattingActions = createFormattingActions(state);
34
+ return {
35
+ nodes: [formattingGroupAction as NodeArg<any>, ...formattingActions],
36
+ edges: [
37
+ { source: 'root', target: 'formatting' },
38
+ ...formattingActions.map(({ id }) => ({ source: formattingGroupAction.id, target: id })),
39
+ ],
40
+ };
41
+ };
@@ -0,0 +1,59 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { type NodeArg } from '@dxos/app-graph';
6
+ import { type ToolbarMenuActionGroupProperties } from '@dxos/react-ui-menu';
7
+
8
+ import { createEditorAction, createEditorActionGroup, type EditorToolbarState } from './util';
9
+ import { translationKey } from '../../translations';
10
+
11
+ const createHeadingGroupAction = (value: string) =>
12
+ createEditorActionGroup(
13
+ 'heading',
14
+ {
15
+ variant: 'dropdownMenu',
16
+ applyActive: true,
17
+ selectCardinality: 'single',
18
+ value,
19
+ } as ToolbarMenuActionGroupProperties,
20
+ 'ph--text-h--regular',
21
+ );
22
+
23
+ const createHeadingActions = (value: string) =>
24
+ Object.entries({
25
+ '0': 'ph--paragraph--regular',
26
+ '1': 'ph--text-h-one--regular',
27
+ '2': 'ph--text-h-two--regular',
28
+ '3': 'ph--text-h-three--regular',
29
+ '4': 'ph--text-h-four--regular',
30
+ '5': 'ph--text-h-five--regular',
31
+ '6': 'ph--text-h-six--regular',
32
+ }).map(([levelStr, icon]) => {
33
+ const level = parseInt(levelStr);
34
+ return createEditorAction(
35
+ { type: 'heading', data: level, checked: value === levelStr },
36
+ icon,
37
+ ['heading level label', { count: level, ns: translationKey }],
38
+ `heading--${levelStr}`,
39
+ );
40
+ });
41
+
42
+ const computeHeadingValue = (state: EditorToolbarState) => {
43
+ const blockType = state ? state.blockType : 'paragraph';
44
+ const header = blockType && /heading(\d)/.exec(blockType);
45
+ return header ? header[1] : blockType === 'paragraph' || !blockType ? '0' : '';
46
+ };
47
+
48
+ export const createHeadings = (state: EditorToolbarState) => {
49
+ const headingValue = computeHeadingValue(state);
50
+ const headingGroupAction = createHeadingGroupAction(headingValue);
51
+ const headingActions = createHeadingActions(headingValue);
52
+ return {
53
+ nodes: [headingGroupAction as NodeArg<any>, ...headingActions],
54
+ edges: [
55
+ { source: 'root', target: 'heading' },
56
+ ...headingActions.map(({ id }) => ({ source: headingGroupAction.id, target: id })),
57
+ ],
58
+ };
59
+ };
@@ -0,0 +1,6 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ export * from './EditorToolbar';
6
+ export { type EditorToolbarState, useEditorToolbarState, createEditorAction, createEditorActionGroup } from './util';
@@ -0,0 +1,40 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { type NodeArg } from '@dxos/app-graph';
6
+ import { type ToolbarMenuActionGroupProperties } from '@dxos/react-ui-menu';
7
+
8
+ import { createEditorAction, createEditorActionGroup, type EditorToolbarState } from './util';
9
+ import { type PayloadType } from '../../extensions';
10
+
11
+ const listStyles = {
12
+ bullet: 'ph--list-bullets--regular',
13
+ ordered: 'ph--list-numbers--regular',
14
+ task: 'ph--list-checks--regular',
15
+ };
16
+
17
+ const createListGroupAction = (value: string) =>
18
+ createEditorActionGroup('list', {
19
+ variant: 'toggleGroup',
20
+ selectCardinality: 'single',
21
+ value,
22
+ } as ToolbarMenuActionGroupProperties);
23
+
24
+ const createListActions = (value: string) =>
25
+ Object.entries(listStyles).map(([listStyle, icon]) =>
26
+ createEditorAction({ type: `list-${listStyle}` as PayloadType, checked: value === listStyle }, icon),
27
+ );
28
+
29
+ export const createLists = (state: EditorToolbarState) => {
30
+ const value = state.listStyle ?? '';
31
+ const listGroupAction = createListGroupAction(value);
32
+ const listActionsMap = createListActions(value);
33
+ return {
34
+ nodes: [listGroupAction as NodeArg<any>, ...listActionsMap],
35
+ edges: [
36
+ { source: 'root', target: 'list' },
37
+ ...listActionsMap.map(({ id }) => ({ source: listGroupAction.id, target: id })),
38
+ ],
39
+ };
40
+ };
@@ -0,0 +1,65 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { useMemo } from 'react';
6
+
7
+ import { create, type ReactiveObject } from '@dxos/live-object';
8
+ import { type Label, type ThemedClassName } from '@dxos/react-ui';
9
+ import {
10
+ type MenuSeparator,
11
+ type MenuItemGroup,
12
+ type ToolbarMenuActionGroupProperties,
13
+ type MenuActionProperties,
14
+ createMenuAction,
15
+ createMenuItemGroup,
16
+ type ActionGraphProps,
17
+ } from '@dxos/react-ui-menu';
18
+
19
+ import type { EditorAction, EditorActionPayload, EditorViewMode, Formatting } from '../../extensions';
20
+ import { translationKey } from '../../translations';
21
+
22
+ export type EditorToolbarState = Formatting &
23
+ Partial<{ comment: boolean; viewMode: EditorViewMode; selection: boolean }>;
24
+
25
+ export const useEditorToolbarState = (initialState: Partial<EditorToolbarState> = {}) => {
26
+ return useMemo(() => create<EditorToolbarState>(initialState), []);
27
+ };
28
+
29
+ export type EditorToolbarFeatureFlags = Partial<{
30
+ headings: boolean;
31
+ formatting: boolean;
32
+ lists: boolean;
33
+ blocks: boolean;
34
+ comment: boolean;
35
+ search: boolean;
36
+ viewMode: boolean;
37
+ }>;
38
+
39
+ export type EditorToolbarActionGraphProps = {
40
+ state: ReactiveObject<EditorToolbarState>;
41
+ // TODO(wittjosiah): Control positioning.
42
+ customActions?: () => ActionGraphProps;
43
+ onAction: (action: EditorAction) => void;
44
+ };
45
+
46
+ export type EditorToolbarProps = ThemedClassName<
47
+ EditorToolbarActionGraphProps & EditorToolbarFeatureFlags & { attendableId?: string; role?: string }
48
+ >;
49
+
50
+ export type EditorToolbarItem = EditorAction | MenuItemGroup | MenuSeparator;
51
+
52
+ export const createEditorAction = (
53
+ payload: EditorActionPayload & Partial<MenuActionProperties>,
54
+ icon: string,
55
+ label: Label = [`${payload.type} label`, { ns: translationKey }],
56
+ id: string = payload.type,
57
+ ) => createMenuAction(id, { icon, label, ...payload }) as EditorAction;
58
+
59
+ export const createEditorActionGroup = (
60
+ id: string,
61
+ props: Omit<ToolbarMenuActionGroupProperties, 'icon'>,
62
+ icon?: string,
63
+ ) => createMenuItemGroup(id, { icon, iconOnly: true, ...props });
64
+
65
+ export const editorToolbarSearch = createEditorAction({ type: 'search' }, 'ph--magnifying-glass--regular');
@@ -0,0 +1,48 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { type NodeArg } from '@dxos/app-graph';
6
+ import { type ToolbarMenuActionGroupProperties } from '@dxos/react-ui-menu';
7
+
8
+ import { createEditorAction, createEditorActionGroup, type EditorToolbarState } from './util';
9
+ import { translationKey } from '../../translations';
10
+
11
+ const createViewModeGroupAction = (value: string) =>
12
+ createEditorActionGroup(
13
+ 'viewMode',
14
+ {
15
+ variant: 'dropdownMenu',
16
+ applyActive: true,
17
+ selectCardinality: 'single',
18
+ value,
19
+ } as ToolbarMenuActionGroupProperties,
20
+ 'ph--eye--regular',
21
+ );
22
+
23
+ const createViewModeActions = (value: string) =>
24
+ Object.entries({
25
+ preview: 'ph--eye--regular',
26
+ source: 'ph--pencil-simple--regular',
27
+ readonly: 'ph--pencil-slash--regular',
28
+ }).map(([viewMode, icon]) => {
29
+ return createEditorAction(
30
+ { type: 'view-mode', data: viewMode, checked: viewMode === value },
31
+ icon,
32
+ [`${viewMode} mode label`, { ns: translationKey }],
33
+ `view-mode--${viewMode}`,
34
+ );
35
+ });
36
+
37
+ export const createViewMode = (state: EditorToolbarState) => {
38
+ const value = state.viewMode ?? 'source';
39
+ const viewModeGroupAction = createViewModeGroupAction(value);
40
+ const viewModeActions = createViewModeActions(value);
41
+ return {
42
+ nodes: [viewModeGroupAction as NodeArg<any>, ...viewModeActions],
43
+ edges: [
44
+ { source: 'root', target: 'viewMode' },
45
+ ...viewModeActions.map(({ id }) => ({ source: viewModeGroupAction.id, target: id })),
46
+ ],
47
+ };
48
+ };
@@ -2,4 +2,4 @@
2
2
  // Copyright 2022 DXOS.org
3
3
  //
4
4
 
5
- export * from './Toolbar';
5
+ export * from './EditorToolbar';
@@ -74,12 +74,12 @@ const Story = () => {
74
74
  const repo1 = new Repo({ network: [new BroadcastChannelNetworkAdapter()] });
75
75
  const repo2 = new Repo({ network: [new BroadcastChannelNetworkAdapter()] });
76
76
 
77
- const object1 = repo1.create();
77
+ const object1 = repo1.create<TestObject>();
78
78
  object1.change((doc: TestObject) => {
79
79
  doc.text = initialContent;
80
80
  });
81
81
 
82
- const object2 = repo2.find(object1.url);
82
+ const object2 = repo2.find<TestObject>(object1.url);
83
83
  await object2.whenReady();
84
84
 
85
85
  setObject1({ handle: object1, path: ['text'] });
@@ -22,13 +22,15 @@ import {
22
22
  ViewPlugin,
23
23
  } from '@codemirror/view';
24
24
  import sortBy from 'lodash.sortby';
25
- import { useEffect, useMemo, useState } from 'react';
25
+ import { useEffect, useMemo } from 'react';
26
26
 
27
27
  import { debounce, type UnsubscribeCallback } from '@dxos/async';
28
+ import { type ReactiveObject } from '@dxos/live-object';
28
29
  import { log } from '@dxos/log';
29
- import { nonNullable } from '@dxos/util';
30
+ import { isNonNullable } from '@dxos/util';
30
31
 
31
32
  import { documentId } from './selection';
33
+ import { type EditorToolbarState } from '../components';
32
34
  import { type Comment, type Range } from '../types';
33
35
  import { Cursor, overlap, singleValueFacet, callbackWrapper } from '../util';
34
36
 
@@ -85,7 +87,7 @@ export const commentsState = StateField.define<CommentsState>({
85
87
  const range = Cursor.getRangeFromCursor(tr.state, comment.cursor);
86
88
  return range && { comment, range };
87
89
  })
88
- .filter(nonNullable);
90
+ .filter(isNonNullable);
89
91
 
90
92
  return { ...value, comments: commentStates };
91
93
  }
@@ -149,7 +151,7 @@ const commentsDecorations = EditorView.decorations.compute([commentsState], (sta
149
151
  const mark = createCommentMark(comment.comment.id, comment.comment.id === current);
150
152
  return mark.range(range.from, range.to);
151
153
  })
152
- .filter(nonNullable);
154
+ .filter(isNonNullable);
153
155
 
154
156
  return Decoration.set(decorations);
155
157
  });
@@ -505,7 +507,7 @@ export const comments = (options: CommentsOptions = {}): Extension => {
505
507
  }),
506
508
 
507
509
  options.onUpdate && trackPastedComments(options.onUpdate),
508
- ].filter(nonNullable);
510
+ ].filter(isNonNullable);
509
511
  };
510
512
 
511
513
  //
@@ -608,26 +610,17 @@ export const createExternalCommentSync = (
608
610
  },
609
611
  );
610
612
 
611
- export const useCommentState = (): [{ comment: boolean; selection: boolean }, Extension] => {
612
- const [state, setState] = useState<{ comment: boolean; selection: boolean }>({
613
- comment: false,
614
- selection: false,
615
- });
616
-
617
- const observer = useMemo(
613
+ export const useCommentState = (state: ReactiveObject<EditorToolbarState>): Extension => {
614
+ return useMemo(
618
615
  () =>
619
616
  EditorView.updateListener.of((update) => {
620
617
  if (update.docChanged || update.selectionSet) {
621
- setState({
622
- comment: selectionOverlapsComment(update.state),
623
- selection: hasActiveSelection(update.state),
624
- });
618
+ state.comment = selectionOverlapsComment(update.state);
619
+ state.selection = hasActiveSelection(update.state);
625
620
  }
626
621
  }),
627
- [],
622
+ [state],
628
623
  );
629
-
630
- return [state, observer];
631
624
  };
632
625
 
633
626
  /**
@@ -27,7 +27,7 @@ import { log } from '@dxos/log';
27
27
  import { type DocAccessor, type Space } from '@dxos/react-client/echo';
28
28
  import { type Identity } from '@dxos/react-client/halo';
29
29
  import { type ThemeMode } from '@dxos/react-ui';
30
- import { type HuePalette, hueTokens } from '@dxos/react-ui-theme';
30
+ import { type HuePalette } from '@dxos/react-ui-theme';
31
31
  import { hexToHue, isNotFalsy } from '@dxos/util';
32
32
 
33
33
  import { automerge } from './automerge';
@@ -124,6 +124,13 @@ export const createBasicExtensions = (_props?: BasicExtensionsOptions): Extensio
124
124
  ...(props.history ? historyKeymap : []),
125
125
  // https://codemirror.net/docs/ref/#search.searchKeymap
126
126
  ...(props.search ? searchKeymap : []),
127
+ // Disable bindings that conflict with system shortcuts.
128
+ // TODO(burdon): Catalog global shortcuts.
129
+ {
130
+ key: 'Mod-Shift-k',
131
+ preventDefault: true,
132
+ run: () => true,
133
+ },
127
134
  ].filter(isNotFalsy),
128
135
  ),
129
136
  ].filter(isNotFalsy);
@@ -194,8 +201,7 @@ export const createDataExtensions = <T>({ id, text, space, identity }: DataExten
194
201
 
195
202
  if (space && identity) {
196
203
  const peerId = identity?.identityKey.toHex();
197
- const { cursorLightValue, cursorDarkValue } =
198
- hueTokens[(identity?.profile?.data?.hue as HuePalette | undefined) ?? hexToHue(peerId ?? '0')];
204
+ const hue = (identity?.profile?.data?.hue as HuePalette | undefined) ?? hexToHue(peerId ?? '0');
199
205
 
200
206
  extensions.push(
201
207
  awareness(
@@ -205,8 +211,8 @@ export const createDataExtensions = <T>({ id, text, space, identity }: DataExten
205
211
  peerId: identity.identityKey.toHex(),
206
212
  info: {
207
213
  displayName: identity.profile?.displayName ?? generateName(identity.identityKey.toHex()),
208
- darkColor: cursorDarkValue,
209
- lightColor: cursorLightValue,
214
+ darkColor: `var(--dx-${hue}Cursor)`,
215
+ lightColor: `var(--dx-${hue}Cursor)`,
210
216
  },
211
217
  }),
212
218
  ),
@@ -73,7 +73,7 @@ class CheckboxWidget extends WidgetType {
73
73
 
74
74
  override toDOM(view: EditorView) {
75
75
  const input = document.createElement('input');
76
- input.className = 'cm-task-checkbox ch-checkbox';
76
+ input.className = 'cm-task-checkbox dx-checkbox';
77
77
  input.type = 'checkbox';
78
78
  input.tabIndex = -1;
79
79
  input.checked = this._checked;