@openeditor/react 0.0.25 → 0.0.27
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 +42 -10
- package/dist/index.d.ts +49 -24
- package/dist/index.js +1483 -2747
- package/dist/index.js.map +1 -1
- package/package.json +9 -7
package/README.md
CHANGED
|
@@ -11,7 +11,8 @@ Pass `attachmentRuntime` to `useOpenEditorController` for editable attachments a
|
|
|
11
11
|
- `OpenEditorViewer` for read-only React rendering
|
|
12
12
|
- `getDefaultBlockPickerItems` for host-owned block rails, pickers, and command palettes
|
|
13
13
|
- `getDefaultSlashMenuItems` for UI packages that want the built-in insert actions
|
|
14
|
-
-
|
|
14
|
+
- stable-identity block targeting and transaction-backed copy, duplicate, move, and delete commands
|
|
15
|
+
- `OpenEditorBlockDragHandle` and `useOpenEditorBlockInteraction` for headless block-handle UIs
|
|
15
16
|
- `defineOpenEditorReactNode` for consumer-owned React blocks
|
|
16
17
|
- `defineOpenEditorReactExtension` for advanced integrations that contribute multiple Tiptap extensions
|
|
17
18
|
|
|
@@ -19,21 +20,52 @@ This package intentionally does not import CSS and does not render styled menus.
|
|
|
19
20
|
|
|
20
21
|
## Block actions
|
|
21
22
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
Every addressable node has a persistent `openeditor-id`. The block interaction
|
|
24
|
+
snapshot publishes the stable block reference under the handle without
|
|
25
|
+
putting pointer-frequency state into the editor controller. Mutating commands
|
|
26
|
+
resolve that identity against the current ProseMirror document immediately
|
|
27
|
+
before dispatch, preserving history and avoiding stale positions:
|
|
25
28
|
|
|
26
29
|
```tsx
|
|
27
|
-
controller
|
|
28
|
-
|
|
29
|
-
controller.
|
|
30
|
-
controller.
|
|
30
|
+
const { activeBlock } = useOpenEditorBlockInteraction(controller);
|
|
31
|
+
if (activeBlock) {
|
|
32
|
+
controller.selectBlock(activeBlock);
|
|
33
|
+
await controller.copyBlock(activeBlock);
|
|
34
|
+
controller.duplicateBlock(activeBlock);
|
|
35
|
+
controller.moveBlock(1, activeBlock);
|
|
36
|
+
controller.deleteBlock(activeBlock);
|
|
37
|
+
}
|
|
31
38
|
```
|
|
32
39
|
|
|
33
40
|
Pass `blockActions` to contribute product-specific actions. The optional
|
|
34
41
|
`OpenEditorBlockMenu` from `@openeditor/ui` combines those contributions with
|
|
35
|
-
Copy, Duplicate, and Delete.
|
|
36
|
-
|
|
42
|
+
Move Up, Move Down, Copy, Duplicate, and Delete. Action contexts receive
|
|
43
|
+
`context.block`, never a ProseMirror position or DOM rectangle. Clipboard copies
|
|
44
|
+
use a versioned OpenEditor block envelope so blocks can move between independent
|
|
45
|
+
editor instances and documents while receiving fresh identities.
|
|
46
|
+
|
|
47
|
+
Handle ownership is structural, not inferred from pointer proximity. Callouts,
|
|
48
|
+
quotes, tables, and consumer blocks own a single handle for their content by
|
|
49
|
+
default. Lists, toggles, and columns expose their independent nested blocks.
|
|
50
|
+
Set `blockHandle: "nested"` on a consumer extension whose child blocks should
|
|
51
|
+
receive their own handles. Placement always follows the live DOM element for the
|
|
52
|
+
resolved ProseMirror node, including through scrolling, resizing, and reflow.
|
|
53
|
+
|
|
54
|
+
Hover targeting is owned by OpenEditor rather than the Tiptap drag-handle
|
|
55
|
+
extension. The editor's interaction surface is one continuous hover region:
|
|
56
|
+
content, the reserved left gutter, and the handle itself. Pointer coordinates
|
|
57
|
+
resolve to the deepest structurally addressable block, with collapsed outer
|
|
58
|
+
margins included so gaps between blocks do not become dead zones. A short hide
|
|
59
|
+
delay lets the pointer cross from content to the handle, while pressing,
|
|
60
|
+
dragging, or opening the block menu freezes the active target. Pointer movement
|
|
61
|
+
is coalesced to one resolution per animation frame and identity changes are the
|
|
62
|
+
only changes published to React.
|
|
63
|
+
|
|
64
|
+
The gutter is part of layout, not a collision workaround. Its reservation is
|
|
65
|
+
continuous rather than breakpoint-based: the editor borrows only the portion
|
|
66
|
+
of the 42px gutter that physically fits to its left and reserves the remainder
|
|
67
|
+
inside its width. Floating placement may correct vertical overflow but must
|
|
68
|
+
never shift horizontally into block content.
|
|
37
69
|
|
|
38
70
|
## Host-owned block pickers
|
|
39
71
|
|
package/dist/index.d.ts
CHANGED
|
@@ -1,13 +1,22 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
|
-
import { ReactNode, CSSProperties, ComponentType } from 'react';
|
|
3
|
-
import { OpenEditorDocument, BlockSpec, ProseMirrorNode, OpenEditorAttachmentRuntime } from '@openeditor/core';
|
|
2
|
+
import { ReactNode, CSSProperties, RefObject, ComponentType } from 'react';
|
|
3
|
+
import { OpenEditorBlockRef, OpenEditorDocument, BlockSpec, ProseMirrorNode, OpenEditorAttachmentRuntime } from '@openeditor/core';
|
|
4
|
+
export { OpenEditorBlockRef } from '@openeditor/core';
|
|
4
5
|
import { toExportBundle, OpenEditorExporters } from '@openeditor/exporters';
|
|
5
6
|
export { seedDocument } from '@openeditor/extensions';
|
|
6
|
-
import {
|
|
7
|
-
export { DragHandle as OpenEditorDragHandle } from '@tiptap/extension-drag-handle-react';
|
|
7
|
+
import { 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
|
|
|
11
|
+
type OpenEditorBlockInteractionSnapshot = {
|
|
12
|
+
activeBlock: OpenEditorBlockRef | null;
|
|
13
|
+
dragging: boolean;
|
|
14
|
+
};
|
|
15
|
+
type OpenEditorBlockInteractionStore = {
|
|
16
|
+
getSnapshot: () => OpenEditorBlockInteractionSnapshot;
|
|
17
|
+
subscribe: (listener: () => void) => () => void;
|
|
18
|
+
};
|
|
19
|
+
|
|
11
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"];
|
|
12
21
|
type OpenEditorThemeToken = (typeof openEditorThemeTokenNames)[number];
|
|
13
22
|
type OpenEditorTheme = Partial<Record<OpenEditorThemeToken, string>>;
|
|
@@ -49,16 +58,9 @@ type OpenEditorReactProps = {
|
|
|
49
58
|
extensions?: readonly OpenEditorReactExtension[];
|
|
50
59
|
blockActions?: readonly OpenEditorBlockAction[];
|
|
51
60
|
};
|
|
52
|
-
type OpenEditorBlockTarget = {
|
|
53
|
-
pos: number;
|
|
54
|
-
depth: number;
|
|
55
|
-
nodeType: string;
|
|
56
|
-
blockName?: string;
|
|
57
|
-
rect: OpenEditorOverlayRect;
|
|
58
|
-
};
|
|
59
61
|
type OpenEditorBlockActionContext = {
|
|
60
62
|
controller: OpenEditorController;
|
|
61
|
-
|
|
63
|
+
block: OpenEditorBlockRef;
|
|
62
64
|
};
|
|
63
65
|
type OpenEditorBlockAction = {
|
|
64
66
|
id: string;
|
|
@@ -69,9 +71,9 @@ type OpenEditorBlockAction = {
|
|
|
69
71
|
};
|
|
70
72
|
type OpenEditorReactConfig = OpenEditorReactProps;
|
|
71
73
|
type OpenEditorController = {
|
|
72
|
-
editor: Editor | null;
|
|
73
74
|
document: OpenEditorDocument;
|
|
74
75
|
exports: ReturnType<typeof toExportBundle>;
|
|
76
|
+
ready: boolean;
|
|
75
77
|
editable: boolean;
|
|
76
78
|
placeholder: string;
|
|
77
79
|
selectedBlock: string;
|
|
@@ -95,17 +97,17 @@ type OpenEditorController = {
|
|
|
95
97
|
insertBlock: (blockName?: string, range?: InsertRange) => boolean;
|
|
96
98
|
setLink: (url?: string | null) => void;
|
|
97
99
|
getActiveLink: () => string | undefined;
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
deleteBlock: (target?: OpenEditorBlockTarget) => boolean;
|
|
105
|
-
moveBlock: (direction: -1 | 1, target?: OpenEditorBlockTarget) => boolean;
|
|
100
|
+
selectBlock: (block: OpenEditorBlockRef) => boolean;
|
|
101
|
+
copyBlock: (block?: OpenEditorBlockRef) => Promise<boolean>;
|
|
102
|
+
duplicateBlock: (block?: OpenEditorBlockRef) => boolean;
|
|
103
|
+
deleteBlock: (block?: OpenEditorBlockRef) => boolean;
|
|
104
|
+
moveBlock: (direction: -1 | 1, block?: OpenEditorBlockRef) => boolean;
|
|
105
|
+
canMoveBlock: (direction: -1 | 1, block?: OpenEditorBlockRef) => boolean;
|
|
106
106
|
blockActions: readonly OpenEditorBlockAction[];
|
|
107
|
-
|
|
108
|
-
|
|
107
|
+
blockInteraction: OpenEditorBlockInteractionStore;
|
|
108
|
+
getRootElement: () => HTMLElement | null;
|
|
109
|
+
setBlockHandleLocked: (locked: boolean) => void;
|
|
110
|
+
isActive: (name: string, attributes?: Record<string, unknown>) => boolean;
|
|
109
111
|
slashMenuItems: readonly OpenEditorSlashMenuItem[];
|
|
110
112
|
extensions: readonly OpenEditorReactExtension[];
|
|
111
113
|
pageRuntime?: OpenEditorPageRuntime;
|
|
@@ -196,6 +198,12 @@ type OpenEditorExtensionMenu = {
|
|
|
196
198
|
type OpenEditorReactExtension = {
|
|
197
199
|
block: BlockSpec;
|
|
198
200
|
tiptapExtensions: readonly AnyExtension[];
|
|
201
|
+
/**
|
|
202
|
+
* Controls ownership when this block contains other block nodes. Containers
|
|
203
|
+
* own one handle by default; use `nested` when their children are independent
|
|
204
|
+
* draggable blocks, as in column layouts.
|
|
205
|
+
*/
|
|
206
|
+
blockHandle?: "self" | "nested";
|
|
199
207
|
/** Discovery metadata shared by slash menus and host-owned block pickers. */
|
|
200
208
|
insertMenu?: false | OpenEditorExtensionMenu;
|
|
201
209
|
/** @deprecated Use `insertMenu`. */
|
|
@@ -227,6 +235,23 @@ declare module "@tiptap/core" {
|
|
|
227
235
|
}
|
|
228
236
|
declare const formatAttachmentSize: (size: number | null) => string;
|
|
229
237
|
declare const useOpenEditorController: ({ initialDocument, editable, placeholder, onChange, onFilePasteDrop, pageRuntime, attachmentRuntime, slashMenuItems, extensions, blockActions, }?: OpenEditorReactProps) => OpenEditorController;
|
|
238
|
+
declare const useOpenEditorBlockInteraction: (controller: OpenEditorController) => OpenEditorBlockInteractionSnapshot;
|
|
239
|
+
type OpenEditorBlockDragHandleProps = {
|
|
240
|
+
controller: OpenEditorController;
|
|
241
|
+
children: ReactNode;
|
|
242
|
+
className?: string;
|
|
243
|
+
ariaLabel?: string;
|
|
244
|
+
elementRef?: RefObject<HTMLDivElement | null>;
|
|
245
|
+
expanded?: boolean;
|
|
246
|
+
onActivate?: () => void;
|
|
247
|
+
onDraggingChange?: (dragging: boolean) => void;
|
|
248
|
+
};
|
|
249
|
+
/**
|
|
250
|
+
* Headless block handle whose outer DOM element owns both native dragging and
|
|
251
|
+
* activation. Keep visual children pointer-transparent so nested controls do
|
|
252
|
+
* not intercept the dragstart gesture.
|
|
253
|
+
*/
|
|
254
|
+
declare const OpenEditorBlockDragHandle: ({ controller, children, className, ariaLabel, elementRef, expanded, onActivate, onDraggingChange, }: OpenEditorBlockDragHandleProps) => react.ReactPortal | null;
|
|
230
255
|
declare const sortSlashMenuItems: (items: readonly OpenEditorSlashMenuItem[]) => OpenEditorSlashMenuItem[];
|
|
231
256
|
declare const sortBlockPickerItems: (items: readonly OpenEditorBlockPickerItem[]) => OpenEditorBlockPickerItem[];
|
|
232
257
|
declare const getDefaultBlockPickerItems: (controller: OpenEditorController) => OpenEditorBlockPickerItem[];
|
|
@@ -235,4 +260,4 @@ declare const OpenEditorContent: ({ controller, className, }: OpenEditorContentP
|
|
|
235
260
|
declare const OpenEditorViewer: ({ document, className, renderers, extensions, pageRuntime, attachmentRuntime, }: OpenEditorViewerProps) => react.JSX.Element;
|
|
236
261
|
declare const useOpenEditor: ({ initialDocument, editable, placeholder, onChange, onFilePasteDrop, pageRuntime, attachmentRuntime, slashMenuItems, extensions, blockActions, }?: OpenEditorReactProps) => OpenEditorController;
|
|
237
262
|
|
|
238
|
-
export { type InsertRange, type OpenEditorBlockAction, type OpenEditorBlockActionContext, type
|
|
263
|
+
export { type InsertRange, type OpenEditorBlockAction, type OpenEditorBlockActionContext, OpenEditorBlockDragHandle, type OpenEditorBlockDragHandleProps, type OpenEditorBlockPickerItem, OpenEditorContent, type OpenEditorContentProps, type OpenEditorController, type OpenEditorExtensionMenu, type OpenEditorOverlayRect, type OpenEditorPageRuntime, type OpenEditorPageSnapshot, 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 };
|