@domternal/extension-block-controls 0.10.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.
- package/README.md +44 -0
- package/dist/index.cjs +2903 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +488 -0
- package/dist/index.d.ts +488 -0
- package/dist/index.js +2878 -0
- package/dist/index.js.map +1 -0
- package/package.json +65 -0
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,488 @@
|
|
|
1
|
+
import { Extension, FloatingMenuOptions, Editor, FloatingMenuItemsOverride, FloatingMenuItem } from '@domternal/core';
|
|
2
|
+
export { CreateFloatingMenuPluginOptions, FloatingMenuKeymap, FloatingMenuOptions, createFloatingMenuPlugin, floatingMenuPluginKey } from '@domternal/core';
|
|
3
|
+
import { PluginKey, Plugin } from '@domternal/pm/state';
|
|
4
|
+
import { Node, ResolvedPos, Attrs } from '@domternal/pm/model';
|
|
5
|
+
import { EditorView } from '@domternal/pm/view';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* FloatingMenu extension: registers the floating-menu plugin under the shared
|
|
9
|
+
* `floatingMenuPluginKey`. The plugin machinery (visibility, positioning,
|
|
10
|
+
* dismiss, keyboard entry) lives in `@domternal/core` so framework wrappers
|
|
11
|
+
* can build their menus without depending on this package; everything is
|
|
12
|
+
* re-exported here for backward compatibility.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
declare const FloatingMenu: Extension<FloatingMenuOptions, unknown>;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Gutter-bias resolution for nested drag-target selection.
|
|
19
|
+
*
|
|
20
|
+
* When the cursor sits near a configured edge of a candidate's rect, that
|
|
21
|
+
* candidate counts as "in the gutter" and its rank is reduced proportionally to
|
|
22
|
+
* its depth, so shallower ancestors win against deeper descendants in the gutter
|
|
23
|
+
* zone. "Deepest-match" mode skips this bias and returns the innermost allowed
|
|
24
|
+
* block under the cursor.
|
|
25
|
+
*/
|
|
26
|
+
/** Cardinal edge of a candidate's bounding rect. */
|
|
27
|
+
type GutterEdge = 'left' | 'right' | 'top' | 'bottom';
|
|
28
|
+
/**
|
|
29
|
+
* Named presets for the bias config:
|
|
30
|
+
* - `'left'` → ['left', 'top'] (left gutter, default)
|
|
31
|
+
* - `'right'` → ['right', 'top'] (right gutter, RTL-friendly)
|
|
32
|
+
* - `'both'` → ['left', 'right', 'top']
|
|
33
|
+
* - `'none'` → no gutter bias (deepest match wins)
|
|
34
|
+
*/
|
|
35
|
+
type GutterBiasPreset = 'left' | 'right' | 'both' | 'none';
|
|
36
|
+
interface GutterBiasConfig {
|
|
37
|
+
/** Edges that constitute the "gutter" zone. */
|
|
38
|
+
edges: GutterEdge[];
|
|
39
|
+
/** Pixel distance from the edge at which the bias activates. */
|
|
40
|
+
threshold: number;
|
|
41
|
+
/** Bias factor applied per depth level when in the gutter. */
|
|
42
|
+
strength: number;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Predicate-based block matching for nested drag-target resolution. A matcher
|
|
47
|
+
* answers "is this candidate eligible to become the drag target?" with an
|
|
48
|
+
* allow/reject verdict; the resolver filters before ranking. The contract is
|
|
49
|
+
* binary (eligible or not), with rank decided separately by depth + optional
|
|
50
|
+
* gutter bias.
|
|
51
|
+
*/
|
|
52
|
+
|
|
53
|
+
/** Eligibility verdict returned by a matcher's `test()`. */
|
|
54
|
+
type MatchVerdict = 'allow' | 'reject';
|
|
55
|
+
/**
|
|
56
|
+
* Information passed to a matcher: the node, its position metadata, parent
|
|
57
|
+
* context, and the live editor view (for DOM lookups).
|
|
58
|
+
*/
|
|
59
|
+
interface BlockCandidate {
|
|
60
|
+
/** The PM node being considered as a drag target. */
|
|
61
|
+
block: Node;
|
|
62
|
+
/** Document position immediately before `block`. */
|
|
63
|
+
documentPos: number;
|
|
64
|
+
/** Depth in the PM tree; 0 is the doc root and is never a candidate. */
|
|
65
|
+
treeDepth: number;
|
|
66
|
+
/** Parent node, or `null` when there is no parent. */
|
|
67
|
+
container: Node | null;
|
|
68
|
+
/** Index of `block` inside `container.content`. */
|
|
69
|
+
positionInContainer: number;
|
|
70
|
+
/** Convenience: `positionInContainer === 0`. */
|
|
71
|
+
isFirstChild: boolean;
|
|
72
|
+
/** Convenience: `positionInContainer === container.childCount - 1`. */
|
|
73
|
+
isLastChild: boolean;
|
|
74
|
+
/** The ProseMirror resolved position the resolver started from. */
|
|
75
|
+
resolvedPos: ResolvedPos;
|
|
76
|
+
/** Live editor view; matchers may call `editorView.nodeDOM(pos)`. */
|
|
77
|
+
editorView: EditorView;
|
|
78
|
+
}
|
|
79
|
+
/** A matcher: a stable name (for debugging / opt-out) and a pure predicate. */
|
|
80
|
+
interface BlockMatcher {
|
|
81
|
+
name: string;
|
|
82
|
+
test(candidate: BlockCandidate): MatchVerdict;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* BlockHandle Extension
|
|
87
|
+
*
|
|
88
|
+
* Notion-style gutter handle on the left of each top-level block, shown on
|
|
89
|
+
* hover. Two buttons:
|
|
90
|
+
*
|
|
91
|
+
* 1. `⋮⋮` drag handle (click → opens BlockContextMenu, drag → reorder)
|
|
92
|
+
* 2. `+` insert button (inserts an empty paragraph below; FloatingMenu
|
|
93
|
+
* picks up the empty-line state and auto-shows its insert menu)
|
|
94
|
+
*
|
|
95
|
+
* This plugin owns visibility + drag + context-menu trigger; the menu UI
|
|
96
|
+
* lives in `BlockContextMenu.ts`, which listens for the
|
|
97
|
+
* `dm:block-context-menu-open` event dispatched here.
|
|
98
|
+
*
|
|
99
|
+
* Styles ship via `@domternal/theme` (`_block-handle.scss`). The plugin adds
|
|
100
|
+
* a `dm-editor--has-block-handle` class so the theme can widen `.ProseMirror`
|
|
101
|
+
* padding-left for gutter space inside the `overflow:hidden` wrapper.
|
|
102
|
+
*/
|
|
103
|
+
|
|
104
|
+
/** Default list of nodes treated as drag-targetable when `nested: true`. */
|
|
105
|
+
declare const DEFAULT_NESTED_NODES: string[];
|
|
106
|
+
declare const blockHandlePluginKey: PluginKey<BlockHandlePluginState>;
|
|
107
|
+
interface BlockHandleOptions {
|
|
108
|
+
/**
|
|
109
|
+
* Ms to wait before hiding the handle after the mouse leaves the editor, so
|
|
110
|
+
* users can move onto the handle without it vanishing.
|
|
111
|
+
* @default 200
|
|
112
|
+
*/
|
|
113
|
+
hideDelay?: number;
|
|
114
|
+
/**
|
|
115
|
+
* Disable drag-to-reorder while still showing the plus/drag buttons
|
|
116
|
+
* (drag becomes a no-op, click opens context menu only).
|
|
117
|
+
* @default false
|
|
118
|
+
*/
|
|
119
|
+
disableDrag?: boolean;
|
|
120
|
+
/**
|
|
121
|
+
* Auto-scroll the nearest scrollable ancestor when dragging near the
|
|
122
|
+
* top/bottom edge. Disable if the host app manages its own drag scroll.
|
|
123
|
+
* @default true
|
|
124
|
+
*/
|
|
125
|
+
autoScroll?: boolean;
|
|
126
|
+
/**
|
|
127
|
+
* Distance in CSS px from the top/bottom edge that triggers auto-scroll.
|
|
128
|
+
* @default 48
|
|
129
|
+
*/
|
|
130
|
+
autoScrollThreshold?: number;
|
|
131
|
+
/**
|
|
132
|
+
* Peak scroll speed in CSS px per frame. Ramps linearly from 0 at the
|
|
133
|
+
* threshold to this value at the edge.
|
|
134
|
+
* @default 18
|
|
135
|
+
*/
|
|
136
|
+
autoScrollMaxSpeed?: number;
|
|
137
|
+
/**
|
|
138
|
+
* Whether the handle should resolve to nested block containers (list
|
|
139
|
+
* items, task items, and optionally others) instead of always the
|
|
140
|
+
* top-level block.
|
|
141
|
+
*
|
|
142
|
+
* - `false` - only top-level blocks are hoverable / draggable (default).
|
|
143
|
+
* - `true` - list items and task items resolve individually (Notion behaviour).
|
|
144
|
+
* - object - fine-grained config; see `NestedConfig`.
|
|
145
|
+
*
|
|
146
|
+
* @default false
|
|
147
|
+
*/
|
|
148
|
+
nested?: boolean | NestedConfig;
|
|
149
|
+
/**
|
|
150
|
+
* Px threshold from a list item's LEFT edge past which a drop becomes
|
|
151
|
+
* nested-child (dragged block becomes a child of that item) instead of
|
|
152
|
+
* sibling. Mirrors Notion's "drop indented = nested, drop on the marker =
|
|
153
|
+
* sibling" UX. Set to `0` to disable nested-drop (every drop is sibling).
|
|
154
|
+
*
|
|
155
|
+
* X-detection only fires when nested mode is on AND the target is a
|
|
156
|
+
* `listItem`/`taskItem`; other containers stay sibling-only.
|
|
157
|
+
*
|
|
158
|
+
* @default 28
|
|
159
|
+
*/
|
|
160
|
+
nestThreshold?: number;
|
|
161
|
+
/**
|
|
162
|
+
* Custom drop indicator that mirrors exactly where a handle-drag lands.
|
|
163
|
+
* Replaces `prosemirror-dropcursor` for handle drags (PM's `posAtCoords` can
|
|
164
|
+
* disagree with our resolver in the gutter / inter-block gap). During a drag
|
|
165
|
+
* the editor gets a `dm-block-handle-dragging` class so the theme can hide
|
|
166
|
+
* the native dropcursor for this drag only; non-handle drags (text selection,
|
|
167
|
+
* file drops) keep it. Set `false` to use the native dropcursor.
|
|
168
|
+
* @default true
|
|
169
|
+
*/
|
|
170
|
+
dropIndicator?: boolean;
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Configuration for nested resolution. Backwards-compatible with the
|
|
174
|
+
* earlier `{ allowedNodes }` literal - every field is optional.
|
|
175
|
+
*/
|
|
176
|
+
interface NestedConfig {
|
|
177
|
+
/**
|
|
178
|
+
* Node type names treated as drag targets when nested mode is on.
|
|
179
|
+
* @default ['listItem', 'taskItem']
|
|
180
|
+
*/
|
|
181
|
+
allowedNodes?: string[];
|
|
182
|
+
/**
|
|
183
|
+
* Restrict resolution to nodes that have at least one ancestor of one
|
|
184
|
+
* of these type names. Use to scope nested mode to specific structures
|
|
185
|
+
* (e.g. only inside `table`). Empty / omitted → no restriction.
|
|
186
|
+
*/
|
|
187
|
+
allowedContainers?: string[];
|
|
188
|
+
/**
|
|
189
|
+
* "Promote to parent at the gutter": within `threshold` px of a configured
|
|
190
|
+
* edge, a candidate's score drops by `strength * depth`, so a shallower
|
|
191
|
+
* ancestor (e.g. the wrapping list) wins near the boundary.
|
|
192
|
+
*
|
|
193
|
+
* - `false` / `undefined` / `'none'` → deepest match wins.
|
|
194
|
+
* - `true` / `'left'` → defaults: edges `['left','top']`, threshold 12, strength 500.
|
|
195
|
+
* - `'right'` / `'both'` → preset variants.
|
|
196
|
+
* - object → custom config (any field optional, merged over defaults).
|
|
197
|
+
*
|
|
198
|
+
* @default false
|
|
199
|
+
*/
|
|
200
|
+
promoteOnEdge?: boolean | GutterBiasPreset | Partial<GutterBiasConfig>;
|
|
201
|
+
/**
|
|
202
|
+
* Append custom block matchers. Default matchers still apply unless
|
|
203
|
+
* `defaultMatchers: false` is also set.
|
|
204
|
+
*/
|
|
205
|
+
matchers?: BlockMatcher[];
|
|
206
|
+
/**
|
|
207
|
+
* Set `false` to disable the built-in matchers (firstChildOfListItem,
|
|
208
|
+
* listContainerSkip, tableInternals, inlineNodes). Almost always wanted;
|
|
209
|
+
* opt out only for testing or specialised host editors.
|
|
210
|
+
* @default true
|
|
211
|
+
*/
|
|
212
|
+
defaultMatchers?: boolean;
|
|
213
|
+
}
|
|
214
|
+
interface BlockHandlePluginState {
|
|
215
|
+
/** Absolute position of the top-level block currently under the cursor, or null. */
|
|
216
|
+
hoveredPos: number | null;
|
|
217
|
+
/**
|
|
218
|
+
* Source position of the block being dragged (set on dragstart, cleared on
|
|
219
|
+
* dragend). `handleDrop` uses it to tell whether the drop came from our handle.
|
|
220
|
+
*/
|
|
221
|
+
draggedFrom: number | null;
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Internal, fully-resolved view of `NestedConfig`: plain arrays + optional edge
|
|
225
|
+
* config so the resolver doesn't interpret presets at hover time.
|
|
226
|
+
*/
|
|
227
|
+
interface NestedResolution {
|
|
228
|
+
/** Allowed drag targets. Empty array → top-level-only mode. */
|
|
229
|
+
allowedNodes: string[];
|
|
230
|
+
/** Optional ancestor whitelist; empty array → no restriction. */
|
|
231
|
+
allowedContainers: string[];
|
|
232
|
+
/** Gutter bias config; `null` → deepest match wins. */
|
|
233
|
+
gutterBias: GutterBiasConfig | null;
|
|
234
|
+
/** Effective matcher list (defaults + user, or just user when defaults off). */
|
|
235
|
+
matchers: BlockMatcher[];
|
|
236
|
+
}
|
|
237
|
+
interface CreateBlockHandlePluginOptions {
|
|
238
|
+
pluginKey: PluginKey<BlockHandlePluginState>;
|
|
239
|
+
editor: Editor;
|
|
240
|
+
hideDelay: number;
|
|
241
|
+
disableDrag: boolean;
|
|
242
|
+
autoScroll: boolean;
|
|
243
|
+
autoScrollThreshold: number;
|
|
244
|
+
autoScrollMaxSpeed: number;
|
|
245
|
+
nested: NestedResolution;
|
|
246
|
+
dropIndicator: boolean;
|
|
247
|
+
/**
|
|
248
|
+
* Pixel threshold for nested-drop X-detection (see `BlockHandleOptions.nestThreshold`).
|
|
249
|
+
* `0` disables nested-drop.
|
|
250
|
+
*/
|
|
251
|
+
nestThreshold: number;
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Creates a standalone BlockHandle PM plugin. Exported so framework wrappers can
|
|
255
|
+
* build on it without the Extension factory.
|
|
256
|
+
*/
|
|
257
|
+
declare function createBlockHandlePlugin(options: CreateBlockHandlePluginOptions): Plugin<BlockHandlePluginState>;
|
|
258
|
+
declare const BlockHandle: Extension<BlockHandleOptions, unknown>;
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Built-in eligibility matchers. They constrain the drag resolver to
|
|
262
|
+
* user-visible block units (paragraphs, headings, list/task items, images) and
|
|
263
|
+
* exclude structural plumbing (table cells, inline text, list containers whose
|
|
264
|
+
* items are draggable individually). Hosts can disable them with
|
|
265
|
+
* `defaultMatchers: false` and supply their own via `matchers`.
|
|
266
|
+
*/
|
|
267
|
+
|
|
268
|
+
declare const DEFAULT_BLOCK_MATCHERS: readonly BlockMatcher[];
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* `Mod-Shift-ArrowUp` / `Mod-Shift-ArrowDown` move the top-level block
|
|
272
|
+
* containing the selection. Accessibility companion to BlockHandle drag.
|
|
273
|
+
* Shares `moveBlock` so position math and self-move rejection match.
|
|
274
|
+
*/
|
|
275
|
+
|
|
276
|
+
declare const KeyboardReorder: Extension<unknown, unknown>;
|
|
277
|
+
|
|
278
|
+
/** Wrapper-style "Turn into" commands, each from the matching node extension.
|
|
279
|
+
* Lists use the per-item `turnInto*` commands (Notion turn-into: convert only
|
|
280
|
+
* the targeted block and split the run); the whole-list `toggle*List` commands
|
|
281
|
+
* stay reserved for the toolbar button and Mod-Shift shortcut. */
|
|
282
|
+
type WrapperCommand = 'turnIntoBulletList' | 'turnIntoOrderedList' | 'turnIntoTaskList' | 'toggleBlockquote';
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Popup menu opened by clicking the BlockHandle drag handle without dragging.
|
|
286
|
+
* Offers Delete, Duplicate, and Turn into (change block type) for the target.
|
|
287
|
+
*
|
|
288
|
+
* Triggered by the `dm:block-context-menu-open` event from BlockHandle, with
|
|
289
|
+
* payload `{ blockPos, anchorElement }`. Mirrors FloatingMenu / SlashCommand
|
|
290
|
+
* styling: `role="menu"`, `role="menuitem"`, `data-show`, positionFloatingOnce.
|
|
291
|
+
*/
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* `props.decorations` applies the `dm-block-context-active` class via a PM
|
|
295
|
+
* Decoration (not inline classList) so the highlight survives view rerenders
|
|
296
|
+
* from other transactions, e.g. UniqueID stamping `id` via setNodeMarkup.
|
|
297
|
+
*/
|
|
298
|
+
interface BlockContextMenuPluginState {
|
|
299
|
+
activeBlockPos: number | null;
|
|
300
|
+
}
|
|
301
|
+
declare const blockContextMenuPluginKey: PluginKey<BlockContextMenuPluginState>;
|
|
302
|
+
/** A block type offered by the "Turn into" submenu. */
|
|
303
|
+
interface TurnIntoTarget {
|
|
304
|
+
/** Display label, e.g. "Heading 1". */
|
|
305
|
+
label: string;
|
|
306
|
+
/** Icon key resolved against `defaultIcons`. */
|
|
307
|
+
icon: string;
|
|
308
|
+
/** Schema node name, e.g. "heading", "paragraph", "blockquote". */
|
|
309
|
+
nodeType: string;
|
|
310
|
+
/** Optional node attributes (e.g. `{ level: 1 }` for Heading 1). */
|
|
311
|
+
attrs?: Attrs;
|
|
312
|
+
/**
|
|
313
|
+
* Command for wrapper (non-textblock) targets like lists and blockquote.
|
|
314
|
+
* When set, runTurnInto routes through `turnIntoWrapper` instead of
|
|
315
|
+
* `setBlockType`. Leave undefined for textblock targets (paragraph,
|
|
316
|
+
* heading, codeBlock).
|
|
317
|
+
*/
|
|
318
|
+
command?: WrapperCommand;
|
|
319
|
+
}
|
|
320
|
+
interface BlockContextMenuOptions {
|
|
321
|
+
/**
|
|
322
|
+
* Show the "Turn into" section of the menu. Disable to limit operations
|
|
323
|
+
* to Delete + Duplicate.
|
|
324
|
+
* @default true
|
|
325
|
+
*/
|
|
326
|
+
turnIntoEnabled?: boolean;
|
|
327
|
+
/**
|
|
328
|
+
* Block types offered by "Turn into". Override to curate the list or
|
|
329
|
+
* add project-specific block types.
|
|
330
|
+
* @default DEFAULT_TURN_INTO
|
|
331
|
+
*/
|
|
332
|
+
turnIntoTargets?: TurnIntoTarget[];
|
|
333
|
+
/**
|
|
334
|
+
* Show "Copy link" when the target block has an id attribute AND the
|
|
335
|
+
* `UniqueID` extension is loaded. No effect without UniqueID.
|
|
336
|
+
* @default true
|
|
337
|
+
*/
|
|
338
|
+
copyLinkEnabled?: boolean;
|
|
339
|
+
/**
|
|
340
|
+
* Builds the URL written to the clipboard on "Copy link". Default appends
|
|
341
|
+
* `#<id>` to the current pathname+search (works for static pages); apps
|
|
342
|
+
* with client-side routing should provide a callback matching their scheme.
|
|
343
|
+
*/
|
|
344
|
+
onCopyLink?: (blockId: string, editor: Editor) => string;
|
|
345
|
+
/**
|
|
346
|
+
* Show the Colors section (text + background) when the `BlockColor`
|
|
347
|
+
* extension is loaded and the target block is in its `types` list.
|
|
348
|
+
* @default true
|
|
349
|
+
*/
|
|
350
|
+
blockColorEnabled?: boolean;
|
|
351
|
+
}
|
|
352
|
+
interface CreateBlockContextMenuPluginOptions {
|
|
353
|
+
pluginKey: PluginKey<BlockContextMenuPluginState>;
|
|
354
|
+
editor: Editor;
|
|
355
|
+
turnIntoEnabled: boolean;
|
|
356
|
+
turnIntoTargets: TurnIntoTarget[];
|
|
357
|
+
copyLinkEnabled: boolean;
|
|
358
|
+
onCopyLink: (blockId: string, editor: Editor) => string;
|
|
359
|
+
blockColorEnabled: boolean;
|
|
360
|
+
}
|
|
361
|
+
/**
|
|
362
|
+
* Builds the popup DOM, listens for `dm:block-context-menu-open` on
|
|
363
|
+
* `.dm-editor`, and runs block operations via `helpers/blockOperations.ts`.
|
|
364
|
+
*/
|
|
365
|
+
declare function createBlockContextMenuPlugin(options: CreateBlockContextMenuPluginOptions): Plugin;
|
|
366
|
+
declare const BlockContextMenu: Extension<BlockContextMenuOptions, unknown>;
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* `/` trigger that opens a filtered popup of insertable blocks. Items are
|
|
370
|
+
* shared with FloatingMenu and BlockHandle via `addFloatingMenuItems()`.
|
|
371
|
+
* On select, the `/query` range is deleted and the item's command runs at
|
|
372
|
+
* the now-empty cursor (so "Heading 1" transforms the block, "Image" opens
|
|
373
|
+
* its popover, etc.).
|
|
374
|
+
*/
|
|
375
|
+
|
|
376
|
+
declare const slashCommandPluginKey: PluginKey<SlashCommandPluginState>;
|
|
377
|
+
interface SlashCommandProps {
|
|
378
|
+
/** The editor instance (passed so custom renderers can read state). */
|
|
379
|
+
editor: Editor;
|
|
380
|
+
/** Current query string (text after the `/`). */
|
|
381
|
+
query: string;
|
|
382
|
+
/** Document range of the `/` + query (for replacement). */
|
|
383
|
+
range: {
|
|
384
|
+
from: number;
|
|
385
|
+
to: number;
|
|
386
|
+
};
|
|
387
|
+
/** Filtered and ranked items matching the query. */
|
|
388
|
+
items: FloatingMenuItem[];
|
|
389
|
+
/** Execute the selected item and close the popup. */
|
|
390
|
+
command: (item: FloatingMenuItem) => void;
|
|
391
|
+
/** Returns the client rect of the cursor for positioning the popup. */
|
|
392
|
+
clientRect: () => DOMRect | null;
|
|
393
|
+
/** The editor's ProseMirror DOM node (for portal parents etc.). */
|
|
394
|
+
element: HTMLElement;
|
|
395
|
+
}
|
|
396
|
+
interface SlashCommandRenderer {
|
|
397
|
+
onStart: (props: SlashCommandProps) => void;
|
|
398
|
+
onUpdate: (props: SlashCommandProps) => void;
|
|
399
|
+
onExit: () => void;
|
|
400
|
+
/** Return `true` to consume the key event and prevent default handling. */
|
|
401
|
+
onKeyDown: (event: KeyboardEvent) => boolean;
|
|
402
|
+
}
|
|
403
|
+
interface SlashCommandOptions {
|
|
404
|
+
/**
|
|
405
|
+
* The trigger character. @default '/'
|
|
406
|
+
*/
|
|
407
|
+
char?: string;
|
|
408
|
+
/**
|
|
409
|
+
* Items override. When omitted, items from `editor.floatingMenuItems`
|
|
410
|
+
* (collected via `addFloatingMenuItems()`) are used. An array replaces
|
|
411
|
+
* defaults; a function transforms them.
|
|
412
|
+
*/
|
|
413
|
+
items?: FloatingMenuItemsOverride;
|
|
414
|
+
/**
|
|
415
|
+
* Factory returning render callbacks for the popup. Default uses
|
|
416
|
+
* `createSlashSuggestionRenderer()`.
|
|
417
|
+
*/
|
|
418
|
+
render?: () => SlashCommandRenderer;
|
|
419
|
+
/**
|
|
420
|
+
* Node types where slash should NOT activate (e.g. `codeBlock`).
|
|
421
|
+
* @default ['codeBlock']
|
|
422
|
+
*/
|
|
423
|
+
invalidNodes?: string[];
|
|
424
|
+
}
|
|
425
|
+
interface CreateSlashCommandPluginOptions {
|
|
426
|
+
pluginKey: PluginKey<SlashCommandPluginState>;
|
|
427
|
+
editor: Editor;
|
|
428
|
+
char: string;
|
|
429
|
+
items?: FloatingMenuItemsOverride;
|
|
430
|
+
render: () => SlashCommandRenderer;
|
|
431
|
+
invalidNodes: string[];
|
|
432
|
+
}
|
|
433
|
+
interface SlashCommandPluginState {
|
|
434
|
+
active: boolean;
|
|
435
|
+
query: string;
|
|
436
|
+
range: {
|
|
437
|
+
from: number;
|
|
438
|
+
to: number;
|
|
439
|
+
} | null;
|
|
440
|
+
}
|
|
441
|
+
/**
|
|
442
|
+
* Filters and ranks FloatingMenuItems against a query. Priority:
|
|
443
|
+
* 1. Exact label prefix (case-insensitive)
|
|
444
|
+
* 2. Label substring match
|
|
445
|
+
* 3. Keyword match (preserving original keyword index for stable ranking)
|
|
446
|
+
* Returns items in rank order, stable within the same rank.
|
|
447
|
+
*/
|
|
448
|
+
declare function filterSlashItems(items: FloatingMenuItem[], query: string): FloatingMenuItem[];
|
|
449
|
+
declare function createSlashCommandPlugin(options: CreateSlashCommandPluginOptions): Plugin<SlashCommandPluginState>;
|
|
450
|
+
declare const SlashCommand: Extension<SlashCommandOptions, unknown>;
|
|
451
|
+
/** Programmatically dismisses the slash suggestion. */
|
|
452
|
+
declare function dismissSlashCommand(view: EditorView): void;
|
|
453
|
+
|
|
454
|
+
declare function createSlashSuggestionRenderer(): SlashCommandRenderer;
|
|
455
|
+
|
|
456
|
+
/**
|
|
457
|
+
* Pasting block-level content at an INLINE position normally goes through PM's
|
|
458
|
+
* content fitter, which strips the block wrapper and pastes only inline text.
|
|
459
|
+
* SmartPaste catches the relevant cases and routes each to the right strategy:
|
|
460
|
+
*
|
|
461
|
+
* 1. List slice into a list ancestor: same-kind items merge as siblings; a
|
|
462
|
+
* different-kind list keeps its kind and splits the host list around it.
|
|
463
|
+
* 2. Trailing hardBreak (Shift+Enter): trim the hardBreak, insert as sibling.
|
|
464
|
+
* 3. Truly empty parent paragraph (`parentSize === 0`): replace the parent.
|
|
465
|
+
* 4-6. Caret at start / end / middle: insert as sibling or split-and-insert.
|
|
466
|
+
* 7. Range selection: delete first, then run through 2-6.
|
|
467
|
+
*
|
|
468
|
+
* Skipped (PM default applies) when: cursor isn't in a textblock; the slice's
|
|
469
|
+
* top-level blocks are ALL plain paragraphs; or the slice is a SINGLE top-level
|
|
470
|
+
* block of the SAME TYPE as the destination (heading-into-heading, etc.) where
|
|
471
|
+
* PM's inline merge is what the user wants.
|
|
472
|
+
*
|
|
473
|
+
* Do NOT bail on `openStart > 0`: PM's clipboard parser routinely sets
|
|
474
|
+
* `openStart=1` even for closed-looking input like `<h1>x</h1>`. Top-level
|
|
475
|
+
* children of the slice are what matter.
|
|
476
|
+
*/
|
|
477
|
+
|
|
478
|
+
interface SmartPasteOptions {
|
|
479
|
+
/**
|
|
480
|
+
* Disable the plugin without removing the extension (falls back to PM's
|
|
481
|
+
* default paste handling).
|
|
482
|
+
* @default true
|
|
483
|
+
*/
|
|
484
|
+
enabled?: boolean;
|
|
485
|
+
}
|
|
486
|
+
declare const SmartPaste: Extension<SmartPasteOptions, unknown>;
|
|
487
|
+
|
|
488
|
+
export { type BlockCandidate, BlockContextMenu, type BlockContextMenuOptions, BlockHandle, type BlockHandleOptions, type BlockHandlePluginState, type BlockMatcher, type CreateBlockContextMenuPluginOptions, type CreateBlockHandlePluginOptions, type CreateSlashCommandPluginOptions, DEFAULT_BLOCK_MATCHERS, DEFAULT_NESTED_NODES, FloatingMenu, KeyboardReorder, type MatchVerdict, type NestedConfig, SlashCommand, type SlashCommandOptions, type SlashCommandPluginState, type SlashCommandProps, type SlashCommandRenderer, SmartPaste, type SmartPasteOptions, type TurnIntoTarget, type WrapperCommand, blockContextMenuPluginKey, blockHandlePluginKey, createBlockContextMenuPlugin, createBlockHandlePlugin, createSlashCommandPlugin, createSlashSuggestionRenderer, dismissSlashCommand, filterSlashItems, slashCommandPluginKey };
|