@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.
- package/LICENSE +21 -0
- package/README.md +44 -0
- package/dist/index.cjs +2622 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +607 -0
- package/dist/index.d.ts +607 -0
- package/dist/index.js +2602 -0
- package/dist/index.js.map +1 -0
- package/package.json +61 -0
package/dist/index.d.ts
ADDED
|
@@ -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 };
|