edytor 1.0.0 → 3.0.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.
Files changed (287) hide show
  1. package/README.md +26 -69
  2. package/dist/block/block.svelte.d.ts +6 -4
  3. package/dist/block/block.svelte.js +12 -8
  4. package/dist/block/inlineBlock.svelte.d.ts +6 -2
  5. package/dist/block/inlineBlock.svelte.js +7 -2
  6. package/dist/clipboard/htmlFlow.d.ts +20 -7
  7. package/dist/clipboard/htmlFlow.js +23 -15
  8. package/dist/clipboard/serializeClipboardFragment.d.ts +37 -4
  9. package/dist/clipboard/serializeClipboardFragment.js +6 -4
  10. package/dist/clipboard/textBlocks.d.ts +8 -0
  11. package/dist/clipboard/textBlocks.js +6 -2
  12. package/dist/cloudflare/room/admission.js +3 -3
  13. package/dist/cloudflare/room/context.d.ts +7 -1
  14. package/dist/cloudflare/routeDocumentSocket.d.ts +17 -0
  15. package/dist/cloudflare/routeDocumentSocket.js +53 -0
  16. package/dist/collaboration/awarenessSelection.d.ts +22 -1
  17. package/dist/collaboration/awarenessSelection.js +42 -0
  18. package/dist/collaboration/comments/client.d.ts +1 -1
  19. package/dist/collaboration/comments/client.js +1 -1
  20. package/dist/collaboration/remoteSelection.js +3 -3
  21. package/dist/components/Block.svelte +3 -1
  22. package/dist/components/Edytor.svelte +38 -20
  23. package/dist/components/Edytor.svelte.d.ts +9 -4
  24. package/dist/components/InlineBlock.svelte +1 -1
  25. package/dist/crdt/admission.d.ts +5 -10
  26. package/dist/crdt/admission.js +5 -11
  27. package/dist/crdt/data.d.ts +10 -0
  28. package/dist/crdt/data.js +20 -2
  29. package/dist/crdt/doc/funnel.d.ts +7 -6
  30. package/dist/crdt/doc/funnel.js +10 -9
  31. package/dist/crdt/doc/gate.d.ts +0 -17
  32. package/dist/crdt/doc/gate.js +2 -41
  33. package/dist/crdt/doc/history.js +2 -2
  34. package/dist/crdt/doc/reads.d.ts +7 -0
  35. package/dist/crdt/doc/reads.js +11 -1
  36. package/dist/crdt/document.d.ts +13 -14
  37. package/dist/crdt/document.js +17 -19
  38. package/dist/crdt/edytor-doc.d.ts +22 -4
  39. package/dist/crdt/edytor-doc.js +5 -8
  40. package/dist/crdt/index.d.ts +9 -3
  41. package/dist/crdt/index.js +2 -2
  42. package/dist/crdt/operations.d.ts +1 -1
  43. package/dist/crdt/operations.js +1 -0
  44. package/dist/crdt/props.d.ts +32 -0
  45. package/dist/crdt/props.js +172 -0
  46. package/dist/crdt/protocol.d.ts +2 -2
  47. package/dist/crdt/protocol.js +2 -2
  48. package/dist/crdt/providers/index.d.ts +1 -1
  49. package/dist/crdt/providers/room.d.ts +10 -9
  50. package/dist/crdt/providers/room.js +13 -10
  51. package/dist/crdt/providers/websocket.d.ts +1 -1
  52. package/dist/crdt/providers/websocket.js +28 -29
  53. package/dist/crdt/semantics.js +3 -1
  54. package/dist/dnd/autoScroll.d.ts +15 -7
  55. package/dist/dnd/autoScroll.js +10 -15
  56. package/dist/{plugins/blockHandles → dnd}/dropIndicator.d.ts +2 -2
  57. package/dist/{plugins/blockHandles → dnd}/dropIndicator.js +2 -5
  58. package/dist/dnd/external.d.ts +6 -0
  59. package/dist/dnd/external.js +17 -0
  60. package/dist/{plugins/blockHandles → dnd}/geometry.d.ts +4 -2
  61. package/dist/{plugins/blockHandles → dnd}/geometry.js +3 -4
  62. package/dist/dnd/placement.d.ts +200 -0
  63. package/dist/dnd/placement.js +426 -0
  64. package/dist/dnd/pragmatic.d.ts +3 -1
  65. package/dist/dnd/pragmatic.js +12 -2
  66. package/dist/dnd/vendor/pragmatic/UPSTREAM.md +51 -18
  67. package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/adapter/drop-target-for-external.d.ts +4 -0
  68. package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/adapter/drop-target-for-external.js +6 -0
  69. package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/adapter/element-adapter.d.ts +0 -2
  70. package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/adapter/element-adapter.js +4 -22
  71. package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/adapter/external-adapter-types.d.ts +26 -0
  72. package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/adapter/external-adapter.d.ts +30 -0
  73. package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/adapter/external-adapter.js +203 -0
  74. package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/adapter/get-available-items.js +13 -0
  75. package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/adapter/get-available-types.js +9 -0
  76. package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/adapter/is-an-available-type.js +19 -0
  77. package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/adapter/monitor-for-external.d.ts +4 -0
  78. package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/adapter/monitor-for-external.js +6 -0
  79. package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/honey-pot-fix/make-honey-pot-fix.js +4 -16
  80. package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/internal-types.d.ts +1 -8
  81. package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/make-adapter/make-drop-target.js +5 -6
  82. package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/make-adapter/make-monitor.js +2 -3
  83. package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/public-utils/element/custom-native-drag-preview/set-custom-native-drag-preview.d.ts +1 -1
  84. package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/public-utils/element/custom-native-drag-preview/set-custom-native-drag-preview.js +6 -24
  85. package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/public-utils/external/contains-files.d.ts +2 -0
  86. package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/public-utils/external/contains-files.js +5 -0
  87. package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/public-utils/external/get-files.d.ts +3 -0
  88. package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/public-utils/external/get-files.js +12 -0
  89. package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/public-utils/external/native-types.d.ts +12 -0
  90. package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/util/changing-window/is-entering-window-in-safari.js +10 -0
  91. package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/util/changing-window/is-entering-window.js +55 -0
  92. package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop-auto-scroll/entry-point/external.d.ts +4 -0
  93. package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop-auto-scroll/entry-point/external.js +8 -0
  94. package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop-auto-scroll/internal-types.d.ts +0 -4
  95. package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop-auto-scroll/over-element/get-scroll-by.js +16 -72
  96. package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop-auto-scroll/over-element/try-scroll.js +1 -9
  97. package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop-auto-scroll/shared/configuration.js +4 -29
  98. package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop-auto-scroll/shared/get-scroll-change.js +2 -3
  99. package/dist/edytor.svelte.d.ts +19 -9
  100. package/dist/edytor.svelte.js +28 -33
  101. package/dist/events/beforeInputCommands.d.ts +2 -2
  102. package/dist/events/beforeInputCommands.js +8 -4
  103. package/dist/events/onBeforeInput.js +1 -1
  104. package/dist/events/onDrop.js +25 -10
  105. package/dist/events/onInput.d.ts +0 -12
  106. package/dist/events/onInput.js +1 -67
  107. package/dist/events/onPaste.d.ts +2 -1
  108. package/dist/events/onPaste.js +8 -3
  109. package/dist/export/forms.d.ts +627 -0
  110. package/dist/export/forms.js +749 -0
  111. package/dist/export/index.d.ts +170 -0
  112. package/dist/export/index.js +161 -0
  113. package/dist/export/markdown.d.ts +41 -0
  114. package/dist/export/markdown.js +585 -0
  115. package/dist/export/markdownWriter.d.ts +9 -0
  116. package/dist/export/markdownWriter.js +133 -0
  117. package/dist/export/mdx.d.ts +15 -0
  118. package/dist/export/mdx.js +33 -0
  119. package/dist/index.d.ts +4 -4
  120. package/dist/index.js +2 -2
  121. package/dist/kinds.js +1 -1
  122. package/dist/labels.d.ts +2 -0
  123. package/dist/labels.js +2 -1
  124. package/dist/plugins/anchored.svelte.d.ts +21 -25
  125. package/dist/plugins/anchored.svelte.js +23 -39
  126. package/dist/plugins/arrowMove/arrowMove.d.ts +1 -1
  127. package/dist/plugins/arrowMove/arrowMove.js +1 -1
  128. package/dist/plugins/blockHandles/BlockHandleController.svelte.d.ts +2 -122
  129. package/dist/plugins/blockHandles/BlockHandleController.svelte.js +48 -414
  130. package/dist/plugins/blockHandles/BlockHandles.svelte +14 -32
  131. package/dist/plugins/blockHandles/blockHandlesPlugin.js +2 -1
  132. package/dist/plugins/blockHandles/dragPreview.d.ts +26 -7
  133. package/dist/plugins/blockHandles/dragPreview.js +33 -0
  134. package/dist/plugins/blockMenu/BlockMenuController.svelte.d.ts +5 -0
  135. package/dist/plugins/blockMenu/BlockMenuController.svelte.js +7 -2
  136. package/dist/plugins/blockMenu/blockMenuPlugin.js +5 -50
  137. package/dist/plugins/chrome.d.ts +27 -5
  138. package/dist/plugins/chrome.js +24 -9
  139. package/dist/plugins/code/CodeHeader.svelte +5 -3
  140. package/dist/plugins/code/CodePlugin.svelte +15 -12
  141. package/dist/plugins/code/header.svelte.js +7 -11
  142. package/dist/plugins/code/languageMenu.svelte.d.ts +5 -2
  143. package/dist/plugins/code/languageMenu.svelte.js +13 -20
  144. package/dist/plugins/code/languages.d.ts +0 -3
  145. package/dist/plugins/code/languages.js +0 -2
  146. package/dist/plugins/columns/ColumnResize.svelte +3 -2
  147. package/dist/plugins/columns/ColumnsPlugin.svelte +8 -21
  148. package/dist/plugins/columns/gaps.d.ts +7 -5
  149. package/dist/plugins/columns/gaps.js +11 -6
  150. package/dist/plugins/columns/resize.svelte.d.ts +5 -6
  151. package/dist/plugins/columns/resize.svelte.js +12 -14
  152. package/dist/plugins/comments/CommentsController.svelte.d.ts +6 -0
  153. package/dist/plugins/comments/CommentsController.svelte.js +20 -0
  154. package/dist/plugins/equation/EquationPlugin.svelte +13 -30
  155. package/dist/plugins/equation/EquationPlugin.svelte.d.ts +1 -0
  156. package/dist/plugins/equation/equation.css +7 -0
  157. package/dist/plugins/equation/equation.svelte.d.ts +7 -15
  158. package/dist/plugins/equation/equation.svelte.js +13 -50
  159. package/dist/plugins/equation/tex.d.ts +17 -0
  160. package/dist/plugins/equation/tex.js +42 -0
  161. package/dist/plugins/fileDrop.d.ts +43 -0
  162. package/dist/plugins/fileDrop.js +122 -0
  163. package/dist/plugins/find/FindController.svelte.d.ts +5 -0
  164. package/dist/plugins/find/FindController.svelte.js +14 -14
  165. package/dist/plugins/find/findPlugin.js +1 -15
  166. package/dist/plugins/image/ImageChrome.svelte +8 -2
  167. package/dist/plugins/image/ImagePlugin.svelte +56 -84
  168. package/dist/plugins/image/ImagePlugin.svelte.d.ts +13 -1
  169. package/dist/plugins/image/controls.svelte.js +16 -2
  170. package/dist/plugins/image/image.css +17 -0
  171. package/dist/plugins/image/toolbar.svelte.d.ts +5 -4
  172. package/dist/plugins/image/toolbar.svelte.js +21 -31
  173. package/dist/plugins/index.d.ts +4 -5
  174. package/dist/plugins/index.js +0 -1
  175. package/dist/plugins/list.svelte.d.ts +9 -0
  176. package/dist/plugins/list.svelte.js +18 -0
  177. package/dist/plugins/markdownShortcuts.js +5 -5
  178. package/dist/plugins/marquee/marqueePlugin.js +0 -4
  179. package/dist/plugins/media/AudioPlugin.svelte +13 -1
  180. package/dist/plugins/media/AudioPlugin.svelte.d.ts +11 -1
  181. package/dist/plugins/media/BookmarkPlugin.svelte +6 -97
  182. package/dist/plugins/media/EmbedPlugin.svelte +5 -90
  183. package/dist/plugins/media/EmptySlot.svelte +36 -20
  184. package/dist/plugins/media/EmptySlot.svelte.d.ts +6 -15
  185. package/dist/plugins/media/FilePlugin.svelte +12 -20
  186. package/dist/plugins/media/FilePlugin.svelte.d.ts +9 -0
  187. package/dist/plugins/media/MediaEmpty.svelte +2 -55
  188. package/dist/plugins/media/MediaEmpty.svelte.d.ts +18 -14
  189. package/dist/plugins/media/SourceBlock.svelte +178 -52
  190. package/dist/plugins/media/SourceBlock.svelte.d.ts +13 -6
  191. package/dist/plugins/media/UploadPreview.svelte +32 -0
  192. package/dist/plugins/media/UploadPreview.svelte.d.ts +11 -0
  193. package/dist/plugins/media/VideoPlugin.svelte +13 -1
  194. package/dist/plugins/media/VideoPlugin.svelte.d.ts +11 -1
  195. package/dist/plugins/media/empty.svelte.d.ts +13 -4
  196. package/dist/plugins/media/empty.svelte.js +32 -26
  197. package/dist/plugins/media/media.d.ts +9 -63
  198. package/dist/plugins/media/media.js +1 -194
  199. package/dist/plugins/media/sources.d.ts +60 -0
  200. package/dist/plugins/media/sources.js +165 -0
  201. package/dist/plugins/media/upload.css +23 -0
  202. package/dist/plugins/media/urlPaste.svelte.d.ts +2 -1
  203. package/dist/plugins/media/urlPaste.svelte.js +12 -21
  204. package/dist/plugins/mention/MentionPlugin.svelte +50 -38
  205. package/dist/plugins/mention/MentionPlugin.svelte.d.ts +11 -2
  206. package/dist/plugins/mention/mention.css +6 -0
  207. package/dist/plugins/page/PagePlugin.svelte +2 -9
  208. package/dist/plugins/page/page.js +1 -1
  209. package/dist/plugins/pageLink/PageLinkPlugin.svelte +126 -28
  210. package/dist/plugins/pageLink/PageLinkPlugin.svelte.d.ts +13 -1
  211. package/dist/plugins/pageLink/pageLink.css +6 -0
  212. package/dist/plugins/richtext/RichTextPlugin.svelte +99 -204
  213. package/dist/plugins/richtext/RichTextPlugin.svelte.d.ts +1 -0
  214. package/dist/plugins/richtext/body.js +1 -1
  215. package/dist/plugins/richtext/calloutIcons.svelte.d.ts +2 -2
  216. package/dist/plugins/richtext/calloutIcons.svelte.js +5 -7
  217. package/dist/plugins/richtext/richTextOperations.d.ts +1 -19
  218. package/dist/plugins/richtext/richTextOperations.js +3 -49
  219. package/dist/plugins/richtext/richtext.css +6 -0
  220. package/dist/plugins/slashMenu/SlashMenuController.svelte.d.ts +2 -0
  221. package/dist/plugins/slashMenu/SlashMenuController.svelte.js +7 -0
  222. package/dist/plugins/slashMenu/slashMenuPlugin.js +5 -14
  223. package/dist/plugins/table/TableChrome.svelte +2 -3
  224. package/dist/plugins/table/TablePlugin.svelte +20 -54
  225. package/dist/plugins/table/chrome.svelte.d.ts +5 -2
  226. package/dist/plugins/table/chrome.svelte.js +33 -56
  227. package/dist/plugins/table/table.js +3 -3
  228. package/dist/plugins/toc/TocPlugin.svelte +2 -4
  229. package/dist/plugins/toolbar/ToolbarController.svelte.d.ts +7 -0
  230. package/dist/plugins/toolbar/ToolbarController.svelte.js +20 -13
  231. package/dist/plugins/toolbar/toolbarPlugin.js +19 -53
  232. package/dist/plugins/triggers/TriggerController.svelte.d.ts +7 -2
  233. package/dist/plugins/triggers/TriggerController.svelte.js +11 -4
  234. package/dist/plugins/triggers/place.js +1 -2
  235. package/dist/plugins/triggers/triggers.d.ts +6 -1
  236. package/dist/plugins/triggers/triggers.js +12 -13
  237. package/dist/plugins/uploads.svelte.d.ts +48 -32
  238. package/dist/plugins/uploads.svelte.js +119 -43
  239. package/dist/plugins.d.ts +66 -14
  240. package/dist/plugins.js +0 -24
  241. package/dist/selection/replaceSelection.js +2 -2
  242. package/dist/selection/selection.svelte.d.ts +4 -10
  243. package/dist/selection/selection.svelte.js +7 -11
  244. package/dist/session/attempt.d.ts +16 -6
  245. package/dist/session/attempt.js +1 -0
  246. package/dist/session/bindings.js +35 -32
  247. package/dist/session/commands.d.ts +7 -11
  248. package/dist/session/commands.js +15 -19
  249. package/dist/session/history.js +1 -0
  250. package/dist/session/inputRules.d.ts +6 -3
  251. package/dist/session/inputRules.js +10 -6
  252. package/dist/session/moves.d.ts +33 -0
  253. package/dist/session/moves.js +71 -1
  254. package/dist/session/navigation.d.ts +7 -0
  255. package/dist/session/navigation.js +20 -11
  256. package/dist/session/props.d.ts +28 -0
  257. package/dist/session/props.js +65 -0
  258. package/dist/session/suggestions.svelte.d.ts +0 -11
  259. package/dist/session/suggestions.svelte.js +15 -3
  260. package/dist/session/uploads.d.ts +29 -0
  261. package/dist/session/uploads.js +10 -0
  262. package/dist/surface/lines.js +1 -3
  263. package/dist/surface/observer.svelte.d.ts +17 -1
  264. package/dist/surface/observer.svelte.js +72 -17
  265. package/dist/surface/overlay.d.ts +0 -14
  266. package/dist/surface/overlay.js +0 -25
  267. package/dist/surface/projector.svelte.js +1 -1
  268. package/dist/text/text.svelte.js +3 -13
  269. package/dist/themes/notion.css +9 -0
  270. package/dist/utils/sanitize.d.ts +38 -0
  271. package/dist/utils/sanitize.js +82 -0
  272. package/dist/utils.d.ts +2 -0
  273. package/dist/utils.js +5 -0
  274. package/package.json +18 -1
  275. package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/util/max-z-index.js +0 -3
  276. package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/util/supports-popover.js +0 -16
  277. package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop-auto-scroll/shared/is-axis-allowed.js +0 -3
  278. package/dist/plugins/code/LanguageMenu.svelte +0 -25
  279. package/dist/plugins/code/LanguageMenu.svelte.d.ts +0 -9
  280. package/dist/plugins/equation/EquationEditor.svelte +0 -27
  281. package/dist/plugins/equation/EquationEditor.svelte.d.ts +0 -9
  282. package/dist/plugins/image/ImageToolbarHost.svelte +0 -37
  283. package/dist/plugins/image/ImageToolbarHost.svelte.d.ts +0 -9
  284. package/dist/plugins/richtext/CalloutIconPicker.svelte +0 -26
  285. package/dist/plugins/richtext/CalloutIconPicker.svelte.d.ts +0 -9
  286. /package/dist/{plugins/columns → dnd}/stacking.d.ts +0 -0
  287. /package/dist/{plugins/columns → dnd}/stacking.js +0 -0
@@ -340,7 +340,7 @@ export const deleteSelectedBlocks = (edytor, blocks = selectedMembers(edytor)) =
340
340
  ...beside(blocks[0], 'blockBefore', blocks),
341
341
  ...beside(blocks.at(-1), 'blockAfter', blocks)
342
342
  ];
343
- const deleted = edytor.dispatcher.caret(at?.text, at?.offset ?? 0, () => edytor.deleteBlocks(blocks.map((block) => block.id)));
343
+ const deleted = edytor.dispatcher.caretIn(at?.text, at?.offset ?? 0, () => edytor.deleteBlocks(blocks.map((block) => block.id)));
344
344
  if (deleted?.status !== 'applied')
345
345
  return null;
346
346
  edytor.announcer.deleted(what);
@@ -354,7 +354,7 @@ export const deleteSelectedBlocks = (edytor, blocks = selectedMembers(edytor)) =
354
354
  const text = virtual ? edytor.idToBlock.get(virtual)?.firstText : undefined;
355
355
  const survivor = besides.find((block) => block.isInTree);
356
356
  if (text)
357
- edytor.dispatcher.caret(text, 0);
357
+ edytor.dispatcher.caretIn(text, 0);
358
358
  else if (survivor)
359
359
  edytor.selection.select(blockSelection([survivor.id]));
360
360
  return text ?? null;
@@ -94,7 +94,7 @@ export declare class EdytorSelection {
94
94
  * the start binds right (boundary inserts stay outside), the end and a
95
95
  * caret bind left; `none` when the start text is not bound to the document.
96
96
  */
97
- textValue: (startText: Text, yStart: number, endText?: Text, yEnd?: number, isReversed?: boolean, startAffinity?: "left" | "right", mint?: (text: Text, offset: number, affinity?: "left" | "right") => TextAnchor | null) => SelectionValue;
97
+ textValue: (startText: Text, yStart: number, endText?: Text, yEnd?: number, isReversed?: boolean, startAffinity?: "left" | "right") => SelectionValue;
98
98
  /**
99
99
  * A pointer drag is in progress (a press in the host, until its release):
100
100
  * the projector does not display under it, and the overlay chrome
@@ -123,9 +123,9 @@ export declare class EdytorSelection {
123
123
  * facade maps to the owning backing text — content routed from other
124
124
  * blocks' backing texts (merges/splits) anchors correctly.
125
125
  *
126
- * `null` while the text isn't bound to a live block (mirrors the
127
- * baseline where a non-integrated Y.Text couldn't produce an absolute
128
- * position).
126
+ * Minted from the text's block record, live or not (the atoms of a text
127
+ * that died to a merge live on in the block that claimed them); `null`
128
+ * for the root's own text.
129
129
  */
130
130
  createTextAnchor: (text: Text, offset: number, affinity?: "left" | "right") => TextAnchor | null;
131
131
  /**
@@ -148,12 +148,6 @@ export declare class EdytorSelection {
148
148
  * never mounts.
149
149
  */
150
150
  displayable: (id: string) => boolean;
151
- /** The view's visibility rule (`selection/visibility.ts`), for extensions: hidden by view state, and the shown neighbour. */
152
- hidden: (block: Block, removed?: ReadonlySet<Block>) => boolean;
153
- shown: (block: Block, step: "blockBefore" | "blockAfter", { sealed, removed }?: {
154
- sealed?: boolean;
155
- removed?: ReadonlySet<Block>;
156
- }) => Block | null;
157
151
  /** The text's computed direction (a Surface fact): arrow and word keys are visual. */
158
152
  rtl: (text: Text) => boolean;
159
153
  }
@@ -389,7 +389,8 @@ export class EdytorSelection {
389
389
  * the start binds right (boundary inserts stay outside), the end and a
390
390
  * caret bind left; `none` when the start text is not bound to the document.
391
391
  */
392
- textValue = (startText, yStart, endText = startText, yEnd = yStart, isReversed = false, startAffinity, mint = this.createTextAnchor) => {
392
+ textValue = (startText, yStart, endText = startText, yEnd = yStart, isReversed = false, startAffinity) => {
393
+ const mint = this.createTextAnchor;
393
394
  const collapsed = startText === endText && yStart === yEnd;
394
395
  const start = mint(startText, yStart, startAffinity ?? (collapsed ? 'left' : 'right'));
395
396
  if (!start)
@@ -413,9 +414,7 @@ export class EdytorSelection {
413
414
  if (!startText || !endText)
414
415
  return;
415
416
  const at = (text, offset) => Math.min(Math.max(offset, 0), text.length);
416
- const value = this.textValue(startText, at(startText, yStart), endText, at(endText, yEnd), isReversed, undefined, (text, offset, side) => text.parent.isRoot
417
- ? null
418
- : this.edytor.facade.anchorAt(text.blockId, text.segStart + offset, side));
417
+ const value = this.textValue(startText, at(startText, yStart), endText, at(endText, yEnd), isReversed);
419
418
  const target = project(value, this.edytor.facade).start
420
419
  ? value
421
420
  : this.#seamValue(startText.parent?.id ?? null);
@@ -652,12 +651,12 @@ export class EdytorSelection {
652
651
  * facade maps to the owning backing text — content routed from other
653
652
  * blocks' backing texts (merges/splits) anchors correctly.
654
653
  *
655
- * `null` while the text isn't bound to a live block (mirrors the
656
- * baseline where a non-integrated Y.Text couldn't produce an absolute
657
- * position).
654
+ * Minted from the text's block record, live or not (the atoms of a text
655
+ * that died to a merge live on in the block that claimed them); `null`
656
+ * for the root's own text.
658
657
  */
659
658
  createTextAnchor = (text, offset, affinity = 'left') => {
660
- if (text.parent.isRoot || !text.isInDocument)
659
+ if (text.parent.isRoot)
661
660
  return null;
662
661
  return this.edytor.facade.anchorAt(text.blockId, text.segStart + offset, affinity);
663
662
  };
@@ -688,9 +687,6 @@ export class EdytorSelection {
688
687
  ?.content.find((part) => part instanceof Text && part.node != null)?.node;
689
688
  return !!node && !node.closest(visibility.HIDDEN);
690
689
  };
691
- /** The view's visibility rule (`selection/visibility.ts`), for extensions: hidden by view state, and the shown neighbour. */
692
- hidden = visibility.hidden;
693
- shown = visibility.shown;
694
690
  /** The text's computed direction (a Surface fact): arrow and word keys are visual. */
695
691
  rtl = (text) => !!text.node?.isConnected && getComputedStyle(text.node).direction === 'rtl';
696
692
  /** A caret at the seam `dead` vacated (`doc/anchors`); `null` when nothing displays. */
@@ -18,6 +18,7 @@
18
18
  */
19
19
  import type { Edytor } from '../edytor.svelte.js';
20
20
  import type { Text } from '../text/text.svelte.js';
21
+ import type { BlockMovePosition } from './moves.js';
21
22
  type Kind = 'text' | 'composition' | 'payload' | 'break' | 'delete' | 'history';
22
23
  export type IntentRow = {
23
24
  kind: Kind;
@@ -63,10 +64,10 @@ export declare const untargeted: (edytor: Edytor) => boolean;
63
64
  * void's own field.
64
65
  */
65
66
  declare const facts: (edytor: Edytor) => {
66
- projection: import("./selection").SelectionProjection;
67
+ projection: import("./selection.js").SelectionProjection;
67
68
  isCollapsed: boolean;
68
69
  /** The admitted mark-edge side of a caret. */
69
- edge: import("./editing/text").EdgeSide | undefined;
70
+ edge: import("./editing/text.js").EdgeSide | undefined;
70
71
  isVoidEditableElement: boolean;
71
72
  startText: Text | null;
72
73
  endText: Text | null;
@@ -74,7 +75,7 @@ declare const facts: (edytor: Edytor) => {
74
75
  yEnd: number;
75
76
  texts: Text[];
76
77
  /** The anchored target, fixed at admission. */
77
- target: import("./selection").SelectionValue;
78
+ target: import("./selection.js").SelectionValue;
78
79
  };
79
80
  /**
80
81
  * How DOM drift around a model-owned attempt is repaired: the observer
@@ -107,9 +108,16 @@ export type Occurrence = {
107
108
  dataTransfer?: DataTransfer | null;
108
109
  /** The range the browser declared (`getTargetRanges()`, a drop point). */
109
110
  declared?: StaticRange | null;
111
+ /** Where a file drop lands, the indicator's placement (`plugins/fileDrop.ts`). */
112
+ placement?: DropAt | null;
110
113
  cancelable: boolean;
111
114
  event?: InputEvent;
112
115
  };
116
+ /** A drop's placement in block terms: beside, before, after or inside `block`. */
117
+ export type DropAt = {
118
+ block: string;
119
+ position: BlockMovePosition;
120
+ };
113
121
  export type Attempt = ReturnType<typeof facts> & {
114
122
  id: number;
115
123
  /** The intent, fixed at admission. */
@@ -121,6 +129,7 @@ export type Attempt = ReturnType<typeof facts> & {
121
129
  dataTransfer: DataTransfer | null;
122
130
  hasDataTransferTextPayload: boolean;
123
131
  declared: StaticRange | null;
132
+ placement: DropAt | null;
124
133
  /** Admitted from a keydown (the key's intent wins; the browser may never announce it). */
125
134
  isStructuralKeyFallback: boolean;
126
135
  owner: 'model' | 'browser';
@@ -135,10 +144,10 @@ export declare const intentSnapshot: (edytor: Edytor, inputType: string) => Atte
135
144
  export declare const caretAt: (edytor: Edytor, text: Text | null | undefined, offset: number) => void;
136
145
  /** Re-project an attempt's anchored target when it runs later than its admission. */
137
146
  export declare const reproject: (edytor: Edytor, attempt: Attempt) => {
138
- projection: import("./selection").SelectionProjection;
147
+ projection: import("./selection.js").SelectionProjection;
139
148
  isCollapsed: boolean;
140
149
  /** The admitted mark-edge side of a caret. */
141
- edge: import("./editing/text").EdgeSide | undefined;
150
+ edge: import("./editing/text.js").EdgeSide | undefined;
142
151
  isVoidEditableElement: boolean;
143
152
  startText: Text | null;
144
153
  endText: Text | null;
@@ -146,7 +155,7 @@ export declare const reproject: (edytor: Edytor, attempt: Attempt) => {
146
155
  yEnd: number;
147
156
  texts: Text[];
148
157
  /** The anchored target, fixed at admission. */
149
- target: import("./selection").SelectionValue;
158
+ target: import("./selection.js").SelectionValue;
150
159
  } & {
151
160
  id: number;
152
161
  /** The intent, fixed at admission. */
@@ -158,6 +167,7 @@ export declare const reproject: (edytor: Edytor, attempt: Attempt) => {
158
167
  dataTransfer: DataTransfer | null;
159
168
  hasDataTransferTextPayload: boolean;
160
169
  declared: StaticRange | null;
170
+ placement: DropAt | null;
161
171
  /** Admitted from a keydown (the key's intent wins; the browser may never announce it). */
162
172
  isStructuralKeyFallback: boolean;
163
173
  owner: "model" | "browser";
@@ -119,6 +119,7 @@ export const attemptOf = (edytor, occurrence, keyIntent) => {
119
119
  dataTransfer: occurrence.dataTransfer ?? null,
120
120
  hasDataTransferTextPayload: payload !== null,
121
121
  declared: occurrence.declared ?? null,
122
+ placement: occurrence.placement ?? null,
122
123
  isStructuralKeyFallback: Boolean(keyIntent),
123
124
  owner: 'model',
124
125
  expect: null,
@@ -24,7 +24,7 @@ export const replaceSelectedAtom = (edytor, value = '') => {
24
24
  edytor.removeInline(parent.id, selected.id);
25
25
  if (value)
26
26
  edytor.insertText(parent.id, text.segStart + offset, value);
27
- edytor.dispatcher.caret(text, offset + value.length);
27
+ edytor.dispatcher.caretIn(text, offset + value.length);
28
28
  return true;
29
29
  };
30
30
  /** The selection is the editor's own: an inline atom or blocks (no DOM range shows it). */
@@ -119,41 +119,44 @@ const moveRoots = (edytor, blocks, direction) => {
119
119
  .flatMap((moved) => moved ?? []);
120
120
  };
121
121
  /**
122
- * Tab / Shift+Tab: nest or unnest the selected block, or the caret's block.
122
+ * Tab / Shift+Tab, with `allowNesting`: nest or unnest the selected block, or the caret's block.
123
123
  * Selected blocks, or the blocks a text range spans, move one level per
124
124
  * group of siblings, a selected block's selected descendants with it, as
125
125
  * one command; the selection stays (its anchors moved with the blocks).
126
126
  */
127
- const nest = (operation) => ({ edytor, prevent }) => prevent(() => {
128
- const { selection } = edytor;
129
- const { value } = selection;
130
- const direction = operation === 'nestBlock' ? 'in' : 'out';
131
- const blocks = getSelectionBlocks(edytor);
132
- if (blocks.length > 1) {
133
- if (moveRoots(edytor, blocks, direction).length)
134
- selection.select(value);
135
- return;
136
- }
137
- const [selected] = selection.selectedBlocks;
138
- const at = selection.textAt(selection.projection.start);
139
- const block = selected ?? at?.text.parent;
140
- if (!block)
141
- return;
142
- // Into the block before it; the view's move reveals a closed toggle it lands in or that adopts.
143
- const into = block.previousBlock;
144
- if (operation === 'nestBlock' && !into)
145
- return edytor.dispatcher.record({ operation, status: 'refused' });
146
- const result = operation === 'unNestBlock'
147
- ? edytor.unNestBlock(block.id)
148
- : edytor.nestBlock(block.id, into.id);
149
- if (result.status !== 'applied')
150
- return;
151
- edytor.announcer.moved({ blocks: [block], direction }, [block]);
152
- if (selected)
153
- selection.select(blockSelection([block.id]));
154
- else if (at)
155
- caretAt(edytor, block.content[at.text.index], at.offset);
156
- });
127
+ const nest = (operation) => ({ edytor, prevent }) =>
128
+ // Nesting off (`allowNesting`, the default): the key is not the editor's, the browser moves the focus on.
129
+ edytor.allowNesting &&
130
+ prevent(() => {
131
+ const { selection } = edytor;
132
+ const { value } = selection;
133
+ const direction = operation === 'nestBlock' ? 'in' : 'out';
134
+ const blocks = getSelectionBlocks(edytor);
135
+ if (blocks.length > 1) {
136
+ if (moveRoots(edytor, blocks, direction).length)
137
+ selection.select(value);
138
+ return;
139
+ }
140
+ const [selected] = selection.selectedBlocks;
141
+ const at = selection.textAt(selection.projection.start);
142
+ const block = selected ?? at?.text.parent;
143
+ if (!block)
144
+ return;
145
+ // Into the block before it; the view's move reveals a closed toggle it lands in or that adopts.
146
+ const into = block.previousBlock;
147
+ if (operation === 'nestBlock' && !into)
148
+ return edytor.dispatcher.record({ operation, status: 'refused' });
149
+ const result = operation === 'unNestBlock'
150
+ ? edytor.unNestBlock(block.id)
151
+ : edytor.nestBlock(block.id, into.id);
152
+ if (result.status !== 'applied')
153
+ return;
154
+ edytor.announcer.moved({ blocks: [block], direction }, [block]);
155
+ if (selected)
156
+ selection.select(blockSelection([block.id]));
157
+ else if (at)
158
+ caretAt(edytor, block.content[at.text.index], at.offset);
159
+ });
157
160
  /**
158
161
  * Backspace/Delete over an editor-owned selection: the selected atom, or
159
162
  * the selected blocks (the caret lands at the end of the block before them).
@@ -128,18 +128,14 @@ export declare class Dispatcher {
128
128
  taken: boolean;
129
129
  };
130
130
  /**
131
- * A command's result caret: selected once and recorded on the result;
132
- * the projector displays it after the flush. With `ops`, the
133
- * caret is declared before the command's operations run — minted while its
134
- * text is live, so it survives them — and written only when they applied;
135
- * meanwhile the seam repair leaves this view's endpoints to it. Takes a
136
- * text segment and an offset in it, or a `{ block, offset }` caret in
137
- * block offsets (an inline atom counting 1).
131
+ * A command's result caret, `{ block, offset }` in block offsets (an
132
+ * inline atom counting 1): selected once and recorded on the result;
133
+ * the projector displays it after the flush. With `ops`, the caret is
134
+ * declared before the command's operations run — minted while its text
135
+ * is live, so it survives them — and written only when they applied;
136
+ * meanwhile the seam repair leaves this view's endpoints to it.
138
137
  */
139
- caret: {
140
- (text: Text | null | undefined, offset: number, ops?: () => OpResult): OpResult | undefined;
141
- (caret: Caret | DocPosition | null | undefined, ops?: () => OpResult): OpResult | undefined;
142
- };
138
+ caret: (caret: Caret | DocPosition | null | undefined, ops?: () => OpResult) => OpResult | undefined;
143
139
  /**
144
140
  * The cut an operation applies as it writes. Inside a user command none
145
141
  * (the command cut); outside one, its bare policy. A led operation is a
@@ -458,6 +458,8 @@ export class Dispatcher {
458
458
  */
459
459
  operate = (operation, args) => {
460
460
  const { edytor } = this;
461
+ // The property writes queued in this task go first (`session/props`).
462
+ edytor.dataWrites.flush();
461
463
  const own = edytor.facade.prepare[operation];
462
464
  const prepare = (a) => {
463
465
  if (operation === 'insertText' && a[3] === undefined)
@@ -564,27 +566,20 @@ export class Dispatcher {
564
566
  return this.running ? this.kind : null;
565
567
  }
566
568
  /**
567
- * A command's result caret: selected once and recorded on the result;
568
- * the projector displays it after the flush. With `ops`, the
569
- * caret is declared before the command's operations run — minted while its
570
- * text is live, so it survives them — and written only when they applied;
571
- * meanwhile the seam repair leaves this view's endpoints to it. Takes a
572
- * text segment and an offset in it, or a `{ block, offset }` caret in
573
- * block offsets (an inline atom counting 1).
569
+ * A command's result caret, `{ block, offset }` in block offsets (an
570
+ * inline atom counting 1): selected once and recorded on the result;
571
+ * the projector displays it after the flush. With `ops`, the caret is
572
+ * declared before the command's operations run — minted while its text
573
+ * is live, so it survives them — and written only when they applied;
574
+ * meanwhile the seam repair leaves this view's endpoints to it.
574
575
  */
575
- caret = (target, ...rest) => {
576
- // The form is the arguments', not the target's: a nullish caret still runs its `ops`.
577
- if (typeof rest[0] === 'number') {
578
- return this.#caret(target, rest[0], rest[1]);
579
- }
580
- const ops = rest[0];
581
- const caret = target;
582
- // `{ block, offset }`: block offsets (a handle or an id), mapped to the text segment that shows the offset.
576
+ caret = (caret, ops) => {
583
577
  const block = typeof caret?.block === 'string' ? this.edytor.idToBlock.get(caret.block) : caret?.block;
584
578
  const at = block?.textAtOffset(Math.max(0, caret.offset));
585
- return this.#caret(at?.text, at?.offset ?? 0, ops);
579
+ return this.caretIn(at?.text, at?.offset ?? 0, ops);
586
580
  };
587
- #caret = (text, offset, ops) => {
581
+ /** @internal `caret` at an offset inside a text segment. */
582
+ caretIn = (text, offset, ops) => {
588
583
  const selection = this.edytor.selection;
589
584
  const at = text ? Math.max(0, Math.min(offset, text.length)) : 0;
590
585
  const value = text ? selection.textValue(text, at) : null;
@@ -640,7 +635,8 @@ export class Dispatcher {
640
635
  try {
641
636
  for (let i = 0; i < this.queue.length; i++) {
642
637
  if (i === 1000) {
643
- console.warn('[edytor] normalization is not converging; skipping further passes');
638
+ if (DEV)
639
+ console.warn('[edytor] normalization is not converging; skipping further passes');
644
640
  break;
645
641
  }
646
642
  const entry = this.queue[i];
@@ -652,7 +648,7 @@ export class Dispatcher {
652
648
  const count = (counts.get(id) ?? 0) + 1;
653
649
  counts.set(id, count);
654
650
  if (count > MAX_PASSES) {
655
- if (count === MAX_PASSES + 1)
651
+ if (DEV && count === MAX_PASSES + 1)
656
652
  console.warn(`edytor: ${normalize} on block "${id}" exceeded ${MAX_PASSES - 1} passes — a plugin normalizer is not converging; skipping further passes`);
657
653
  continue;
658
654
  }
@@ -114,6 +114,7 @@ export class History {
114
114
  replay = (command) => !this.edytor.dispatcher.permits() ? 'refused' : this.#run(command) ? 'applied' : 'noop';
115
115
  #run(command) {
116
116
  const { undoManager: um, transaction: key, selection, facade } = this.edytor;
117
+ this.edytor.dataWrites.flush(); // this task's property writes are a step first
117
118
  this.#open = null;
118
119
  // Other writers (remote-apply and repair restores) stand aside while the
119
120
  // replay commits: this view's recorded value decides.
@@ -3,9 +3,12 @@
3
3
  * (`## ` → a heading, `:smile:` → 😄). The one engine behind every
4
4
  * plugin's `inputRules`: it reads the typed text and the caret, finds the
5
5
  * first rule whose `find` ends at the typed text, and replaces the typed
6
- * `insertText` with the rule's replacement — a command of its own, the
7
- * matched text's removal leading its first operation (`dispatcher.lead`), so
8
- * a refusal of either writes neither and the typed text lands as typed.
6
+ * `insertText` with two steps: the typed text as typed (an insertion, which
7
+ * may continue the typing before it), then the rule's replacement, a command
8
+ * of its own whose first operation the matched text's removal leads
9
+ * (`dispatcher.lead`), so a refusal of either writes neither and the typed
10
+ * text stays as typed. The first undo gives the typed text back, the
11
+ * caret after it (as the autolink does).
9
12
  */
10
13
  import type { Block } from '../block/block.svelte.js';
11
14
  import type { Edytor } from '../edytor.svelte.js';
@@ -47,7 +47,7 @@ export const inputRulesHook = (edytor, rules) => {
47
47
  /** The typed text lands as typed, the caret after it. */
48
48
  const typeAsIs = () => {
49
49
  edytor.insertText(...payload);
50
- dispatcher.caret(text, yStart + value.length);
50
+ dispatcher.caretIn(text, yStart + value.length);
51
51
  };
52
52
  /**
53
53
  * Run `rule` over `match`: whether it wrote, or `null` when it declined
@@ -57,7 +57,8 @@ export const inputRulesHook = (edytor, rules) => {
57
57
  /** The match's start in the segment; the match in the block, `from` to the caret. */
58
58
  const start = match.index;
59
59
  const from = text.segStart + start;
60
- const to = text.segStart + yStart;
60
+ // The typed text is in the document: the match ends after it.
61
+ const to = text.segStart + yStart + value.length;
61
62
  let removed = null;
62
63
  /** Remove the match in the same plan as `then`'s first operation: `ctx.remove`. */
63
64
  const remove = (then) => {
@@ -104,12 +105,15 @@ export const inputRulesHook = (edytor, rules) => {
104
105
  prevent(() => {
105
106
  running = true;
106
107
  try {
108
+ // The typed text first, a step the replacement does not join.
109
+ const last = dispatcher.last;
110
+ typeAsIs();
111
+ if (dispatcher.last === last || dispatcher.last?.status !== 'applied')
112
+ return;
107
113
  for (const { rule, match } of matches) {
108
- const written = apply(rule, match);
109
- if (written !== null)
110
- return written || typeAsIs();
114
+ if (apply(rule, match) !== null)
115
+ return;
111
116
  }
112
- typeAsIs();
113
117
  }
114
118
  finally {
115
119
  running = false;
@@ -25,6 +25,17 @@ export type BlockMoveRequest = {
25
25
  } | {
26
26
  direction: BlockMoveDirection;
27
27
  });
28
+ /**
29
+ * The view's nesting rule (`allowNesting`, off by default): whether a user
30
+ * gesture may place `blocks` under `parent`. With nesting off, blocks land
31
+ * only at the root or in a block made to hold children — a container (a
32
+ * list, a column, a toggle's or a callout's body: `isContainer`, the kind's
33
+ * `container`), an island (a code block's lines) — or stay under the parent
34
+ * they already have (a document holding nested blocks still edits them).
35
+ * Every move (`destination`: keys, drops, menus, `edytor.move`) and every
36
+ * new block a drop places (`canPlaceNew`) reads it.
37
+ */
38
+ export declare const nestsInto: (edytor: Edytor, parent: Block, blocks?: readonly Block[]) => boolean;
28
39
  /** Whether the move is allowed: for runs (`parts`), whether one of them may move. */
29
40
  export declare const canMove: (edytor: Edytor, request: BlockMoveRequest) => boolean;
30
41
  /**
@@ -38,3 +49,25 @@ export declare const canMove: (edytor: Edytor, request: BlockMoveRequest) => boo
38
49
  * (`announcer.moved`).
39
50
  */
40
51
  export declare const move: (edytor: Edytor, request: BlockMoveRequest) => Block[];
52
+ /** A new block a drop places (`placeNew`): its kind, and its id when the caller names it. */
53
+ export type NewBlock = {
54
+ type: string;
55
+ id?: string;
56
+ };
57
+ /**
58
+ * Whether new blocks of `kinds` may land at `target`'s `position` (a file
59
+ * dropped there, `placeNew`): the move's places (`destination`) asked of
60
+ * blocks not yet in the document — the container rule (`fits`) of the
61
+ * parent they land in, never inside a void or an island; beside, a layout
62
+ * kind and a target outside any island (`placeBeside` takes any block a
63
+ * column holds).
64
+ */
65
+ export declare const canPlaceNew: (edytor: Edytor, kinds: readonly string[], target: Block, position: BlockMovePosition) => boolean;
66
+ /**
67
+ * Insert new blocks at `target`'s `position` (a file drop's placement):
68
+ * before or after it, inside it (its last children), or beside it — inserted
69
+ * after the block it stands beside (`besideAt`) and moved there by the one
70
+ * move path (`layout.place-beside`) — in one transaction. The caller's
71
+ * command makes it one undo step. Answers the ids placed (none: refused).
72
+ */
73
+ export declare const placeNew: (edytor: Edytor, blocks: readonly NewBlock[], target: Block, position: BlockMovePosition) => string[];
@@ -24,6 +24,22 @@ const placement = (edytor, request) => {
24
24
  return step?.[0] ? { blocks, target: step[0], position: step[1] } : null;
25
25
  };
26
26
  const POSITIONS = ['before', 'after', 'inside', ...BESIDE];
27
+ /**
28
+ * The view's nesting rule (`allowNesting`, off by default): whether a user
29
+ * gesture may place `blocks` under `parent`. With nesting off, blocks land
30
+ * only at the root or in a block made to hold children — a container (a
31
+ * list, a column, a toggle's or a callout's body: `isContainer`, the kind's
32
+ * `container`), an island (a code block's lines) — or stay under the parent
33
+ * they already have (a document holding nested blocks still edits them).
34
+ * Every move (`destination`: keys, drops, menus, `edytor.move`) and every
35
+ * new block a drop places (`canPlaceNew`) reads it.
36
+ */
37
+ export const nestsInto = (edytor, parent, blocks = []) => edytor.allowNesting ||
38
+ parent.isRoot ||
39
+ parent.isContainer ||
40
+ !!parent.definition.container ||
41
+ edytor.facade.isIsland(parent.id) ||
42
+ (blocks.length > 0 && blocks.every((block) => block.parent === parent));
27
43
  /**
28
44
  * The document write a move request is, or `null` when refused: a
29
45
  * well-formed request, then the document's one structural answer
@@ -59,7 +75,7 @@ const destination = (edytor, request) => {
59
75
  ? { blocks, write: () => edytor.unNestBlocks(ids) }
60
76
  : null;
61
77
  const to = parent?.isRoot ? null : parent?.id;
62
- if (!parent || !edytor.facade.canPlace(ids, to))
78
+ if (!parent || !nestsInto(edytor, parent, blocks) || !edytor.facade.canPlace(ids, to))
63
79
  return null;
64
80
  const at = position === 'inside' ? parent.children.length : target.index + (position === 'after' ? 1 : 0);
65
81
  // The document counts the destination's children without the moved blocks.
@@ -122,3 +138,57 @@ const place = (edytor, request) => {
122
138
  const result = move.write();
123
139
  return result.status === 'applied' ? move.blocks : [];
124
140
  };
141
+ /**
142
+ * Whether new blocks of `kinds` may land at `target`'s `position` (a file
143
+ * dropped there, `placeNew`): the move's places (`destination`) asked of
144
+ * blocks not yet in the document — the container rule (`fits`) of the
145
+ * parent they land in, never inside a void or an island; beside, a layout
146
+ * kind and a target outside any island (`placeBeside` takes any block a
147
+ * column holds).
148
+ */
149
+ export const canPlaceNew = (edytor, kinds, target, position) => {
150
+ const { facade } = edytor;
151
+ if (edytor.readonly || !kinds.length || !target.isInTree || target.isRoot)
152
+ return false;
153
+ if (!POSITIONS.includes(position) || facade.insideIsland(target.id))
154
+ return false;
155
+ if (isBeside(position))
156
+ return ([...edytor.blocks.values()].some((definition) => definition.layout) &&
157
+ !facade.isIsland(target.id));
158
+ const parent = position === 'inside' ? target : target.parent;
159
+ if (!parent || !nestsInto(edytor, parent))
160
+ return false;
161
+ const to = parent.isRoot ? null : parent.id;
162
+ if (to !== null && (facade.isVoid(to) || facade.isIsland(to)))
163
+ return false;
164
+ return kinds.every((kind) => facade.fits(to, kind));
165
+ };
166
+ /**
167
+ * Insert new blocks at `target`'s `position` (a file drop's placement):
168
+ * before or after it, inside it (its last children), or beside it — inserted
169
+ * after the block it stands beside (`besideAt`) and moved there by the one
170
+ * move path (`layout.place-beside`) — in one transaction. The caller's
171
+ * command makes it one undo step. Answers the ids placed (none: refused).
172
+ */
173
+ export const placeNew = (edytor, blocks, target, position) => {
174
+ const kinds = blocks.map((block) => block.type);
175
+ if (!canPlaceNew(edytor, kinds, target, position))
176
+ return [];
177
+ const beside = isBeside(position);
178
+ const at = beside ? (edytor.idToBlock.get(edytor.facade.besideAt(target.id)) ?? target) : target;
179
+ const parent = position === 'inside' ? target : at.parent;
180
+ const destination = position === 'inside'
181
+ ? { parent: target.id, index: target.children.length }
182
+ : {
183
+ parent: !parent || parent.isRoot ? null : parent.id,
184
+ index: at.index + (position === 'before' ? 0 : 1)
185
+ };
186
+ return edytor.transact(() => {
187
+ const { ids } = edytor.insertBlocks(destination, blocks.map((block) => ({ ...block })));
188
+ if (!beside || !ids.length)
189
+ return [...ids];
190
+ const placed = ids.flatMap((id) => edytor.idToBlock.get(id) ?? []);
191
+ move(edytor, { blocks: placed, target, position });
192
+ return [...ids];
193
+ });
194
+ };
@@ -23,6 +23,7 @@
23
23
  * lands it on no caret stop (`landed`).
24
24
  */
25
25
  import type { Edytor } from '../edytor.svelte.js';
26
+ import type { Block } from '../block/block.svelte.js';
26
27
  import type { HotKey } from './keymap.js';
27
28
  /**
28
29
  * With no target (`untargeted`: the value is `none`, no gesture placed the
@@ -48,6 +49,12 @@ export declare const move: (edytor: Edytor, unit: Unit, key: Dir | "left" | "rig
48
49
  * moves keep the goal column. Answers whether a text selection moved.
49
50
  */
50
51
  export declare const extendVertically: (edytor: Edytor, dir: Dir) => boolean;
52
+ /**
53
+ * The block a collapsed text caret sits on the last line of (a line box
54
+ * where the view lays one out, else a line break), if it does: where
55
+ * ArrowDown leaves the block (`nav.trailing.exit`, `body.down`).
56
+ */
57
+ export declare const onLastLine: (edytor: Edytor) => Block | undefined;
51
58
  /**
52
59
  * The trailing paragraph (`nav.trailing`, Notion): the caret goes to an
53
60
  * empty top-level block of the root's default kind after the document's