@domternal/extension-block-menu 0.7.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.
@@ -0,0 +1,607 @@
1
+ import { Extension, Editor, FloatingMenuItemsOverride, FloatingMenuItem } from '@domternal/core';
2
+ import { EditorState, PluginKey, Plugin } from '@domternal/pm/state';
3
+ import { EditorView } from '@domternal/pm/view';
4
+ import { Node, ResolvedPos, Attrs } from '@domternal/pm/model';
5
+
6
+ /**
7
+ * Block-insert menu shown when the cursor sits at the start of an empty
8
+ * paragraph. Items collected via `addFloatingMenuItems()`. This file owns
9
+ * visibility, positioning, dismiss, and keyboard entry; framework wrappers
10
+ * render the UI.
11
+ */
12
+
13
+ declare const floatingMenuPluginKey: PluginKey<any>;
14
+ /**
15
+ * Keyboard shortcuts that move focus from the editor into the menu.
16
+ * Defaults combine the WAI-ARIA recommendation (`Alt-F10`) with a
17
+ * modern slash-command-friendly shortcut (`Mod-/`). Set to an empty
18
+ * array to disable keyboard entry entirely.
19
+ */
20
+ interface FloatingMenuKeymap {
21
+ /** Shortcuts that focus the first menu item when the menu is visible. */
22
+ enterMenu?: string[];
23
+ }
24
+ interface FloatingMenuOptions {
25
+ /**
26
+ * The HTML element that contains the menu. Required when used directly;
27
+ * framework wrappers create this element and pass it in.
28
+ */
29
+ element: HTMLElement | null;
30
+ /**
31
+ * Visibility predicate. Defaults to `defaultShouldShow` (empty paragraph
32
+ * at cursor start).
33
+ */
34
+ shouldShow: (props: {
35
+ editor: Editor;
36
+ view: EditorView;
37
+ state: EditorState;
38
+ }) => boolean;
39
+ /**
40
+ * Pixel offset from the anchor. @default 0
41
+ */
42
+ offset: number;
43
+ /**
44
+ * Items override. Array replaces defaults entirely; function receives
45
+ * the collected defaults and returns a new list (filter/reorder/extend).
46
+ * Consumed by the controller; the plugin itself only reads it when the
47
+ * wrapper does not construct its own controller.
48
+ */
49
+ items?: FloatingMenuItemsOverride;
50
+ /** Keyboard shortcuts for entering the menu via keyboard. */
51
+ keymap?: FloatingMenuKeymap;
52
+ /**
53
+ * When `true`, the menu only appears when EXPLICITLY triggered via
54
+ * `showFloatingMenu(view)` (typically from BlockHandle's `+` button).
55
+ * Pressing `Enter` to create a new empty paragraph no longer auto-shows
56
+ * the menu. Mirrors Notion's behaviour: empty rows display a placeholder
57
+ * hint, the slash command (`/`) is the keyboard trigger, and the menu
58
+ * opens via the gutter `+` button only.
59
+ *
60
+ * @default false (auto-shows on every empty paragraph - backward compat)
61
+ */
62
+ requireExplicitTrigger?: boolean;
63
+ }
64
+ interface CreateFloatingMenuPluginOptions {
65
+ pluginKey: PluginKey;
66
+ editor: Editor;
67
+ element: HTMLElement;
68
+ shouldShow?: FloatingMenuOptions['shouldShow'];
69
+ offset?: number;
70
+ keymap?: FloatingMenuKeymap | undefined;
71
+ requireExplicitTrigger?: boolean;
72
+ }
73
+ /**
74
+ * Creates a standalone FloatingMenu ProseMirror plugin. Framework wrappers
75
+ * call this directly so they can own element creation and rendering.
76
+ */
77
+ declare function createFloatingMenuPlugin(options: CreateFloatingMenuPluginOptions): Plugin;
78
+ declare const FloatingMenu: Extension<FloatingMenuOptions, unknown>;
79
+
80
+ /**
81
+ * Gutter-bias resolution for nested drag-target selection.
82
+ *
83
+ * When the cursor sits near a configured edge of a candidate's rect, the
84
+ * candidate is treated as "in the gutter" and its rank weight is reduced
85
+ * proportionally to its tree depth. Shallower (outer) ancestors therefore
86
+ * win against deeply-nested descendants in the gutter zone.
87
+ *
88
+ * The "deepest-match" mode skips this bias entirely and always returns the
89
+ * innermost allowed block under the cursor.
90
+ */
91
+ /** Cardinal edge of a candidate's bounding rect. */
92
+ type GutterEdge = 'left' | 'right' | 'top' | 'bottom';
93
+ /**
94
+ * Named presets for the bias config:
95
+ * - `'left'` → ['left', 'top'] (left gutter, default)
96
+ * - `'right'` → ['right', 'top'] (right gutter, RTL-friendly)
97
+ * - `'both'` → ['left', 'right', 'top']
98
+ * - `'none'` → no gutter bias (deepest match wins)
99
+ */
100
+ type GutterBiasPreset = 'left' | 'right' | 'both' | 'none';
101
+ interface GutterBiasConfig {
102
+ /** Edges that constitute the "gutter" zone. */
103
+ edges: GutterEdge[];
104
+ /** Pixel distance from the edge at which the bias activates. */
105
+ threshold: number;
106
+ /** Bias factor applied per depth level when in the gutter. */
107
+ strength: number;
108
+ }
109
+
110
+ /**
111
+ * Predicate-based block matching for nested drag-target resolution.
112
+ *
113
+ * A matcher answers one question for a candidate node: "should this be
114
+ * eligible to become the drag target?" Predicates return a verdict (allow
115
+ * or reject), and the resolver filters the candidate list before ranking.
116
+ *
117
+ * This replaces the score-deduction approach with explicit verdicts so
118
+ * the contract is binary: a candidate is either eligible or it isn't,
119
+ * with rank decided separately by depth + optional gutter bias.
120
+ */
121
+
122
+ /** Eligibility verdict returned by a matcher's `test()`. */
123
+ type MatchVerdict = 'allow' | 'reject';
124
+ /**
125
+ * Information passed to a matcher about the node being considered.
126
+ * Bundles the node itself, its position metadata, its parent context,
127
+ * and the live editor view (in case a matcher needs DOM lookups).
128
+ */
129
+ interface BlockCandidate {
130
+ /** The PM node being considered as a drag target. */
131
+ block: Node;
132
+ /** Document position immediately before `block`. */
133
+ documentPos: number;
134
+ /** Depth in the PM tree; 0 is the doc root and is never a candidate. */
135
+ treeDepth: number;
136
+ /** Parent node, or `null` when there is no parent. */
137
+ container: Node | null;
138
+ /** Index of `block` inside `container.content`. */
139
+ positionInContainer: number;
140
+ /** Convenience: `positionInContainer === 0`. */
141
+ isFirstChild: boolean;
142
+ /** Convenience: `positionInContainer === container.childCount - 1`. */
143
+ isLastChild: boolean;
144
+ /** The ProseMirror resolved position the resolver started from. */
145
+ resolvedPos: ResolvedPos;
146
+ /** Live editor view; matchers may call `editorView.nodeDOM(pos)`. */
147
+ editorView: EditorView;
148
+ }
149
+ /**
150
+ * A single matcher with a stable name (for debugging / opt-out) and a
151
+ * pure-function predicate.
152
+ */
153
+ interface BlockMatcher {
154
+ name: string;
155
+ test(candidate: BlockCandidate): MatchVerdict;
156
+ }
157
+
158
+ /**
159
+ * BlockHandle Extension
160
+ *
161
+ * Renders a Notion-style gutter handle on the left side of each top-level
162
+ * block when the user hovers over the editor. The handle exposes two
163
+ * buttons:
164
+ *
165
+ * 1. `⋮⋮` drag handle (click → opens BlockContextMenu, drag → reorder)
166
+ * 2. `+` insert button (inserts an empty paragraph below the block; the
167
+ * FloatingMenu extension picks up the empty-line state and auto-shows
168
+ * its insert menu)
169
+ *
170
+ * Visibility + drag + context-menu trigger are fully owned by this plugin;
171
+ * the context menu UI itself lives in `BlockContextMenu.ts` and listens for
172
+ * the `dm:block-context-menu-open` custom event that this plugin dispatches.
173
+ *
174
+ * Styles ship via `@domternal/theme` (`_block-handle.scss`). The plugin
175
+ * adds a `dm-editor--has-block-handle` class to the `.dm-editor` container
176
+ * so the theme stylesheet can widen `.ProseMirror` padding-left to create
177
+ * gutter space inside the `overflow:hidden` editor wrapper.
178
+ */
179
+
180
+ /** Default list of nodes treated as drag-targetable when `nested: true`. */
181
+ declare const DEFAULT_NESTED_NODES: string[];
182
+ declare const blockHandlePluginKey: PluginKey<BlockHandlePluginState>;
183
+ interface BlockHandleOptions {
184
+ /**
185
+ * Milliseconds to wait before hiding the handle after the mouse leaves
186
+ * the editor. Gives users time to move into the handle without it
187
+ * disappearing.
188
+ * @default 200
189
+ */
190
+ hideDelay?: number;
191
+ /**
192
+ * Disable drag-to-reorder while still showing the plus/drag buttons
193
+ * (drag becomes a no-op, click opens context menu only).
194
+ * @default false
195
+ */
196
+ disableDrag?: boolean;
197
+ /**
198
+ * Auto-scroll the nearest scrollable ancestor when the user drags near
199
+ * the top/bottom edge of the viewport. Disable if the host app manages
200
+ * its own drag scroll behaviour.
201
+ * @default true
202
+ */
203
+ autoScroll?: boolean;
204
+ /**
205
+ * Distance in CSS pixels from the top/bottom edge that triggers
206
+ * auto-scroll. Larger values start scrolling sooner.
207
+ * @default 48
208
+ */
209
+ autoScrollThreshold?: number;
210
+ /**
211
+ * Peak scroll speed in CSS pixels per animation frame. Speed ramps
212
+ * linearly from 0 at the threshold to this value at the edge.
213
+ * @default 18
214
+ */
215
+ autoScrollMaxSpeed?: number;
216
+ /**
217
+ * Whether the handle should resolve to nested block containers (list
218
+ * items, task items, and optionally others) instead of always the
219
+ * top-level block.
220
+ *
221
+ * - `false` - only top-level blocks are hoverable / draggable (default).
222
+ * - `true` - list items and task items resolve individually (Notion behaviour).
223
+ * - object - fine-grained config; see `NestedConfig`.
224
+ *
225
+ * @default false
226
+ */
227
+ nested?: boolean | NestedConfig;
228
+ /**
229
+ * Pixel threshold from the LEFT edge of a list item beyond which a
230
+ * drop commits to nested-child mode (the dragged block becomes the
231
+ * last child of that item) instead of sibling mode (a new list item
232
+ * created next to it). Mirrors Notion's "drop indented = nested,
233
+ * drop on the marker = sibling" UX.
234
+ *
235
+ * Set to `0` to disable nested-drop entirely (every drop is sibling).
236
+ *
237
+ * The X-detection only fires when nested mode is on AND the resolved
238
+ * target is a `listItem` / `taskItem`. Other containers stay sibling-
239
+ * only until explicit support lands.
240
+ *
241
+ * @default 28
242
+ */
243
+ nestThreshold?: number;
244
+ /**
245
+ * Show a custom drop indicator line during drag-from-handle that
246
+ * mirrors EXACTLY where the drop will land. Replaces the need for
247
+ * `prosemirror-dropcursor` for our drag flow - `dropcursor` uses
248
+ * PM's default `posAtCoords` which can disagree with our resolver
249
+ * (especially in side-gutter / inter-block-gap drops).
250
+ *
251
+ * Set to `false` if you want the standard `prosemirror-dropcursor`
252
+ * behaviour or are providing your own indicator.
253
+ * @default true
254
+ */
255
+ dropIndicator?: boolean;
256
+ }
257
+ /**
258
+ * Configuration for nested resolution. Backwards-compatible with the
259
+ * earlier `{ allowedNodes }` literal - every field is optional.
260
+ */
261
+ interface NestedConfig {
262
+ /**
263
+ * Node type names treated as drag targets when nested mode is on.
264
+ * @default ['listItem', 'taskItem']
265
+ */
266
+ allowedNodes?: string[];
267
+ /**
268
+ * Restrict resolution to nodes that have at least one ancestor of one
269
+ * of these type names. Use to scope nested mode to specific structures
270
+ * (e.g. only inside `table`). Empty / omitted → no restriction.
271
+ */
272
+ allowedContainers?: string[];
273
+ /**
274
+ * "Promote to parent at the gutter" behaviour. When the cursor is
275
+ * within `threshold` px of a configured edge, the candidate's score
276
+ * is reduced by `strength * depth` - deeper nodes are penalised
277
+ * more, so a shallower ancestor (e.g. the wrapping list) wins near
278
+ * the boundary.
279
+ *
280
+ * - `false` / `undefined` / `'none'` → deepest match wins.
281
+ * - `true` / `'left'` → defaults: edges `['left','top']`, threshold 12, strength 500.
282
+ * - `'right'` / `'both'` → preset variants.
283
+ * - object → custom config (any field optional, merged over defaults).
284
+ *
285
+ * @default false
286
+ */
287
+ promoteOnEdge?: boolean | GutterBiasPreset | Partial<GutterBiasConfig>;
288
+ /**
289
+ * Append custom block matchers. Default matchers still apply unless
290
+ * `defaultMatchers: false` is also set.
291
+ */
292
+ matchers?: BlockMatcher[];
293
+ /**
294
+ * Set to `false` to disable the four built-in matchers
295
+ * (firstChildOfListItem, listContainerSkip, tableInternals,
296
+ * inlineNodes). Almost always wanted; opt-out only for testing or
297
+ * specialised host editors.
298
+ * @default true
299
+ */
300
+ defaultMatchers?: boolean;
301
+ }
302
+ interface BlockHandlePluginState {
303
+ /** Absolute position of the top-level block currently under the cursor, or null. */
304
+ hoveredPos: number | null;
305
+ /**
306
+ * Source position of the block currently being dragged (set on
307
+ * dragstart, cleared on dragend). Used by `handleDrop` to determine
308
+ * whether the drop originated from our handle.
309
+ */
310
+ draggedFrom: number | null;
311
+ }
312
+ /**
313
+ * Internal, fully-resolved view of `NestedConfig`. Plain string arrays
314
+ * + an optional edge config so the resolver doesn't have to interpret
315
+ * presets at hover time.
316
+ */
317
+ interface NestedResolution {
318
+ /** Allowed drag targets. Empty array → top-level-only mode. */
319
+ allowedNodes: string[];
320
+ /** Optional ancestor whitelist; empty array → no restriction. */
321
+ allowedContainers: string[];
322
+ /** Gutter bias config; `null` → deepest match wins. */
323
+ gutterBias: GutterBiasConfig | null;
324
+ /** Effective matcher list (defaults + user, or just user when defaults off). */
325
+ matchers: BlockMatcher[];
326
+ }
327
+ interface CreateBlockHandlePluginOptions {
328
+ pluginKey: PluginKey<BlockHandlePluginState>;
329
+ editor: Editor;
330
+ hideDelay: number;
331
+ disableDrag: boolean;
332
+ autoScroll: boolean;
333
+ autoScrollThreshold: number;
334
+ autoScrollMaxSpeed: number;
335
+ nested: NestedResolution;
336
+ dropIndicator: boolean;
337
+ /**
338
+ * Pixel threshold for nested-drop X-detection (see `BlockHandleOptions.nestThreshold`).
339
+ * `0` disables nested-drop.
340
+ */
341
+ nestThreshold: number;
342
+ }
343
+ /**
344
+ * Creates a standalone BlockHandle ProseMirror plugin. Exported so
345
+ * framework wrappers can build on top without going through the Extension
346
+ * factory.
347
+ */
348
+ declare function createBlockHandlePlugin(options: CreateBlockHandlePluginOptions): Plugin<BlockHandlePluginState>;
349
+ declare const BlockHandle: Extension<BlockHandleOptions, unknown>;
350
+
351
+ /**
352
+ * Built-in eligibility matchers. Together they constrain the drag
353
+ * resolver to "user-visible block units" (paragraphs, headings, list
354
+ * items, task items, images, etc.) and exclude structural plumbing
355
+ * (table cells, inline text, list containers whose items are draggable
356
+ * individually).
357
+ *
358
+ * Hosts can disable the bundled set with `defaultMatchers: false` and
359
+ * supply their own matcher list via `matchers`.
360
+ */
361
+
362
+ declare const DEFAULT_BLOCK_MATCHERS: readonly BlockMatcher[];
363
+
364
+ /**
365
+ * `Mod-Shift-ArrowUp` / `Mod-Shift-ArrowDown` move the top-level block
366
+ * containing the selection. Accessibility companion to BlockHandle drag.
367
+ * Shares `moveBlock` so position math and self-move rejection match.
368
+ */
369
+
370
+ declare const KeyboardReorder: Extension<unknown, unknown>;
371
+
372
+ /**
373
+ * Editor commands the menu can route to for wrapper-style "Turn into".
374
+ * Each maps to a command from the corresponding node extension.
375
+ */
376
+ type WrapperCommand = 'toggleBulletList' | 'toggleOrderedList' | 'toggleTaskList' | 'toggleBlockquote';
377
+
378
+ /**
379
+ * BlockContextMenu Extension
380
+ *
381
+ * Popup menu that opens when the user clicks the BlockHandle `⋮⋮` drag
382
+ * handle without dragging. Offers block-level operations for the target:
383
+ *
384
+ * - **Delete** - remove the block
385
+ * - **Duplicate** - insert an identical copy below
386
+ * - **Turn into** - change the block type (paragraph, heading, quote, etc.)
387
+ *
388
+ * Triggered by the `dm:block-context-menu-open` custom event dispatched
389
+ * from BlockHandle; payload carries `{ blockPos, anchorElement }`.
390
+ *
391
+ * Implementation mirrors FloatingMenu / SlashCommand styling conventions
392
+ * so theming is consistent: `role="menu"` container, `role="menuitem"`
393
+ * buttons, `data-show` for visibility, positioned via `positionFloatingOnce`.
394
+ */
395
+
396
+ /**
397
+ * `props.decorations` reads `activeBlockPos` and applies the
398
+ * `dm-block-context-active` class via a PM Decoration. Decoration
399
+ * survives view rerenders that other transactions trigger (e.g.
400
+ * UniqueID stamping `id` via setNodeMarkup); inline classList
401
+ * mutation does not.
402
+ */
403
+ interface BlockContextMenuPluginState {
404
+ activeBlockPos: number | null;
405
+ }
406
+ declare const blockContextMenuPluginKey: PluginKey<BlockContextMenuPluginState>;
407
+ /**
408
+ * A target type the user can convert the current block into via the
409
+ * "Turn into" submenu.
410
+ */
411
+ interface TurnIntoTarget {
412
+ /** Display label, e.g. "Heading 1". */
413
+ label: string;
414
+ /** Icon key resolved against `defaultIcons`. */
415
+ icon: string;
416
+ /** Schema node name, e.g. "heading", "paragraph", "blockquote". */
417
+ nodeType: string;
418
+ /** Optional node attributes (e.g. `{ level: 1 }` for Heading 1). */
419
+ attrs?: Attrs;
420
+ /**
421
+ * Editor command to invoke for non-textblock (wrapper) targets like
422
+ * lists and blockquote. When set, runTurnInto routes through
423
+ * `turnIntoWrapper` which sets selection then dispatches the
424
+ * command, instead of calling `setBlockType`. Leave undefined for
425
+ * textblock targets (paragraph, heading, codeBlock).
426
+ */
427
+ command?: WrapperCommand;
428
+ }
429
+ interface BlockContextMenuOptions {
430
+ /**
431
+ * Show the "Turn into" section of the menu. Disable to limit operations
432
+ * to Delete + Duplicate.
433
+ * @default true
434
+ */
435
+ turnIntoEnabled?: boolean;
436
+ /**
437
+ * Block types offered by "Turn into". Override to curate the list or
438
+ * add project-specific block types.
439
+ * @default DEFAULT_TURN_INTO
440
+ */
441
+ turnIntoTargets?: TurnIntoTarget[];
442
+ /**
443
+ * Show "Copy link" when the target block has an id attribute AND the
444
+ * `UniqueID` extension is loaded in the editor. Without UniqueID this
445
+ * option has no effect - the item never appears regardless of value.
446
+ * @default true
447
+ */
448
+ copyLinkEnabled?: boolean;
449
+ /**
450
+ * Build the URL written to the clipboard when the user clicks "Copy link".
451
+ * Receives the block's id and the editor; returns a full URL. Default
452
+ * appends `#<id>` to the current pathname+search, which works for static
453
+ * pages. Frameworks with client-side routing should provide a callback
454
+ * matching their URL scheme.
455
+ */
456
+ onCopyLink?: (blockId: string, editor: Editor) => string;
457
+ /**
458
+ * Show the Colors section (text color + background) when the `BlockColor`
459
+ * extension is loaded and the target block is in its `types` list.
460
+ * Without BlockColor this option has no effect.
461
+ * @default true
462
+ */
463
+ blockColorEnabled?: boolean;
464
+ }
465
+ interface CreateBlockContextMenuPluginOptions {
466
+ pluginKey: PluginKey<BlockContextMenuPluginState>;
467
+ editor: Editor;
468
+ turnIntoEnabled: boolean;
469
+ turnIntoTargets: TurnIntoTarget[];
470
+ copyLinkEnabled: boolean;
471
+ onCopyLink: (blockId: string, editor: Editor) => string;
472
+ blockColorEnabled: boolean;
473
+ }
474
+ /**
475
+ * Creates the BlockContextMenu ProseMirror plugin. Builds an absolutely
476
+ * positioned popup DOM, listens for `dm:block-context-menu-open` on the
477
+ * `.dm-editor` container, and executes block operations via the shared
478
+ * helpers in `helpers/blockOperations.ts`.
479
+ */
480
+ declare function createBlockContextMenuPlugin(options: CreateBlockContextMenuPluginOptions): Plugin;
481
+ declare const BlockContextMenu: Extension<BlockContextMenuOptions, unknown>;
482
+
483
+ /**
484
+ * `/` trigger that opens a filtered popup of insertable blocks. Items are
485
+ * shared with FloatingMenu and BlockHandle via `addFloatingMenuItems()`.
486
+ * On select, the `/query` range is deleted and the item's command runs at
487
+ * the now-empty cursor (so "Heading 1" transforms the block, "Image" opens
488
+ * its popover, etc.).
489
+ */
490
+
491
+ declare const slashCommandPluginKey: PluginKey<SlashCommandPluginState>;
492
+ interface SlashCommandProps {
493
+ /** The editor instance (passed so custom renderers can read state). */
494
+ editor: Editor;
495
+ /** Current query string (text after the `/`). */
496
+ query: string;
497
+ /** Document range of the `/` + query (for replacement). */
498
+ range: {
499
+ from: number;
500
+ to: number;
501
+ };
502
+ /** Filtered and ranked items matching the query. */
503
+ items: FloatingMenuItem[];
504
+ /** Execute the selected item and close the popup. */
505
+ command: (item: FloatingMenuItem) => void;
506
+ /** Returns the client rect of the cursor for positioning the popup. */
507
+ clientRect: () => DOMRect | null;
508
+ /** The editor's ProseMirror DOM node (for portal parents etc.). */
509
+ element: HTMLElement;
510
+ }
511
+ interface SlashCommandRenderer {
512
+ onStart: (props: SlashCommandProps) => void;
513
+ onUpdate: (props: SlashCommandProps) => void;
514
+ onExit: () => void;
515
+ /** Return `true` to consume the key event and prevent default handling. */
516
+ onKeyDown: (event: KeyboardEvent) => boolean;
517
+ }
518
+ interface SlashCommandOptions {
519
+ /**
520
+ * The trigger character. @default '/'
521
+ */
522
+ char?: string;
523
+ /**
524
+ * Items override. When omitted, items from `editor.floatingMenuItems`
525
+ * (collected via `addFloatingMenuItems()`) are used. An array replaces
526
+ * defaults; a function transforms them.
527
+ */
528
+ items?: FloatingMenuItemsOverride;
529
+ /**
530
+ * Factory returning render callbacks for the popup. Default uses
531
+ * `createSlashSuggestionRenderer()`.
532
+ */
533
+ render?: () => SlashCommandRenderer;
534
+ /**
535
+ * Node types where slash should NOT activate (e.g. `codeBlock`).
536
+ * @default ['codeBlock']
537
+ */
538
+ invalidNodes?: string[];
539
+ }
540
+ interface CreateSlashCommandPluginOptions {
541
+ pluginKey: PluginKey<SlashCommandPluginState>;
542
+ editor: Editor;
543
+ char: string;
544
+ items?: FloatingMenuItemsOverride;
545
+ render: () => SlashCommandRenderer;
546
+ invalidNodes: string[];
547
+ }
548
+ interface SlashCommandPluginState {
549
+ active: boolean;
550
+ query: string;
551
+ range: {
552
+ from: number;
553
+ to: number;
554
+ } | null;
555
+ }
556
+ /**
557
+ * Filters and ranks FloatingMenuItems against a query. Priority:
558
+ * 1. Exact label prefix (case-insensitive)
559
+ * 2. Label substring match
560
+ * 3. Keyword match (preserving original keyword index for stable ranking)
561
+ * Returns items in rank order, stable within the same rank.
562
+ */
563
+ declare function filterSlashItems(items: FloatingMenuItem[], query: string): FloatingMenuItem[];
564
+ declare function createSlashCommandPlugin(options: CreateSlashCommandPluginOptions): Plugin<SlashCommandPluginState>;
565
+ declare const SlashCommand: Extension<SlashCommandOptions, unknown>;
566
+ /**
567
+ * Programmatically dismisses the slash suggestion.
568
+ */
569
+ declare function dismissSlashCommand(view: EditorView): void;
570
+
571
+ declare function createSlashSuggestionRenderer(): SlashCommandRenderer;
572
+
573
+ /**
574
+ * Pasting block-level content at an INLINE position normally goes through PM's
575
+ * content fitter, which strips the block wrapper and pastes only inline text.
576
+ * SmartPaste catches the relevant cases and routes each to the right strategy:
577
+ *
578
+ * 1. List slice into a list ancestor: items adapted (`convertListItemForParent`)
579
+ * and merged as siblings, preserving the surrounding list.
580
+ * 2. Trailing hardBreak (Shift+Enter scenario): trim the hardBreak and insert
581
+ * the slice as a sibling after the parent.
582
+ * 3. Truly empty parent paragraph (`parentSize === 0`): replace the parent.
583
+ * 4-6. Caret at start / end / middle: insert as sibling or split-and-insert.
584
+ * 7. Range selection: delete first, then run through 2-6.
585
+ *
586
+ * Skipped (PM default applies) when:
587
+ * - Cursor isn't inside a textblock.
588
+ * - Slice's top-level blocks are ALL plain paragraphs.
589
+ * - Slice is a SINGLE top-level block of the SAME TYPE as the destination
590
+ * (heading-into-heading, etc.) - PM's inline merge is the intended behavior.
591
+ *
592
+ * Note: do NOT bail on `openStart > 0` - PM's clipboard parser routinely sets
593
+ * `openStart=1` even for closed-looking input like `<h1>x</h1>`. Top-level
594
+ * children of the slice are what matter.
595
+ */
596
+
597
+ interface SmartPasteOptions {
598
+ /**
599
+ * Disable the plugin without removing the extension. Useful for tests
600
+ * or to fall back to PM's default paste handling temporarily.
601
+ * @default true
602
+ */
603
+ enabled?: boolean;
604
+ }
605
+ declare const SmartPaste: Extension<SmartPasteOptions, unknown>;
606
+
607
+ export { type BlockCandidate, BlockContextMenu, type BlockContextMenuOptions, BlockHandle, type BlockHandleOptions, type BlockHandlePluginState, type BlockMatcher, type CreateBlockContextMenuPluginOptions, type CreateBlockHandlePluginOptions, type CreateFloatingMenuPluginOptions, type CreateSlashCommandPluginOptions, DEFAULT_BLOCK_MATCHERS, DEFAULT_NESTED_NODES, FloatingMenu, type FloatingMenuKeymap, type FloatingMenuOptions, 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, createFloatingMenuPlugin, createSlashCommandPlugin, createSlashSuggestionRenderer, dismissSlashCommand, filterSlashItems, floatingMenuPluginKey, slashCommandPluginKey };