@dxos/react-ui-editor 0.14.0 → 0.16.0

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 (25) hide show
  1. package/dist/lib/index.mjs +82 -70
  2. package/dist/lib/index.mjs.map +1 -1
  3. package/dist/types/src/components/EditorMenuProvider/menu.d.ts +2 -0
  4. package/dist/types/src/components/EditorMenuProvider/menu.d.ts.map +1 -1
  5. package/dist/types/src/components/EditorMenuProvider/popover.d.ts +7 -1
  6. package/dist/types/src/components/EditorMenuProvider/popover.d.ts.map +1 -1
  7. package/dist/types/src/components/EditorMenuProvider/useEditorMenu.d.ts +2 -2
  8. package/dist/types/src/components/EditorMenuProvider/useEditorMenu.d.ts.map +1 -1
  9. package/dist/types/src/components/EditorMenuProvider/useEditorMenu.test.d.ts +2 -0
  10. package/dist/types/src/components/EditorMenuProvider/useEditorMenu.test.d.ts.map +1 -0
  11. package/dist/types/src/hooks/useTextEditor.d.ts.map +1 -1
  12. package/dist/types/src/stories/Comments.stories.d.ts.map +1 -1
  13. package/dist/types/src/stories/Widgets.stories.d.ts +5 -0
  14. package/dist/types/src/stories/Widgets.stories.d.ts.map +1 -1
  15. package/dist/types/tsconfig.tsbuildinfo +1 -1
  16. package/package.json +33 -33
  17. package/src/components/EditorMenuProvider/EditorMenuProvider.tsx +1 -1
  18. package/src/components/EditorMenuProvider/menu.ts +2 -0
  19. package/src/components/EditorMenuProvider/popover.ts +12 -3
  20. package/src/components/EditorMenuProvider/useEditorMenu.test.tsx +125 -0
  21. package/src/components/EditorMenuProvider/useEditorMenu.ts +79 -56
  22. package/src/hooks/useTextEditor.test.tsx +45 -4
  23. package/src/hooks/useTextEditor.ts +37 -9
  24. package/src/stories/Comments.stories.tsx +25 -21
  25. package/src/stories/Widgets.stories.tsx +59 -0
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../../src/components/Editor/controller.ts","../../src/components/Editor/EditorContext.ts","../../src/components/EditorMenuProvider/menu.ts","../../src/components/EditorMenuProvider/popover.ts","../../src/components/EditorMenuProvider/menu-presets.ts","../../src/components/EditorMenuProvider/EditorMenuProvider.tsx","../../src/components/EditorMenuProvider/useEditorMenu.ts","../../src/components/EditorToolbar/blocks.ts","../../src/components/EditorToolbar/formatting.ts","../../src/components/EditorToolbar/headings.ts","../../src/components/EditorToolbar/image.ts","../../src/components/EditorToolbar/lists.ts","../../src/components/EditorToolbar/search.ts","../../src/components/EditorToolbar/view-mode.ts","../../src/components/EditorToolbar/EditorToolbar.tsx","../../src/hooks/useBasicMarkdownExtensions.ts","../../src/hooks/useTextEditor.ts","../../src/components/Editor/EditorView.tsx","../../src/components/Editor/Editor.tsx","../../src/components/EditorPreviewProvider/EditorPreviewContext.ts","../../src/components/EditorPreviewProvider/EditorPreviewProvider.tsx"],"sourcesContent":["//\n// Copyright 2025 DXOS.org\n//\n\nimport { type EditorView } from '@codemirror/view';\n\nexport interface EditorController {\n get view(): EditorView | null;\n getText: () => string;\n setText: (text: string, focus?: boolean) => void;\n focus: () => void;\n}\n\n// Hide `view` from own-enumerable traversal. Controllers are passed through React props/context, and the\n// live `EditorView` reaches the global `window` via `view.observer.win` (CodeMirror's DOMObserver). React\n// 19.2's dev render-logger walks changed props' object graphs and would otherwise descend into `window`,\n// enumerate `window[0]` (a cross-origin iframe) and throw SecurityError. Direct access is unaffected.\nconst withHiddenView = (controller: EditorController): EditorController => {\n Object.defineProperty(controller, 'view', { enumerable: false });\n return controller;\n};\n\nexport const noopController: EditorController = withHiddenView({\n get view() {\n return null;\n },\n getText: () => '',\n setText: () => {},\n focus: () => {},\n});\n\nexport const createEditorController = (view: EditorView | null): EditorController => {\n return withHiddenView({\n get view() {\n return view;\n },\n getText: () => {\n return view?.state.doc.toString() ?? '';\n },\n setText: (text: string, focus?: boolean) => {\n view?.dispatch({\n changes: {\n from: 0,\n to: view?.state.doc.length ?? 0,\n insert: text,\n },\n selection: {\n anchor: text.length,\n head: text.length,\n },\n });\n\n if (focus) {\n view?.focus();\n }\n },\n focus: () => view?.focus(),\n });\n};\n","//\n// Copyright 2025 DXOS.org\n//\n\nimport { type Extension } from '@codemirror/state';\nimport type * as Atom from 'effect/reactivity/Atom';\n\nimport { createContext } from '@dxos/react-hooks';\nimport { type WidgetState } from '@dxos/ui-editor';\n\nimport { type EditorToolbarState } from '../EditorToolbar/types.ts';\nimport { type EditorController } from './controller.ts';\n\n// Kept out of `Editor.tsx`: react-refresh only fast-refreshes a module whose exports are all\n// components, so a context and its hook exported beside them force a full page reload on every edit.\n\n//\n// Context\n//\n\nexport type EditorContextValue = {\n controller?: EditorController;\n setController: (controller: EditorController) => void;\n extensions?: Extension[];\n /** xmlTags widget portals (embedded blocks); rendered by `Editor.Blocks`, fed via `setWidgets`. */\n widgets?: WidgetState[];\n state: Atom.Writable<EditorToolbarState>;\n};\n\nexport const [EditorContextProvider, useEditorContext] = createContext<EditorContextValue>('Editor');\n\n/**\n * Access the editor context. Must be used within `Editor.Root`.\n */\n","//\n// Copyright 2025 DXOS.org\n//\n\nimport { type EditorView } from '@codemirror/view';\n\nimport type * as Theme from '@dxos/react-ui/Theme';\nimport { insertAtCursor } from '@dxos/ui-editor';\nimport { type MaybePromise } from '@dxos/util';\n\nexport type EditorMenuGroup = {\n id: string;\n label?: Theme.Label;\n items: EditorMenuItem[];\n};\n\nexport type EditorMenuItem = {\n id: string;\n label: Theme.Label;\n icon?: string;\n onSelect?: (event: { view: EditorView; head: number }) => MaybePromise<void>;\n};\n\nexport const getMenuItem = (groups: EditorMenuGroup[], id?: string): EditorMenuItem | undefined => {\n return groups.flatMap((group) => group.items).find((item) => item.id === id);\n};\n\n/** Undefined when the menu is empty; at either end the current item is returned unchanged. */\nexport const getNextMenuItem = (groups: EditorMenuGroup[], id?: string): EditorMenuItem | undefined => {\n const items = groups.flatMap((group) => group.items);\n const index = items.findIndex((item) => item.id === id);\n return index < items.length - 1 ? items[index + 1] : items[index];\n};\n\n/** Undefined when the menu is empty; at either end the current item is returned unchanged. */\nexport const getPreviousMenuItem = (groups: EditorMenuGroup[], id?: string): EditorMenuItem | undefined => {\n const items = groups.flatMap((group) => group.items);\n const index = items.findIndex((item) => item.id === id);\n return index > 0 ? items[index - 1] : items[index];\n};\n\nexport const createMenuGroup = ({\n id = 'menu',\n label,\n filter,\n items,\n}: {\n id?: string;\n label?: Theme.Label;\n filter?: string;\n items: string[];\n}): EditorMenuGroup => ({\n id,\n label,\n items: items\n .filter((item) => !filter || item.toLowerCase().includes(filter.toLowerCase()))\n .map((item, i) => ({\n id: `${id}-${i}`,\n label: item,\n onSelect: ({ view, head }) => insertAtCursor(view, head, item),\n })),\n});\n\nexport const filterMenuGroups = (\n groups: EditorMenuGroup[],\n filter: (item: EditorMenuItem) => boolean,\n): EditorMenuGroup[] => {\n return groups.map((group) => ({\n ...group,\n items: group.items.filter(filter),\n }));\n};\n","//\n// Copyright 2024 DXOS.org\n//\n\nimport { type Extension, Prec, RangeSetBuilder, StateEffect, StateField } from '@codemirror/state';\nimport {\n Decoration,\n type DecorationSet,\n EditorView,\n type KeyBinding,\n ViewPlugin,\n type ViewUpdate,\n keymap,\n} from '@codemirror/view';\n\nimport { type PlaceholderOptions, modalStateField, placeholder } from '@dxos/ui-editor';\nimport { type Range } from '@dxos/ui-editor/types';\nimport { isNonNullable, isTruthy } from '@dxos/util';\n\nconst DELIMITERS = [' ', ':'];\n\nexport type PopoverOptions = {\n trigger?: string | string[];\n triggerKey?: string;\n placeholder?: Partial<PlaceholderOptions>;\n delimiters?: string[];\n\n /**\n * Open the popover automatically while typing a word (no trigger character required).\n * The completion range covers the word under the cursor (delimited by {@link PopoverOptions.delimiters}).\n */\n activateOnTyping?: boolean;\n\n /**\n * Delimiters after which an EMPTY token still opens the menu — `type:` offering every typename,\n * say. Opt-in per consumer because the empty-token guard is load-bearing elsewhere: `RefEditor`\n * uses `activateOnTyping` with no trigger, where an empty query would match every object.\n */\n activateOnDelimiters?: string[];\n\n // Trigger update.\n onTextChange?: (event: { view: EditorView; pos: number; text: string; trigger?: string }) => void;\n onClose?: (event: { view: EditorView }) => void;\n\n // Menu specific.\n /** Returns true when the menu consumed the key (an item was selected); false falls through. */\n onEnter?: (event: { view: EditorView }) => boolean | void;\n onArrowUp?: (event: { view: EditorView }) => void;\n onArrowDown?: (event: { view: EditorView }) => void;\n};\n\n/**\n * Creates a popover that appears when the trigger character is inserted.\n * This can be used for context menus or autocompletion.\n */\nexport const popover = (options: PopoverOptions = {}): Extension => {\n return [\n Prec.highest(popoverKeymap(options)),\n popoverStateField,\n popoverTriggerListener(options),\n (options.activateOnTyping || options.activateOnDelimiters?.length) && popoverAutoActivate(options),\n popoverAnchorDecoration(options),\n modalStateField,\n options.trigger &&\n placeholder({\n // TODO(burdon): Translations.\n content: `Press '${Array.isArray(options.trigger) ? options.trigger[0] : options.trigger}' for commands`,\n focusOnly: true,\n ...options.placeholder,\n }),\n ].filter(isTruthy);\n};\n\n/**\n * Listen for selection and document changes.\n */\nconst popoverTriggerListener = (options: PopoverOptions) =>\n EditorView.updateListener.of(({ view, docChanged }) => {\n const { range: activeRange, trigger } = view.state.field(popoverStateField) ?? {};\n if (!activeRange) {\n return;\n }\n\n const text = view.state.doc.sliceString(activeRange.from, activeRange.to);\n const selection = view.state.selection.main;\n const shouldClose =\n // Trigger deleted.\n (trigger ? trigger !== text[0] : false) ||\n // Whitespace in text.\n /\\s/.test(trigger ? text.slice(1) : text) ||\n // Cursor moved before the range.\n selection.head < activeRange.from ||\n // Cursor moved after the range (+1 to handle selection changing before doc).\n selection.head > activeRange.to + 1;\n\n const nextRange = shouldClose ? null : docChanged ? { from: activeRange.from, to: selection.head } : activeRange;\n if (nextRange !== activeRange) {\n view.dispatch({\n effects: popoverRangeEffect.of(nextRange ? { range: nextRange, trigger } : null),\n });\n }\n\n if (shouldClose) {\n options.onClose?.({ view });\n }\n });\n\n/**\n * Open the popover while typing a word, without requiring a trigger character. Once a range is\n * active, {@link popoverTriggerListener} keeps it in sync and closes it (whitespace, cursor moves).\n */\nconst popoverAutoActivate = (options: PopoverOptions) =>\n EditorView.updateListener.of(({ view, docChanged, transactions }) => {\n // Only user input opens the popover — programmatic syncs (e.g. a controlled `value` update on\n // mount) must not pop the menu.\n if (!docChanged || !transactions.some((tr) => tr.isUserEvent('input')) || view.state.field(popoverStateField)) {\n return;\n }\n const selection = view.state.selection.main;\n if (!selection.empty) {\n return;\n }\n const line = view.state.doc.lineAt(selection.head);\n const before = line.text.slice(0, selection.head - line.from);\n const idx = getLastIndexOf(before, options.delimiters ?? DELIMITERS);\n const token = before.slice(idx + 1);\n // Two independent reasons to open: mid-word under `activateOnTyping`, or immediately after a\n // delimiter that has a list behind it. They are separate because opening on every word character\n // moves focus to the popover on each keystroke, which is intolerable in an editor whose menu is\n // empty for most positions.\n const onDelimiter = token.length === 0 && (options.activateOnDelimiters ?? []).includes(before[idx]);\n if (!onDelimiter && (!options.activateOnTyping || token.length === 0)) {\n return;\n }\n view.dispatch({\n effects: popoverRangeEffect.of({ range: { from: line.from + idx + 1, to: selection.head } }),\n });\n });\n\n/**\n * Popover navigation.\n */\nconst popoverKeymap = (options: PopoverOptions) => {\n const triggers = Array.isArray(options.trigger) ? options.trigger : [options.trigger];\n return keymap.of(\n [\n // Prefix triggers.\n ...triggers.filter(isNonNullable).map((trigger) => ({\n key: trigger,\n run: (view: EditorView) => {\n // Determine if we should trigger the popover:\n // 1. Empty lines or at the beginning of a line.\n // 2. When there's a preceding space.\n const selection = view.state.selection.main;\n const line = view.state.doc.lineAt(selection.head);\n if (\n line.text.trim() === '' ||\n selection.head === line.from ||\n (selection.head > line.from && line.text[selection.head - line.from - 1] === ' ')\n ) {\n // Insert and select the trigger.\n view.dispatch({\n changes: { from: selection.head, insert: trigger },\n selection: { anchor: selection.head + 1, head: selection.head + 1 },\n effects: popoverRangeEffect.of({ trigger, range: { from: selection.head, to: selection.head + 1 } }),\n });\n\n return true;\n }\n\n return false;\n },\n })),\n\n //\n // Custom trigger.\n //\n options.triggerKey &&\n ({\n key: options.triggerKey,\n run: (view: EditorView) => {\n const selection = view.state.selection.main;\n const line = view.state.doc.lineAt(selection.head);\n\n // Get last word.\n let str = line.text.slice(0, selection.head - line.from);\n const idx = getLastIndexOf(str, options.delimiters ?? DELIMITERS);\n if (idx !== -1) {\n str = str.slice(idx + 1);\n }\n\n // Create anchor even if zero length (append space).\n const from = line.from + idx;\n view.dispatch({\n effects: popoverRangeEffect.of({ range: { from: from + 1, to: selection.head } }),\n changes:\n selection.head === view.state.doc.length\n ? { from: from + 1, to: selection.head, insert: ' ' }\n : undefined,\n });\n return true;\n },\n } satisfies KeyBinding),\n\n //\n // Nav keys.\n //\n {\n key: 'ArrowUp',\n run: (view: EditorView) => {\n const range = view.state.field(popoverStateField)?.range;\n if (range) {\n options.onArrowUp?.({ view });\n return true;\n }\n\n return false;\n },\n },\n {\n key: 'ArrowDown',\n run: (view: EditorView) => {\n const range = view.state.field(popoverStateField)?.range;\n if (range) {\n options.onArrowDown?.({ view });\n return true;\n }\n\n return false;\n },\n },\n {\n key: 'Enter',\n run: (view: EditorView) => {\n const range = view.state.field(popoverStateField)?.range;\n if (range) {\n // The consumer deletes the range when it selects an item; when nothing is selectable\n // (empty menu over an `activateOnTyping` range) the key must fall through so the\n // typed text is preserved.\n return options.onEnter?.({ view }) ?? false;\n }\n\n return false;\n },\n },\n ].filter(isTruthy),\n );\n};\n\n/**\n * Creates a <dx-anchor> tag, which is used to anchor the Popver.\n */\nconst popoverAnchorDecoration = (options: PopoverOptions) => {\n return ViewPlugin.fromClass(\n class {\n _decorations: DecorationSet = Decoration.none;\n\n constructor(readonly view: EditorView) {}\n\n // TODO(wittjosiah): The decorations are repainted on every update, this occasionally causes menu to flicker.\n update({ view, transactions }: ViewUpdate) {\n const builder = new RangeSetBuilder<Decoration>();\n const { range, trigger } = view.state.field(popoverStateField) ?? {};\n if (range) {\n // Check if we should show the widget (only if cursor is within the active command range).\n const selection = view.state.selection.main;\n const showWidget = selection.head >= range.from && selection.head <= range.to;\n if (showWidget) {\n builder.add(\n range.from,\n range.to + 1,\n Decoration.mark({\n tagName: 'dx-anchor',\n class: 'cm-popover-trigger',\n attributes: {\n 'data-visible-focus': 'false',\n 'data-auto-trigger': 'true',\n 'data-trigger': trigger ?? options.triggerKey ?? '',\n },\n }),\n );\n }\n\n const rangeChanged = transactions.some((tr) => tr.effects.some((effect) => effect.is(popoverRangeEffect)));\n if (rangeChanged) {\n // NOTE: Content skips the trigger character.\n const content = view.state.sliceDoc(range.from + (trigger ? trigger.length : 0), range.to);\n options.onTextChange?.({ view, pos: selection.head, text: content, trigger });\n }\n }\n\n this._decorations = builder.finish();\n }\n },\n {\n decorations: (v) => v._decorations,\n },\n );\n};\n\ntype PopoverState = {\n /**\n * Trigger prefix (in document).\n */\n trigger?: string;\n\n /**\n * Current document completion range.\n */\n range: Range;\n};\n\n// State effects for managing popover state.\nexport const popoverRangeEffect = StateEffect.define<PopoverState | null>();\n\n// State field to track the active popover trigger range.\nexport const popoverStateField = StateField.define<PopoverState | null>({\n create: () => null,\n update: (value, tr) => {\n let newValue = value;\n for (const effect of tr.effects) {\n if (effect.is(popoverRangeEffect)) {\n newValue = effect.value;\n }\n }\n\n return newValue;\n },\n});\n\nconst getLastIndexOf = (str: string, delimiters: string[]) =>\n Math.max(...delimiters.map((delim) => str.lastIndexOf(delim)));\n","//\n// Copyright 2025 DXOS.org\n//\n\nimport { type EditorView } from '@codemirror/view';\n\nimport { insertAtLineStart, insertTable } from '@dxos/ui-editor';\n\nimport { type EditorMenuGroup } from './menu.ts';\nimport { popoverRangeEffect } from './popover.ts';\n\n/**\n * Re-open the popover in link-query (\"@\") mode at the cursor, seeding the same query shape the user\n * would get by typing the trigger manually: a single \"@\" for an inline link, \"@@\" for a block embed.\n * The consumer's `@` handler (e.g. plugin-markdown's link query) renders the object picker and, on\n * selection, replaces this range with the corresponding markdown link.\n *\n * Deferred to the next frame: selecting a slash-menu item deletes the \"/\" range, which closes the\n * popover; opening synchronously races that close (`onActivate` reads a stale open state) and the\n * picker never appears.\n */\nconst openLinkQuery = (view: EditorView, insert: '@' | '@@'): void => {\n requestAnimationFrame(() => {\n const { head } = view.state.selection.main;\n const to = head + insert.length;\n view.dispatch({\n changes: { from: head, insert },\n selection: { anchor: to, head: to },\n // The popover trigger is the single \"@\"; a second \"@\" lives in the query range and switches to block mode.\n effects: popoverRangeEffect.of({ trigger: '@', range: { from: head, to } }),\n });\n });\n};\n\nexport const formattingCommands: EditorMenuGroup = {\n id: 'markdown',\n label: 'Markdown',\n items: [\n {\n id: 'heading-1',\n label: 'Heading 1',\n icon: 'ph--text-h-one--regular',\n onSelect: ({ view, head }) => insertAtLineStart(view, head, '# '),\n },\n {\n id: 'heading-2',\n label: 'Heading 2',\n icon: 'ph--text-h-two--regular',\n onSelect: ({ view, head }) => insertAtLineStart(view, head, '## '),\n },\n {\n id: 'heading-3',\n label: 'Heading 3',\n icon: 'ph--text-h-three--regular',\n onSelect: ({ view, head }) => insertAtLineStart(view, head, '### '),\n },\n {\n id: 'heading-4',\n label: 'Heading 4',\n icon: 'ph--text-h-four--regular',\n onSelect: ({ view, head }) => insertAtLineStart(view, head, '#### '),\n },\n {\n id: 'heading-5',\n label: 'Heading 5',\n icon: 'ph--text-h-five--regular',\n onSelect: ({ view, head }) => insertAtLineStart(view, head, '##### '),\n },\n {\n id: 'heading-6',\n label: 'Heading 6',\n icon: 'ph--text-h-six--regular',\n onSelect: ({ view, head }) => insertAtLineStart(view, head, '###### '),\n },\n {\n id: 'bullet-list',\n label: 'Bullet List',\n icon: 'ph--list-bullets--regular',\n onSelect: ({ view, head }) => insertAtLineStart(view, head, '- '),\n },\n {\n id: 'numbered-list',\n label: 'Numbered List',\n icon: 'ph--list-numbers--regular',\n onSelect: ({ view, head }) => insertAtLineStart(view, head, '1. '),\n },\n {\n id: 'task-list',\n label: 'Task List',\n icon: 'ph--list-checks--regular',\n onSelect: ({ view, head }) => insertAtLineStart(view, head, '- [ ] '),\n },\n {\n id: 'quote',\n label: 'Quote',\n icon: 'ph--quotes--regular',\n onSelect: ({ view, head }) => insertAtLineStart(view, head, '> '),\n },\n {\n id: 'code-block',\n label: 'Code Block',\n icon: 'ph--code-block--regular',\n onSelect: ({ view, head }) => insertAtLineStart(view, head, '```\\n\\n```'),\n },\n {\n id: 'table',\n // Shares the toolbar's snippet rather than a literal of its own: two spellings of \"insert a\n // table\" drifted into different shapes (3 empty columns here, 2 placeholder-filled ones there).\n label: 'Table',\n icon: 'ph--table--regular',\n onSelect: ({ view, head }) => insertTable(view, head),\n },\n ],\n};\n\nexport const linkSlashCommands: EditorMenuGroup = {\n id: 'link',\n label: 'Link',\n items: [\n {\n id: 'inline-link',\n label: 'Inline link',\n icon: 'ph--link--regular',\n onSelect: ({ view }) => openLinkQuery(view, '@'),\n },\n {\n id: 'inline-object',\n label: 'Inline object',\n icon: 'ph--lego--regular',\n onSelect: ({ view }) => openLinkQuery(view, '@@'),\n },\n ],\n};\n","//\n// Copyright 2025 DXOS.org\n//\n\nimport { type EditorView } from '@codemirror/view';\nimport React, {\n Fragment,\n type KeyboardEvent,\n type PropsWithChildren,\n useCallback,\n useEffect,\n useRef,\n useState,\n} from 'react';\n\nimport { addEventListener } from '@dxos/async';\nimport { invariant } from '@dxos/invariant';\nimport { useControllableState } from '@dxos/react-hooks';\nimport * as Field from '@dxos/react-ui/Field';\nimport * as Hooks from '@dxos/react-ui/Hooks';\nimport * as Icon from '@dxos/react-ui/Icon';\nimport * as Input from '@dxos/react-ui/Input';\nimport * as Layout from '@dxos/react-ui/Layout';\nimport * as Popover from '@dxos/react-ui/Popover';\nimport * as Theme from '@dxos/react-ui/Theme';\nimport * as Typography from '@dxos/react-ui/Typography';\nimport * as VirtualAnchor from '@dxos/react-ui/VirtualAnchor';\nimport { DX_ANCHOR_ACTIVATE, type DxAnchorActivate } from '@dxos/ui-types';\n\nimport { translationKey } from '#translations';\n\nimport { type EditorMenuGroup, type EditorMenuItem, getMenuItem } from './menu.ts';\n\nexport type EditorMenuProviderProps = PropsWithChildren<{\n // Provided as a getter (not a value prop) so the live `EditorView` is never carried in a React prop that\n // the dev render-logger would walk into a cross-origin frame. See `controller.ts` for the full rationale.\n getView?: () => EditorView | null;\n groups?: EditorMenuGroup[];\n currentItem?: string;\n open?: boolean;\n defaultOpen?: boolean;\n numItems?: number;\n /** Render the menu as a combobox: the popover owns the query input and takes focus from the editor. */\n search?: boolean;\n query?: string;\n searchPlaceholder?: string;\n onOpenChange?: (event: { view: EditorView; open: boolean; trigger?: string }) => void;\n onActivate?: (event: { view: EditorView; trigger?: string }) => void;\n onSelect?: (event: { view: EditorView; item: EditorMenuItem }) => void;\n onCancel?: (event: { view: EditorView }) => void;\n onQueryChange?: (query: string) => void;\n onNavigate?: (direction: 'up' | 'down') => void;\n}>;\n\n/**\n * Implements the Popover and listens for the `dx-anchor-activate` event from the `popover` extension's decoration.\n * NOTE: We don't use Menu because the command menu needs to manage focus explicitly.\n * I.e., focus must remain in the editor while displaying the menu (for type-ahead).\n */\nexport const EditorMenuProvider = ({\n children,\n getView,\n groups,\n currentItem,\n open: openProp,\n defaultOpen,\n numItems = 8,\n search,\n query = '',\n searchPlaceholder,\n onOpenChange,\n onActivate,\n onSelect,\n onCancel,\n onQueryChange,\n onNavigate,\n}: EditorMenuProviderProps) => {\n const { t } = Hooks.useTranslation(translationKey);\n const triggerRef = useRef<HTMLButtonElement | null>(null);\n\n // Hold the latest `getView` so callbacks/effects always read the current view without re-subscribing.\n const getViewRef = Hooks.useDynamicRef(getView);\n const [open, setOpen] = useControllableState({\n prop: openProp,\n defaultProp: defaultOpen,\n onChange: (open) => {\n const view = getViewRef.current?.();\n invariant(view);\n onOpenChange?.({ view, open });\n },\n });\n\n const [root, setRoot] = useState<HTMLDivElement | null>(null);\n useEffect(() => {\n if (!root) {\n return;\n }\n\n // Listen for trigger.\n return addEventListener(\n root,\n DX_ANCHOR_ACTIVATE as any,\n (event: DxAnchorActivate) => {\n const { trigger, eid } = event;\n if (!eid) {\n triggerRef.current = trigger as HTMLButtonElement;\n if (onActivate) {\n const view = getViewRef.current?.();\n if (view) {\n onActivate({ view, trigger: trigger.getAttribute('data-trigger') ?? undefined });\n }\n } else {\n requestAnimationFrame(() => setOpen(true));\n }\n }\n },\n {\n capture: true,\n passive: false,\n },\n );\n }, [root, onActivate]);\n\n const handleSelect = useCallback<NonNullable<MenuProps['onSelect']>>(\n (item) => {\n const view = getViewRef.current?.();\n invariant(view);\n onSelect?.({ view, item });\n // Close the popover after acting (e.g. Delete) and return focus to the editor; otherwise the menu\n // lingers open over the now-changed document.\n setOpen(false);\n view.focus();\n },\n [getViewRef, onSelect, setOpen],\n );\n\n const menuGroups = groups?.filter((group) => group.items.length > 0) ?? [];\n\n // Handing off from the slash menu refocuses the editor and reopens the popover a frame later, so\n // neither `autoFocus` nor `onOpenAutoFocus` lands on the input. Claim focus a frame after the\n // modal-state dispatch `handleOpenChange` schedules, which otherwise hands focus back to CodeMirror.\n const searchInputRef = useRef<HTMLInputElement>(null);\n useEffect(() => {\n if (!search || !open) {\n return;\n }\n\n let inner: number;\n const outer = requestAnimationFrame(() => {\n inner = requestAnimationFrame(() => searchInputRef.current?.focus());\n });\n\n return () => {\n cancelAnimationFrame(outer);\n cancelAnimationFrame(inner);\n };\n }, [search, open]);\n\n const handleSearchKeyDown = useCallback(\n (event: KeyboardEvent<HTMLInputElement>) => {\n switch (event.key) {\n case 'ArrowUp':\n case 'ArrowDown': {\n event.preventDefault();\n onNavigate?.(event.key === 'ArrowUp' ? 'up' : 'down');\n break;\n }\n\n case 'Enter': {\n const item = getMenuItem(menuGroups, currentItem);\n if (item) {\n event.preventDefault();\n handleSelect(item);\n }\n break;\n }\n }\n },\n [menuGroups, currentItem, handleSelect, onNavigate],\n );\n\n return (\n <Popover.Root\n open={open}\n onOpenChange={({ open }) => setOpen(open)}\n positioning={{ ...VirtualAnchor.virtualAnchor(triggerRef), placement: 'bottom-start' }}\n // In search mode the query is typed into the popover's own input, so it must take focus.\n autoFocus={!!search}\n // Focus stays in the editor; the menu machine still routes Escape here.\n onEscapeKeyDown={() => {\n const currentView = getViewRef.current?.();\n if (currentView) {\n onCancel?.({ view: currentView });\n }\n }}\n >\n {/* Menu. */}\n <Popover.Content\n classNames={!search && !menuGroups.length ? 'hidden' : undefined}\n style={{\n // The search input shares the box, so `numItems` keeps meaning \"items visible\".\n maxBlockSize: 36 * numItems + 10 + (search ? 36 : 0),\n }}\n >\n {search && (\n <Field.Root>\n <Input.Root\n ref={searchInputRef}\n variant='subdued'\n classNames='shrink-0 mb-1'\n value={query}\n placeholder={searchPlaceholder}\n // Placeholder text is not a persistent accessible name, and it is optional — fall\n // back to the generic label so the input is never anonymous.\n aria-label={searchPlaceholder ?? t('search.label')}\n onChange={(event) => onQueryChange?.(event.target.value)}\n onKeyDown={handleSearchKeyDown}\n />\n </Field.Root>\n )}\n <Popover.Body>\n <Menu groups={menuGroups} currentItem={currentItem} onSelect={handleSelect} />\n </Popover.Body>\n </Popover.Content>\n\n {/* Content */}\n <div className='contents' ref={setRoot}>\n {children}\n </div>\n </Popover.Root>\n );\n};\n\n//\n// Menu\n//\n\ntype MenuProps = {\n groups: EditorMenuGroup[];\n} & Pick<MenuGroupProps, 'currentItem' | 'onSelect'>;\n\nconst Menu = ({ groups, currentItem, onSelect }: MenuProps) => {\n return (\n <ul>\n {groups.map((group, index) => (\n <Fragment key={group.id}>\n <MenuGroup group={group} currentItem={currentItem} onSelect={onSelect} />\n {index < groups.length - 1 && <Layout.Separator />}\n </Fragment>\n ))}\n </ul>\n );\n};\n\n//\n// Menu Group\n//\n\ntype MenuGroupProps = {\n group: EditorMenuGroup;\n currentItem?: string;\n} & Pick<MenuItemProps, 'onSelect'>;\n\nconst MenuGroup = ({ group, currentItem, onSelect }: MenuGroupProps) => {\n const { t } = Hooks.useTranslation();\n\n return (\n <>\n {group.label && (\n <Typography.Text tone='muted' classNames='px-2'>\n {Theme.toLocalizedString(group.label, t)}\n </Typography.Text>\n )}\n\n {group.items.map((item) => (\n <MenuItem key={item.id} item={item} current={currentItem === item.id} onSelect={onSelect} />\n ))}\n </>\n );\n};\n\n//\n// Menu Item\n//\n\ntype MenuItemProps = {\n item: EditorMenuItem;\n current: boolean;\n onSelect?: (item: EditorMenuItem) => void;\n};\n\nconst MenuItem = ({ item, current, onSelect }: MenuItemProps) => {\n const { t } = Hooks.useTranslation();\n\n const listRef = useRef<HTMLLIElement>(null);\n useEffect(() => {\n if (current && listRef.current) {\n // Instant, not smooth: held arrow keys retarget faster than a smooth scroll can settle, and each\n // new animation supersedes the last, so the list stalls then jumps.\n listRef.current.scrollIntoView({ behavior: 'instant', block: 'nearest' });\n }\n }, [current]);\n\n const handleSelect = useCallback(() => onSelect?.(item), [item, onSelect]);\n\n return (\n // Menu row metrics without a Menu machine: the popover keeps focus in the editor, so `current` is the highlight.\n <li ref={listRef} className='dx-menu-item' data-highlighted={current ? '' : undefined} onClick={handleSelect}>\n {item.icon && <Icon.Icon icon={item.icon} />}\n <span className='dx-menu-item-text'>{Theme.toLocalizedString(item.label, t)}</span>\n </li>\n );\n};\n","//\n// Copyright 2024 DXOS.org\n//\n\nimport { type Extension } from '@codemirror/state';\nimport { type EditorState } from '@codemirror/state';\nimport { type EditorView } from '@codemirror/view';\nimport { type RefObject, useCallback, useMemo, useRef, useState } from 'react';\n\nimport { invariant } from '@dxos/invariant';\nimport { modalStateEffect } from '@dxos/ui-editor';\nimport { type MaybePromise } from '@dxos/util';\n\nimport { type EditorMenuProviderProps } from './EditorMenuProvider.tsx';\nimport { type EditorMenuGroup, type EditorMenuItem } from './menu.ts';\nimport { filterMenuGroups, getMenuItem, getNextMenuItem, getPreviousMenuItem } from './menu.ts';\nimport { type PopoverOptions, popover, popoverRangeEffect, popoverStateField } from './popover.ts';\n\nexport type GetMenuContext = {\n state: EditorState;\n pos: number;\n text: string;\n trigger?: string;\n};\n\nexport type UseEditorMenuProps = {\n filter?: boolean;\n /**\n * Triggers whose menu is rendered as a combobox: the popover owns a search input and the query is\n * typed there rather than into the document.\n */\n searchTriggers?: string[];\n getMenu?: (context: GetMenuContext) => MaybePromise<EditorMenuGroup[]>;\n} & Pick<PopoverOptions, 'trigger' | 'triggerKey' | 'placeholder' | 'activateOnTyping' | 'activateOnDelimiters'>;\n\nexport type UseEditorMenu = {\n groupsRef: RefObject<EditorMenuGroup[]>;\n extension: Extension;\n} & Pick<\n EditorMenuProviderProps,\n | 'currentItem'\n | 'open'\n | 'search'\n | 'query'\n | 'onOpenChange'\n | 'onActivate'\n | 'onSelect'\n | 'onCancel'\n | 'onQueryChange'\n | 'onNavigate'\n>;\n\n/**\n * ```tsx\n * const { groupsRef, extension, ...menuProps } = useEditorMenu();\n * const { parentRef, viewRef } = useTextEditor({ extensions: [extension] });\n * return (\n * <EditorMenuProvider getView={() => viewRef.current} groups={groupsRef.current} {...menuProps}>\n * <div ref={parentRef} />\n * </EditorMenuProvider>\n * );\n * ```\n */\nexport const useEditorMenu = ({\n trigger,\n triggerKey,\n placeholder,\n activateOnTyping,\n activateOnDelimiters,\n filter = true,\n searchTriggers,\n getMenu,\n}: UseEditorMenuProps): UseEditorMenu => {\n const groupsRef = useRef<EditorMenuGroup[]>([]);\n const currentRef = useRef<EditorMenuItem | null>(null);\n const [currentItem, setCurrentItem] = useState<string>();\n const [open, setOpen] = useState(false);\n const [search, setSearch] = useState(false);\n const [query, setQuery] = useState('');\n const [_, refresh] = useState({});\n\n // The document range only supplies the trigger-relative prefix (e.g. the second \"@\" of a block\n // embed); in search mode the rest of the query comes from the popover's input.\n const contextRef = useRef<{ view: EditorView; pos: number; text: string; trigger?: string } | null>(null);\n\n // Monotonic token identifying the latest menu query; bumped per request and on close.\n const requestRef = useRef(0);\n\n /**\n * Get filtered options.\n */\n const getMenuOptions = useCallback<NonNullable<UseEditorMenuProps['getMenu']>>(\n async ({ text, trigger, ...props }) => {\n const groups = (await getMenu?.({ text, trigger, ...props })) ?? [];\n // The \"@\" menu can use \"@@\" as syntax for block embeds, so it owns its own query filtering.\n return filter && trigger !== '@'\n ? filterMenuGroups(groups, (item) =>\n text ? (item.label as string).toLowerCase().startsWith(text.toLowerCase()) : true,\n )\n : groups;\n },\n [getMenu, filter],\n );\n\n /**\n * Recompute the menu and preselect the first item.\n */\n const updateGroups = useCallback(\n async ({ view, pos, text, trigger }: { view: EditorView; pos: number; text: string; trigger?: string }) => {\n const request = ++requestRef.current;\n const groups = (await getMenuOptions({ state: view.state, pos, text, trigger })) ?? [];\n // Queries resolve out of order, so a slower earlier one must not overwrite a newer result\n // (or repopulate the menu after it closed).\n if (request !== requestRef.current) {\n return;\n }\n\n groupsRef.current = groups;\n const firstItem = groupsRef.current.filter((group) => group.items.length > 0)[0]?.items[0];\n if (firstItem) {\n setCurrentItem(firstItem.id);\n currentRef.current = firstItem;\n } else {\n // No matches: clear the selection so Enter falls through instead of\n // selecting a stale item from a previous query.\n setCurrentItem(undefined);\n currentRef.current = null;\n }\n\n refresh({});\n },\n [getMenuOptions],\n );\n\n const handleOpenChange = useCallback<NonNullable<UseEditorMenu['onOpenChange']>>(\n async ({ view, open }) => {\n invariant(view);\n setOpen(open);\n if (!open) {\n setCurrentItem(undefined);\n setSearch(false);\n setQuery('');\n contextRef.current = null;\n // Invalidate in-flight queries so a late response cannot repopulate the closed menu.\n requestRef.current++;\n groupsRef.current = [];\n currentRef.current = null;\n view.dispatch({\n effects: [popoverRangeEffect.of(null)],\n });\n }\n\n // TODO(burdon): Possible race condition.\n // useTextEditor.handleKeyDown will get called after this handler completes.\n requestAnimationFrame(() => {\n view.dispatch({\n effects: [modalStateEffect.of(open)],\n });\n });\n },\n [getMenuOptions],\n );\n\n const handleActivate = useCallback<NonNullable<UseEditorMenu['onActivate']>>(\n async ({ view, trigger }) => {\n const item = getMenuItem(groupsRef.current, currentItem);\n if (item) {\n currentRef.current = item;\n }\n\n if (!open) {\n handleOpenChange({ view, open: true, trigger });\n }\n },\n [open, handleOpenChange],\n );\n\n const handleSelect = useCallback<NonNullable<UseEditorMenu['onSelect']>>(({ view, item }) => {\n // Delete trigger range (e.g., \"/\" and any typed filter text).\n const { range } = view.state.field(popoverStateField) ?? {};\n if (range) {\n view.dispatch({ changes: { from: range.from, to: range.to, insert: '' } });\n }\n void item.onSelect?.({ view, head: view.state.selection.main.head });\n view.focus();\n }, []);\n\n const handleCancel = useCallback<NonNullable<UseEditorMenu['onCancel']>>(({ view }) => {\n // Delete trigger.\n const { range, trigger } = view.state.field(popoverStateField) ?? {};\n if (range && trigger) {\n view.dispatch({\n changes: { ...range, insert: '' },\n });\n }\n }, []);\n\n const handleQueryChange = useCallback<NonNullable<UseEditorMenu['onQueryChange']>>(\n (query) => {\n setQuery(query);\n const context = contextRef.current;\n if (context) {\n void updateGroups({ ...context, text: context.text + query });\n }\n },\n [updateGroups],\n );\n\n const handleNavigate = useCallback<NonNullable<UseEditorMenu['onNavigate']>>((direction) => {\n setCurrentItem((currentItem) => {\n const next =\n direction === 'up'\n ? getPreviousMenuItem(groupsRef.current, currentItem)\n : getNextMenuItem(groupsRef.current, currentItem);\n // A query that matches nothing leaves an empty menu; arrow keys then have nowhere to go.\n if (!next) {\n return currentItem;\n }\n\n currentRef.current = next;\n return next.id;\n });\n }, []);\n\n // Array props are compared by CONTENT, not identity: a caller writing `trigger={['#']}` inline\n // hands over a new array every render, and a changed extension destroys and recreates the view —\n // blurring the editor mid-keystroke.\n const serializedTrigger = Array.isArray(trigger) ? trigger.join(',') : trigger;\n const serializedSearchTriggers = searchTriggers?.join(',');\n const serializedDelimiters = activateOnDelimiters?.join(',');\n const extension = useMemo<Extension>(() => {\n return popover({\n trigger,\n triggerKey,\n placeholder,\n activateOnTyping,\n activateOnDelimiters,\n onClose: ({ view }) => handleOpenChange({ view, open: false }),\n onEnter: ({ view }) => {\n if (currentRef.current) {\n handleSelect({ view, item: currentRef.current });\n return true;\n }\n return false;\n },\n onArrowUp: () => handleNavigate('up'),\n onArrowDown: () => handleNavigate('down'),\n onTextChange: async ({ view, pos, text, trigger }) => {\n contextRef.current = { view, pos, text, trigger };\n // The input is remounted per activation, so the query always starts empty.\n setSearch(!!trigger && !!searchTriggers?.includes(trigger));\n setQuery('');\n await updateGroups({ view, pos, text, trigger });\n },\n });\n }, [\n handleOpenChange,\n handleNavigate,\n updateGroups,\n serializedTrigger,\n serializedSearchTriggers,\n serializedDelimiters,\n placeholder,\n activateOnTyping,\n ]);\n\n return {\n groupsRef,\n extension,\n currentItem,\n open,\n search,\n query,\n onOpenChange: handleOpenChange,\n onActivate: handleActivate,\n onSelect: handleSelect,\n onCancel: handleCancel,\n onQueryChange: handleQueryChange,\n onNavigate: handleNavigate,\n };\n};\n","//\n// Copyright 2025 DXOS.org\n//\n\nimport { type EditorView } from '@codemirror/view';\n\nimport type { ActionGroupBuilderFn, ToolbarMenuActionGroupProperties } from '@dxos/react-ui-menu';\nimport { addBlockquote, addCodeblock, insertTable, removeBlockquote, removeCodeblock } from '@dxos/ui-editor';\n\nimport { translationKey } from '#translations';\n\nimport { type EditorToolbarState } from './types.ts';\n\nconst blockTypes = {\n blockquote: 'ph--quotes--regular',\n codeblock: 'ph--code-block--regular',\n table: 'ph--table--regular',\n};\n\n/** Add block actions to the builder. */\nexport const addBlocks =\n (state: EditorToolbarState, getView: () => EditorView): ActionGroupBuilderFn =>\n (builder) => {\n const value = state?.blockQuote ? 'blockquote' : (state.blockType ?? '');\n builder.group(\n 'block',\n {\n label: ['block.label', { ns: translationKey }],\n iconOnly: true,\n variant: 'toggleGroup',\n selectCardinality: 'single',\n value,\n } as ToolbarMenuActionGroupProperties,\n (group) => {\n for (const [type, icon] of Object.entries(blockTypes)) {\n const checked = type === value;\n group.action(\n type,\n {\n label: [`block.${type}.label`, { ns: translationKey }],\n checked,\n // A table replaces the line it lands on, so it is offered on a blank one and withheld\n // where it would clobber content — the condition was inverted.\n ...(type === 'table' && { disabled: !state.blankLine }),\n icon,\n },\n () => {\n const view = getView();\n if (!view) {\n return;\n }\n\n switch (type) {\n case 'blockquote':\n checked ? removeBlockquote(view) : addBlockquote(view);\n break;\n case 'codeblock':\n checked ? removeCodeblock(view) : addCodeblock(view);\n break;\n case 'table':\n insertTable(view);\n break;\n }\n },\n );\n }\n },\n );\n };\n","//\n// Copyright 2025 DXOS.org\n//\n\nimport { type EditorView } from '@codemirror/view';\n\nimport type { ActionGroupBuilderFn, ToolbarMenuActionGroupProperties } from '@dxos/react-ui-menu';\nimport { type Formatting, Inline, addLink, removeLink, setStyle } from '@dxos/ui-editor';\n\nimport { translationKey } from '#translations';\n\nimport { type EditorToolbarState } from './types.ts';\n\nconst formats = {\n strong: 'ph--text-b--regular',\n emphasis: 'ph--text-italic--regular',\n strikethrough: 'ph--text-strikethrough--regular',\n code: 'ph--code--regular',\n link: 'ph--link--regular',\n};\n\n/** Add formatting actions to the builder. */\nexport const addFormatting =\n (state: EditorToolbarState, getView: () => EditorView): ActionGroupBuilderFn =>\n (builder) => {\n const formatting: Formatting = state;\n builder.group(\n 'formatting',\n {\n label: ['formatting.label', { ns: translationKey }],\n iconOnly: true,\n variant: 'toggleGroup',\n selectCardinality: 'multiple',\n value: Object.keys(formats).filter((key) => !!formatting[key as keyof Formatting]),\n } as ToolbarMenuActionGroupProperties,\n (group) => {\n for (const [type, icon] of Object.entries(formats)) {\n const checked = !!formatting[type as keyof Formatting];\n group.action(\n type,\n {\n label: [`formatting.${type}.label`, { ns: translationKey }],\n testId: `editor.toolbar.${type}`,\n checked,\n icon,\n },\n () => {\n const view = getView();\n if (!view) {\n return;\n }\n\n if (type === 'link') {\n checked ? removeLink(view) : addLink()(view);\n return;\n }\n\n setStyle(\n type === 'strong'\n ? Inline.Strong\n : type === 'emphasis'\n ? Inline.Emphasis\n : type === 'strikethrough'\n ? Inline.Strikethrough\n : Inline.Code,\n !checked,\n )(view);\n },\n );\n }\n },\n );\n };\n","//\n// Copyright 2025 DXOS.org\n//\n\nimport { type EditorView } from '@codemirror/view';\n\nimport type { ActionGroupBuilderFn, ToolbarMenuActionGroupProperties } from '@dxos/react-ui-menu';\nimport { setHeading } from '@dxos/ui-editor';\n\nimport { translationKey } from '#translations';\n\nimport { type EditorToolbarState } from './types.ts';\n\nconst headingIcons: Record<string, string> = {\n 0: 'ph--paragraph--regular',\n 1: 'ph--text-h-one--regular',\n 2: 'ph--text-h-two--regular',\n 3: 'ph--text-h-three--regular',\n 4: 'ph--text-h-four--regular',\n 5: 'ph--text-h-five--regular',\n 6: 'ph--text-h-six--regular',\n};\n\nconst computeHeadingValue = (state: EditorToolbarState) => {\n const blockType = state ? state.blockType : 'paragraph';\n const heading = blockType && /heading(\\d)/.exec(blockType);\n return heading ? heading[1] : blockType === 'paragraph' || !blockType ? '0' : '';\n};\n\n/** Add heading actions to the builder. */\nexport const addHeadings =\n (state: EditorToolbarState, getView: () => EditorView): ActionGroupBuilderFn =>\n (builder) => {\n const headingValue = computeHeadingValue(state);\n builder.group(\n 'heading',\n {\n label: ['heading.label', { ns: translationKey }],\n icon: 'ph--text-h--regular',\n iconOnly: true,\n variant: 'dropdownMenu',\n applyActive: true,\n selectCardinality: 'single',\n // TODO(wittjosiah): Remove? Not sure this does anything.\n value: headingValue,\n } as ToolbarMenuActionGroupProperties,\n (group) => {\n for (const [levelStr, icon] of Object.entries(headingIcons)) {\n const level = parseInt(levelStr);\n group.action(\n `heading--${levelStr}`,\n {\n label: ['heading-level.label', { count: level, ns: translationKey }],\n icon,\n checked: levelStr === headingValue,\n },\n () => setHeading(level)(getView()),\n );\n }\n },\n );\n };\n","//\n// Copyright 2025 DXOS.org\n//\n\nimport type { ActionGroupBuilderFn } from '@dxos/react-ui-menu';\n\nimport { translationKey } from '#translations';\n\n/** Add image upload action to the builder. */\nexport const addImageUpload =\n (onImageUpload: () => void): ActionGroupBuilderFn =>\n (builder) => {\n builder.action(\n 'image',\n {\n label: ['image.label', { ns: translationKey }],\n testId: 'editor.toolbar.image',\n icon: 'ph--image-square--regular',\n },\n onImageUpload,\n );\n };\n","//\n// Copyright 2025 DXOS.org\n//\n\nimport { type EditorView } from '@codemirror/view';\n\nimport type { ActionGroupBuilderFn, ToolbarMenuActionGroupProperties } from '@dxos/react-ui-menu';\nimport { List, toggleList } from '@dxos/ui-editor';\n\nimport { translationKey } from '#translations';\n\nimport { type EditorToolbarState } from './types.ts';\n\nconst listStyles = {\n bullet: 'ph--list-bullets--regular',\n ordered: 'ph--list-numbers--regular',\n task: 'ph--list-checks--regular',\n};\n\n/** Add list actions to the builder. */\nexport const addLists =\n (state: EditorToolbarState, getView: () => EditorView): ActionGroupBuilderFn =>\n (builder) => {\n const value = state.listStyle ?? '';\n builder.group(\n 'list',\n {\n label: ['list.label', { ns: translationKey }],\n iconOnly: true,\n variant: 'toggleGroup',\n selectCardinality: 'single',\n value,\n } as ToolbarMenuActionGroupProperties,\n (group) => {\n for (const [listStyle, icon] of Object.entries(listStyles)) {\n const checked = value === listStyle;\n group.action(\n `list-${listStyle}`,\n { label: [`list.${listStyle}.label`, { ns: translationKey }], checked, icon },\n () => {\n const view = getView();\n if (!view) {\n return;\n }\n\n const listType =\n listStyle === 'ordered' ? List.Ordered : listStyle === 'bullet' ? List.Bullet : List.Task;\n toggleList(listType)(view);\n },\n );\n }\n },\n );\n };\n","//\n// Copyright 2025 DXOS.org\n//\n\nimport { openSearchPanel } from '@codemirror/search';\nimport { type EditorView } from '@codemirror/view';\n\nimport type { ActionGroupBuilderFn } from '@dxos/react-ui-menu';\n\nimport { translationKey } from '#translations';\n\n/** Add search action to the builder. */\nexport const addSearch =\n (getView: () => EditorView): ActionGroupBuilderFn =>\n (builder) => {\n builder.action(\n 'search',\n {\n label: ['search.label', { ns: translationKey }],\n testId: 'editor.toolbar.search',\n icon: 'ph--magnifying-glass--regular',\n },\n () => openSearchPanel(getView()),\n );\n };\n","//\n// Copyright 2025 DXOS.org\n//\n\nimport type { ActionGroupBuilderFn, ToolbarMenuActionGroupProperties } from '@dxos/react-ui-menu';\nimport { type EditorViewMode } from '@dxos/ui-editor/types';\nimport { type Label } from '@dxos/ui-types';\n\nimport { translationKey } from '#translations';\n\nimport { type EditorToolbarState } from './types.ts';\n\n/**\n * One entry in the editor's view-mode dropdown. A built-in entry references an {@link EditorViewMode}\n * by `id` and relies on the defaults (label `view-mode.<id>.label`, checked when it is the active view\n * mode, select via `onViewModeChange`). A contributed entry (e.g. a review \"Suggesting\" mode) supplies\n * its own `label`, `checked`, and `onSelect` so it can drive state other than the editor view mode.\n */\nexport type ViewModeItem = {\n id: string;\n icon: string;\n label?: Label;\n checked?: boolean;\n onSelect?: () => void;\n};\n\n/**\n * The built-in view modes: preview (rendered), source (raw edit), readonly (non-editable source). Typed\n * so each `id` is an {@link EditorViewMode}, letting callers pass `item.id` to `onViewModeChange` without a cast.\n */\nexport const defaultViewModeItems: Array<ViewModeItem & { id: EditorViewMode }> = [\n { id: 'preview', icon: 'ph--eye--regular' },\n { id: 'source', icon: 'ph--pencil-simple--regular' },\n { id: 'readonly', icon: 'ph--pencil-slash--regular' },\n];\n\n/**\n * Add view mode actions to the builder. `items` defaults to the three built-in editor view modes;\n * callers pass an extended list to surface contributed modes (see {@link ViewModeItem}) in the same\n * single-select dropdown. The trigger tracks the checked item (via the group's `applyActive`).\n */\nexport const addViewMode =\n (\n state: EditorToolbarState,\n onViewModeChange: (mode: EditorViewMode) => void,\n items: ViewModeItem[] = defaultViewModeItems,\n ): ActionGroupBuilderFn =>\n (builder) => {\n const value = state.viewMode ?? 'source';\n builder.group(\n 'viewMode',\n {\n label: ['view-mode.label', { ns: translationKey }],\n testId: 'editor.toolbar.viewMode',\n icon: 'ph--eye--regular',\n iconOnly: true,\n variant: 'dropdownMenu',\n applyActive: true,\n selectCardinality: 'single',\n value,\n } as ToolbarMenuActionGroupProperties,\n (group) => {\n for (const item of items) {\n // A contributed entry may drive non-view-mode state, so honour its explicit `checked`;\n // a built-in entry is checked when it matches the active editor view mode.\n const checked = item.checked ?? item.id === value;\n group.action(\n `view-mode--${item.id}`,\n {\n label: item.label ?? [`view-mode.${item.id}.label`, { ns: translationKey }],\n checked,\n icon: item.icon,\n },\n () => (item.onSelect ? item.onSelect() : onViewModeChange(item.id as EditorViewMode)),\n );\n }\n },\n );\n };\n","//\n// Copyright 2024 DXOS.org\n//\n\nimport { type EditorView } from '@codemirror/view';\nimport * as Atom from 'effect/reactivity/Atom';\nimport React, { memo, useMemo } from 'react';\n\nimport type * as AppGraphNode from '@dxos/app-graph/AppGraphNode';\nimport {\n type ActionGraphProps,\n ActionToolbar,\n type MenuAction,\n MenuBuilder,\n useMenuActions,\n} from '@dxos/react-ui-menu';\nimport type * as Util from '@dxos/react-ui/Util';\nimport { type EditorViewMode } from '@dxos/ui-editor/types';\n\nimport { addBlocks } from './blocks.ts';\nimport { addFormatting } from './formatting.ts';\nimport { addHeadings } from './headings.ts';\nimport { addImageUpload } from './image.ts';\nimport { addLists } from './lists.ts';\nimport { addSearch } from './search.ts';\nimport { type EditorToolbarState } from './types.ts';\nimport { type ViewModeItem, addViewMode } from './view-mode.ts';\n\n// TODO(burdon): Enable toolbar variants (e.g., markdown, code).\n\n// Local copy of AppSurface.Section.role — react-ui-editor cannot depend on app-toolkit without a circular dep.\nconst SECTION_ROLE = 'org.dxos.role.section';\n\nexport type EditorToolbarFeatureFlags = Partial<{\n showHeadings: boolean;\n showFormatting: boolean;\n showLists: boolean;\n showBlocks: boolean;\n showSearch: boolean;\n\n // TODO(wittjosiah): Factor out (depends on plugin-level capabilities.)\n onImageUpload: () => void;\n onViewModeChange: (mode: EditorViewMode) => void;\n /** Override the view-mode dropdown entries (default: the three built-in modes). See {@link ViewModeItem}. */\n viewModes: ViewModeItem[];\n}>;\n\nexport type EditorToolbarActionGraphProps = {\n state: Atom.Atom<EditorToolbarState>;\n getView: () => EditorView;\n // TODO(wittjosiah): Control positioning.\n customActions?: Atom.Atom<ActionGraphProps>;\n};\n\nexport type EditorToolbarProps = Util.ThemedClassName<\n {\n role?: string;\n attendableId?: string;\n /** Handler for executing actions. Required when customActions use Operation.invoke. */\n onAction?: (action: MenuAction, params: AppGraphNode.InvokeProps) => void;\n } & (EditorToolbarActionGraphProps & EditorToolbarFeatureFlags)\n>;\n\nexport const EditorToolbar = memo(({ classNames, role, attendableId, onAction, ...props }: EditorToolbarProps) => {\n const menuActions = useMarkdownMenuActions(props);\n\n return <ActionToolbar {...menuActions} attendableId={attendableId} onAction={onAction} classNames={classNames} />;\n});\n\ntype ToolbarActionsProps = Pick<EditorToolbarActionGraphProps, 'state' | 'getView' | 'customActions'> &\n EditorToolbarFeatureFlags;\n\n// TODO(burdon): Some actions should toggle the state (e.g., toggle bullets on/off depending on the current state).\n// TODO(wittjosiah): Toolbar re-rendering is causing this graph to be recreated and breaking reactivity in some cases.\n// E.g. for toolbar dropdowns which use active icon, the icon is not updated when the active item changes.\n// This is currently only happening in the markdown plugin usage and should be reproduced in an editor story.\nconst useMarkdownMenuActions = ({ state, getView, customActions, ...features }: ToolbarActionsProps) => {\n const menuCreator = useMemo(\n () => createMarkdownActions({ state, getView, customActions, ...features }),\n [\n state,\n getView,\n customActions,\n features?.showHeadings,\n features?.showFormatting,\n features?.showLists,\n features?.showBlocks,\n features?.showSearch,\n features?.onImageUpload,\n features?.onViewModeChange,\n features?.viewModes,\n ],\n );\n\n return useMenuActions(menuCreator);\n};\n\nconst createMarkdownActions = ({\n state,\n getView,\n customActions,\n ...features\n}: ToolbarActionsProps): Atom.Atom<ActionGraphProps> => {\n return Atom.make((get) => {\n // Subscribe to state changes.\n const stateSnapshot = get(state);\n return MenuBuilder.make()\n .subgraph(features?.showHeadings !== false && addHeadings(stateSnapshot, getView))\n .subgraph(features?.showFormatting !== false && addFormatting(stateSnapshot, getView))\n .subgraph(features?.showLists !== false && addLists(stateSnapshot, getView))\n .subgraph(features?.showBlocks !== false && addBlocks(stateSnapshot, getView))\n .subgraph(features?.onImageUpload && addImageUpload(features.onImageUpload))\n .separator('gap')\n .subgraph(customActions && get(customActions))\n .subgraph(features?.showSearch !== false && addSearch(getView))\n .subgraph(features?.onViewModeChange && addViewMode(stateSnapshot, features.onViewModeChange, features.viewModes))\n .build();\n });\n};\n","//\n// Copyright 2026 DXOS.org\n//\n\nimport { type Extension } from '@codemirror/state';\nimport { useMemo } from 'react';\n\nimport * as Hooks from '@dxos/react-ui/Hooks';\nimport {\n createBasicExtensions,\n createMarkdownExtensions,\n createThemeExtensions,\n decorateMarkdown,\n} from '@dxos/ui-editor';\n\nexport type UseBasicMarkdownExtensionsOptions = {\n /** Placeholder text shown when the editor is empty. */\n placeholder?: string;\n /** Enables markdown visual decorations. Pass `false` to disable. Defaults to `true`. */\n decorate?: boolean;\n /** Renders the editor read-only (selectable but not editable). Defaults to `false`. */\n readonly?: boolean;\n /**\n * Extra extensions appended to the returned array.\n * Callers must memoize this array — it is used as a dependency of {@link useMemo}.\n */\n extensions?: Extension[];\n};\n\n/**\n * Returns the standard CodeMirror extension stack for an inline markdown editor:\n * basic editor behaviors, themed syntax highlighting (theme mode read from {@link useThemeMode}),\n * markdown parsing, and visual decorations.\n *\n * Used by surfaces such as `AgentProperties` and `MagazineProperties` to render the\n * `instructions` field of an object as a small editable markdown block.\n *\n * The memoization depends on primitive option values plus the `extensions` array reference,\n * so callers passing fresh option literals each render still hit the cache.\n */\nexport const useBasicMarkdownExtensions = ({\n placeholder,\n decorate = true,\n readonly = false,\n extensions,\n}: UseBasicMarkdownExtensionsOptions = {}): Extension[] => {\n const themeMode = Hooks.useThemeMode();\n return useMemo(\n () => [\n createBasicExtensions({ placeholder, readOnly: readonly }),\n createThemeExtensions({ syntaxHighlighting: true, themeMode }),\n createMarkdownExtensions(),\n ...(decorate ? [decorateMarkdown()] : []),\n ...(extensions ?? []),\n ],\n [placeholder, themeMode, decorate, readonly, extensions],\n );\n};\n","//\n// Copyright 2024 DXOS.org\n//\n\nimport { EditorState, type EditorStateConfig, type Text } from '@codemirror/state';\nimport { EditorView } from '@codemirror/view';\nimport {\n type ComponentPropsWithoutRef,\n type DependencyList,\n type KeyboardEventHandler,\n type RefObject,\n useCallback,\n useEffect,\n useLayoutEffect,\n useMemo,\n useRef,\n useState,\n} from 'react';\n\nimport { log } from '@dxos/log';\nimport {\n type EditorSelection,\n debugDispatcher,\n documentId,\n modalStateField,\n restoreEditorState,\n} from '@dxos/ui-editor';\nimport { type MaybeProvider, getProviderValue, isTruthy } from '@dxos/util';\n\nlet instanceCount = 0;\n\nexport type CursorInfo = {\n from: number;\n to: number;\n line: number;\n lines: number;\n length: number;\n after?: string;\n};\n\nexport type UseTextEditor = {\n parentRef: RefObject<HTMLDivElement | null>;\n view: EditorView | null;\n focusAttributes?: ComponentPropsWithoutRef<'div'>;\n};\n\nexport type UseTextEditorProps = Pick<EditorStateConfig, 'extensions'> & {\n id?: string;\n doc?: Text;\n initialValue?: string;\n autoFocus?: boolean;\n scrollTo?: number;\n scrollOffset?: number;\n selection?: EditorSelection;\n selectionEnd?: boolean;\n debug?: boolean;\n};\n\n/**\n * Creates codemirror text editor.\n */\nexport const useTextEditor = (\n props: MaybeProvider<UseTextEditorProps> = {},\n deps: DependencyList = [],\n): UseTextEditor => {\n const { id, doc, initialValue, extensions, autoFocus, scrollTo, scrollOffset, selection, selectionEnd, debug } =\n useMemo<UseTextEditorProps>(() => getProviderValue(props), deps ?? []);\n\n // NOTE: Increments by 2 in strict mode.\n const [instanceId] = useState(() => `text-editor-${++instanceCount}`);\n const [view, setView] = useState<EditorView | null>(null);\n const parentRef = useRef<HTMLDivElement>(null);\n\n // Layout effect, not a passive one: the view is what fills the editor's box, so building it after\n // the browser paints shows a frame of empty container and then reflows everything below it — a\n // field in a pane (a task's description) drops its siblings by the editor's height on every\n // subject change. Constructing before paint costs that work in the commit instead; see PERF.md in\n // `react-ui-markdown/src/MarkdownEditable` for what that costs on a large document.\n useLayoutEffect(() => {\n let view: EditorView | null = null;\n if (parentRef.current) {\n log('create', { id, instanceId, doc: initialValue?.length ?? 0 });\n\n // Only records whether an explicit selection governs, so that `selectionEnd` defers to it; the\n // effect below applies it. `!== undefined`, since an explicit `{ anchor: 0 }` is a selection.\n const length = (doc ?? initialValue)?.length ?? 0;\n let initialSelection;\n if (selection?.anchor !== undefined && length) {\n if (selection.anchor <= length && (selection?.head ?? 0) <= length) {\n initialSelection = selection;\n }\n }\n\n // https://codemirror.net/docs/ref/#state.EditorStateConfig\n const state = EditorState.create({\n doc: doc ?? initialValue,\n // selection: initialSelection,\n extensions: [\n id && documentId.of(id),\n extensions,\n // NOTE: This doesn't catch errors in keymap functions.\n EditorView.exceptionSink.of((err) => {\n log.catch(err);\n }),\n ].filter(isTruthy),\n });\n\n // https://codemirror.net/docs/ref/#view.EditorViewConfig\n view = new EditorView({\n parent: parentRef.current,\n state,\n // `scrollTo` is the position recorded at the top of the viewport, so restore it there.\n scrollTo: scrollTo != null ? EditorView.scrollIntoView(scrollTo, { y: 'start', yMargin: 0 }) : undefined,\n dispatchTransactions: debug ? debugDispatcher : undefined,\n });\n\n // Move to end of line after document loaded (unless selection is specified).\n if (selectionEnd && !initialSelection) {\n const { to } = view.state.doc.lineAt(0);\n view.dispatch({ selection: { anchor: to } });\n }\n\n setView(view);\n }\n\n return () => {\n log('destroy', { id });\n view?.destroy();\n };\n }, deps);\n\n useEffect(() => {\n if (view) {\n if (scrollTo != null || selection) {\n if (selection && selection.anchor > view.state.doc.length) {\n log.warn('invalid selection', { length: view.state.doc.length, scrollTo, selection });\n return;\n }\n\n restoreEditorState(view, { scrollTo, scrollOffset, selection });\n }\n }\n }, [view, scrollTo, scrollOffset, selection]);\n\n useEffect(() => {\n // Focus in an open menu or listbox is the user's, and taking it would dismiss that popup.\n if (view && autoFocus && !view.dom.ownerDocument.activeElement?.closest('[role=\"menu\"], [role=\"listbox\"]')) {\n view.focus();\n }\n }, [autoFocus, view]);\n\n // Focus editor on Enter (e.g., when tabbing to this component).\n const handleKeyDown = useCallback<KeyboardEventHandler<HTMLDivElement>>(\n (event) => {\n const { key, target, currentTarget } = event;\n switch (key) {\n case 'Escape': {\n // Check if popover is open.\n const modal = view?.state.field(modalStateField, false);\n if (modal) {\n return;\n }\n\n // Focus the closest focusable parent.\n const element = view?.contentDOM.closest('[tabindex=\"0\"]') as HTMLDivElement | null;\n element?.focus();\n break;\n }\n\n case 'Enter': {\n event.preventDefault();\n if (target === currentTarget) {\n view?.focus();\n }\n break;\n }\n }\n },\n [view],\n );\n\n return {\n parentRef,\n view,\n focusAttributes: {\n tabIndex: 0 as const,\n onKeyDown: handleKeyDown,\n },\n };\n};\n","//\n// Copyright 2025 DXOS.org\n//\n\nimport { Transaction } from '@codemirror/state';\nimport { EditorView as NaturalEditorView } from '@codemirror/view';\nimport React, { forwardRef, useEffect, useImperativeHandle, useRef } from 'react';\n\nimport type * as Util from '@dxos/react-ui/Util';\nimport { computeDocChanges, initialSync } from '@dxos/ui-editor';\nimport { mx } from '@dxos/ui-theme';\n\nimport { type UseTextEditorProps, useTextEditor } from '../../hooks/index.ts';\nimport { type EditorController, createEditorController } from './controller.ts';\n\nexport type EditorViewProps = Util.ThemedClassName<\n {\n focusable?: boolean;\n value?: string;\n onChange?: (value: string) => void;\n } & UseTextEditorProps\n>;\n\n/**\n * Minimal text editor.\n * NOTE: This shouold not be used with the automerge extension.\n */\n// TODO(burdon): Move controller to Root component, then make composable.\nexport const EditorView = forwardRef<EditorController, EditorViewProps>(\n ({ classNames, id, extensions, selectionEnd, focusable = true, value, onChange, ...props }, forwardedRef) => {\n // Hold the latest onChange in a ref so callers may pass an inline callback\n // without forcing the underlying editor to be destroyed and recreated on\n // every render — which would blur the focused input on each keystroke.\n const onChangeRef = useRef(onChange);\n onChangeRef.current = onChange;\n\n const { parentRef, focusAttributes, view } = useTextEditor(\n () => ({\n id,\n initialValue: value,\n selectionEnd,\n extensions: [\n extensions ?? [],\n NaturalEditorView.updateListener.of(({ view, docChanged, transactions }) => {\n const isInitialSync = transactions.some((tr) => tr.annotation(Transaction.userEvent) === initialSync.value);\n if (!isInitialSync && docChanged) {\n onChangeRef.current?.(view.state.doc.toString());\n }\n }),\n ],\n ...props,\n }),\n [id, extensions, selectionEnd],\n );\n\n // External controller.\n useImperativeHandle(forwardedRef, () => {\n return createEditorController(view);\n }, [id, view]);\n\n // Sync the editor doc to the controlled `value` prop, but only when they\n // disagree. After internal typing the prop will already match the editor's\n // doc, and dispatching anyway would race fast keystrokes — a stale rAF\n // closure can replace doc content with an older value, dropping characters.\n //\n // `undefined` means uncontrolled: the doc was seeded once from `value` at creation and the caller\n // isn't driving it, so leave it be (coalescing undefined→'' here would wipe that seed on mount). A\n // controlled caller clears the doc by passing '' explicitly.\n useEffect(() => {\n if (!view || value === undefined) {\n return;\n }\n const next = value;\n if (view.state.doc.toString() === next) {\n return;\n }\n requestAnimationFrame(() => {\n const current = view.state.doc.toString();\n if (current === next) {\n return;\n }\n // Only what moved: replacing the whole document would drop the reader's folds, selection\n // and scroll position on every update of a value they are mostly already looking at.\n const changes = computeDocChanges(current, next);\n if (changes.length === 0) {\n return;\n }\n\n view.dispatch({\n annotations: initialSync,\n changes,\n selection: selectionEnd ? { anchor: next.length } : undefined,\n });\n\n if (selectionEnd) {\n view.focus();\n }\n });\n }, [view, value, selectionEnd]);\n\n // Focus chrome (border/ring) is the caller's responsibility (e.g. `dx-input-box`).\n return (\n <div\n className={mx('w-full outline-hidden', classNames)}\n {...(focusable ? focusAttributes : {})}\n ref={parentRef}\n />\n );\n },\n);\n","//\n// Copyright 2025 DXOS.org\n//\n\nimport { type EditorState, type Extension } from '@codemirror/state';\nimport * as Atom from 'effect/reactivity/Atom';\nimport React, {\n type PropsWithChildren,\n Suspense,\n forwardRef,\n useCallback,\n useEffect,\n useImperativeHandle,\n useMemo,\n useRef,\n useState,\n} from 'react';\nimport { createPortal } from 'react-dom';\n\nimport { invariant } from '@dxos/invariant';\nimport type * as Util from '@dxos/react-ui/Util';\nimport { mx } from '@dxos/ui-theme';\nimport { isNonNullable } from '@dxos/util';\n\nimport {\n EditorMenuProvider,\n type EditorMenuProviderProps,\n type UseEditorMenuProps,\n useEditorMenu,\n} from '../EditorMenuProvider/index.ts';\nimport {\n type EditorToolbarState,\n EditorToolbar as NaturalEditorToolbar,\n type EditorToolbarProps as NaturalEditorToolbarProps,\n} from '../EditorToolbar/index.ts';\nimport { type EditorController, noopController } from './controller.ts';\nimport { EditorContextProvider, type EditorContextValue, useEditorContext } from './EditorContext.ts';\nimport {\n EditorView as NaturalEditorContent,\n type EditorViewProps as NaturalEditorContentProps,\n} from './EditorView.tsx';\n\n//\n// Root\n//\n\ntype EditorRootProps = PropsWithChildren<\n Pick<EditorContextValue, 'extensions' | 'widgets'> &\n Omit<UseEditorMenuProps, 'viewRef'> &\n Pick<EditorMenuProviderProps, 'numItems' | 'searchPlaceholder'> & {\n viewMode?: EditorToolbarState['viewMode'];\n }\n>;\n\n/**\n * Root component for the Editor compound component.\n * Provides context for all child components and manages the editor controller state.\n */\nconst EditorRoot = forwardRef<EditorController | null, EditorRootProps>(\n (\n { children, extensions: extensionsProp, widgets, viewMode, numItems, searchPlaceholder, ...props },\n forwardedRef,\n ) => {\n // TODO(wittjosiah): Including initialState in the deps causes reactivity issues.\n const state = useMemo(() => Atom.make<EditorToolbarState>({ viewMode }), [viewMode]);\n\n // TODO(burdon): Consider lighter-weight approach if EditorMenuProvider is not needed.\n const { groupsRef, extension, ...menuProps } = useEditorMenu(props);\n const extensions = useMemo(\n () => [extension, extensionsProp].filter(isNonNullable).flat(),\n [extension, extensionsProp],\n );\n\n // External controller.\n const [controller, setController] = useState<EditorController>(noopController);\n useImperativeHandle(forwardedRef, () => controller, [controller]);\n const getView = useCallback(() => controller?.view ?? null, [controller]);\n\n return (\n <EditorContextProvider\n controller={controller}\n setController={setController}\n extensions={extensions}\n widgets={widgets}\n state={state}\n >\n <EditorMenuProvider\n getView={getView}\n groups={groupsRef.current}\n numItems={numItems}\n searchPlaceholder={searchPlaceholder}\n {...menuProps}\n >\n {children}\n </EditorMenuProvider>\n </EditorContextProvider>\n );\n },\n);\n\nEditorRoot.displayName = 'Editor.Root';\n\n//\n// Content\n//\n\nconst EDITOR_CONTENT_NAME = 'Editor.Content';\n\ntype EditorContentProps = Util.ThemedClassName<PropsWithChildren<{}>>;\n\n/**\n * Content component that wraps the toolbar and editor view area.\n */\nconst EditorContent = ({ classNames, children }: EditorContentProps) => {\n return <div className={mx('grid grid-rows-[min-content_1fr] h-full overflow-hidden', classNames)}>{children}</div>;\n};\n\nEditorContent.displayName = EDITOR_CONTENT_NAME;\n\n//\n// View\n//\n\nconst EDITOR_VIEW_NAME = 'Editor.View';\n\ntype EditorViewProps = Omit<NaturalEditorContentProps, 'ref'>;\n\n/**\n * View component that renders the actual CodeMirror editor.\n * Automatically registers the editor controller with the context.\n */\nconst EditorView = ({ extensions: providedExtensions, ...props }: EditorViewProps) => {\n const { extensions: additionalExtensions = [], setController } = useEditorContext(EDITOR_VIEW_NAME);\n const extensions = useMemo(\n () => [additionalExtensions, providedExtensions].filter(isNonNullable).flat(),\n [providedExtensions, additionalExtensions],\n );\n\n return <NaturalEditorContent {...props} extensions={extensions} ref={setController} />;\n};\n\nEditorView.displayName = EDITOR_VIEW_NAME;\n\n//\n// Toolbar\n//\n\nconst EDITOR_TOOLBAR_NAME = 'Editor.Toolbar';\n\ntype EditorToolbarProps = Omit<NaturalEditorToolbarProps, 'getView' | 'state'>;\n\n/**\n * Toolbar component that provides editor formatting and control actions.\n * Automatically connects to the editor view through context.\n */\nconst EditorToolbar = (props: EditorToolbarProps) => {\n const { controller, state } = useEditorContext(EDITOR_TOOLBAR_NAME);\n const getView = useCallback(() => {\n invariant(controller?.view);\n return controller?.view;\n }, [controller]);\n\n return <NaturalEditorToolbar {...props} getView={getView} state={state} />;\n};\n\nEditorToolbar.displayName = EDITOR_TOOLBAR_NAME;\n\n//\n// Blocks\n//\n\nconst EDITOR_BLOCKS_NAME = 'Editor.Blocks';\n\n/**\n * Renders the xmlTags widget portals (embedded blocks) contributed via the editor's `setWidgets`\n * callback. Place inside `Editor.Root`; the widgets come from context (pass them to `Editor.Root`).\n */\nconst EditorBlocks = () => {\n const { widgets = [] } = useEditorContext(EDITOR_BLOCKS_NAME);\n return (\n <>\n {widgets.map(({ id, root, Component, props }) => (\n <div key={id} data-testid='editor.blocks.portal'>\n {/* Per-portal boundary: a block that suspends (capability wait, lazy surface module) must\n not hold the surrounding editor tree un-committed — that made embeds invisible until\n a view-mode toggle rebuilt everything. */}\n {createPortal(\n <Suspense fallback={null}>\n <Component {...props} />\n </Suspense>,\n root,\n )}\n </div>\n ))}\n </>\n );\n};\n\nEditorBlocks.displayName = EDITOR_BLOCKS_NAME;\n\n//\n// Diagnostics\n//\n\nconst EDITOR_DIAGNOSTICS_NAME = 'Editor.Diagnostics';\n\n/** Rough leaf count of a (recursively nested) CodeMirror extension tree. */\nconst countExtensions = (extension: Extension | undefined): number =>\n extension == null\n ? 0\n : Array.isArray(extension)\n ? extension.reduce((sum, child) => sum + countExtensions(child), 0)\n : 1;\n\ntype EditorDiagnosticsProps = Util.ThemedClassName<{}>;\n\n/**\n * Developer panel showing live editor state read from the CodeMirror `EditorState` (document size,\n * selection, viewport, configured-extension count). A debugging aid — gate it behind a dev/debug\n * setting; unrelated to `@codemirror/lint` diagnostics. Place inside `Editor.Root`, below the content.\n */\nconst EditorDiagnostics = ({ classNames }: EditorDiagnosticsProps) => {\n const { controller, extensions } = useEditorContext(EDITOR_DIAGNOSTICS_NAME);\n const view = controller?.view ?? null;\n // `EditorState` is immutable (a new object per transaction), so a reference change signals an update.\n // Poll on animation frames while mounted — self-contained (no injected updateListener) and re-renders\n // only when the state actually changes; catches programmatic transactions too.\n const [, setRevision] = useState(0);\n const lastState = useRef<EditorState | null>(null);\n useEffect(() => {\n if (!view) {\n return;\n }\n let frame = 0;\n const tick = () => {\n if (view.state !== lastState.current) {\n lastState.current = view.state;\n setRevision((revision) => revision + 1);\n }\n frame = requestAnimationFrame(tick);\n };\n frame = requestAnimationFrame(tick);\n return () => cancelAnimationFrame(frame);\n }, [view]);\n\n if (!view) {\n return null;\n }\n\n const { state } = view;\n const { doc, selection } = state;\n const { main } = selection;\n const head = doc.lineAt(main.head);\n // The context holds the extensions passed to `Root`; count leaves (CodeMirror does not expose the\n // resolved extension set at runtime, so this is the configured, not the active, count).\n const extensionCount = countExtensions(extensions);\n const item = (label: string, value: string | number) => (\n <span>\n <span className='text-fg-subtle'>{label} </span>\n {value}\n </span>\n );\n\n return (\n <div\n className={mx(\n 'flex flex-wrap gap-x-4 gap-y-0.5 border-bs border-separator px-2 py-1 text-xs font-mono text-fg-muted',\n classNames,\n )}\n data-testid='editor.diagnostics'\n >\n {item('chars', doc.length)}\n {item('lines', doc.lines)}\n {item(\n 'cursor',\n main.empty ? `${head.number}:${main.head - head.from + 1}` : `${main.from}–${main.to} (${main.to - main.from})`,\n )}\n {selection.ranges.length > 1 && item('ranges', selection.ranges.length)}\n {item('viewport', `${view.viewport.from}–${view.viewport.to}`)}\n {item('mode', state.readOnly ? 'read-only' : 'editable')}\n {item('extensions', extensionCount)}\n </div>\n );\n};\n\nEditorDiagnostics.displayName = EDITOR_DIAGNOSTICS_NAME;\n\n//\n// Editor\n//\n\nexport const Editor = {\n Root: EditorRoot,\n Toolbar: EditorToolbar,\n Content: EditorContent,\n View: EditorView,\n Blocks: EditorBlocks,\n Diagnostics: EditorDiagnostics,\n};\n\nexport type {\n EditorContentProps,\n EditorController,\n EditorRootProps,\n EditorToolbarProps,\n EditorToolbarState,\n EditorViewProps,\n};\n","//\n// Copyright 2025 DXOS.org\n//\n\nimport { createContext } from '@dxos/react-hooks';\nimport { type PreviewLinkRef, type PreviewLinkTarget } from '@dxos/ui-types';\n\n// Kept out of `EditorPreviewProvider.tsx`: react-refresh only fast-refreshes a module whose exports are all\n// components, so a context and its hook exported beside them force a full page reload on every edit.\n\nexport type EditorPreviewPopoverValue = Partial<{\n link: PreviewLinkRef;\n target: PreviewLinkTarget;\n pending: boolean;\n}>;\n\nexport const [EditorPreviewContextProvider, useEditorPreview] = createContext<EditorPreviewPopoverValue>(\n 'PreviewPopover',\n {},\n);\n","//\n// Copyright 2025 DXOS.org\n//\n\nimport React, { type PropsWithChildren, useCallback, useEffect, useRef, useState } from 'react';\n\nimport { addEventListener } from '@dxos/async';\nimport * as Popover from '@dxos/react-ui/Popover';\nimport * as VirtualAnchor from '@dxos/react-ui/VirtualAnchor';\nimport { DX_ANCHOR_ACTIVATE, type DxAnchorActivate, type PreviewLinkRef, type PreviewLinkTarget } from '@dxos/ui-types';\n\nimport { EditorPreviewContextProvider, type EditorPreviewPopoverValue } from './EditorPreviewContext.ts';\n\nexport type EditorPreviewProviderProps = PropsWithChildren<{\n onLookup?: (link: PreviewLinkRef) => Promise<PreviewLinkTarget | null | undefined>;\n}>;\n\n/**\n * Standalone anchor-preview popover for use outside Composer (storybook, tests).\n * In Composer, PreviewPlugin listens on window for DX_ANCHOR_ACTIVATE events and\n * dispatches LayoutOperation.UpdatePopover instead; this provider is not needed there.\n */\nexport const EditorPreviewProvider = ({ children, onLookup }: EditorPreviewProviderProps) => {\n const triggerRef = useRef<HTMLElement | null>(null);\n const [value, setValue] = useState<EditorPreviewPopoverValue>({});\n const [open, setOpen] = useState(false);\n // Monotonic activation token: only the most recent open may commit its async lookup result, so a\n // slow lookup for a closed/superseded anchor cannot fill the popover for a later one.\n const activationRef = useRef(0);\n\n const handleActivate = useCallback(\n (event: DxAnchorActivate) => {\n // Hover-driven anchors dispatch `state: false` when the pointer leaves the anchor/card — after\n // a grace period, so the pointer may already have opened another anchor by then. Only the\n // anchor currently shown may close the popover; a stale close from the one just left is dropped.\n if (event.state === false) {\n if (event.trigger !== triggerRef.current) {\n return;\n }\n activationRef.current++;\n setOpen(false);\n return;\n }\n\n const sequence = ++activationRef.current;\n const { eid, label, trigger } = event;\n setValue((value) => ({\n ...value,\n link: { label, eid },\n pending: true,\n }));\n\n triggerRef.current = trigger;\n queueMicrotask(() => setOpen(true));\n void onLookup?.({ label, eid }).then((target) => {\n if (sequence !== activationRef.current) {\n return;\n }\n setValue((value) => ({\n ...value,\n target: target ?? undefined,\n pending: false,\n }));\n });\n },\n [onLookup],\n );\n\n // Dismissals from the popover itself (Escape, outside click) also invalidate in-flight lookups.\n const handleOpenChange = useCallback((next: boolean) => {\n if (!next) {\n activationRef.current++;\n }\n setOpen(next);\n }, []);\n\n const [root, setRoot] = useState<HTMLDivElement | null>(null);\n useEffect(() => {\n if (!root || !handleActivate) {\n return;\n }\n\n return addEventListener(root, DX_ANCHOR_ACTIVATE as any, handleActivate, {\n capture: true,\n passive: false,\n });\n }, [root, handleActivate]);\n\n return (\n <EditorPreviewContextProvider pending={value.pending} link={value.link} target={value.target}>\n <Popover.Root\n open={open}\n onOpenChange={({ open }) => handleOpenChange(open)}\n positioning={VirtualAnchor.virtualAnchor(triggerRef)}\n // A preview card shows beside the link; focus stays in the editor.\n autoFocus={false}\n >\n <div className='contents' ref={setRoot}>\n {children}\n </div>\n </Popover.Root>\n </EditorPreviewContextProvider>\n );\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":["EditorView","__dxlog_file","_Fragment","__dxlog_file","EditorToolbar","__dxlog_file","EditorView","NaturalEditorView","EditorView","NaturalEditorContent","NaturalEditorToolbar","_Fragment"],"sources":["../../src/components/Editor/controller.ts","../../src/components/Editor/EditorContext.ts","../../src/components/EditorMenuProvider/menu.ts","../../src/components/EditorMenuProvider/popover.ts","../../src/components/EditorMenuProvider/menu-presets.ts","../../src/components/EditorMenuProvider/EditorMenuProvider.tsx","../../src/components/EditorMenuProvider/useEditorMenu.ts","../../src/components/EditorToolbar/blocks.ts","../../src/components/EditorToolbar/formatting.ts","../../src/components/EditorToolbar/headings.ts","../../src/components/EditorToolbar/image.ts","../../src/components/EditorToolbar/lists.ts","../../src/components/EditorToolbar/search.ts","../../src/components/EditorToolbar/view-mode.ts","../../src/components/EditorToolbar/EditorToolbar.tsx","../../src/hooks/useBasicMarkdownExtensions.ts","../../src/hooks/useTextEditor.ts","../../src/components/Editor/EditorView.tsx","../../src/components/Editor/Editor.tsx","../../src/components/EditorPreviewProvider/EditorPreviewContext.ts","../../src/components/EditorPreviewProvider/EditorPreviewProvider.tsx"],"sourcesContent":["//\n// Copyright 2025 DXOS.org\n//\n\nimport { type EditorView } from '@codemirror/view';\n\nexport interface EditorController {\n get view(): EditorView | null;\n getText: () => string;\n setText: (text: string, focus?: boolean) => void;\n focus: () => void;\n}\n\n// Hide `view` from own-enumerable traversal. Controllers are passed through React props/context, and the\n// live `EditorView` reaches the global `window` via `view.observer.win` (CodeMirror's DOMObserver). React\n// 19.2's dev render-logger walks changed props' object graphs and would otherwise descend into `window`,\n// enumerate `window[0]` (a cross-origin iframe) and throw SecurityError. Direct access is unaffected.\nconst withHiddenView = (controller: EditorController): EditorController => {\n Object.defineProperty(controller, 'view', { enumerable: false });\n return controller;\n};\n\nexport const noopController: EditorController = withHiddenView({\n get view() {\n return null;\n },\n getText: () => '',\n setText: () => {},\n focus: () => {},\n});\n\nexport const createEditorController = (view: EditorView | null): EditorController => {\n return withHiddenView({\n get view() {\n return view;\n },\n getText: () => {\n return view?.state.doc.toString() ?? '';\n },\n setText: (text: string, focus?: boolean) => {\n view?.dispatch({\n changes: {\n from: 0,\n to: view?.state.doc.length ?? 0,\n insert: text,\n },\n selection: {\n anchor: text.length,\n head: text.length,\n },\n });\n\n if (focus) {\n view?.focus();\n }\n },\n focus: () => view?.focus(),\n });\n};\n","//\n// Copyright 2025 DXOS.org\n//\n\nimport { type Extension } from '@codemirror/state';\nimport type * as Atom from 'effect/reactivity/Atom';\n\nimport { createContext } from '@dxos/react-hooks';\nimport { type WidgetState } from '@dxos/ui-editor';\n\nimport { type EditorToolbarState } from '../EditorToolbar/types.ts';\nimport { type EditorController } from './controller.ts';\n\n// Kept out of `Editor.tsx`: react-refresh only fast-refreshes a module whose exports are all\n// components, so a context and its hook exported beside them force a full page reload on every edit.\n\n//\n// Context\n//\n\nexport type EditorContextValue = {\n controller?: EditorController;\n setController: (controller: EditorController) => void;\n extensions?: Extension[];\n /** xmlTags widget portals (embedded blocks); rendered by `Editor.Blocks`, fed via `setWidgets`. */\n widgets?: WidgetState[];\n state: Atom.Writable<EditorToolbarState>;\n};\n\nexport const [EditorContextProvider, useEditorContext] = createContext<EditorContextValue>('Editor');\n\n/**\n * Access the editor context. Must be used within `Editor.Root`.\n */\n","//\n// Copyright 2025 DXOS.org\n//\n\nimport { type EditorView } from '@codemirror/view';\n\nimport type * as Theme from '@dxos/react-ui/Theme';\nimport { insertAtCursor } from '@dxos/ui-editor';\nimport { type ChromaticPalette } from '@dxos/ui-types';\nimport { type MaybePromise } from '@dxos/util';\n\nexport type EditorMenuGroup = {\n id: string;\n label?: Theme.Label;\n items: EditorMenuItem[];\n};\n\nexport type EditorMenuItem = {\n id: string;\n label: Theme.Label;\n icon?: string;\n iconHue?: ChromaticPalette;\n onSelect?: (event: { view: EditorView; head: number }) => MaybePromise<void>;\n};\n\nexport const getMenuItem = (groups: EditorMenuGroup[], id?: string): EditorMenuItem | undefined => {\n return groups.flatMap((group) => group.items).find((item) => item.id === id);\n};\n\n/** Undefined when the menu is empty; at either end the current item is returned unchanged. */\nexport const getNextMenuItem = (groups: EditorMenuGroup[], id?: string): EditorMenuItem | undefined => {\n const items = groups.flatMap((group) => group.items);\n const index = items.findIndex((item) => item.id === id);\n return index < items.length - 1 ? items[index + 1] : items[index];\n};\n\n/** Undefined when the menu is empty; at either end the current item is returned unchanged. */\nexport const getPreviousMenuItem = (groups: EditorMenuGroup[], id?: string): EditorMenuItem | undefined => {\n const items = groups.flatMap((group) => group.items);\n const index = items.findIndex((item) => item.id === id);\n return index > 0 ? items[index - 1] : items[index];\n};\n\nexport const createMenuGroup = ({\n id = 'menu',\n label,\n filter,\n items,\n}: {\n id?: string;\n label?: Theme.Label;\n filter?: string;\n items: string[];\n}): EditorMenuGroup => ({\n id,\n label,\n items: items\n .filter((item) => !filter || item.toLowerCase().includes(filter.toLowerCase()))\n .map((item, i) => ({\n id: `${id}-${i}`,\n label: item,\n onSelect: ({ view, head }) => insertAtCursor(view, head, item),\n })),\n});\n\nexport const filterMenuGroups = (\n groups: EditorMenuGroup[],\n filter: (item: EditorMenuItem) => boolean,\n): EditorMenuGroup[] => {\n return groups.map((group) => ({\n ...group,\n items: group.items.filter(filter),\n }));\n};\n","//\n// Copyright 2024 DXOS.org\n//\n\nimport { type Extension, Prec, RangeSetBuilder, StateEffect, StateField } from '@codemirror/state';\nimport {\n Decoration,\n type DecorationSet,\n EditorView,\n type KeyBinding,\n ViewPlugin,\n type ViewUpdate,\n keymap,\n} from '@codemirror/view';\n\nimport { type PlaceholderOptions, modalStateField, placeholder } from '@dxos/ui-editor';\nimport { type Range } from '@dxos/ui-editor/types';\nimport { isNonNullable, isTruthy } from '@dxos/util';\n\nconst DELIMITERS = [' ', ':'];\n\nexport type PopoverOptions = {\n trigger?: string | string[];\n triggerKey?: string;\n /** Overrides the trigger hint shown in an empty, focused editor; `false` omits the hint. */\n placeholder?: Partial<PlaceholderOptions> | false;\n delimiters?: string[];\n\n /**\n * Open the popover automatically while typing a word (no trigger character required).\n * The completion range covers the word under the cursor (delimited by {@link PopoverOptions.delimiters}).\n */\n activateOnTyping?: boolean;\n\n /**\n * Delimiters after which an EMPTY token still opens the menu — `type:` offering every typename,\n * say. Opt-in per consumer because the empty-token guard is load-bearing elsewhere: `RefEditor`\n * uses `activateOnTyping` with no trigger, where an empty query would match every object.\n */\n activateOnDelimiters?: string[];\n\n /**\n * Triggers whose query may contain spaces (e.g. multi-word names). A double space, a leading space or a\n * line break still ends it, and Escape keeps the typed text.\n */\n multiWordTriggers?: string[];\n\n // Trigger update.\n onTextChange?: (event: { view: EditorView; pos: number; text: string; trigger?: string }) => void;\n onClose?: (event: { view: EditorView }) => void;\n\n // Menu specific.\n /** Returns true when the menu consumed the key (an item was selected); false falls through. */\n onEnter?: (event: { view: EditorView }) => boolean | void;\n onArrowUp?: (event: { view: EditorView }) => void;\n onArrowDown?: (event: { view: EditorView }) => void;\n};\n\n/**\n * Creates a popover that appears when the trigger character is inserted.\n * This can be used for context menus or autocompletion.\n */\nexport const popover = (options: PopoverOptions = {}): Extension => {\n return [\n Prec.highest(popoverKeymap(options)),\n popoverStateField,\n popoverTriggerListener(options),\n (options.activateOnTyping || options.activateOnDelimiters?.length) && popoverAutoActivate(options),\n popoverAnchorDecoration(options),\n modalStateField,\n options.trigger &&\n options.placeholder !== false &&\n placeholder({\n // TODO(burdon): Translations.\n content: `Press '${Array.isArray(options.trigger) ? options.trigger[0] : options.trigger}' for commands`,\n focusOnly: true,\n ...options.placeholder,\n }),\n ].filter(isTruthy);\n};\n\n/**\n * Listen for selection and document changes.\n */\nconst popoverTriggerListener = (options: PopoverOptions) =>\n EditorView.updateListener.of(({ view, docChanged }) => {\n const { range: activeRange, trigger } = view.state.field(popoverStateField) ?? {};\n if (!activeRange) {\n return;\n }\n\n const text = view.state.doc.sliceString(activeRange.from, activeRange.to);\n const query = trigger ? text.slice(1) : text;\n const selection = view.state.selection.main;\n const shouldClose =\n // Trigger deleted.\n (trigger ? trigger !== text[0] : false) ||\n // Whitespace in text; a multi-word query may hold single spaces between words.\n (trigger && options.multiWordTriggers?.includes(trigger) ? /^\\s|[^\\S ]| {2}/.test(query) : /\\s/.test(query)) ||\n // Cursor moved before the range.\n selection.head < activeRange.from ||\n // Cursor moved after the range (+1 to handle selection changing before doc).\n selection.head > activeRange.to + 1;\n\n const nextRange = shouldClose ? null : docChanged ? { from: activeRange.from, to: selection.head } : activeRange;\n if (nextRange !== activeRange) {\n view.dispatch({\n effects: popoverRangeEffect.of(nextRange ? { range: nextRange, trigger } : null),\n });\n }\n\n if (shouldClose) {\n options.onClose?.({ view });\n }\n });\n\n/**\n * Open the popover while typing a word, without requiring a trigger character. Once a range is\n * active, {@link popoverTriggerListener} keeps it in sync and closes it (whitespace, cursor moves).\n */\nconst popoverAutoActivate = (options: PopoverOptions) =>\n EditorView.updateListener.of(({ view, docChanged, transactions }) => {\n // Only user input opens the popover — programmatic syncs (e.g. a controlled `value` update on\n // mount) must not pop the menu.\n if (!docChanged || !transactions.some((tr) => tr.isUserEvent('input')) || view.state.field(popoverStateField)) {\n return;\n }\n const selection = view.state.selection.main;\n if (!selection.empty) {\n return;\n }\n const line = view.state.doc.lineAt(selection.head);\n const before = line.text.slice(0, selection.head - line.from);\n const idx = getLastIndexOf(before, options.delimiters ?? DELIMITERS);\n const token = before.slice(idx + 1);\n // Two independent reasons to open: mid-word under `activateOnTyping`, or immediately after a\n // delimiter that has a list behind it. They are separate because opening on every word character\n // moves focus to the popover on each keystroke, which is intolerable in an editor whose menu is\n // empty for most positions.\n const onDelimiter = token.length === 0 && (options.activateOnDelimiters ?? []).includes(before[idx]);\n if (!onDelimiter && (!options.activateOnTyping || token.length === 0)) {\n return;\n }\n view.dispatch({\n effects: popoverRangeEffect.of({ range: { from: line.from + idx + 1, to: selection.head } }),\n });\n });\n\n/**\n * Popover navigation.\n */\nconst popoverKeymap = (options: PopoverOptions) => {\n const triggers = Array.isArray(options.trigger) ? options.trigger : [options.trigger];\n return keymap.of(\n [\n // Prefix triggers.\n ...triggers.filter(isNonNullable).map((trigger) => ({\n key: trigger,\n run: (view: EditorView) => {\n // Determine if we should trigger the popover:\n // 1. Empty lines or at the beginning of a line.\n // 2. When there's a preceding space.\n const selection = view.state.selection.main;\n const line = view.state.doc.lineAt(selection.head);\n if (\n line.text.trim() === '' ||\n selection.head === line.from ||\n (selection.head > line.from && line.text[selection.head - line.from - 1] === ' ')\n ) {\n // Insert and select the trigger.\n view.dispatch({\n changes: { from: selection.head, insert: trigger },\n selection: { anchor: selection.head + 1, head: selection.head + 1 },\n effects: popoverRangeEffect.of({ trigger, range: { from: selection.head, to: selection.head + 1 } }),\n });\n\n return true;\n }\n\n return false;\n },\n })),\n\n //\n // Custom trigger.\n //\n options.triggerKey &&\n ({\n key: options.triggerKey,\n run: (view: EditorView) => {\n const selection = view.state.selection.main;\n const line = view.state.doc.lineAt(selection.head);\n\n // Get last word.\n let str = line.text.slice(0, selection.head - line.from);\n const idx = getLastIndexOf(str, options.delimiters ?? DELIMITERS);\n if (idx !== -1) {\n str = str.slice(idx + 1);\n }\n\n // Create anchor even if zero length (append space).\n const from = line.from + idx;\n view.dispatch({\n effects: popoverRangeEffect.of({ range: { from: from + 1, to: selection.head } }),\n changes:\n selection.head === view.state.doc.length\n ? { from: from + 1, to: selection.head, insert: ' ' }\n : undefined,\n });\n return true;\n },\n } satisfies KeyBinding),\n\n //\n // Nav keys.\n //\n {\n key: 'ArrowUp',\n run: (view: EditorView) => {\n const range = view.state.field(popoverStateField)?.range;\n if (range) {\n options.onArrowUp?.({ view });\n return true;\n }\n\n return false;\n },\n },\n {\n key: 'ArrowDown',\n run: (view: EditorView) => {\n const range = view.state.field(popoverStateField)?.range;\n if (range) {\n options.onArrowDown?.({ view });\n return true;\n }\n\n return false;\n },\n },\n {\n key: 'Enter',\n run: (view: EditorView) => {\n const range = view.state.field(popoverStateField)?.range;\n if (range) {\n // The consumer deletes the range when it selects an item; when nothing is selectable\n // (empty menu over an `activateOnTyping` range) the key must fall through so the\n // typed text is preserved.\n return options.onEnter?.({ view }) ?? false;\n }\n\n return false;\n },\n },\n ].filter(isTruthy),\n );\n};\n\n/**\n * Creates a <dx-anchor> tag, which is used to anchor the Popver.\n */\nconst popoverAnchorDecoration = (options: PopoverOptions) => {\n return ViewPlugin.fromClass(\n class {\n _decorations: DecorationSet = Decoration.none;\n\n constructor(readonly view: EditorView) {}\n\n // TODO(wittjosiah): The decorations are repainted on every update, this occasionally causes menu to flicker.\n update({ view, transactions }: ViewUpdate) {\n const builder = new RangeSetBuilder<Decoration>();\n const { range, trigger } = view.state.field(popoverStateField) ?? {};\n if (range) {\n // Check if we should show the widget (only if cursor is within the active command range).\n const selection = view.state.selection.main;\n const showWidget = selection.head >= range.from && selection.head <= range.to;\n if (showWidget) {\n builder.add(\n range.from,\n range.to + 1,\n Decoration.mark({\n tagName: 'dx-anchor',\n class: 'cm-popover-trigger',\n attributes: {\n 'data-visible-focus': 'false',\n 'data-auto-trigger': 'true',\n 'data-trigger': trigger ?? options.triggerKey ?? '',\n },\n }),\n );\n }\n\n const rangeChanged = transactions.some((tr) => tr.effects.some((effect) => effect.is(popoverRangeEffect)));\n if (rangeChanged) {\n // NOTE: Content skips the trigger character.\n const content = view.state.sliceDoc(range.from + (trigger ? trigger.length : 0), range.to);\n options.onTextChange?.({ view, pos: selection.head, text: content, trigger });\n }\n }\n\n this._decorations = builder.finish();\n }\n },\n {\n decorations: (v) => v._decorations,\n },\n );\n};\n\ntype PopoverState = {\n /**\n * Trigger prefix (in document).\n */\n trigger?: string;\n\n /**\n * Current document completion range.\n */\n range: Range;\n};\n\n// State effects for managing popover state.\nexport const popoverRangeEffect = StateEffect.define<PopoverState | null>();\n\n// State field to track the active popover trigger range.\nexport const popoverStateField = StateField.define<PopoverState | null>({\n create: () => null,\n update: (value, tr) => {\n let newValue = value;\n for (const effect of tr.effects) {\n if (effect.is(popoverRangeEffect)) {\n newValue = effect.value;\n }\n }\n\n return newValue;\n },\n});\n\nconst getLastIndexOf = (str: string, delimiters: string[]) =>\n Math.max(...delimiters.map((delim) => str.lastIndexOf(delim)));\n","//\n// Copyright 2025 DXOS.org\n//\n\nimport { type EditorView } from '@codemirror/view';\n\nimport { insertAtLineStart, insertTable } from '@dxos/ui-editor';\n\nimport { type EditorMenuGroup } from './menu.ts';\nimport { popoverRangeEffect } from './popover.ts';\n\n/**\n * Re-open the popover in link-query (\"@\") mode at the cursor, seeding the same query shape the user\n * would get by typing the trigger manually: a single \"@\" for an inline link, \"@@\" for a block embed.\n * The consumer's `@` handler (e.g. plugin-markdown's link query) renders the object picker and, on\n * selection, replaces this range with the corresponding markdown link.\n *\n * Deferred to the next frame: selecting a slash-menu item deletes the \"/\" range, which closes the\n * popover; opening synchronously races that close (`onActivate` reads a stale open state) and the\n * picker never appears.\n */\nconst openLinkQuery = (view: EditorView, insert: '@' | '@@'): void => {\n requestAnimationFrame(() => {\n const { head } = view.state.selection.main;\n const to = head + insert.length;\n view.dispatch({\n changes: { from: head, insert },\n selection: { anchor: to, head: to },\n // The popover trigger is the single \"@\"; a second \"@\" lives in the query range and switches to block mode.\n effects: popoverRangeEffect.of({ trigger: '@', range: { from: head, to } }),\n });\n });\n};\n\nexport const formattingCommands: EditorMenuGroup = {\n id: 'markdown',\n label: 'Markdown',\n items: [\n {\n id: 'heading-1',\n label: 'Heading 1',\n icon: 'ph--text-h-one--regular',\n onSelect: ({ view, head }) => insertAtLineStart(view, head, '# '),\n },\n {\n id: 'heading-2',\n label: 'Heading 2',\n icon: 'ph--text-h-two--regular',\n onSelect: ({ view, head }) => insertAtLineStart(view, head, '## '),\n },\n {\n id: 'heading-3',\n label: 'Heading 3',\n icon: 'ph--text-h-three--regular',\n onSelect: ({ view, head }) => insertAtLineStart(view, head, '### '),\n },\n {\n id: 'heading-4',\n label: 'Heading 4',\n icon: 'ph--text-h-four--regular',\n onSelect: ({ view, head }) => insertAtLineStart(view, head, '#### '),\n },\n {\n id: 'heading-5',\n label: 'Heading 5',\n icon: 'ph--text-h-five--regular',\n onSelect: ({ view, head }) => insertAtLineStart(view, head, '##### '),\n },\n {\n id: 'heading-6',\n label: 'Heading 6',\n icon: 'ph--text-h-six--regular',\n onSelect: ({ view, head }) => insertAtLineStart(view, head, '###### '),\n },\n {\n id: 'bullet-list',\n label: 'Bullet List',\n icon: 'ph--list-bullets--regular',\n onSelect: ({ view, head }) => insertAtLineStart(view, head, '- '),\n },\n {\n id: 'numbered-list',\n label: 'Numbered List',\n icon: 'ph--list-numbers--regular',\n onSelect: ({ view, head }) => insertAtLineStart(view, head, '1. '),\n },\n {\n id: 'task-list',\n label: 'Task List',\n icon: 'ph--list-checks--regular',\n onSelect: ({ view, head }) => insertAtLineStart(view, head, '- [ ] '),\n },\n {\n id: 'quote',\n label: 'Quote',\n icon: 'ph--quotes--regular',\n onSelect: ({ view, head }) => insertAtLineStart(view, head, '> '),\n },\n {\n id: 'code-block',\n label: 'Code Block',\n icon: 'ph--code-block--regular',\n onSelect: ({ view, head }) => insertAtLineStart(view, head, '```\\n\\n```'),\n },\n {\n id: 'table',\n // Shares the toolbar's snippet rather than a literal of its own: two spellings of \"insert a\n // table\" drifted into different shapes (3 empty columns here, 2 placeholder-filled ones there).\n label: 'Table',\n icon: 'ph--table--regular',\n onSelect: ({ view, head }) => insertTable(view, head),\n },\n ],\n};\n\nexport const linkSlashCommands: EditorMenuGroup = {\n id: 'link',\n label: 'Link',\n items: [\n {\n id: 'inline-link',\n label: 'Inline link',\n icon: 'ph--link--regular',\n onSelect: ({ view }) => openLinkQuery(view, '@'),\n },\n {\n id: 'inline-object',\n label: 'Inline object',\n icon: 'ph--lego--regular',\n onSelect: ({ view }) => openLinkQuery(view, '@@'),\n },\n ],\n};\n","//\n// Copyright 2025 DXOS.org\n//\n\nimport { type EditorView } from '@codemirror/view';\nimport React, {\n Fragment,\n type KeyboardEvent,\n type PropsWithChildren,\n useCallback,\n useEffect,\n useRef,\n useState,\n} from 'react';\n\nimport { addEventListener } from '@dxos/async';\nimport { invariant } from '@dxos/invariant';\nimport { useControllableState } from '@dxos/react-hooks';\nimport * as Field from '@dxos/react-ui/Field';\nimport * as Hooks from '@dxos/react-ui/Hooks';\nimport * as Icon from '@dxos/react-ui/Icon';\nimport * as Input from '@dxos/react-ui/Input';\nimport * as Layout from '@dxos/react-ui/Layout';\nimport * as Popover from '@dxos/react-ui/Popover';\nimport * as Theme from '@dxos/react-ui/Theme';\nimport * as Typography from '@dxos/react-ui/Typography';\nimport * as VirtualAnchor from '@dxos/react-ui/VirtualAnchor';\nimport { DX_ANCHOR_ACTIVATE, type DxAnchorActivate } from '@dxos/ui-types';\n\nimport { translationKey } from '#translations';\n\nimport { type EditorMenuGroup, type EditorMenuItem, getMenuItem } from './menu.ts';\n\nexport type EditorMenuProviderProps = PropsWithChildren<{\n // Provided as a getter (not a value prop) so the live `EditorView` is never carried in a React prop that\n // the dev render-logger would walk into a cross-origin frame. See `controller.ts` for the full rationale.\n getView?: () => EditorView | null;\n groups?: EditorMenuGroup[];\n currentItem?: string;\n open?: boolean;\n defaultOpen?: boolean;\n numItems?: number;\n /** Render the menu as a combobox: the popover owns the query input and takes focus from the editor. */\n search?: boolean;\n query?: string;\n searchPlaceholder?: string;\n onOpenChange?: (event: { view: EditorView; open: boolean; trigger?: string }) => void;\n onActivate?: (event: { view: EditorView; trigger?: string }) => void;\n onSelect?: (event: { view: EditorView; item: EditorMenuItem }) => void;\n onCancel?: (event: { view: EditorView }) => void;\n onQueryChange?: (query: string) => void;\n onNavigate?: (direction: 'up' | 'down') => void;\n}>;\n\n/**\n * Implements the Popover and listens for the `dx-anchor-activate` event from the `popover` extension's decoration.\n * NOTE: We don't use Menu because the command menu needs to manage focus explicitly.\n * I.e., focus must remain in the editor while displaying the menu (for type-ahead).\n */\nexport const EditorMenuProvider = ({\n children,\n getView,\n groups,\n currentItem,\n open: openProp,\n defaultOpen,\n numItems = 8,\n search,\n query = '',\n searchPlaceholder,\n onOpenChange,\n onActivate,\n onSelect,\n onCancel,\n onQueryChange,\n onNavigate,\n}: EditorMenuProviderProps) => {\n const { t } = Hooks.useTranslation(translationKey);\n const triggerRef = useRef<HTMLButtonElement | null>(null);\n\n // Hold the latest `getView` so callbacks/effects always read the current view without re-subscribing.\n const getViewRef = Hooks.useDynamicRef(getView);\n const [open, setOpen] = useControllableState({\n prop: openProp,\n defaultProp: defaultOpen,\n onChange: (open) => {\n const view = getViewRef.current?.();\n invariant(view);\n onOpenChange?.({ view, open });\n },\n });\n\n const [root, setRoot] = useState<HTMLDivElement | null>(null);\n useEffect(() => {\n if (!root) {\n return;\n }\n\n // Listen for trigger.\n return addEventListener(\n root,\n DX_ANCHOR_ACTIVATE as any,\n (event: DxAnchorActivate) => {\n const { trigger, eid } = event;\n if (!eid) {\n triggerRef.current = trigger as HTMLButtonElement;\n if (onActivate) {\n const view = getViewRef.current?.();\n if (view) {\n onActivate({ view, trigger: trigger.getAttribute('data-trigger') ?? undefined });\n }\n } else {\n requestAnimationFrame(() => setOpen(true));\n }\n }\n },\n {\n capture: true,\n passive: false,\n },\n );\n }, [root, onActivate]);\n\n const handleSelect = useCallback<NonNullable<MenuProps['onSelect']>>(\n (item) => {\n const view = getViewRef.current?.();\n invariant(view);\n onSelect?.({ view, item });\n // Close the popover after acting (e.g. Delete) and return focus to the editor; otherwise the menu\n // lingers open over the now-changed document.\n setOpen(false);\n view.focus();\n },\n [getViewRef, onSelect, setOpen],\n );\n\n const menuGroups = groups?.filter((group) => group.items.length > 0) ?? [];\n\n // Handing off from the slash menu refocuses the editor and reopens the popover a frame later, so\n // neither `autoFocus` nor `onOpenAutoFocus` lands on the input. Claim focus a frame after the\n // modal-state dispatch `handleOpenChange` schedules, which otherwise hands focus back to CodeMirror.\n const searchInputRef = useRef<HTMLInputElement>(null);\n useEffect(() => {\n if (!search || !open) {\n return;\n }\n\n let inner: number;\n const outer = requestAnimationFrame(() => {\n inner = requestAnimationFrame(() => searchInputRef.current?.focus());\n });\n\n return () => {\n cancelAnimationFrame(outer);\n cancelAnimationFrame(inner);\n };\n }, [search, open]);\n\n const handleSearchKeyDown = useCallback(\n (event: KeyboardEvent<HTMLInputElement>) => {\n switch (event.key) {\n case 'ArrowUp':\n case 'ArrowDown': {\n event.preventDefault();\n onNavigate?.(event.key === 'ArrowUp' ? 'up' : 'down');\n break;\n }\n\n case 'Enter': {\n const item = getMenuItem(menuGroups, currentItem);\n if (item) {\n event.preventDefault();\n handleSelect(item);\n }\n break;\n }\n }\n },\n [menuGroups, currentItem, handleSelect, onNavigate],\n );\n\n return (\n <Popover.Root\n open={open}\n onOpenChange={({ open }) => setOpen(open)}\n positioning={{ ...VirtualAnchor.virtualAnchor(triggerRef), placement: 'bottom-start' }}\n // In search mode the query is typed into the popover's own input, so it must take focus.\n autoFocus={!!search}\n // Focus stays in the editor; the menu machine still routes Escape here.\n onEscapeKeyDown={() => {\n const currentView = getViewRef.current?.();\n if (currentView) {\n onCancel?.({ view: currentView });\n }\n }}\n >\n {/* Menu. */}\n <Popover.Content\n classNames={!search && !menuGroups.length ? 'hidden' : undefined}\n style={{\n // The search input shares the box, so `numItems` keeps meaning \"items visible\".\n maxBlockSize: 36 * numItems + 10 + (search ? 36 : 0),\n }}\n >\n {search && (\n <Field.Root>\n <Input.Root\n ref={searchInputRef}\n variant='subdued'\n classNames='shrink-0 mb-1'\n value={query}\n placeholder={searchPlaceholder}\n // Placeholder text is not a persistent accessible name, and it is optional — fall\n // back to the generic label so the input is never anonymous.\n aria-label={searchPlaceholder ?? t('search.label')}\n onChange={(event) => onQueryChange?.(event.target.value)}\n onKeyDown={handleSearchKeyDown}\n />\n </Field.Root>\n )}\n <Popover.Body>\n <Menu groups={menuGroups} currentItem={currentItem} onSelect={handleSelect} />\n </Popover.Body>\n </Popover.Content>\n\n {/* Content */}\n <div className='contents' ref={setRoot}>\n {children}\n </div>\n </Popover.Root>\n );\n};\n\n//\n// Menu\n//\n\ntype MenuProps = {\n groups: EditorMenuGroup[];\n} & Pick<MenuGroupProps, 'currentItem' | 'onSelect'>;\n\nconst Menu = ({ groups, currentItem, onSelect }: MenuProps) => {\n return (\n <ul>\n {groups.map((group, index) => (\n <Fragment key={group.id}>\n <MenuGroup group={group} currentItem={currentItem} onSelect={onSelect} />\n {index < groups.length - 1 && <Layout.Separator />}\n </Fragment>\n ))}\n </ul>\n );\n};\n\n//\n// Menu Group\n//\n\ntype MenuGroupProps = {\n group: EditorMenuGroup;\n currentItem?: string;\n} & Pick<MenuItemProps, 'onSelect'>;\n\nconst MenuGroup = ({ group, currentItem, onSelect }: MenuGroupProps) => {\n const { t } = Hooks.useTranslation();\n\n return (\n <>\n {group.label && (\n <Typography.Text tone='muted' classNames='px-2'>\n {Theme.toLocalizedString(group.label, t)}\n </Typography.Text>\n )}\n\n {group.items.map((item) => (\n <MenuItem key={item.id} item={item} current={currentItem === item.id} onSelect={onSelect} />\n ))}\n </>\n );\n};\n\n//\n// Menu Item\n//\n\ntype MenuItemProps = {\n item: EditorMenuItem;\n current: boolean;\n onSelect?: (item: EditorMenuItem) => void;\n};\n\nconst MenuItem = ({ item, current, onSelect }: MenuItemProps) => {\n const { t } = Hooks.useTranslation();\n\n const listRef = useRef<HTMLLIElement>(null);\n useEffect(() => {\n if (current && listRef.current) {\n // Instant, not smooth: held arrow keys retarget faster than a smooth scroll can settle, and each\n // new animation supersedes the last, so the list stalls then jumps.\n listRef.current.scrollIntoView({ behavior: 'instant', block: 'nearest' });\n }\n }, [current]);\n\n const handleSelect = useCallback(() => onSelect?.(item), [item, onSelect]);\n\n return (\n // Menu row metrics without a Menu machine: the popover keeps focus in the editor, so `current` is the highlight.\n <li ref={listRef} className='dx-menu-item' data-highlighted={current ? '' : undefined} onClick={handleSelect}>\n {item.icon && <Icon.Icon icon={item.icon} hue={item.iconHue} />}\n <span className='dx-menu-item-text'>{Theme.toLocalizedString(item.label, t)}</span>\n </li>\n );\n};\n","//\n// Copyright 2024 DXOS.org\n//\n\nimport { type Extension } from '@codemirror/state';\nimport { type EditorState } from '@codemirror/state';\nimport { type EditorView } from '@codemirror/view';\nimport { type RefObject, useCallback, useLayoutEffect, useMemo, useRef, useState } from 'react';\n\nimport { invariant } from '@dxos/invariant';\nimport { modalStateEffect } from '@dxos/ui-editor';\nimport { type MaybePromise } from '@dxos/util';\n\nimport { type EditorMenuProviderProps } from './EditorMenuProvider.tsx';\nimport { type EditorMenuGroup, type EditorMenuItem } from './menu.ts';\nimport { filterMenuGroups, getMenuItem, getNextMenuItem, getPreviousMenuItem } from './menu.ts';\nimport { type PopoverOptions, popover, popoverRangeEffect, popoverStateField } from './popover.ts';\n\nexport type GetMenuContext = {\n state: EditorState;\n pos: number;\n text: string;\n trigger?: string;\n};\n\nexport type UseEditorMenuProps = {\n filter?: boolean;\n /**\n * Triggers whose menu is rendered as a combobox: the popover owns a search input and the query is\n * typed there rather than into the document.\n */\n // TODO(wittjosiah): Remove? Prefer inline instead.\n searchTriggers?: string[];\n getMenu?: (context: GetMenuContext) => MaybePromise<EditorMenuGroup[]>;\n} & Pick<\n PopoverOptions,\n 'trigger' | 'triggerKey' | 'placeholder' | 'activateOnTyping' | 'activateOnDelimiters' | 'multiWordTriggers'\n>;\n\ntype MenuContext = {\n view: EditorView;\n pos: number;\n text: string;\n trigger?: string;\n /** The trigger's query may hold spaces; see {@link PopoverOptions.multiWordTriggers}. */\n multiWord?: boolean;\n};\n\nexport type UseEditorMenu = {\n groupsRef: RefObject<EditorMenuGroup[]>;\n extension: Extension;\n} & Pick<\n EditorMenuProviderProps,\n | 'currentItem'\n | 'open'\n | 'search'\n | 'query'\n | 'onOpenChange'\n | 'onActivate'\n | 'onSelect'\n | 'onCancel'\n | 'onQueryChange'\n | 'onNavigate'\n>;\n\n/**\n * ```tsx\n * const { groupsRef, extension, ...menuProps } = useEditorMenu();\n * const { parentRef, viewRef } = useTextEditor({ extensions: [extension] });\n * return (\n * <EditorMenuProvider getView={() => viewRef.current} groups={groupsRef.current} {...menuProps}>\n * <div ref={parentRef} />\n * </EditorMenuProvider>\n * );\n * ```\n */\nexport const useEditorMenu = ({\n trigger,\n triggerKey,\n placeholder,\n activateOnTyping,\n activateOnDelimiters,\n multiWordTriggers,\n filter = true,\n searchTriggers,\n getMenu,\n}: UseEditorMenuProps): UseEditorMenu => {\n const groupsRef = useRef<EditorMenuGroup[]>([]);\n const currentRef = useRef<EditorMenuItem | null>(null);\n const [currentItem, setCurrentItem] = useState<string>();\n const [open, setOpen] = useState(false);\n const [search, setSearch] = useState(false);\n const [query, setQuery] = useState('');\n const [_, refresh] = useState({});\n\n // The document range only supplies the trigger-relative prefix (e.g. the second \"@\" of a block\n // embed); in search mode the rest of the query comes from the popover's input.\n const contextRef = useRef<MenuContext | null>(null);\n\n // Monotonic token identifying the latest menu query; bumped per request and on close.\n const requestRef = useRef(0);\n\n // The latest `getMenu`, read when the menu opens: it changes whenever the caller's data does, and the extension\n // built from it would otherwise rebuild the view, blurring the editor mid-keystroke.\n const getMenuRef = useRef(getMenu);\n useLayoutEffect(() => {\n getMenuRef.current = getMenu;\n }, [getMenu]);\n\n /**\n * Get filtered options.\n */\n const getMenuOptions = useCallback<NonNullable<UseEditorMenuProps['getMenu']>>(\n async ({ text, trigger, ...props }) => {\n const groups = (await getMenuRef.current?.({ text, trigger, ...props })) ?? [];\n // The \"@\" menu can use \"@@\" as syntax for block embeds, so it owns its own query filtering.\n return filter && trigger !== '@'\n ? filterMenuGroups(groups, (item) =>\n text ? (item.label as string).toLowerCase().startsWith(text.toLowerCase()) : true,\n )\n : groups;\n },\n [filter],\n );\n\n const handleOpenChange = useCallback<NonNullable<UseEditorMenu['onOpenChange']>>(\n async ({ view, open }) => {\n invariant(view);\n setOpen(open);\n if (!open) {\n setCurrentItem(undefined);\n setSearch(false);\n setQuery('');\n contextRef.current = null;\n // Invalidate in-flight queries so a late response cannot repopulate the closed menu.\n requestRef.current++;\n groupsRef.current = [];\n currentRef.current = null;\n view.dispatch({\n effects: [popoverRangeEffect.of(null)],\n });\n }\n\n // TODO(burdon): Possible race condition.\n // useTextEditor.handleKeyDown will get called after this handler completes.\n requestAnimationFrame(() => {\n view.dispatch({\n effects: [modalStateEffect.of(open)],\n });\n });\n },\n [getMenuOptions],\n );\n\n /**\n * Recompute the menu and preselect the first item.\n */\n const updateGroups = useCallback(\n async ({ view, pos, text, trigger }: MenuContext) => {\n const request = ++requestRef.current;\n const groups = (await getMenuOptions({ state: view.state, pos, text, trigger })) ?? [];\n // Queries resolve out of order, so a slower earlier one must not overwrite a newer result\n // (or repopulate the menu after it closed).\n if (request !== requestRef.current) {\n return;\n }\n\n groupsRef.current = groups;\n const firstItem = groupsRef.current.filter((group) => group.items.length > 0)[0]?.items[0];\n if (firstItem) {\n setCurrentItem(firstItem.id);\n currentRef.current = firstItem;\n } else {\n // No matches: clear the selection so Enter falls through instead of\n // selecting a stale item from a previous query.\n setCurrentItem(undefined);\n currentRef.current = null;\n }\n\n refresh({});\n },\n [getMenuOptions],\n );\n\n const handleActivate = useCallback<NonNullable<UseEditorMenu['onActivate']>>(\n async ({ view, trigger }) => {\n const item = getMenuItem(groupsRef.current, currentItem);\n if (item) {\n currentRef.current = item;\n }\n\n if (!open) {\n handleOpenChange({ view, open: true, trigger });\n }\n },\n [open, handleOpenChange],\n );\n\n const handleSelect = useCallback<NonNullable<UseEditorMenu['onSelect']>>(({ view, item }) => {\n // Delete trigger range (e.g., \"/\" and any typed filter text).\n const { range } = view.state.field(popoverStateField) ?? {};\n if (range) {\n view.dispatch({ changes: { from: range.from, to: range.to, insert: '' } });\n }\n void item.onSelect?.({ view, head: view.state.selection.main.head });\n view.focus();\n }, []);\n\n const handleCancel = useCallback<NonNullable<UseEditorMenu['onCancel']>>(({ view }) => {\n // Delete trigger; a multi-word query may be a sentence the user wants to keep.\n const { range, trigger } = view.state.field(popoverStateField) ?? {};\n if (range && trigger && !contextRef.current?.multiWord) {\n view.dispatch({\n changes: { ...range, insert: '' },\n });\n }\n }, []);\n\n const handleQueryChange = useCallback<NonNullable<UseEditorMenu['onQueryChange']>>(\n (query) => {\n setQuery(query);\n const context = contextRef.current;\n if (context) {\n void updateGroups({ ...context, text: context.text + query });\n }\n },\n [updateGroups],\n );\n\n const handleNavigate = useCallback<NonNullable<UseEditorMenu['onNavigate']>>((direction) => {\n setCurrentItem((currentItem) => {\n const next =\n direction === 'up'\n ? getPreviousMenuItem(groupsRef.current, currentItem)\n : getNextMenuItem(groupsRef.current, currentItem);\n // A query that matches nothing leaves an empty menu; arrow keys then have nowhere to go.\n if (!next) {\n return currentItem;\n }\n\n currentRef.current = next;\n return next.id;\n });\n }, []);\n\n // Array props are compared by CONTENT, not identity: a caller writing `trigger={['#']}` inline\n // hands over a new array every render, and a changed extension destroys and recreates the view —\n // blurring the editor mid-keystroke. The memo reads them back from the key, so the React Compiler keys it the\n // same way.\n const arraysKey = JSON.stringify({ trigger, searchTriggers, activateOnDelimiters, multiWordTriggers });\n const extension = useMemo<Extension>(() => {\n const arrays: Pick<\n UseEditorMenuProps,\n 'trigger' | 'searchTriggers' | 'activateOnDelimiters' | 'multiWordTriggers'\n > = JSON.parse(arraysKey);\n return popover({\n trigger: arrays.trigger,\n triggerKey,\n placeholder,\n activateOnTyping,\n activateOnDelimiters: arrays.activateOnDelimiters,\n multiWordTriggers: arrays.multiWordTriggers,\n onClose: ({ view }) => handleOpenChange({ view, open: false }),\n onEnter: ({ view }) => {\n if (currentRef.current) {\n handleSelect({ view, item: currentRef.current });\n return true;\n }\n return false;\n },\n onArrowUp: () => handleNavigate('up'),\n onArrowDown: () => handleNavigate('down'),\n onTextChange: async ({ view, pos, text, trigger }) => {\n const context: MenuContext = {\n view,\n pos,\n text,\n trigger,\n multiWord: !!trigger && !!arrays.multiWordTriggers?.includes(trigger),\n };\n contextRef.current = context;\n // The input is remounted per activation, so the query always starts empty.\n setSearch(!!trigger && !!arrays.searchTriggers?.includes(trigger));\n setQuery('');\n await updateGroups(context);\n },\n });\n }, [handleOpenChange, handleNavigate, updateGroups, arraysKey, triggerKey, placeholder, activateOnTyping]);\n\n return {\n groupsRef,\n extension,\n currentItem,\n open,\n search,\n query,\n onOpenChange: handleOpenChange,\n onActivate: handleActivate,\n onSelect: handleSelect,\n onCancel: handleCancel,\n onQueryChange: handleQueryChange,\n onNavigate: handleNavigate,\n };\n};\n","//\n// Copyright 2025 DXOS.org\n//\n\nimport { type EditorView } from '@codemirror/view';\n\nimport type { ActionGroupBuilderFn, ToolbarMenuActionGroupProperties } from '@dxos/react-ui-menu';\nimport { addBlockquote, addCodeblock, insertTable, removeBlockquote, removeCodeblock } from '@dxos/ui-editor';\n\nimport { translationKey } from '#translations';\n\nimport { type EditorToolbarState } from './types.ts';\n\nconst blockTypes = {\n blockquote: 'ph--quotes--regular',\n codeblock: 'ph--code-block--regular',\n table: 'ph--table--regular',\n};\n\n/** Add block actions to the builder. */\nexport const addBlocks =\n (state: EditorToolbarState, getView: () => EditorView): ActionGroupBuilderFn =>\n (builder) => {\n const value = state?.blockQuote ? 'blockquote' : (state.blockType ?? '');\n builder.group(\n 'block',\n {\n label: ['block.label', { ns: translationKey }],\n iconOnly: true,\n variant: 'toggleGroup',\n selectCardinality: 'single',\n value,\n } as ToolbarMenuActionGroupProperties,\n (group) => {\n for (const [type, icon] of Object.entries(blockTypes)) {\n const checked = type === value;\n group.action(\n type,\n {\n label: [`block.${type}.label`, { ns: translationKey }],\n checked,\n // A table replaces the line it lands on, so it is offered on a blank one and withheld\n // where it would clobber content — the condition was inverted.\n ...(type === 'table' && { disabled: !state.blankLine }),\n icon,\n },\n () => {\n const view = getView();\n if (!view) {\n return;\n }\n\n switch (type) {\n case 'blockquote':\n checked ? removeBlockquote(view) : addBlockquote(view);\n break;\n case 'codeblock':\n checked ? removeCodeblock(view) : addCodeblock(view);\n break;\n case 'table':\n insertTable(view);\n break;\n }\n },\n );\n }\n },\n );\n };\n","//\n// Copyright 2025 DXOS.org\n//\n\nimport { type EditorView } from '@codemirror/view';\n\nimport type { ActionGroupBuilderFn, ToolbarMenuActionGroupProperties } from '@dxos/react-ui-menu';\nimport { type Formatting, Inline, addLink, removeLink, setStyle } from '@dxos/ui-editor';\n\nimport { translationKey } from '#translations';\n\nimport { type EditorToolbarState } from './types.ts';\n\nconst formats = {\n strong: 'ph--text-b--regular',\n emphasis: 'ph--text-italic--regular',\n strikethrough: 'ph--text-strikethrough--regular',\n code: 'ph--code--regular',\n link: 'ph--link--regular',\n};\n\n/** Add formatting actions to the builder. */\nexport const addFormatting =\n (state: EditorToolbarState, getView: () => EditorView): ActionGroupBuilderFn =>\n (builder) => {\n const formatting: Formatting = state;\n builder.group(\n 'formatting',\n {\n label: ['formatting.label', { ns: translationKey }],\n iconOnly: true,\n variant: 'toggleGroup',\n selectCardinality: 'multiple',\n value: Object.keys(formats).filter((key) => !!formatting[key as keyof Formatting]),\n } as ToolbarMenuActionGroupProperties,\n (group) => {\n for (const [type, icon] of Object.entries(formats)) {\n const checked = !!formatting[type as keyof Formatting];\n group.action(\n type,\n {\n label: [`formatting.${type}.label`, { ns: translationKey }],\n testId: `editor.toolbar.${type}`,\n checked,\n icon,\n },\n () => {\n const view = getView();\n if (!view) {\n return;\n }\n\n if (type === 'link') {\n checked ? removeLink(view) : addLink()(view);\n return;\n }\n\n setStyle(\n type === 'strong'\n ? Inline.Strong\n : type === 'emphasis'\n ? Inline.Emphasis\n : type === 'strikethrough'\n ? Inline.Strikethrough\n : Inline.Code,\n !checked,\n )(view);\n },\n );\n }\n },\n );\n };\n","//\n// Copyright 2025 DXOS.org\n//\n\nimport { type EditorView } from '@codemirror/view';\n\nimport type { ActionGroupBuilderFn, ToolbarMenuActionGroupProperties } from '@dxos/react-ui-menu';\nimport { setHeading } from '@dxos/ui-editor';\n\nimport { translationKey } from '#translations';\n\nimport { type EditorToolbarState } from './types.ts';\n\nconst headingIcons: Record<string, string> = {\n 0: 'ph--paragraph--regular',\n 1: 'ph--text-h-one--regular',\n 2: 'ph--text-h-two--regular',\n 3: 'ph--text-h-three--regular',\n 4: 'ph--text-h-four--regular',\n 5: 'ph--text-h-five--regular',\n 6: 'ph--text-h-six--regular',\n};\n\nconst computeHeadingValue = (state: EditorToolbarState) => {\n const blockType = state ? state.blockType : 'paragraph';\n const heading = blockType && /heading(\\d)/.exec(blockType);\n return heading ? heading[1] : blockType === 'paragraph' || !blockType ? '0' : '';\n};\n\n/** Add heading actions to the builder. */\nexport const addHeadings =\n (state: EditorToolbarState, getView: () => EditorView): ActionGroupBuilderFn =>\n (builder) => {\n const headingValue = computeHeadingValue(state);\n builder.group(\n 'heading',\n {\n label: ['heading.label', { ns: translationKey }],\n icon: 'ph--text-h--regular',\n iconOnly: true,\n variant: 'dropdownMenu',\n applyActive: true,\n selectCardinality: 'single',\n // TODO(wittjosiah): Remove? Not sure this does anything.\n value: headingValue,\n } as ToolbarMenuActionGroupProperties,\n (group) => {\n for (const [levelStr, icon] of Object.entries(headingIcons)) {\n const level = parseInt(levelStr);\n group.action(\n `heading--${levelStr}`,\n {\n label: ['heading-level.label', { count: level, ns: translationKey }],\n icon,\n checked: levelStr === headingValue,\n },\n () => setHeading(level)(getView()),\n );\n }\n },\n );\n };\n","//\n// Copyright 2025 DXOS.org\n//\n\nimport type { ActionGroupBuilderFn } from '@dxos/react-ui-menu';\n\nimport { translationKey } from '#translations';\n\n/** Add image upload action to the builder. */\nexport const addImageUpload =\n (onImageUpload: () => void): ActionGroupBuilderFn =>\n (builder) => {\n builder.action(\n 'image',\n {\n label: ['image.label', { ns: translationKey }],\n testId: 'editor.toolbar.image',\n icon: 'ph--image-square--regular',\n },\n onImageUpload,\n );\n };\n","//\n// Copyright 2025 DXOS.org\n//\n\nimport { type EditorView } from '@codemirror/view';\n\nimport type { ActionGroupBuilderFn, ToolbarMenuActionGroupProperties } from '@dxos/react-ui-menu';\nimport { List, toggleList } from '@dxos/ui-editor';\n\nimport { translationKey } from '#translations';\n\nimport { type EditorToolbarState } from './types.ts';\n\nconst listStyles = {\n bullet: 'ph--list-bullets--regular',\n ordered: 'ph--list-numbers--regular',\n task: 'ph--list-checks--regular',\n};\n\n/** Add list actions to the builder. */\nexport const addLists =\n (state: EditorToolbarState, getView: () => EditorView): ActionGroupBuilderFn =>\n (builder) => {\n const value = state.listStyle ?? '';\n builder.group(\n 'list',\n {\n label: ['list.label', { ns: translationKey }],\n iconOnly: true,\n variant: 'toggleGroup',\n selectCardinality: 'single',\n value,\n } as ToolbarMenuActionGroupProperties,\n (group) => {\n for (const [listStyle, icon] of Object.entries(listStyles)) {\n const checked = value === listStyle;\n group.action(\n `list-${listStyle}`,\n { label: [`list.${listStyle}.label`, { ns: translationKey }], checked, icon },\n () => {\n const view = getView();\n if (!view) {\n return;\n }\n\n const listType =\n listStyle === 'ordered' ? List.Ordered : listStyle === 'bullet' ? List.Bullet : List.Task;\n toggleList(listType)(view);\n },\n );\n }\n },\n );\n };\n","//\n// Copyright 2025 DXOS.org\n//\n\nimport { openSearchPanel } from '@codemirror/search';\nimport { type EditorView } from '@codemirror/view';\n\nimport type { ActionGroupBuilderFn } from '@dxos/react-ui-menu';\n\nimport { translationKey } from '#translations';\n\n/** Add search action to the builder. */\nexport const addSearch =\n (getView: () => EditorView): ActionGroupBuilderFn =>\n (builder) => {\n builder.action(\n 'search',\n {\n label: ['search.label', { ns: translationKey }],\n testId: 'editor.toolbar.search',\n icon: 'ph--magnifying-glass--regular',\n },\n () => openSearchPanel(getView()),\n );\n };\n","//\n// Copyright 2025 DXOS.org\n//\n\nimport type { ActionGroupBuilderFn, ToolbarMenuActionGroupProperties } from '@dxos/react-ui-menu';\nimport { type EditorViewMode } from '@dxos/ui-editor/types';\nimport { type Label } from '@dxos/ui-types';\n\nimport { translationKey } from '#translations';\n\nimport { type EditorToolbarState } from './types.ts';\n\n/**\n * One entry in the editor's view-mode dropdown. A built-in entry references an {@link EditorViewMode}\n * by `id` and relies on the defaults (label `view-mode.<id>.label`, checked when it is the active view\n * mode, select via `onViewModeChange`). A contributed entry (e.g. a review \"Suggesting\" mode) supplies\n * its own `label`, `checked`, and `onSelect` so it can drive state other than the editor view mode.\n */\nexport type ViewModeItem = {\n id: string;\n icon: string;\n label?: Label;\n checked?: boolean;\n onSelect?: () => void;\n};\n\n/**\n * The built-in view modes: preview (rendered), source (raw edit), readonly (non-editable source). Typed\n * so each `id` is an {@link EditorViewMode}, letting callers pass `item.id` to `onViewModeChange` without a cast.\n */\nexport const defaultViewModeItems: Array<ViewModeItem & { id: EditorViewMode }> = [\n { id: 'preview', icon: 'ph--eye--regular' },\n { id: 'source', icon: 'ph--pencil-simple--regular' },\n { id: 'readonly', icon: 'ph--pencil-slash--regular' },\n];\n\n/**\n * Add view mode actions to the builder. `items` defaults to the three built-in editor view modes;\n * callers pass an extended list to surface contributed modes (see {@link ViewModeItem}) in the same\n * single-select dropdown. The trigger tracks the checked item (via the group's `applyActive`).\n */\nexport const addViewMode =\n (\n state: EditorToolbarState,\n onViewModeChange: (mode: EditorViewMode) => void,\n items: ViewModeItem[] = defaultViewModeItems,\n ): ActionGroupBuilderFn =>\n (builder) => {\n const value = state.viewMode ?? 'source';\n builder.group(\n 'viewMode',\n {\n label: ['view-mode.label', { ns: translationKey }],\n testId: 'editor.toolbar.viewMode',\n icon: 'ph--eye--regular',\n iconOnly: true,\n variant: 'dropdownMenu',\n applyActive: true,\n selectCardinality: 'single',\n value,\n } as ToolbarMenuActionGroupProperties,\n (group) => {\n for (const item of items) {\n // A contributed entry may drive non-view-mode state, so honour its explicit `checked`;\n // a built-in entry is checked when it matches the active editor view mode.\n const checked = item.checked ?? item.id === value;\n group.action(\n `view-mode--${item.id}`,\n {\n label: item.label ?? [`view-mode.${item.id}.label`, { ns: translationKey }],\n checked,\n icon: item.icon,\n },\n () => (item.onSelect ? item.onSelect() : onViewModeChange(item.id as EditorViewMode)),\n );\n }\n },\n );\n };\n","//\n// Copyright 2024 DXOS.org\n//\n\nimport { type EditorView } from '@codemirror/view';\nimport * as Atom from 'effect/reactivity/Atom';\nimport React, { memo, useMemo } from 'react';\n\nimport type * as AppGraphNode from '@dxos/app-graph/AppGraphNode';\nimport {\n type ActionGraphProps,\n ActionToolbar,\n type MenuAction,\n MenuBuilder,\n useMenuActions,\n} from '@dxos/react-ui-menu';\nimport type * as Util from '@dxos/react-ui/Util';\nimport { type EditorViewMode } from '@dxos/ui-editor/types';\n\nimport { addBlocks } from './blocks.ts';\nimport { addFormatting } from './formatting.ts';\nimport { addHeadings } from './headings.ts';\nimport { addImageUpload } from './image.ts';\nimport { addLists } from './lists.ts';\nimport { addSearch } from './search.ts';\nimport { type EditorToolbarState } from './types.ts';\nimport { type ViewModeItem, addViewMode } from './view-mode.ts';\n\n// TODO(burdon): Enable toolbar variants (e.g., markdown, code).\n\n// Local copy of AppSurface.Section.role — react-ui-editor cannot depend on app-toolkit without a circular dep.\nconst SECTION_ROLE = 'org.dxos.role.section';\n\nexport type EditorToolbarFeatureFlags = Partial<{\n showHeadings: boolean;\n showFormatting: boolean;\n showLists: boolean;\n showBlocks: boolean;\n showSearch: boolean;\n\n // TODO(wittjosiah): Factor out (depends on plugin-level capabilities.)\n onImageUpload: () => void;\n onViewModeChange: (mode: EditorViewMode) => void;\n /** Override the view-mode dropdown entries (default: the three built-in modes). See {@link ViewModeItem}. */\n viewModes: ViewModeItem[];\n}>;\n\nexport type EditorToolbarActionGraphProps = {\n state: Atom.Atom<EditorToolbarState>;\n getView: () => EditorView;\n // TODO(wittjosiah): Control positioning.\n customActions?: Atom.Atom<ActionGraphProps>;\n};\n\nexport type EditorToolbarProps = Util.ThemedClassName<\n {\n role?: string;\n attendableId?: string;\n /** Handler for executing actions. Required when customActions use Operation.invoke. */\n onAction?: (action: MenuAction, params: AppGraphNode.InvokeProps) => void;\n } & (EditorToolbarActionGraphProps & EditorToolbarFeatureFlags)\n>;\n\nexport const EditorToolbar = memo(({ classNames, role, attendableId, onAction, ...props }: EditorToolbarProps) => {\n const menuActions = useMarkdownMenuActions(props);\n\n return <ActionToolbar {...menuActions} attendableId={attendableId} onAction={onAction} classNames={classNames} />;\n});\n\ntype ToolbarActionsProps = Pick<EditorToolbarActionGraphProps, 'state' | 'getView' | 'customActions'> &\n EditorToolbarFeatureFlags;\n\n// TODO(burdon): Some actions should toggle the state (e.g., toggle bullets on/off depending on the current state).\n// TODO(wittjosiah): Toolbar re-rendering is causing this graph to be recreated and breaking reactivity in some cases.\n// E.g. for toolbar dropdowns which use active icon, the icon is not updated when the active item changes.\n// This is currently only happening in the markdown plugin usage and should be reproduced in an editor story.\nconst useMarkdownMenuActions = ({ state, getView, customActions, ...features }: ToolbarActionsProps) => {\n const menuCreator = useMemo(\n () => createMarkdownActions({ state, getView, customActions, ...features }),\n [\n state,\n getView,\n customActions,\n features?.showHeadings,\n features?.showFormatting,\n features?.showLists,\n features?.showBlocks,\n features?.showSearch,\n features?.onImageUpload,\n features?.onViewModeChange,\n features?.viewModes,\n ],\n );\n\n return useMenuActions(menuCreator);\n};\n\nconst createMarkdownActions = ({\n state,\n getView,\n customActions,\n ...features\n}: ToolbarActionsProps): Atom.Atom<ActionGraphProps> => {\n return Atom.make((get) => {\n // Subscribe to state changes.\n const stateSnapshot = get(state);\n return MenuBuilder.make()\n .subgraph(features?.showHeadings !== false && addHeadings(stateSnapshot, getView))\n .subgraph(features?.showFormatting !== false && addFormatting(stateSnapshot, getView))\n .subgraph(features?.showLists !== false && addLists(stateSnapshot, getView))\n .subgraph(features?.showBlocks !== false && addBlocks(stateSnapshot, getView))\n .subgraph(features?.onImageUpload && addImageUpload(features.onImageUpload))\n .separator('gap')\n .subgraph(customActions && get(customActions))\n .subgraph(features?.showSearch !== false && addSearch(getView))\n .subgraph(features?.onViewModeChange && addViewMode(stateSnapshot, features.onViewModeChange, features.viewModes))\n .build();\n });\n};\n","//\n// Copyright 2026 DXOS.org\n//\n\nimport { type Extension } from '@codemirror/state';\nimport { useMemo } from 'react';\n\nimport * as Hooks from '@dxos/react-ui/Hooks';\nimport {\n createBasicExtensions,\n createMarkdownExtensions,\n createThemeExtensions,\n decorateMarkdown,\n} from '@dxos/ui-editor';\n\nexport type UseBasicMarkdownExtensionsOptions = {\n /** Placeholder text shown when the editor is empty. */\n placeholder?: string;\n /** Enables markdown visual decorations. Pass `false` to disable. Defaults to `true`. */\n decorate?: boolean;\n /** Renders the editor read-only (selectable but not editable). Defaults to `false`. */\n readonly?: boolean;\n /**\n * Extra extensions appended to the returned array.\n * Callers must memoize this array — it is used as a dependency of {@link useMemo}.\n */\n extensions?: Extension[];\n};\n\n/**\n * Returns the standard CodeMirror extension stack for an inline markdown editor:\n * basic editor behaviors, themed syntax highlighting (theme mode read from {@link useThemeMode}),\n * markdown parsing, and visual decorations.\n *\n * Used by surfaces such as `AgentProperties` and `MagazineProperties` to render the\n * `instructions` field of an object as a small editable markdown block.\n *\n * The memoization depends on primitive option values plus the `extensions` array reference,\n * so callers passing fresh option literals each render still hit the cache.\n */\nexport const useBasicMarkdownExtensions = ({\n placeholder,\n decorate = true,\n readonly = false,\n extensions,\n}: UseBasicMarkdownExtensionsOptions = {}): Extension[] => {\n const themeMode = Hooks.useThemeMode();\n return useMemo(\n () => [\n createBasicExtensions({ placeholder, readOnly: readonly }),\n createThemeExtensions({ syntaxHighlighting: true, themeMode }),\n createMarkdownExtensions(),\n ...(decorate ? [decorateMarkdown()] : []),\n ...(extensions ?? []),\n ],\n [placeholder, themeMode, decorate, readonly, extensions],\n );\n};\n","//\n// Copyright 2024 DXOS.org\n//\n\nimport { EditorState, type EditorStateConfig, StateEffect, type Text } from '@codemirror/state';\nimport { EditorView } from '@codemirror/view';\nimport {\n type ComponentPropsWithoutRef,\n type DependencyList,\n type KeyboardEventHandler,\n type RefObject,\n useCallback,\n useEffect,\n useLayoutEffect,\n useMemo,\n useRef,\n useState,\n} from 'react';\n\nimport { log } from '@dxos/log';\nimport {\n type EditorSelection,\n type EditorSelectionState,\n debugDispatcher,\n documentId,\n modalStateField,\n restoreEditorState,\n} from '@dxos/ui-editor';\nimport { type MaybeProvider, getProviderValue, isTruthy } from '@dxos/util';\n\nlet instanceCount = 0;\n\nexport type CursorInfo = {\n from: number;\n to: number;\n line: number;\n lines: number;\n length: number;\n after?: string;\n};\n\nexport type UseTextEditor = {\n parentRef: RefObject<HTMLDivElement | null>;\n view: EditorView | null;\n focusAttributes?: ComponentPropsWithoutRef<'div'>;\n};\n\nexport type UseTextEditorProps = Pick<EditorStateConfig, 'extensions'> & {\n id?: string;\n doc?: Text;\n initialValue?: string;\n autoFocus?: boolean;\n scrollTo?: number;\n scrollOffset?: number;\n selection?: EditorSelection;\n selectionEnd?: boolean;\n debug?: boolean;\n};\n\n/** Whether every position in a recorded state lies within a document of `length`. */\nconst fitsDocument = ({ scrollTo, selection }: EditorSelectionState, length: number): boolean =>\n (scrollTo ?? 0) <= length && (selection?.anchor ?? 0) <= length && (selection?.head ?? 0) <= length;\n\n/**\n * Creates codemirror text editor.\n */\nexport const useTextEditor = (\n props: MaybeProvider<UseTextEditorProps> = {},\n deps: DependencyList = [],\n): UseTextEditor => {\n const { id, doc, initialValue, extensions, autoFocus, scrollTo, scrollOffset, selection, selectionEnd, debug } =\n useMemo<UseTextEditorProps>(() => getProviderValue(props), deps ?? []);\n\n // NOTE: Increments by 2 in strict mode.\n const [instanceId] = useState(() => `text-editor-${++instanceCount}`);\n const [view, setView] = useState<EditorView | null>(null);\n const parentRef = useRef<HTMLDivElement>(null);\n\n // Layout effect, not a passive one: the view is what fills the editor's box, so building it after\n // the browser paints shows a frame of empty container and then reflows everything below it — a\n // field in a pane (a task's description) drops its siblings by the editor's height on every\n // subject change. Constructing before paint costs that work in the commit instead; see PERF.md in\n // `react-ui-markdown/src/MarkdownEditable` for what that costs on a large document.\n useLayoutEffect(() => {\n let view: EditorView | null = null;\n if (parentRef.current) {\n log('create', { id, instanceId, doc: initialValue?.length ?? 0 });\n\n // Only records whether an explicit selection governs, so that `selectionEnd` defers to it; the\n // effect below applies it. `!== undefined`, since an explicit `{ anchor: 0 }` is a selection.\n const length = (doc ?? initialValue)?.length ?? 0;\n let initialSelection;\n if (selection?.anchor !== undefined && length) {\n if (selection.anchor <= length && (selection?.head ?? 0) <= length) {\n initialSelection = selection;\n }\n }\n\n // https://codemirror.net/docs/ref/#state.EditorStateConfig\n const state = EditorState.create({\n doc: doc ?? initialValue,\n // selection: initialSelection,\n extensions: [\n id && documentId.of(id),\n extensions,\n // NOTE: This doesn't catch errors in keymap functions.\n EditorView.exceptionSink.of((err) => {\n log.catch(err);\n }),\n ].filter(isTruthy),\n });\n\n // https://codemirror.net/docs/ref/#view.EditorViewConfig\n view = new EditorView({\n parent: parentRef.current,\n state,\n // `scrollTo` is the position recorded at the top of the viewport, so restore it there.\n scrollTo: scrollTo != null ? EditorView.scrollIntoView(scrollTo, { y: 'start', yMargin: 0 }) : undefined,\n dispatchTransactions: debug ? debugDispatcher : undefined,\n });\n\n // Move to end of line after document loaded (unless selection is specified).\n if (selectionEnd && !initialSelection) {\n const { to } = view.state.doc.lineAt(0);\n view.dispatch({ selection: { anchor: to } });\n }\n\n setView(view);\n }\n\n return () => {\n log('destroy', { id });\n view?.destroy();\n };\n }, deps);\n\n useEffect(() => {\n if (!view || (scrollTo == null && !selection)) {\n return;\n }\n\n const state: EditorSelectionState = { scrollTo, scrollOffset, selection };\n if (fitsDocument(state, view.state.doc.length)) {\n restoreEditorState(view, state);\n return;\n }\n\n // A bound editor is created before its content arrives (the automerge binding fills the document\n // on attach), so a restore against the empty document would be dropped; restore on the fill instead.\n let active = true;\n let pending = true;\n view.dispatch({\n effects: StateEffect.appendConfig.of(\n EditorView.updateListener.of((update) => {\n if (pending && update.docChanged && fitsDocument(state, update.state.doc.length)) {\n pending = false;\n // A view cannot be dispatched to from within its own update.\n queueMicrotask(() => {\n if (active) {\n restoreEditorState(update.view, state);\n }\n });\n }\n }),\n ),\n });\n\n return () => {\n active = false;\n };\n }, [view, scrollTo, scrollOffset, selection]);\n\n useEffect(() => {\n // Focus in an open menu or listbox is the user's, and taking it would dismiss that popup.\n if (view && autoFocus && !view.dom.ownerDocument.activeElement?.closest('[role=\"menu\"], [role=\"listbox\"]')) {\n view.focus();\n }\n }, [autoFocus, view]);\n\n // Focus editor on Enter (e.g., when tabbing to this component).\n const handleKeyDown = useCallback<KeyboardEventHandler<HTMLDivElement>>(\n (event) => {\n const { key, target, currentTarget } = event;\n switch (key) {\n case 'Escape': {\n // Check if popover is open.\n const modal = view?.state.field(modalStateField, false);\n if (modal) {\n return;\n }\n\n // Focus the closest focusable parent.\n const element = view?.contentDOM.closest('[tabindex=\"0\"]') as HTMLDivElement | null;\n element?.focus();\n break;\n }\n\n case 'Enter': {\n event.preventDefault();\n if (target === currentTarget) {\n view?.focus();\n }\n break;\n }\n }\n },\n [view],\n );\n\n return {\n parentRef,\n view,\n focusAttributes: {\n tabIndex: 0 as const,\n onKeyDown: handleKeyDown,\n },\n };\n};\n","//\n// Copyright 2025 DXOS.org\n//\n\nimport { Transaction } from '@codemirror/state';\nimport { EditorView as NaturalEditorView } from '@codemirror/view';\nimport React, { forwardRef, useEffect, useImperativeHandle, useRef } from 'react';\n\nimport type * as Util from '@dxos/react-ui/Util';\nimport { computeDocChanges, initialSync } from '@dxos/ui-editor';\nimport { mx } from '@dxos/ui-theme';\n\nimport { type UseTextEditorProps, useTextEditor } from '../../hooks/index.ts';\nimport { type EditorController, createEditorController } from './controller.ts';\n\nexport type EditorViewProps = Util.ThemedClassName<\n {\n focusable?: boolean;\n value?: string;\n onChange?: (value: string) => void;\n } & UseTextEditorProps\n>;\n\n/**\n * Minimal text editor.\n * NOTE: This shouold not be used with the automerge extension.\n */\n// TODO(burdon): Move controller to Root component, then make composable.\nexport const EditorView = forwardRef<EditorController, EditorViewProps>(\n ({ classNames, id, extensions, selectionEnd, focusable = true, value, onChange, ...props }, forwardedRef) => {\n // Hold the latest onChange in a ref so callers may pass an inline callback\n // without forcing the underlying editor to be destroyed and recreated on\n // every render — which would blur the focused input on each keystroke.\n const onChangeRef = useRef(onChange);\n onChangeRef.current = onChange;\n\n const { parentRef, focusAttributes, view } = useTextEditor(\n () => ({\n id,\n initialValue: value,\n selectionEnd,\n extensions: [\n extensions ?? [],\n NaturalEditorView.updateListener.of(({ view, docChanged, transactions }) => {\n const isInitialSync = transactions.some((tr) => tr.annotation(Transaction.userEvent) === initialSync.value);\n if (!isInitialSync && docChanged) {\n onChangeRef.current?.(view.state.doc.toString());\n }\n }),\n ],\n ...props,\n }),\n [id, extensions, selectionEnd],\n );\n\n // External controller.\n useImperativeHandle(forwardedRef, () => {\n return createEditorController(view);\n }, [id, view]);\n\n // Sync the editor doc to the controlled `value` prop, but only when they\n // disagree. After internal typing the prop will already match the editor's\n // doc, and dispatching anyway would race fast keystrokes — a stale rAF\n // closure can replace doc content with an older value, dropping characters.\n //\n // `undefined` means uncontrolled: the doc was seeded once from `value` at creation and the caller\n // isn't driving it, so leave it be (coalescing undefined→'' here would wipe that seed on mount). A\n // controlled caller clears the doc by passing '' explicitly.\n useEffect(() => {\n if (!view || value === undefined) {\n return;\n }\n const next = value;\n if (view.state.doc.toString() === next) {\n return;\n }\n requestAnimationFrame(() => {\n const current = view.state.doc.toString();\n if (current === next) {\n return;\n }\n // Only what moved: replacing the whole document would drop the reader's folds, selection\n // and scroll position on every update of a value they are mostly already looking at.\n const changes = computeDocChanges(current, next);\n if (changes.length === 0) {\n return;\n }\n\n view.dispatch({\n annotations: initialSync,\n changes,\n selection: selectionEnd ? { anchor: next.length } : undefined,\n });\n\n if (selectionEnd) {\n view.focus();\n }\n });\n }, [view, value, selectionEnd]);\n\n // Focus chrome (border/ring) is the caller's responsibility (e.g. `dx-input-box`).\n return (\n <div\n className={mx('w-full outline-hidden', classNames)}\n {...(focusable ? focusAttributes : {})}\n ref={parentRef}\n />\n );\n },\n);\n","//\n// Copyright 2025 DXOS.org\n//\n\nimport { type EditorState, type Extension } from '@codemirror/state';\nimport * as Atom from 'effect/reactivity/Atom';\nimport React, {\n type PropsWithChildren,\n Suspense,\n forwardRef,\n useCallback,\n useEffect,\n useImperativeHandle,\n useMemo,\n useRef,\n useState,\n} from 'react';\nimport { createPortal } from 'react-dom';\n\nimport { invariant } from '@dxos/invariant';\nimport type * as Util from '@dxos/react-ui/Util';\nimport { mx } from '@dxos/ui-theme';\nimport { isNonNullable } from '@dxos/util';\n\nimport {\n EditorMenuProvider,\n type EditorMenuProviderProps,\n type UseEditorMenuProps,\n useEditorMenu,\n} from '../EditorMenuProvider/index.ts';\nimport {\n type EditorToolbarState,\n EditorToolbar as NaturalEditorToolbar,\n type EditorToolbarProps as NaturalEditorToolbarProps,\n} from '../EditorToolbar/index.ts';\nimport { type EditorController, noopController } from './controller.ts';\nimport { EditorContextProvider, type EditorContextValue, useEditorContext } from './EditorContext.ts';\nimport {\n EditorView as NaturalEditorContent,\n type EditorViewProps as NaturalEditorContentProps,\n} from './EditorView.tsx';\n\n//\n// Root\n//\n\ntype EditorRootProps = PropsWithChildren<\n Pick<EditorContextValue, 'extensions' | 'widgets'> &\n Omit<UseEditorMenuProps, 'viewRef'> &\n Pick<EditorMenuProviderProps, 'numItems' | 'searchPlaceholder'> & {\n viewMode?: EditorToolbarState['viewMode'];\n }\n>;\n\n/**\n * Root component for the Editor compound component.\n * Provides context for all child components and manages the editor controller state.\n */\nconst EditorRoot = forwardRef<EditorController | null, EditorRootProps>(\n (\n { children, extensions: extensionsProp, widgets, viewMode, numItems, searchPlaceholder, ...props },\n forwardedRef,\n ) => {\n // TODO(wittjosiah): Including initialState in the deps causes reactivity issues.\n const state = useMemo(() => Atom.make<EditorToolbarState>({ viewMode }), [viewMode]);\n\n // TODO(burdon): Consider lighter-weight approach if EditorMenuProvider is not needed.\n const { groupsRef, extension, ...menuProps } = useEditorMenu(props);\n const extensions = useMemo(\n () => [extension, extensionsProp].filter(isNonNullable).flat(),\n [extension, extensionsProp],\n );\n\n // External controller.\n const [controller, setController] = useState<EditorController>(noopController);\n useImperativeHandle(forwardedRef, () => controller, [controller]);\n const getView = useCallback(() => controller?.view ?? null, [controller]);\n\n return (\n <EditorContextProvider\n controller={controller}\n setController={setController}\n extensions={extensions}\n widgets={widgets}\n state={state}\n >\n <EditorMenuProvider\n getView={getView}\n groups={groupsRef.current}\n numItems={numItems}\n searchPlaceholder={searchPlaceholder}\n {...menuProps}\n >\n {children}\n </EditorMenuProvider>\n </EditorContextProvider>\n );\n },\n);\n\nEditorRoot.displayName = 'Editor.Root';\n\n//\n// Content\n//\n\nconst EDITOR_CONTENT_NAME = 'Editor.Content';\n\ntype EditorContentProps = Util.ThemedClassName<PropsWithChildren<{}>>;\n\n/**\n * Content component that wraps the toolbar and editor view area.\n */\nconst EditorContent = ({ classNames, children }: EditorContentProps) => {\n return <div className={mx('grid grid-rows-[min-content_1fr] h-full overflow-hidden', classNames)}>{children}</div>;\n};\n\nEditorContent.displayName = EDITOR_CONTENT_NAME;\n\n//\n// View\n//\n\nconst EDITOR_VIEW_NAME = 'Editor.View';\n\ntype EditorViewProps = Omit<NaturalEditorContentProps, 'ref'>;\n\n/**\n * View component that renders the actual CodeMirror editor.\n * Automatically registers the editor controller with the context.\n */\nconst EditorView = ({ extensions: providedExtensions, ...props }: EditorViewProps) => {\n const { extensions: additionalExtensions = [], setController } = useEditorContext(EDITOR_VIEW_NAME);\n const extensions = useMemo(\n () => [additionalExtensions, providedExtensions].filter(isNonNullable).flat(),\n [providedExtensions, additionalExtensions],\n );\n\n return <NaturalEditorContent {...props} extensions={extensions} ref={setController} />;\n};\n\nEditorView.displayName = EDITOR_VIEW_NAME;\n\n//\n// Toolbar\n//\n\nconst EDITOR_TOOLBAR_NAME = 'Editor.Toolbar';\n\ntype EditorToolbarProps = Omit<NaturalEditorToolbarProps, 'getView' | 'state'>;\n\n/**\n * Toolbar component that provides editor formatting and control actions.\n * Automatically connects to the editor view through context.\n */\nconst EditorToolbar = (props: EditorToolbarProps) => {\n const { controller, state } = useEditorContext(EDITOR_TOOLBAR_NAME);\n const getView = useCallback(() => {\n invariant(controller?.view);\n return controller?.view;\n }, [controller]);\n\n return <NaturalEditorToolbar {...props} getView={getView} state={state} />;\n};\n\nEditorToolbar.displayName = EDITOR_TOOLBAR_NAME;\n\n//\n// Blocks\n//\n\nconst EDITOR_BLOCKS_NAME = 'Editor.Blocks';\n\n/**\n * Renders the xmlTags widget portals (embedded blocks) contributed via the editor's `setWidgets`\n * callback. Place inside `Editor.Root`; the widgets come from context (pass them to `Editor.Root`).\n */\nconst EditorBlocks = () => {\n const { widgets = [] } = useEditorContext(EDITOR_BLOCKS_NAME);\n return (\n <>\n {widgets.map(({ id, root, Component, props }) => (\n <div key={id} data-testid='editor.blocks.portal'>\n {/* Per-portal boundary: a block that suspends (capability wait, lazy surface module) must\n not hold the surrounding editor tree un-committed — that made embeds invisible until\n a view-mode toggle rebuilt everything. */}\n {createPortal(\n <Suspense fallback={null}>\n <Component {...props} />\n </Suspense>,\n root,\n )}\n </div>\n ))}\n </>\n );\n};\n\nEditorBlocks.displayName = EDITOR_BLOCKS_NAME;\n\n//\n// Diagnostics\n//\n\nconst EDITOR_DIAGNOSTICS_NAME = 'Editor.Diagnostics';\n\n/** Rough leaf count of a (recursively nested) CodeMirror extension tree. */\nconst countExtensions = (extension: Extension | undefined): number =>\n extension == null\n ? 0\n : Array.isArray(extension)\n ? extension.reduce((sum, child) => sum + countExtensions(child), 0)\n : 1;\n\ntype EditorDiagnosticsProps = Util.ThemedClassName<{}>;\n\n/**\n * Developer panel showing live editor state read from the CodeMirror `EditorState` (document size,\n * selection, viewport, configured-extension count). A debugging aid — gate it behind a dev/debug\n * setting; unrelated to `@codemirror/lint` diagnostics. Place inside `Editor.Root`, below the content.\n */\nconst EditorDiagnostics = ({ classNames }: EditorDiagnosticsProps) => {\n const { controller, extensions } = useEditorContext(EDITOR_DIAGNOSTICS_NAME);\n const view = controller?.view ?? null;\n // `EditorState` is immutable (a new object per transaction), so a reference change signals an update.\n // Poll on animation frames while mounted — self-contained (no injected updateListener) and re-renders\n // only when the state actually changes; catches programmatic transactions too.\n const [, setRevision] = useState(0);\n const lastState = useRef<EditorState | null>(null);\n useEffect(() => {\n if (!view) {\n return;\n }\n let frame = 0;\n const tick = () => {\n if (view.state !== lastState.current) {\n lastState.current = view.state;\n setRevision((revision) => revision + 1);\n }\n frame = requestAnimationFrame(tick);\n };\n frame = requestAnimationFrame(tick);\n return () => cancelAnimationFrame(frame);\n }, [view]);\n\n if (!view) {\n return null;\n }\n\n const { state } = view;\n const { doc, selection } = state;\n const { main } = selection;\n const head = doc.lineAt(main.head);\n // The context holds the extensions passed to `Root`; count leaves (CodeMirror does not expose the\n // resolved extension set at runtime, so this is the configured, not the active, count).\n const extensionCount = countExtensions(extensions);\n const item = (label: string, value: string | number) => (\n <span>\n <span className='text-fg-subtle'>{label} </span>\n {value}\n </span>\n );\n\n return (\n <div\n className={mx(\n 'flex flex-wrap gap-x-4 gap-y-0.5 border-bs border-separator px-2 py-1 text-xs font-mono text-fg-muted',\n classNames,\n )}\n data-testid='editor.diagnostics'\n >\n {item('chars', doc.length)}\n {item('lines', doc.lines)}\n {item(\n 'cursor',\n main.empty ? `${head.number}:${main.head - head.from + 1}` : `${main.from}–${main.to} (${main.to - main.from})`,\n )}\n {selection.ranges.length > 1 && item('ranges', selection.ranges.length)}\n {item('viewport', `${view.viewport.from}–${view.viewport.to}`)}\n {item('mode', state.readOnly ? 'read-only' : 'editable')}\n {item('extensions', extensionCount)}\n </div>\n );\n};\n\nEditorDiagnostics.displayName = EDITOR_DIAGNOSTICS_NAME;\n\n//\n// Editor\n//\n\nexport const Editor = {\n Root: EditorRoot,\n Toolbar: EditorToolbar,\n Content: EditorContent,\n View: EditorView,\n Blocks: EditorBlocks,\n Diagnostics: EditorDiagnostics,\n};\n\nexport type {\n EditorContentProps,\n EditorController,\n EditorRootProps,\n EditorToolbarProps,\n EditorToolbarState,\n EditorViewProps,\n};\n","//\n// Copyright 2025 DXOS.org\n//\n\nimport { createContext } from '@dxos/react-hooks';\nimport { type PreviewLinkRef, type PreviewLinkTarget } from '@dxos/ui-types';\n\n// Kept out of `EditorPreviewProvider.tsx`: react-refresh only fast-refreshes a module whose exports are all\n// components, so a context and its hook exported beside them force a full page reload on every edit.\n\nexport type EditorPreviewPopoverValue = Partial<{\n link: PreviewLinkRef;\n target: PreviewLinkTarget;\n pending: boolean;\n}>;\n\nexport const [EditorPreviewContextProvider, useEditorPreview] = createContext<EditorPreviewPopoverValue>(\n 'PreviewPopover',\n {},\n);\n","//\n// Copyright 2025 DXOS.org\n//\n\nimport React, { type PropsWithChildren, useCallback, useEffect, useRef, useState } from 'react';\n\nimport { addEventListener } from '@dxos/async';\nimport * as Popover from '@dxos/react-ui/Popover';\nimport * as VirtualAnchor from '@dxos/react-ui/VirtualAnchor';\nimport { DX_ANCHOR_ACTIVATE, type DxAnchorActivate, type PreviewLinkRef, type PreviewLinkTarget } from '@dxos/ui-types';\n\nimport { EditorPreviewContextProvider, type EditorPreviewPopoverValue } from './EditorPreviewContext.ts';\n\nexport type EditorPreviewProviderProps = PropsWithChildren<{\n onLookup?: (link: PreviewLinkRef) => Promise<PreviewLinkTarget | null | undefined>;\n}>;\n\n/**\n * Standalone anchor-preview popover for use outside Composer (storybook, tests).\n * In Composer, PreviewPlugin listens on window for DX_ANCHOR_ACTIVATE events and\n * dispatches LayoutOperation.UpdatePopover instead; this provider is not needed there.\n */\nexport const EditorPreviewProvider = ({ children, onLookup }: EditorPreviewProviderProps) => {\n const triggerRef = useRef<HTMLElement | null>(null);\n const [value, setValue] = useState<EditorPreviewPopoverValue>({});\n const [open, setOpen] = useState(false);\n // Monotonic activation token: only the most recent open may commit its async lookup result, so a\n // slow lookup for a closed/superseded anchor cannot fill the popover for a later one.\n const activationRef = useRef(0);\n\n const handleActivate = useCallback(\n (event: DxAnchorActivate) => {\n // Hover-driven anchors dispatch `state: false` when the pointer leaves the anchor/card — after\n // a grace period, so the pointer may already have opened another anchor by then. Only the\n // anchor currently shown may close the popover; a stale close from the one just left is dropped.\n if (event.state === false) {\n if (event.trigger !== triggerRef.current) {\n return;\n }\n activationRef.current++;\n setOpen(false);\n return;\n }\n\n const sequence = ++activationRef.current;\n const { eid, label, trigger } = event;\n setValue((value) => ({\n ...value,\n link: { label, eid },\n pending: true,\n }));\n\n triggerRef.current = trigger;\n queueMicrotask(() => setOpen(true));\n void onLookup?.({ label, eid }).then((target) => {\n if (sequence !== activationRef.current) {\n return;\n }\n setValue((value) => ({\n ...value,\n target: target ?? undefined,\n pending: false,\n }));\n });\n },\n [onLookup],\n );\n\n // Dismissals from the popover itself (Escape, outside click) also invalidate in-flight lookups.\n const handleOpenChange = useCallback((next: boolean) => {\n if (!next) {\n activationRef.current++;\n }\n setOpen(next);\n }, []);\n\n const [root, setRoot] = useState<HTMLDivElement | null>(null);\n useEffect(() => {\n if (!root || !handleActivate) {\n return;\n }\n\n return addEventListener(root, DX_ANCHOR_ACTIVATE as any, handleActivate, {\n capture: true,\n passive: false,\n });\n }, [root, handleActivate]);\n\n return (\n <EditorPreviewContextProvider pending={value.pending} link={value.link} target={value.target}>\n <Popover.Root\n open={open}\n onOpenChange={({ open }) => handleOpenChange(open)}\n positioning={VirtualAnchor.virtualAnchor(triggerRef)}\n // A preview card shows beside the link; focus stays in the editor.\n autoFocus={false}\n >\n <div className='contents' ref={setRoot}>\n {children}\n </div>\n </Popover.Root>\n </EditorPreviewContextProvider>\n );\n};\n"],"mappings":"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"}
@@ -1,5 +1,6 @@
1
1
  import { type EditorView } from '@codemirror/view';
2
2
  import type * as Theme from '@dxos/react-ui/Theme';
3
+ import { type ChromaticPalette } from '@dxos/ui-types';
3
4
  import { type MaybePromise } from '@dxos/util';
4
5
  export type EditorMenuGroup = {
5
6
  id: string;
@@ -10,6 +11,7 @@ export type EditorMenuItem = {
10
11
  id: string;
11
12
  label: Theme.Label;
12
13
  icon?: string;
14
+ iconHue?: ChromaticPalette;
13
15
  onSelect?: (event: {
14
16
  view: EditorView;
15
17
  head: number;