@openeditor/react 0.0.28 → 0.0.30

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.
package/README.md CHANGED
@@ -8,17 +8,53 @@ Pass `attachmentRuntime` to `useOpenEditorController` for editable attachments a
8
8
 
9
9
  - `useOpenEditorController` for editor state, commands, document exports, and selection state
10
10
  - `OpenEditorContent` for the editable Tiptap-backed content surface
11
+ - `OpenEditorBubbleMenu` for Tiptap-owned selection visibility and positioning
11
12
  - `OpenEditorViewer` for read-only React rendering
12
13
  - `OpenEditorPageHeader` for host-backed page title and icon editing
13
14
  - `getDefaultBlockPickerItems` for host-owned block rails, pickers, and command palettes
14
15
  - `getDefaultSlashMenuItems` for UI packages that want the built-in insert actions
15
16
  - stable-identity block targeting and transaction-backed copy, duplicate, move, and delete commands
17
+ - a first-class `controller.table` domain API for selection-aware row, column, header, cell, and table commands
16
18
  - `OpenEditorBlockDragHandle` and `useOpenEditorBlockInteraction` for headless block-handle UIs
17
19
  - `defineOpenEditorReactNode` for consumer-owned React blocks
18
20
  - `defineOpenEditorReactExtension` for advanced integrations that contribute multiple Tiptap extensions
19
21
 
20
22
  This package intentionally does not import CSS and does not render styled menus. Use `@openeditor/ui` for the optional styled components.
21
23
 
24
+ ## Read-only rendering and URL policy
25
+
26
+ `OpenEditorViewer` renders the portable document through an explicit registry of
27
+ built-in and structural renderers. Unknown nodes are preserved for forward
28
+ readability but carry `data-openeditor-unknown-node` so unsupported content never
29
+ silently appears to be a fully supported block. Built-in viewer output and safe
30
+ HTML export share stable `oe-*` classes and `data-openeditor-*` hooks for document
31
+ semantics such as columns, tasks, toggles, callouts, tables, pages, and files.
32
+
33
+ Viewer URLs use `openEditorPublicUrlPolicy` by default. The policy covers links,
34
+ images, pages, and attachments, including snapshots returned by page and
35
+ attachment runtimes. It accepts HTTP(S) and ordinary relative references, plus
36
+ `mailto:` and `tel:` for text links, while rejecting executable and local-preview
37
+ schemes. Owner previews can opt into a narrowly scoped policy:
38
+
39
+ ```tsx
40
+ import {
41
+ OpenEditorViewer,
42
+ openEditorPublicUrlPolicy,
43
+ type OpenEditorUrlPolicy,
44
+ } from "@openeditor/react";
45
+
46
+ const previewUrlPolicy: OpenEditorUrlPolicy = (value, context) => {
47
+ if (context === "attachment" && value.startsWith("blob:")) return value;
48
+ return openEditorPublicUrlPolicy(value, context);
49
+ };
50
+
51
+ <OpenEditorViewer document={document} urlPolicy={previewUrlPolicy} />;
52
+ ```
53
+
54
+ Custom viewer renderers receive both `urlPolicy` and a typed `resolveUrl` helper.
55
+ The host remains responsible for arbitrary custom renderer output and for actions
56
+ performed inside `openPage` or `openAttachment` callbacks.
57
+
22
58
  ## Block actions
23
59
 
24
60
  Every addressable node has a persistent `openeditor-id`. The block interaction
@@ -208,7 +244,11 @@ const productCard = defineOpenEditorReactNode({
208
244
  keywords: ["product", "commerce", "card"],
209
245
  order: 250,
210
246
  },
211
- viewer: ({ node }) => <article>{String(node.attrs?.title ?? "")}</article>,
247
+ viewer: ({ node, resolveUrl }) => (
248
+ <article>
249
+ <a href={resolveUrl(node.attrs?.href, "link")}>{String(node.attrs?.title ?? "")}</a>
250
+ </article>
251
+ ),
212
252
  exporters: {
213
253
  html: {
214
254
  acmeProductCard: ({ node, escapeHtml }) =>
package/dist/index.d.ts CHANGED
@@ -1,10 +1,10 @@
1
1
  import * as react from 'react';
2
2
  import { ReactNode, CSSProperties, RefObject, ComponentType } from 'react';
3
- import { OpenEditorBlockRef, OpenEditorDocument, BlockSpec, ProseMirrorNode, OpenEditorPageRuntime, OpenEditorAttachmentRuntime, OpenEditorPageSnapshot, OpenEditorAuthoringCapabilities } from '@openeditor/core';
4
- export { OpenEditorBlockRef, OpenEditorPageRuntime, OpenEditorPageSnapshot } from '@openeditor/core';
3
+ import { OpenEditorBlockRef, OpenEditorDocument, BlockSpec, ProseMirrorNode, OpenEditorUrlPolicy, OpenEditorUrlContext, OpenEditorPageRuntime, OpenEditorAttachmentRuntime, OpenEditorPageSnapshot, OpenEditorAuthoringCapabilities } from '@openeditor/core';
4
+ export { OpenEditorBlockRef, OpenEditorPageRuntime, OpenEditorPageSnapshot, OpenEditorUrlContext, OpenEditorUrlPolicy, openEditorPublicUrlPolicy, openEditorUnsafeUrlPolicy } from '@openeditor/core';
5
5
  import { ExportFormat, OpenEditorExporters } from '@openeditor/exporters';
6
6
  export { seedDocument } from '@openeditor/extensions';
7
- import { AnyExtension, NodeConfig } from '@tiptap/core';
7
+ import { Editor, AnyExtension, NodeConfig } from '@tiptap/core';
8
8
  import { ReactNodeViewProps } from '@tiptap/react';
9
9
  export { NodeViewContent, NodeViewWrapper, ReactNodeViewProps as OpenEditorNodeViewProps } from '@tiptap/react';
10
10
 
@@ -17,7 +17,7 @@ type OpenEditorBlockInteractionStore = {
17
17
  subscribe: (listener: () => void) => () => void;
18
18
  };
19
19
 
20
- declare const openEditorThemeTokenNames: readonly ["surface", "surfaceRaised", "surfaceMuted", "blockSurface", "text", "textSoft", "heading", "muted", "placeholder", "border", "borderStrong", "structuralLine", "accent", "accentText", "accentStrong", "buttonBackground", "buttonText", "codeBackground", "codeText", "link", "linkHover", "shadow", "fontSans", "fontMono", "radiusSmall", "radiusMedium", "radiusLarge", "spaceBlock", "spaceInline"];
20
+ declare const openEditorThemeTokenNames: readonly ["surface", "surfaceRaised", "surfaceMuted", "blockSurface", "text", "textSoft", "heading", "muted", "placeholder", "border", "borderStrong", "structuralLine", "accent", "accentText", "accentStrong", "buttonBackground", "buttonText", "codeBackground", "codeText", "link", "linkHover", "shadow", "fontSans", "fontMono", "radiusSmall", "radiusMedium", "radiusLarge", "spaceBlock", "spaceInline", "bodyFontSize", "bodyLineHeight", "headingFont", "headingLineHeight", "headingWeight", "heading1Size", "heading2Size", "heading3Size", "heading4Size", "heading5Size", "heading6Size"];
21
21
  type OpenEditorThemeToken = (typeof openEditorThemeTokenNames)[number];
22
22
  type OpenEditorTheme = Partial<Record<OpenEditorThemeToken, string>>;
23
23
  type OpenEditorThemeStyle = CSSProperties & Record<`--oe-${string}`, string | undefined>;
@@ -29,6 +29,27 @@ declare function OpenEditorThemeProvider({ children, className, theme }: {
29
29
  theme: OpenEditorTheme;
30
30
  }): react.JSX.Element;
31
31
 
32
+ declare const openEditorTableCommands: readonly ["addRowBefore", "addRowAfter", "deleteRow", "addColumnBefore", "addColumnAfter", "deleteColumn", "toggleHeaderRow", "toggleHeaderColumn", "mergeCells", "splitCell", "deleteTable"];
33
+ type OpenEditorTableCommand = (typeof openEditorTableCommands)[number];
34
+ type OpenEditorTableState = {
35
+ active: boolean;
36
+ rows: number;
37
+ columns: number;
38
+ headerRow: boolean;
39
+ headerColumn: boolean;
40
+ cellSelection: boolean;
41
+ canMergeCells: boolean;
42
+ canSplitCell: boolean;
43
+ };
44
+ type OpenEditorTableController = {
45
+ getState: () => OpenEditorTableState;
46
+ can: (command: OpenEditorTableCommand) => boolean;
47
+ run: (command: OpenEditorTableCommand) => boolean;
48
+ };
49
+ declare const getOpenEditorTableState: (editor: Editor | null) => OpenEditorTableState;
50
+ declare const canRunOpenEditorTableCommand: (editor: Editor | null, command: OpenEditorTableCommand) => boolean;
51
+ declare const runOpenEditorTableCommand: (editor: Editor | null, command: OpenEditorTableCommand) => boolean;
52
+
32
53
  type OpenEditorReactProps = OpenEditorAuthoringCapabilities & {
33
54
  initialDocument?: OpenEditorDocument;
34
55
  editable?: boolean;
@@ -65,6 +86,8 @@ type OpenEditorController = {
65
86
  setSelectedBlock: (blockName: string) => void;
66
87
  getContent: () => OpenEditorDocument;
67
88
  setContent: (document: OpenEditorDocument) => void;
89
+ focus: () => void;
90
+ getPositionRect: (position: number) => OpenEditorOverlayRect | null;
68
91
  setParagraph: () => void;
69
92
  toggleHeading: (level: 1 | 2 | 3 | 4 | 5 | 6) => void;
70
93
  toggleBold: () => void;
@@ -77,6 +100,7 @@ type OpenEditorController = {
77
100
  toggleTaskList: () => void;
78
101
  toggleBlockquote: () => void;
79
102
  toggleCodeBlock: () => void;
103
+ table: OpenEditorTableController;
80
104
  undo: () => void;
81
105
  redo: () => void;
82
106
  insertBlock: (blockName?: string, range?: InsertRange) => boolean;
@@ -100,7 +124,8 @@ type OpenEditorController = {
100
124
  attachmentRuntime?: OpenEditorAttachmentRuntime<any>;
101
125
  enabledBlocks?: readonly string[];
102
126
  slashState: SlashState | null;
103
- selectionBubbleState: SelectionBubbleState | null;
127
+ dismissSlashMenu: () => void;
128
+ registerSlashMenuKeyboardHandler: (handler: (event: globalThis.KeyboardEvent) => boolean) => () => void;
104
129
  };
105
130
  type OpenEditorContentProps = {
106
131
  controller: OpenEditorController;
@@ -126,11 +151,6 @@ type SlashState = {
126
151
  top: number;
127
152
  anchorRect?: OpenEditorOverlayRect;
128
153
  };
129
- type SelectionBubbleState = {
130
- left: number;
131
- top: number;
132
- anchorRect?: OpenEditorOverlayRect;
133
- };
134
154
  type OpenEditorSlashMenuIconProps = {
135
155
  "aria-hidden"?: boolean;
136
156
  className?: string;
@@ -169,6 +189,8 @@ type OpenEditorBlockPickerItem = {
169
189
  type OpenEditorViewerRenderer = (context: {
170
190
  node: ProseMirrorNode;
171
191
  children: ReactNode;
192
+ urlPolicy: OpenEditorUrlPolicy;
193
+ resolveUrl: (value: unknown, context: OpenEditorUrlContext) => string | undefined;
172
194
  }) => ReactNode;
173
195
  type OpenEditorExtensionMenu = {
174
196
  label?: string;
@@ -210,6 +232,8 @@ type OpenEditorViewerProps = {
210
232
  extensions?: readonly OpenEditorReactExtension[];
211
233
  pageRuntime?: OpenEditorPageRuntime;
212
234
  attachmentRuntime?: OpenEditorAttachmentRuntime<any>;
235
+ /** Safe by default. Supply an explicit host policy for trusted preview URLs such as `blob:`. */
236
+ urlPolicy?: OpenEditorUrlPolicy;
213
237
  };
214
238
  declare module "@tiptap/core" {
215
239
  interface Commands<ReturnType> {
@@ -228,6 +252,25 @@ type OpenEditorPageHeaderProps = {
228
252
  /** Canonical Notion-style page metadata editor shared by host page surfaces. */
229
253
  declare const OpenEditorPageHeader: ({ page, runtime, className, onPageChange, }: OpenEditorPageHeaderProps) => react.JSX.Element;
230
254
  declare const useOpenEditorController: ({ initialDocument, editable, placeholder, onChange, onFilePasteDrop, pageRuntime, attachmentRuntime, enabledBlocks, slashMenuItems, extensions, blockActions, }?: OpenEditorReactProps) => OpenEditorController;
255
+ type OpenEditorBubbleMenuProps = {
256
+ controller: OpenEditorController;
257
+ children: ReactNode;
258
+ className?: string;
259
+ container?: HTMLElement | null;
260
+ scrollTarget?: HTMLElement | Window | null;
261
+ style?: CSSProperties;
262
+ };
263
+ type OpenEditorTableBubbleMenuProps = {
264
+ controller: OpenEditorController;
265
+ children: (state: OpenEditorTableState) => ReactNode;
266
+ className?: string;
267
+ container?: HTMLElement | null;
268
+ style?: CSSProperties;
269
+ };
270
+ /** Selection-aware positioning and lifecycle owned by Tiptap's BubbleMenu plugin. */
271
+ declare const OpenEditorBubbleMenu: ({ controller, children, className, container, scrollTarget, style, }: OpenEditorBubbleMenuProps) => react.JSX.Element | null;
272
+ /** A table-owned contextual surface, anchored to the complete selected table. */
273
+ declare const OpenEditorTableBubbleMenu: ({ controller, children, className, container, style, }: OpenEditorTableBubbleMenuProps) => react.JSX.Element | null;
231
274
  declare const useOpenEditorBlockInteraction: (controller: OpenEditorController) => OpenEditorBlockInteractionSnapshot;
232
275
  type OpenEditorBlockDragHandleProps = {
233
276
  controller: OpenEditorController;
@@ -250,7 +293,7 @@ declare const sortBlockPickerItems: (items: readonly OpenEditorBlockPickerItem[]
250
293
  declare const getDefaultBlockPickerItems: (controller: OpenEditorController) => OpenEditorBlockPickerItem[];
251
294
  declare const getDefaultSlashMenuItems: (controller: OpenEditorController) => OpenEditorSlashMenuItem[];
252
295
  declare const OpenEditorContent: ({ controller, className, }: OpenEditorContentProps) => react.JSX.Element;
253
- declare const OpenEditorViewer: ({ document, className, renderers, extensions, pageRuntime, attachmentRuntime, }: OpenEditorViewerProps) => react.JSX.Element;
296
+ declare const OpenEditorViewer: ({ document, className, renderers, extensions, pageRuntime, attachmentRuntime, urlPolicy, }: OpenEditorViewerProps) => react.JSX.Element;
254
297
  declare const useOpenEditor: ({ initialDocument, editable, placeholder, onChange, onFilePasteDrop, pageRuntime, attachmentRuntime, enabledBlocks, slashMenuItems, extensions, blockActions, }?: OpenEditorReactProps) => OpenEditorController;
255
298
 
256
- export { type InsertRange, type OpenEditorBlockAction, type OpenEditorBlockActionContext, OpenEditorBlockDragHandle, type OpenEditorBlockDragHandleProps, type OpenEditorBlockPickerItem, OpenEditorContent, type OpenEditorContentProps, type OpenEditorController, type OpenEditorExtensionMenu, type OpenEditorOverlayRect, OpenEditorPageHeader, type OpenEditorPageHeaderProps, type OpenEditorReactConfig, type OpenEditorReactExtension, type OpenEditorReactNodeExtension, type OpenEditorReactProps, type OpenEditorSlashMenuIcon, type OpenEditorSlashMenuIconProps, type OpenEditorSlashMenuItem, type OpenEditorTheme, OpenEditorThemeProvider, type OpenEditorThemeStyle, type OpenEditorThemeToken, OpenEditorViewer, type OpenEditorViewerProps, type OpenEditorViewerRenderer, type SelectionBubbleState, type SlashState, createOpenEditorThemeStyle, defineOpenEditorReactExtension, defineOpenEditorReactNode, formatAttachmentSize, getDefaultBlockPickerItems, getDefaultSlashMenuItems, openEditorThemeTokenNames, sortBlockPickerItems, sortSlashMenuItems, useOpenEditor, useOpenEditorBlockInteraction, useOpenEditorController, useOpenEditorThemeStyle };
299
+ export { type InsertRange, type OpenEditorBlockAction, type OpenEditorBlockActionContext, OpenEditorBlockDragHandle, type OpenEditorBlockDragHandleProps, type OpenEditorBlockPickerItem, OpenEditorBubbleMenu, type OpenEditorBubbleMenuProps, OpenEditorContent, type OpenEditorContentProps, type OpenEditorController, type OpenEditorExtensionMenu, type OpenEditorOverlayRect, OpenEditorPageHeader, type OpenEditorPageHeaderProps, type OpenEditorReactConfig, type OpenEditorReactExtension, type OpenEditorReactNodeExtension, type OpenEditorReactProps, type OpenEditorSlashMenuIcon, type OpenEditorSlashMenuIconProps, type OpenEditorSlashMenuItem, OpenEditorTableBubbleMenu, type OpenEditorTableBubbleMenuProps, type OpenEditorTableCommand, type OpenEditorTableController, type OpenEditorTableState, type OpenEditorTheme, OpenEditorThemeProvider, type OpenEditorThemeStyle, type OpenEditorThemeToken, OpenEditorViewer, type OpenEditorViewerProps, type OpenEditorViewerRenderer, type SlashState, canRunOpenEditorTableCommand, createOpenEditorThemeStyle, defineOpenEditorReactExtension, defineOpenEditorReactNode, formatAttachmentSize, getDefaultBlockPickerItems, getDefaultSlashMenuItems, getOpenEditorTableState, openEditorTableCommands, openEditorThemeTokenNames, runOpenEditorTableCommand, sortBlockPickerItems, sortSlashMenuItems, useOpenEditor, useOpenEditorBlockInteraction, useOpenEditorController, useOpenEditorThemeStyle };