@agent-native/core 0.39.2 → 0.40.1

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.
Files changed (154) hide show
  1. package/README.md +1 -1
  2. package/dist/action.js +12 -0
  3. package/dist/action.js.map +1 -1
  4. package/dist/cli/create.d.ts.map +1 -1
  5. package/dist/cli/create.js +5 -1
  6. package/dist/cli/create.js.map +1 -1
  7. package/dist/cli/skills.d.ts +4 -3
  8. package/dist/cli/skills.d.ts.map +1 -1
  9. package/dist/cli/skills.js +756 -694
  10. package/dist/cli/skills.js.map +1 -1
  11. package/dist/client/blocks/AiEditableField.d.ts +8 -0
  12. package/dist/client/blocks/AiEditableField.d.ts.map +1 -0
  13. package/dist/client/blocks/AiEditableField.js +10 -0
  14. package/dist/client/blocks/AiEditableField.js.map +1 -0
  15. package/dist/client/blocks/BlockView.d.ts +3 -3
  16. package/dist/client/blocks/BlockView.d.ts.map +1 -1
  17. package/dist/client/blocks/BlockView.js +15 -3
  18. package/dist/client/blocks/BlockView.js.map +1 -1
  19. package/dist/client/blocks/SchemaBlockEditor.js +2 -2
  20. package/dist/client/blocks/SchemaBlockEditor.js.map +1 -1
  21. package/dist/client/blocks/index.d.ts +5 -2
  22. package/dist/client/blocks/index.d.ts.map +1 -1
  23. package/dist/client/blocks/index.js +6 -3
  24. package/dist/client/blocks/index.js.map +1 -1
  25. package/dist/client/blocks/library/ApiEndpointBlock.d.ts.map +1 -1
  26. package/dist/client/blocks/library/ApiEndpointBlock.js +20 -6
  27. package/dist/client/blocks/library/ApiEndpointBlock.js.map +1 -1
  28. package/dist/client/blocks/library/DiffBlock.d.ts +29 -0
  29. package/dist/client/blocks/library/DiffBlock.d.ts.map +1 -1
  30. package/dist/client/blocks/library/DiffBlock.js +190 -30
  31. package/dist/client/blocks/library/DiffBlock.js.map +1 -1
  32. package/dist/client/blocks/library/FileTreeBlock.d.ts.map +1 -1
  33. package/dist/client/blocks/library/FileTreeBlock.js +46 -7
  34. package/dist/client/blocks/library/FileTreeBlock.js.map +1 -1
  35. package/dist/client/blocks/library/HighlightedCode.d.ts +10 -0
  36. package/dist/client/blocks/library/HighlightedCode.d.ts.map +1 -0
  37. package/dist/client/blocks/library/HighlightedCode.js +92 -0
  38. package/dist/client/blocks/library/HighlightedCode.js.map +1 -0
  39. package/dist/client/blocks/library/JsonExplorerBlock.d.ts +9 -4
  40. package/dist/client/blocks/library/JsonExplorerBlock.d.ts.map +1 -1
  41. package/dist/client/blocks/library/JsonExplorerBlock.js +66 -30
  42. package/dist/client/blocks/library/JsonExplorerBlock.js.map +1 -1
  43. package/dist/client/blocks/library/MermaidBlock.d.ts.map +1 -1
  44. package/dist/client/blocks/library/MermaidBlock.js +73 -44
  45. package/dist/client/blocks/library/MermaidBlock.js.map +1 -1
  46. package/dist/client/blocks/library/OpenApiSpecBlock.d.ts.map +1 -1
  47. package/dist/client/blocks/library/OpenApiSpecBlock.js +3 -2
  48. package/dist/client/blocks/library/OpenApiSpecBlock.js.map +1 -1
  49. package/dist/client/blocks/library/checklist.d.ts.map +1 -1
  50. package/dist/client/blocks/library/checklist.js +1 -0
  51. package/dist/client/blocks/library/checklist.js.map +1 -1
  52. package/dist/client/blocks/library/code-tabs.d.ts.map +1 -1
  53. package/dist/client/blocks/library/code-tabs.js +183 -102
  54. package/dist/client/blocks/library/code-tabs.js.map +1 -1
  55. package/dist/client/blocks/library/columns.config.d.ts +60 -0
  56. package/dist/client/blocks/library/columns.config.d.ts.map +1 -0
  57. package/dist/client/blocks/library/columns.config.js +37 -0
  58. package/dist/client/blocks/library/columns.config.js.map +1 -0
  59. package/dist/client/blocks/library/columns.d.ts +25 -0
  60. package/dist/client/blocks/library/columns.d.ts.map +1 -0
  61. package/dist/client/blocks/library/columns.js +199 -0
  62. package/dist/client/blocks/library/columns.js.map +1 -0
  63. package/dist/client/blocks/library/dev-doc-ui.d.ts +2 -1
  64. package/dist/client/blocks/library/dev-doc-ui.d.ts.map +1 -1
  65. package/dist/client/blocks/library/dev-doc-ui.js +2 -1
  66. package/dist/client/blocks/library/dev-doc-ui.js.map +1 -1
  67. package/dist/client/blocks/library/html.d.ts +1 -1
  68. package/dist/client/blocks/library/html.d.ts.map +1 -1
  69. package/dist/client/blocks/library/html.js +34 -4
  70. package/dist/client/blocks/library/html.js.map +1 -1
  71. package/dist/client/blocks/library/json-explorer.config.d.ts +3 -1
  72. package/dist/client/blocks/library/json-explorer.config.d.ts.map +1 -1
  73. package/dist/client/blocks/library/json-explorer.config.js +30 -1
  74. package/dist/client/blocks/library/json-explorer.config.js.map +1 -1
  75. package/dist/client/blocks/library/server-specs.d.ts.map +1 -1
  76. package/dist/client/blocks/library/server-specs.js +13 -3
  77. package/dist/client/blocks/library/server-specs.js.map +1 -1
  78. package/dist/client/blocks/library/specs.d.ts +4 -4
  79. package/dist/client/blocks/library/specs.d.ts.map +1 -1
  80. package/dist/client/blocks/library/specs.js +21 -16
  81. package/dist/client/blocks/library/specs.js.map +1 -1
  82. package/dist/client/blocks/library/table.config.d.ts +3 -0
  83. package/dist/client/blocks/library/table.config.d.ts.map +1 -1
  84. package/dist/client/blocks/library/table.config.js +13 -1
  85. package/dist/client/blocks/library/table.config.js.map +1 -1
  86. package/dist/client/blocks/library/table.d.ts.map +1 -1
  87. package/dist/client/blocks/library/table.js +90 -9
  88. package/dist/client/blocks/library/table.js.map +1 -1
  89. package/dist/client/blocks/library/tabs.config.d.ts +16 -8
  90. package/dist/client/blocks/library/tabs.config.d.ts.map +1 -1
  91. package/dist/client/blocks/library/tabs.config.js +10 -4
  92. package/dist/client/blocks/library/tabs.config.js.map +1 -1
  93. package/dist/client/blocks/library/tabs.d.ts.map +1 -1
  94. package/dist/client/blocks/library/tabs.js +146 -21
  95. package/dist/client/blocks/library/tabs.js.map +1 -1
  96. package/dist/client/blocks/server.d.ts +2 -1
  97. package/dist/client/blocks/server.d.ts.map +1 -1
  98. package/dist/client/blocks/server.js +1 -0
  99. package/dist/client/blocks/server.js.map +1 -1
  100. package/dist/client/blocks/types.d.ts +99 -9
  101. package/dist/client/blocks/types.d.ts.map +1 -1
  102. package/dist/client/blocks/types.js.map +1 -1
  103. package/dist/client/index.d.ts +1 -1
  104. package/dist/client/index.d.ts.map +1 -1
  105. package/dist/client/index.js +2 -2
  106. package/dist/client/index.js.map +1 -1
  107. package/dist/client/rich-markdown-editor/BubbleToolbar.d.ts.map +1 -1
  108. package/dist/client/rich-markdown-editor/BubbleToolbar.js +13 -3
  109. package/dist/client/rich-markdown-editor/BubbleToolbar.js.map +1 -1
  110. package/dist/client/rich-markdown-editor/DragHandle.d.ts +49 -4
  111. package/dist/client/rich-markdown-editor/DragHandle.d.ts.map +1 -1
  112. package/dist/client/rich-markdown-editor/DragHandle.js +656 -88
  113. package/dist/client/rich-markdown-editor/DragHandle.js.map +1 -1
  114. package/dist/client/rich-markdown-editor/RegistryBlockNode.d.ts +10 -1
  115. package/dist/client/rich-markdown-editor/RegistryBlockNode.d.ts.map +1 -1
  116. package/dist/client/rich-markdown-editor/RegistryBlockNode.js +180 -15
  117. package/dist/client/rich-markdown-editor/RegistryBlockNode.js.map +1 -1
  118. package/dist/client/rich-markdown-editor/SharedRichEditor.d.ts +2 -1
  119. package/dist/client/rich-markdown-editor/SharedRichEditor.d.ts.map +1 -1
  120. package/dist/client/rich-markdown-editor/SharedRichEditor.js +3 -1
  121. package/dist/client/rich-markdown-editor/SharedRichEditor.js.map +1 -1
  122. package/dist/client/rich-markdown-editor/SlashCommandMenu.d.ts +5 -0
  123. package/dist/client/rich-markdown-editor/SlashCommandMenu.d.ts.map +1 -1
  124. package/dist/client/rich-markdown-editor/SlashCommandMenu.js +33 -5
  125. package/dist/client/rich-markdown-editor/SlashCommandMenu.js.map +1 -1
  126. package/dist/client/rich-markdown-editor/index.d.ts +3 -3
  127. package/dist/client/rich-markdown-editor/index.d.ts.map +1 -1
  128. package/dist/client/rich-markdown-editor/index.js +2 -2
  129. package/dist/client/rich-markdown-editor/index.js.map +1 -1
  130. package/dist/client/rich-markdown-editor/registrySlashCommands.d.ts +14 -0
  131. package/dist/client/rich-markdown-editor/registrySlashCommands.d.ts.map +1 -1
  132. package/dist/client/rich-markdown-editor/registrySlashCommands.js +38 -0
  133. package/dist/client/rich-markdown-editor/registrySlashCommands.js.map +1 -1
  134. package/dist/client/rich-markdown-editor/useCollabReconcile.d.ts +1 -0
  135. package/dist/client/rich-markdown-editor/useCollabReconcile.d.ts.map +1 -1
  136. package/dist/client/rich-markdown-editor/useCollabReconcile.js +4 -0
  137. package/dist/client/rich-markdown-editor/useCollabReconcile.js.map +1 -1
  138. package/dist/db/client.d.ts.map +1 -1
  139. package/dist/db/client.js +17 -1
  140. package/dist/db/client.js.map +1 -1
  141. package/dist/deploy/build.js +68 -0
  142. package/dist/deploy/build.js.map +1 -1
  143. package/dist/sharing/access.d.ts +4 -2
  144. package/dist/sharing/access.d.ts.map +1 -1
  145. package/dist/sharing/access.js +8 -3
  146. package/dist/sharing/access.js.map +1 -1
  147. package/dist/sharing/actions/set-resource-visibility.d.ts.map +1 -1
  148. package/dist/sharing/actions/set-resource-visibility.js +2 -3
  149. package/dist/sharing/actions/set-resource-visibility.js.map +1 -1
  150. package/dist/sharing/registry.d.ts +13 -0
  151. package/dist/sharing/registry.d.ts.map +1 -1
  152. package/dist/sharing/registry.js.map +1 -1
  153. package/dist/styles/rich-markdown-editor.css +15 -0
  154. package/package.json +16 -1
@@ -1,5 +1,5 @@
1
1
  import { Extension } from "@tiptap/core";
2
- import { Plugin, PluginKey, NodeSelection } from "@tiptap/pm/state";
2
+ import { Plugin, PluginKey, NodeSelection, TextSelection, } from "@tiptap/pm/state";
3
3
  /**
4
4
  * Default editor-wrapper CSS selector the drag handle scopes itself to.
5
5
  *
@@ -12,6 +12,206 @@ import { Plugin, PluginKey, NodeSelection } from "@tiptap/pm/state";
12
12
  export const DEFAULT_DRAG_HANDLE_WRAPPER_SELECTOR = ".visual-editor-wrapper";
13
13
  const dragHandleKey = new PluginKey("dragHandle");
14
14
  const HOVER_SIDE_OUTSET_REM = 8;
15
+ const SIDE_DROP_ZONE_RATIO = 0.28;
16
+ const SIDE_DROP_ZONE_MIN_PX = 48;
17
+ const SIDE_DROP_ZONE_MAX_PX = 140;
18
+ const DRAG_HANDLE_MENU_STYLE_ID = "an-rich-md-drag-menu-styles";
19
+ const DRAG_HANDLE_MENU_WIDTH = 220;
20
+ const DRAG_HANDLE_MENU_GAP = 6;
21
+ const DRAG_HANDLE_MENU_VIEWPORT_PADDING = 8;
22
+ const dragHandleRegistrations = new Set();
23
+ let dragHandleGlobalHoverListeners = 0;
24
+ let activeDragRegistration = null;
25
+ const clamp = (value, min, max) => Math.min(Math.max(value, min), max);
26
+ const editorArea = (registration) => {
27
+ const rect = registration.view.dom.getBoundingClientRect();
28
+ return rect.width * rect.height;
29
+ };
30
+ const updateRegisteredHover = (clientX, clientY) => {
31
+ if (activeDragRegistration) {
32
+ for (const registration of dragHandleRegistrations) {
33
+ registration.hideHover?.();
34
+ }
35
+ return;
36
+ }
37
+ const candidates = [];
38
+ for (const registration of dragHandleRegistrations) {
39
+ if (!registration.view.dom.isConnected || !registration.canHover?.()) {
40
+ registration.hideHover?.();
41
+ continue;
42
+ }
43
+ const block = registration.findHoverBlock?.(clientX, clientY);
44
+ if (block) {
45
+ candidates.push({ registration, block });
46
+ }
47
+ else {
48
+ registration.hideHover?.();
49
+ }
50
+ }
51
+ candidates.sort((a, b) => editorArea(a.registration) - editorArea(b.registration));
52
+ const active = candidates[0] ?? null;
53
+ for (const registration of dragHandleRegistrations) {
54
+ if (registration !== active?.registration)
55
+ registration.hideHover?.();
56
+ }
57
+ active?.registration.showHoverBlock?.(active.block);
58
+ };
59
+ const handleGlobalHoverMove = (event) => {
60
+ updateRegisteredHover(event.clientX, event.clientY);
61
+ };
62
+ const retainGlobalHoverListener = () => {
63
+ dragHandleGlobalHoverListeners += 1;
64
+ if (dragHandleGlobalHoverListeners === 1) {
65
+ document.addEventListener("mousemove", handleGlobalHoverMove);
66
+ }
67
+ };
68
+ const releaseGlobalHoverListener = () => {
69
+ dragHandleGlobalHoverListeners = Math.max(0, dragHandleGlobalHoverListeners - 1);
70
+ if (dragHandleGlobalHoverListeners === 0) {
71
+ document.removeEventListener("mousemove", handleGlobalHoverMove);
72
+ }
73
+ };
74
+ const ensureDragHandleMenuStyles = () => {
75
+ if (document.getElementById(DRAG_HANDLE_MENU_STYLE_ID))
76
+ return;
77
+ const style = document.createElement("style");
78
+ style.id = DRAG_HANDLE_MENU_STYLE_ID;
79
+ style.textContent = `
80
+ .an-rich-md-drag-menu {
81
+ position: fixed;
82
+ z-index: 9999;
83
+ width: ${DRAG_HANDLE_MENU_WIDTH}px;
84
+ padding: 4px;
85
+ border: 1px solid hsl(var(--border, 214.3 31.8% 91.4%));
86
+ border-radius: 7px;
87
+ background: hsl(var(--popover, 0 0% 100%));
88
+ color: hsl(var(--popover-foreground, var(--foreground, 222.2 84% 4.9%)));
89
+ box-shadow:
90
+ 0 12px 32px rgb(15 23 42 / 0.16),
91
+ 0 2px 8px rgb(15 23 42 / 0.08);
92
+ font-family: inherit;
93
+ font-size: 13px;
94
+ line-height: 1.35;
95
+ }
96
+
97
+ .an-rich-md-drag-menu__item {
98
+ display: flex;
99
+ width: 100%;
100
+ align-items: center;
101
+ gap: 9px;
102
+ border: 0;
103
+ border-radius: 5px;
104
+ background: transparent;
105
+ color: inherit;
106
+ cursor: pointer;
107
+ font: inherit;
108
+ letter-spacing: 0;
109
+ padding: 7px 8px;
110
+ text-align: left;
111
+ }
112
+
113
+ .an-rich-md-drag-menu__item:hover,
114
+ .an-rich-md-drag-menu__item:focus-visible {
115
+ background: hsl(var(--accent, 210 40% 96.1%));
116
+ color: hsl(var(--accent-foreground, var(--foreground, 222.2 84% 4.9%)));
117
+ outline: none;
118
+ }
119
+
120
+ .an-rich-md-drag-menu__item[data-danger="true"] {
121
+ color: hsl(var(--destructive, 0 84.2% 60.2%));
122
+ }
123
+
124
+ .an-rich-md-drag-menu__item[data-danger="true"]:hover,
125
+ .an-rich-md-drag-menu__item[data-danger="true"]:focus-visible {
126
+ background: hsl(var(--destructive, 0 84.2% 60.2%) / 0.1);
127
+ }
128
+
129
+ .an-rich-md-drag-menu__icon {
130
+ position: relative;
131
+ flex: 0 0 auto;
132
+ width: 18px;
133
+ height: 18px;
134
+ color: hsl(var(--muted-foreground, 215.4 16.3% 46.9%));
135
+ }
136
+
137
+ .an-rich-md-drag-menu__item[data-danger="true"] .an-rich-md-drag-menu__icon {
138
+ color: currentColor;
139
+ }
140
+
141
+ .an-rich-md-drag-menu__icon::before,
142
+ .an-rich-md-drag-menu__icon::after {
143
+ content: "";
144
+ position: absolute;
145
+ box-sizing: border-box;
146
+ }
147
+
148
+ .an-rich-md-drag-menu__icon--duplicate::before,
149
+ .an-rich-md-drag-menu__icon--duplicate::after {
150
+ width: 11px;
151
+ height: 11px;
152
+ border: 1.5px solid currentColor;
153
+ border-radius: 2px;
154
+ }
155
+
156
+ .an-rich-md-drag-menu__icon--duplicate::before {
157
+ left: 6px;
158
+ top: 2px;
159
+ opacity: 0.55;
160
+ }
161
+
162
+ .an-rich-md-drag-menu__icon--duplicate::after {
163
+ left: 2px;
164
+ top: 6px;
165
+ background: hsl(var(--popover, 0 0% 100%));
166
+ }
167
+
168
+ .an-rich-md-drag-menu__icon--insert::before {
169
+ left: 3px;
170
+ top: 8px;
171
+ width: 12px;
172
+ height: 1.5px;
173
+ border-radius: 999px;
174
+ background: currentColor;
175
+ }
176
+
177
+ .an-rich-md-drag-menu__icon--insert::after {
178
+ left: 8px;
179
+ top: 3px;
180
+ width: 1.5px;
181
+ height: 12px;
182
+ border-radius: 999px;
183
+ background: currentColor;
184
+ }
185
+
186
+ .an-rich-md-drag-menu__icon--delete::before {
187
+ left: 4px;
188
+ top: 7px;
189
+ width: 10px;
190
+ height: 11px;
191
+ border: 1.5px solid currentColor;
192
+ border-top: 0;
193
+ border-radius: 0 0 2px 2px;
194
+ }
195
+
196
+ .an-rich-md-drag-menu__icon--delete::after {
197
+ left: 3px;
198
+ top: 4px;
199
+ width: 12px;
200
+ height: 1.5px;
201
+ border-radius: 999px;
202
+ background: currentColor;
203
+ box-shadow: 3px -2.5px 0 -0.4px currentColor;
204
+ }
205
+
206
+ .an-rich-md-drag-menu__label {
207
+ min-width: 0;
208
+ overflow: hidden;
209
+ text-overflow: ellipsis;
210
+ white-space: nowrap;
211
+ }
212
+ `;
213
+ document.head.appendChild(style);
214
+ };
15
215
  /**
16
216
  * App-agnostic Tiptap extension providing a Notion-style left-margin drag grip
17
217
  * (the `::` handle), block selection, and drag-to-reorder over top-level block
@@ -21,10 +221,10 @@ const HOVER_SIDE_OUTSET_REM = 8;
21
221
  * - On hover over any top-level block, a `.drag-handle` grip appears in the left
22
222
  * margin (forgiving hit zone extends {@link HOVER_SIDE_OUTSET_REM}rem to the
23
223
  * sides and into the gap above/between blocks).
24
- * - `mousedown` on the grip selects the block as a `NodeSelection`; dragging past
25
- * a small threshold starts a reorder, showing a floating clone preview
26
- * (`.notion-drag-preview`) and a `.notion-drop-indicator` line. `Escape`
27
- * cancels.
224
+ * - Single-clicking the grip selects the block and opens a block action menu.
225
+ * Dragging past a small threshold starts a reorder, showing a floating clone
226
+ * preview (`.notion-drag-preview`) and a `.notion-drop-indicator` line.
227
+ * `Escape` cancels.
28
228
  * - While dragging, the source block carries `.notion-block--dragging` and the
29
229
  * document element carries `.notion-editor-is-dragging` so apps can style the
30
230
  * in-flight state. Apps own all of these CSS class names.
@@ -44,15 +244,23 @@ export const DragHandle = Extension.create({
44
244
  addOptions() {
45
245
  return {
46
246
  wrapperSelector: DEFAULT_DRAG_HANDLE_WRAPPER_SELECTOR,
247
+ getDragTransferData: undefined,
248
+ receiveDragTransferData: undefined,
47
249
  };
48
250
  },
49
251
  addProseMirrorPlugins() {
50
252
  const editor = this.editor;
51
253
  const wrapperSelector = this.options.wrapperSelector;
254
+ const getDragTransferData = this.options.getDragTransferData;
255
+ const receiveDragTransferData = this.options.receiveDragTransferData;
256
+ const handleDrop = this.options.handleDrop;
52
257
  let handle = null;
258
+ let menu = null;
259
+ let menuContext = null;
53
260
  let currentBlock = null;
54
261
  let dragStartPos = null;
55
262
  let dragSession = null;
263
+ let currentRegistration = null;
56
264
  const getHoverSideOutset = () => {
57
265
  const rootFontSize = Number.parseFloat(getComputedStyle(document.documentElement).fontSize);
58
266
  return ((Number.isFinite(rootFontSize) ? rootFontSize : 16) *
@@ -72,6 +280,13 @@ export const DragHandle = Extension.create({
72
280
  });
73
281
  return blocks;
74
282
  };
283
+ const registrationForView = (editorView) => {
284
+ for (const registration of dragHandleRegistrations) {
285
+ if (registration.view === editorView)
286
+ return registration;
287
+ }
288
+ return null;
289
+ };
75
290
  const findForgivingBlock = (editorView, clientX, clientY) => {
76
291
  const blocks = getTopLevelBlocks(editorView);
77
292
  if (blocks.length === 0)
@@ -122,11 +337,9 @@ export const DragHandle = Extension.create({
122
337
  handle.style.top = `${block.rect.top - wrapperRect.top + 2}px`;
123
338
  handle.style.left = "-24px";
124
339
  };
125
- const selectCurrentBlock = (editorView) => {
126
- if (dragStartPos === null)
127
- return null;
340
+ const selectBlockAt = (editorView, pos) => {
128
341
  try {
129
- const sel = NodeSelection.create(editorView.state.doc, dragStartPos);
342
+ const sel = NodeSelection.create(editorView.state.doc, pos);
130
343
  editorView.dispatch(editorView.state.tr.setSelection(sel));
131
344
  editorView.focus();
132
345
  return sel;
@@ -162,8 +375,8 @@ export const DragHandle = Extension.create({
162
375
  document.body.appendChild(preview);
163
376
  return preview;
164
377
  };
165
- const createDropLine = (view) => {
166
- const wrapper = view.dom.closest(wrapperSelector);
378
+ const createDropLine = (registration) => {
379
+ const wrapper = registration.view.dom.closest(registration.wrapperSelector);
167
380
  if (!wrapper)
168
381
  return null;
169
382
  const line = document.createElement("div");
@@ -171,23 +384,244 @@ export const DragHandle = Extension.create({
171
384
  wrapper.appendChild(line);
172
385
  return line;
173
386
  };
174
- const findDropTarget = (view, clientX, clientY) => {
387
+ const forceHideHandle = () => {
388
+ if (handle) {
389
+ handle.style.display = "none";
390
+ handle.setAttribute("aria-expanded", "false");
391
+ }
392
+ currentBlock = null;
393
+ dragStartPos = null;
394
+ };
395
+ const closeMenu = ({ hideGrip = false } = {}) => {
396
+ menu?.remove();
397
+ menu = null;
398
+ menuContext = null;
399
+ handle?.setAttribute("aria-expanded", "false");
400
+ document.removeEventListener("mousedown", handleMenuDocumentMouseDown, {
401
+ capture: true,
402
+ });
403
+ document.removeEventListener("keydown", handleMenuKeyDown, {
404
+ capture: true,
405
+ });
406
+ window.removeEventListener("resize", handleMenuViewportChange);
407
+ window.removeEventListener("scroll", handleMenuViewportChange, {
408
+ capture: true,
409
+ });
410
+ if (hideGrip)
411
+ forceHideHandle();
412
+ };
413
+ const resolveMenuContext = (context) => {
414
+ const latestBlock = getTopLevelBlocks(context.view).find((block) => block.node === context.sourceBlock);
415
+ const sourcePos = latestBlock?.pmPos ?? context.sourcePos;
416
+ const sourceNode = context.view.state.doc.nodeAt(sourcePos);
417
+ if (!sourceNode)
418
+ return null;
419
+ return {
420
+ ...context,
421
+ sourcePos,
422
+ sourceNode,
423
+ sourceNodeSize: sourceNode.nodeSize,
424
+ };
425
+ };
426
+ const focusSelectionNear = (view, tr, pos, bias) => {
427
+ tr.setSelection(TextSelection.near(tr.doc.resolve(clamp(pos, 0, tr.doc.content.size)), bias));
428
+ view.dispatch(tr.scrollIntoView());
429
+ view.focus();
430
+ };
431
+ const duplicateBlock = (context) => {
432
+ const resolved = resolveMenuContext(context);
433
+ if (!resolved)
434
+ return;
435
+ const insertPos = resolved.sourcePos + resolved.sourceNodeSize;
436
+ const tr = resolved.view.state.tr.insert(insertPos, resolved.sourceNode);
437
+ try {
438
+ tr.setSelection(NodeSelection.create(tr.doc, insertPos));
439
+ resolved.view.dispatch(tr.scrollIntoView());
440
+ resolved.view.focus();
441
+ }
442
+ catch {
443
+ focusSelectionNear(resolved.view, tr, insertPos, 1);
444
+ }
445
+ };
446
+ const deleteBlock = (context) => {
447
+ const resolved = resolveMenuContext(context);
448
+ if (!resolved)
449
+ return;
450
+ const { view, sourcePos, sourceNodeSize } = resolved;
451
+ const paragraph = view.state.schema.nodes.paragraph;
452
+ const sourceEnd = sourcePos + sourceNodeSize;
453
+ if (view.state.doc.childCount <= 1 && paragraph) {
454
+ const replacement = paragraph.createAndFill() ?? paragraph.create();
455
+ const tr = view.state.tr.replaceWith(sourcePos, sourceEnd, replacement);
456
+ focusSelectionNear(view, tr, sourcePos + 1, 1);
457
+ return;
458
+ }
459
+ const tr = view.state.tr.delete(sourcePos, sourceEnd);
460
+ const selectionBias = sourcePos >= tr.doc.content.size ? -1 : 1;
461
+ focusSelectionNear(view, tr, sourcePos, selectionBias);
462
+ };
463
+ const insertParagraphBelow = (context) => {
464
+ const resolved = resolveMenuContext(context);
465
+ const paragraph = resolved?.view.state.schema.nodes.paragraph;
466
+ if (!resolved || !paragraph)
467
+ return;
468
+ const insertPos = resolved.sourcePos + resolved.sourceNodeSize;
469
+ const paragraphNode = paragraph.createAndFill() ?? paragraph.create();
470
+ const tr = resolved.view.state.tr.insert(insertPos, paragraphNode);
471
+ tr.setSelection(TextSelection.create(tr.doc, insertPos + 1));
472
+ resolved.view.dispatch(tr.scrollIntoView());
473
+ resolved.view.focus();
474
+ };
475
+ const positionMenu = (anchorRect) => {
476
+ if (!menu)
477
+ return;
478
+ const viewportWidth = window.visualViewport?.width ?? window.innerWidth;
479
+ const viewportHeight = window.visualViewport?.height ?? window.innerHeight;
480
+ const menuHeight = menu.offsetHeight || 118;
481
+ const preferredLeft = anchorRect.right + DRAG_HANDLE_MENU_GAP;
482
+ const alternateLeft = anchorRect.left - DRAG_HANDLE_MENU_WIDTH - DRAG_HANDLE_MENU_GAP;
483
+ const left = preferredLeft +
484
+ DRAG_HANDLE_MENU_WIDTH +
485
+ DRAG_HANDLE_MENU_VIEWPORT_PADDING <=
486
+ viewportWidth
487
+ ? preferredLeft
488
+ : alternateLeft;
489
+ menu.style.left = `${clamp(left, DRAG_HANDLE_MENU_VIEWPORT_PADDING, viewportWidth -
490
+ DRAG_HANDLE_MENU_WIDTH -
491
+ DRAG_HANDLE_MENU_VIEWPORT_PADDING)}px`;
492
+ menu.style.top = `${clamp(anchorRect.top - 4, DRAG_HANDLE_MENU_VIEWPORT_PADDING, viewportHeight - menuHeight - DRAG_HANDLE_MENU_VIEWPORT_PADDING)}px`;
493
+ };
494
+ const createMenuItem = (label, iconModifier, action, options = {}) => {
495
+ const button = document.createElement("button");
496
+ button.type = "button";
497
+ button.className = "an-rich-md-drag-menu__item";
498
+ button.setAttribute("role", "menuitem");
499
+ button.setAttribute("data-plan-interactive", "true");
500
+ if (options.danger)
501
+ button.setAttribute("data-danger", "true");
502
+ const icon = document.createElement("span");
503
+ icon.className = `an-rich-md-drag-menu__icon an-rich-md-drag-menu__icon--${iconModifier}`;
504
+ icon.setAttribute("aria-hidden", "true");
505
+ const labelElement = document.createElement("span");
506
+ labelElement.className = "an-rich-md-drag-menu__label";
507
+ labelElement.textContent = label;
508
+ button.append(icon, labelElement);
509
+ button.addEventListener("mousedown", (event) => {
510
+ event.preventDefault();
511
+ });
512
+ button.addEventListener("click", (event) => {
513
+ event.preventDefault();
514
+ event.stopPropagation();
515
+ const context = menuContext;
516
+ if (!context)
517
+ return;
518
+ closeMenu({ hideGrip: true });
519
+ action(context);
520
+ });
521
+ return button;
522
+ };
523
+ const openMenu = (context, anchorRect) => {
524
+ const resolved = resolveMenuContext(context);
525
+ if (!resolved)
526
+ return;
527
+ closeMenu();
528
+ selectBlockAt(resolved.view, resolved.sourcePos);
529
+ ensureDragHandleMenuStyles();
530
+ const el = document.createElement("div");
531
+ el.className = "an-rich-md-drag-menu";
532
+ el.setAttribute("role", "menu");
533
+ el.setAttribute("aria-label", "Block actions");
534
+ el.setAttribute("data-plan-interactive", "true");
535
+ el.append(createMenuItem("Duplicate", "duplicate", duplicateBlock), createMenuItem("Delete", "delete", deleteBlock, { danger: true }), createMenuItem("Insert block below", "insert", insertParagraphBelow));
536
+ menu = el;
537
+ menuContext = {
538
+ view: resolved.view,
539
+ sourceBlock: resolved.sourceBlock,
540
+ sourcePos: resolved.sourcePos,
541
+ sourceNodeSize: resolved.sourceNodeSize,
542
+ };
543
+ document.body.appendChild(el);
544
+ positionMenu(anchorRect);
545
+ handle?.setAttribute("aria-expanded", "true");
546
+ document.addEventListener("mousedown", handleMenuDocumentMouseDown, {
547
+ capture: true,
548
+ });
549
+ document.addEventListener("keydown", handleMenuKeyDown, {
550
+ capture: true,
551
+ });
552
+ window.addEventListener("resize", handleMenuViewportChange);
553
+ window.addEventListener("scroll", handleMenuViewportChange, {
554
+ capture: true,
555
+ });
556
+ el.querySelector("button")?.focus({
557
+ preventScroll: true,
558
+ });
559
+ };
560
+ const findDropTarget = (registration, clientX, clientY) => {
561
+ const view = registration.view;
175
562
  const block = findForgivingBlock(view, clientX, clientY);
176
563
  if (!block)
177
564
  return null;
178
565
  const node = view.state.doc.nodeAt(block.pmPos);
179
566
  if (!node)
180
567
  return null;
181
- const dropBefore = clientY < block.rect.top ||
182
- (clientY <= block.rect.bottom &&
183
- clientY < block.rect.top + block.rect.height / 2);
568
+ let placement;
569
+ const withinBlockY = clientY >= block.rect.top && clientY <= block.rect.bottom;
570
+ const withinSideDropBand = clientY >= block.rect.top + block.rect.height * 0.2 &&
571
+ clientY <= block.rect.bottom - block.rect.height * 0.2;
572
+ const sideZoneWidth = clamp(block.rect.width * SIDE_DROP_ZONE_RATIO, SIDE_DROP_ZONE_MIN_PX, SIDE_DROP_ZONE_MAX_PX);
573
+ if (registration.handleDrop &&
574
+ withinBlockY &&
575
+ withinSideDropBand &&
576
+ clientX <= block.rect.left + sideZoneWidth) {
577
+ placement = "left";
578
+ }
579
+ else if (registration.handleDrop &&
580
+ withinBlockY &&
581
+ withinSideDropBand &&
582
+ clientX >= block.rect.right - sideZoneWidth) {
583
+ placement = "right";
584
+ }
585
+ else {
586
+ placement =
587
+ clientY < block.rect.top ||
588
+ (clientY <= block.rect.bottom &&
589
+ clientY < block.rect.top + block.rect.height / 2)
590
+ ? "before"
591
+ : "after";
592
+ }
593
+ const before = placement === "before" || placement === "left";
184
594
  return {
595
+ registration,
596
+ view,
185
597
  block: block.node,
186
- before: dropBefore,
187
- pos: dropBefore ? block.pmPos : block.pmPos + node.nodeSize,
598
+ placement,
599
+ pos: before ? block.pmPos : block.pmPos + node.nodeSize,
600
+ targetPos: block.pmPos,
601
+ targetNodeSize: node.nodeSize,
188
602
  rect: block.rect,
189
603
  };
190
604
  };
605
+ const findAnyDropTarget = (session, clientX, clientY) => {
606
+ const candidates = [];
607
+ for (const registration of dragHandleRegistrations) {
608
+ if (!registration.view.dom.isConnected)
609
+ continue;
610
+ if (registration.view !== session.view &&
611
+ session.sourceBlock.contains(registration.view.dom)) {
612
+ continue;
613
+ }
614
+ const target = findDropTarget(registration, clientX, clientY);
615
+ if (target)
616
+ candidates.push(target);
617
+ }
618
+ candidates.sort((a, b) => {
619
+ const aRect = a.view.dom.getBoundingClientRect();
620
+ const bRect = b.view.dom.getBoundingClientRect();
621
+ return aRect.width * aRect.height - bRect.width * bRect.height;
622
+ });
623
+ return candidates[0] ?? null;
624
+ };
191
625
  const positionDragPreview = (session, clientX, clientY) => {
192
626
  if (!session.preview)
193
627
  return;
@@ -196,34 +630,52 @@ export const DragHandle = Extension.create({
196
630
  const updateDropLine = (session, target) => {
197
631
  const sourceEnd = session.sourcePos + session.sourceNodeSize;
198
632
  if (!target ||
199
- target.pos === session.sourcePos ||
200
- target.pos === sourceEnd ||
201
- (target.pos > session.sourcePos && target.pos < sourceEnd)) {
202
- session.dropPos = null;
633
+ (target.view === session.view &&
634
+ (target.pos === session.sourcePos ||
635
+ target.pos === sourceEnd ||
636
+ (target.pos > session.sourcePos && target.pos < sourceEnd)))) {
637
+ session.dropTarget = null;
203
638
  session.dropLine?.remove();
204
639
  session.dropLine = null;
205
640
  return;
206
641
  }
207
- if (!session.dropLine)
208
- session.dropLine = createDropLine(session.view);
209
- if (!session.dropLine)
210
- return;
211
- const wrapper = session.view.dom.closest(wrapperSelector);
642
+ const wrapper = target.view.dom.closest(target.registration.wrapperSelector);
212
643
  if (!wrapper)
213
644
  return;
645
+ if (!session.dropLine || session.dropLine.parentElement !== wrapper) {
646
+ session.dropLine?.remove();
647
+ session.dropLine = createDropLine(target.registration);
648
+ }
649
+ if (!session.dropLine)
650
+ return;
214
651
  const wrapperRect = wrapper.getBoundingClientRect();
215
- const editorRect = session.view.dom.getBoundingClientRect();
216
- const top = target.before ? target.rect.top : target.rect.bottom;
217
- session.dropPos = target.pos;
652
+ const editorRect = target.view.dom.getBoundingClientRect();
653
+ session.dropTarget = target;
654
+ if (target.placement === "left" || target.placement === "right") {
655
+ const left = target.placement === "left" ? target.rect.left : target.rect.right;
656
+ session.dropLine.style.left = `${left - wrapperRect.left}px`;
657
+ session.dropLine.style.top = `${target.rect.top - wrapperRect.top}px`;
658
+ session.dropLine.style.width = "3px";
659
+ session.dropLine.style.height = `${target.rect.height}px`;
660
+ return;
661
+ }
662
+ const top = target.placement === "before" ? target.rect.top : target.rect.bottom;
218
663
  session.dropLine.style.left = `${editorRect.left - wrapperRect.left}px`;
219
664
  session.dropLine.style.top = `${top - wrapperRect.top}px`;
220
665
  session.dropLine.style.width = `${editorRect.width}px`;
666
+ session.dropLine.style.height = "3px";
221
667
  };
222
668
  const createHandle = () => {
223
669
  const el = document.createElement("div");
224
670
  el.className = "drag-handle";
225
671
  el.contentEditable = "false";
226
672
  el.draggable = false;
673
+ el.tabIndex = 0;
674
+ el.setAttribute("role", "button");
675
+ el.setAttribute("aria-label", "Open block menu or drag to reorder");
676
+ el.setAttribute("aria-haspopup", "menu");
677
+ el.setAttribute("aria-expanded", "false");
678
+ el.title = "Open block menu or drag to reorder";
227
679
  el.innerHTML = `<svg width="14" height="14" viewBox="0 0 16 16" fill="currentColor">
228
680
  <circle cx="5.5" cy="3" r="1.5"/><circle cx="10.5" cy="3" r="1.5"/>
229
681
  <circle cx="5.5" cy="8" r="1.5"/><circle cx="10.5" cy="8" r="1.5"/>
@@ -232,61 +684,149 @@ export const DragHandle = Extension.create({
232
684
  return el;
233
685
  };
234
686
  const hideHandle = () => {
235
- if (handle)
236
- handle.style.display = "none";
237
- currentBlock = null;
687
+ if (menu)
688
+ return;
689
+ forceHideHandle();
238
690
  };
239
691
  const removeDragListeners = () => {
240
692
  document.removeEventListener("mousemove", handleDocumentMouseMove);
241
693
  document.removeEventListener("mouseup", handleDocumentMouseUp);
242
694
  document.removeEventListener("keydown", handleDocumentKeyDown);
243
695
  };
244
- const createDocumentHoverMove = (editorView) => {
245
- return (event) => {
246
- if (!handle || dragSession)
247
- return;
248
- if (!editor.isEditable) {
249
- hideHandle();
250
- return;
251
- }
252
- const block = findForgivingBlock(editorView, event.clientX, event.clientY);
253
- if (!block) {
254
- hideHandle();
255
- return;
256
- }
257
- showHandleForBlock(editorView, block);
258
- };
259
- };
260
- const finishDragSession = (commit) => {
696
+ function handleMenuDocumentMouseDown(event) {
697
+ const target = event.target;
698
+ if (!(target instanceof Node))
699
+ return;
700
+ if (menu?.contains(target) || handle?.contains(target))
701
+ return;
702
+ closeMenu({ hideGrip: true });
703
+ }
704
+ function handleMenuKeyDown(event) {
705
+ if (!menu)
706
+ return;
707
+ if (event.key === "Escape") {
708
+ event.preventDefault();
709
+ closeMenu({ hideGrip: true });
710
+ return;
711
+ }
712
+ if (event.key !== "ArrowDown" &&
713
+ event.key !== "ArrowUp" &&
714
+ event.key !== "Home" &&
715
+ event.key !== "End") {
716
+ return;
717
+ }
718
+ const buttons = Array.from(menu.querySelectorAll("button"));
719
+ if (buttons.length === 0)
720
+ return;
721
+ event.preventDefault();
722
+ const activeIndex = buttons.indexOf(document.activeElement);
723
+ let nextIndex = activeIndex < 0 ? 0 : activeIndex;
724
+ if (event.key === "ArrowDown") {
725
+ nextIndex = (nextIndex + 1) % buttons.length;
726
+ }
727
+ else if (event.key === "ArrowUp") {
728
+ nextIndex = (nextIndex - 1 + buttons.length) % buttons.length;
729
+ }
730
+ else if (event.key === "Home") {
731
+ nextIndex = 0;
732
+ }
733
+ else if (event.key === "End") {
734
+ nextIndex = buttons.length - 1;
735
+ }
736
+ buttons[nextIndex]?.focus({ preventScroll: true });
737
+ }
738
+ function handleMenuViewportChange() {
739
+ closeMenu({ hideGrip: true });
740
+ }
741
+ const finishDragSession = (commit, event) => {
261
742
  const session = dragSession;
262
743
  if (!session)
263
744
  return;
264
745
  removeDragListeners();
265
- if (commit && session.dragging && session.dropPos !== null) {
746
+ if (commit && session.dragging && session.dropTarget) {
266
747
  const sourceStart = session.sourcePos;
267
748
  const sourceEnd = session.sourcePos + session.sourceNodeSize;
268
- const dropPos = session.dropPos;
269
- if (dropPos !== sourceStart &&
270
- dropPos !== sourceEnd &&
271
- !(dropPos > sourceStart && dropPos < sourceEnd)) {
749
+ const target = session.dropTarget;
750
+ const dropPos = target.pos;
751
+ if (target.view !== session.view ||
752
+ (dropPos !== sourceStart &&
753
+ dropPos !== sourceEnd &&
754
+ !(dropPos > sourceStart && dropPos < sourceEnd))) {
272
755
  const sourceNode = session.view.state.doc.nodeAt(sourceStart);
273
756
  if (sourceNode) {
274
- const insertPos = dropPos > sourceStart ? dropPos - sourceNode.nodeSize : dropPos;
275
- const tr = session.view.state.tr
276
- .delete(sourceStart, sourceEnd)
277
- .insert(insertPos, sourceNode);
278
- tr.setSelection(NodeSelection.create(tr.doc, insertPos));
279
- session.view.dispatch(tr.scrollIntoView());
280
- session.view.focus();
757
+ const sourceRegistration = registrationForView(session.view);
758
+ const transferData = sourceRegistration?.getDragTransferData?.({
759
+ view: session.view,
760
+ node: sourceNode,
761
+ pos: sourceStart,
762
+ });
763
+ const targetNode = target.view.state.doc.nodeAt(target.targetPos);
764
+ const handled = !!targetNode &&
765
+ (target.registration.handleDrop?.(transferData, {
766
+ view: target.view,
767
+ sourceView: session.view,
768
+ sourceNode,
769
+ sourcePos: sourceStart,
770
+ sourceNodeSize: sourceNode.nodeSize,
771
+ targetNode,
772
+ targetPos: target.targetPos,
773
+ targetNodeSize: target.targetNodeSize,
774
+ insertPos: dropPos,
775
+ placement: target.placement,
776
+ }) ??
777
+ false);
778
+ if (handled) {
779
+ target.view.focus();
780
+ }
781
+ else if (target.view === session.view) {
782
+ const insertPos = dropPos > sourceStart ? dropPos - sourceNode.nodeSize : dropPos;
783
+ const tr = session.view.state.tr
784
+ .delete(sourceStart, sourceEnd)
785
+ .insert(insertPos, sourceNode);
786
+ tr.setSelection(NodeSelection.create(tr.doc, insertPos));
787
+ session.view.dispatch(tr.scrollIntoView());
788
+ session.view.focus();
789
+ }
790
+ else {
791
+ try {
792
+ const targetNode = target.view.state.schema.nodeFromJSON(sourceNode.toJSON());
793
+ target.registration.receiveDragTransferData?.(transferData, {
794
+ view: target.view,
795
+ node: targetNode,
796
+ pos: dropPos,
797
+ sourceView: session.view,
798
+ });
799
+ const insertTr = target.view.state.tr.insert(dropPos, targetNode);
800
+ insertTr.setSelection(NodeSelection.create(insertTr.doc, dropPos));
801
+ target.view.dispatch(insertTr.scrollIntoView());
802
+ const deleteTr = session.view.state.tr.delete(sourceStart, sourceEnd);
803
+ session.view.dispatch(deleteTr);
804
+ target.view.focus();
805
+ }
806
+ catch {
807
+ // If the target schema cannot accept this node, leave the
808
+ // source document untouched.
809
+ }
810
+ }
281
811
  }
282
812
  }
283
813
  }
284
- else if (!session.dragging) {
285
- selectCurrentBlock(session.view);
814
+ else if (commit && !session.dragging && event) {
815
+ openMenu({
816
+ view: session.view,
817
+ sourceBlock: session.sourceBlock,
818
+ sourcePos: session.sourcePos,
819
+ sourceNodeSize: session.sourceNodeSize,
820
+ }, handle?.getBoundingClientRect() ??
821
+ session.sourceBlock.getBoundingClientRect());
286
822
  }
287
823
  cleanupDragVisuals();
288
824
  dragSession = null;
289
- hideHandle();
825
+ if (activeDragRegistration === currentRegistration) {
826
+ activeDragRegistration = null;
827
+ }
828
+ if (session.dragging || !commit)
829
+ hideHandle();
290
830
  };
291
831
  const beginDragSession = (session, event) => {
292
832
  session.dragging = true;
@@ -294,7 +834,7 @@ export const DragHandle = Extension.create({
294
834
  session.sourceBlock.classList.add("notion-block--dragging");
295
835
  document.documentElement.classList.add("notion-editor-is-dragging");
296
836
  positionDragPreview(session, event.clientX, event.clientY);
297
- updateDropLine(session, findDropTarget(session.view, event.clientX, event.clientY));
837
+ updateDropLine(session, findAnyDropTarget(session, event.clientX, event.clientY));
298
838
  };
299
839
  function handleDocumentMouseMove(event) {
300
840
  if (!dragSession)
@@ -307,11 +847,11 @@ export const DragHandle = Extension.create({
307
847
  if (!dragSession.dragging)
308
848
  return;
309
849
  positionDragPreview(dragSession, event.clientX, event.clientY);
310
- updateDropLine(dragSession, findDropTarget(dragSession.view, event.clientX, event.clientY));
850
+ updateDropLine(dragSession, findAnyDropTarget(dragSession, event.clientX, event.clientY));
311
851
  }
312
852
  function handleDocumentMouseUp(event) {
313
853
  event.preventDefault();
314
- finishDragSession(true);
854
+ finishDragSession(true, event);
315
855
  }
316
856
  function handleDocumentKeyDown(event) {
317
857
  if (event.key !== "Escape")
@@ -323,16 +863,31 @@ export const DragHandle = Extension.create({
323
863
  new Plugin({
324
864
  key: dragHandleKey,
325
865
  view(editorView) {
866
+ const registration = {
867
+ view: editorView,
868
+ wrapperSelector,
869
+ getDragTransferData,
870
+ receiveDragTransferData,
871
+ handleDrop,
872
+ canHover: () => !!handle && !menu && !dragSession && editor.isEditable,
873
+ findHoverBlock: (clientX, clientY) => findForgivingBlock(editorView, clientX, clientY),
874
+ showHoverBlock: (block) => showHandleForBlock(editorView, block),
875
+ hideHover: () => hideHandle(),
876
+ };
877
+ currentRegistration = registration;
878
+ dragHandleRegistrations.add(registration);
879
+ retainGlobalHoverListener();
326
880
  handle = createHandle();
327
- const handleDocumentHoverMove = createDocumentHoverMove(editorView);
328
881
  const wrapper = editorView.dom.closest(wrapperSelector);
329
882
  if (wrapper) {
330
883
  wrapper.style.position = "relative";
331
884
  wrapper.appendChild(handle);
332
885
  }
333
- document.addEventListener("mousemove", handleDocumentHoverMove);
334
886
  handle.addEventListener("mousedown", (e) => {
335
887
  e.stopPropagation();
888
+ if (e.button !== 0)
889
+ return;
890
+ closeMenu();
336
891
  if (!editor.isEditable) {
337
892
  e.preventDefault();
338
893
  return;
@@ -353,43 +908,56 @@ export const DragHandle = Extension.create({
353
908
  dragging: false,
354
909
  preview: null,
355
910
  dropLine: null,
356
- dropPos: null,
911
+ dropTarget: null,
357
912
  };
913
+ activeDragRegistration = registration;
358
914
  document.addEventListener("mousemove", handleDocumentMouseMove);
359
915
  document.addEventListener("mouseup", handleDocumentMouseUp);
360
916
  document.addEventListener("keydown", handleDocumentKeyDown);
361
917
  });
918
+ handle.addEventListener("keydown", (e) => {
919
+ if (e.key !== "Enter" && e.key !== " ")
920
+ return;
921
+ e.preventDefault();
922
+ e.stopPropagation();
923
+ closeMenu();
924
+ if (!editor.isEditable || !currentBlock || dragStartPos === null) {
925
+ return;
926
+ }
927
+ const sourceNode = editorView.state.doc.nodeAt(dragStartPos);
928
+ if (!sourceNode)
929
+ return;
930
+ openMenu({
931
+ view: editorView,
932
+ sourceBlock: currentBlock,
933
+ sourcePos: dragStartPos,
934
+ sourceNodeSize: sourceNode.nodeSize,
935
+ }, handle?.getBoundingClientRect() ??
936
+ currentBlock.getBoundingClientRect());
937
+ });
362
938
  return {
363
939
  destroy() {
364
- document.removeEventListener("mousemove", handleDocumentHoverMove);
940
+ closeMenu({ hideGrip: true });
365
941
  finishDragSession(false);
942
+ releaseGlobalHoverListener();
943
+ dragHandleRegistrations.delete(registration);
944
+ if (activeDragRegistration === registration) {
945
+ activeDragRegistration = null;
946
+ }
366
947
  handle?.remove();
367
948
  handle = null;
949
+ currentRegistration = null;
368
950
  },
369
951
  };
370
952
  },
371
953
  props: {
372
954
  handleDOMEvents: {
373
- mousemove(view, event) {
374
- if (!handle)
375
- return false;
376
- if (!editor.isEditable) {
377
- hideHandle();
378
- return false;
379
- }
380
- if (dragSession)
381
- return false;
382
- const block = findForgivingBlock(view, event.clientX, event.clientY);
383
- if (!block) {
384
- hideHandle();
385
- return false;
386
- }
387
- if (block.node === currentBlock)
388
- return false;
389
- showHandleForBlock(view, block);
955
+ mousemove(_view, event) {
956
+ updateRegisteredHover(event.clientX, event.clientY);
390
957
  return false;
391
958
  },
392
959
  drop() {
960
+ closeMenu({ hideGrip: true });
393
961
  finishDragSession(false);
394
962
  hideHandle();
395
963
  return false;