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
@@ -10,7 +10,7 @@
10
10
  import type { BlockHandleController } from './BlockHandleController.svelte.js';
11
11
  import { gapBefore, handleSpan } from '../columns/gaps.js';
12
12
  import { renderSkipped, rendered } from '../../surface/overlay.js';
13
- import { getOwnRowBottom } from './geometry.js';
13
+ import { getOwnRowBottom } from '../../dnd/geometry.js';
14
14
 
15
15
  /**
16
16
  * The handles, in the overlay: one per registered movable block that
@@ -201,14 +201,15 @@
201
201
  pointer-events: none;
202
202
  }
203
203
 
204
- /* Over a gap between two columns (one hit rule, the handles'): the handle
205
- * spans the gap right of the column resize band, its `+` (compact) and its
206
- * grip together flush with its block (Notion's "+ ⋮⋮"), and its box takes
207
- * the pointer at its block's whole row, shown or not — a pointer there
208
- * hovers the block, so its grip is reached from any side. The band is the
209
- * gap's left part (`columns/gaps.ts`, one measurement): at the default
210
- * 46px gap (10 + 18 + 18) neither button is under it, and where its guide
211
- * shows a press resizes. */
204
+ /* Over a gap between two columns (one hit rule, the handles'): the handle's
205
+ * box spans the gap right of the column resize band, centered in the gap
206
+ * (`columns/gaps.ts`, one measurement), and takes the pointer at its
207
+ * block's whole row, shown or not — a pointer there hovers the block, so
208
+ * its grip is reached from any side. Its `+` (compact) and grip stand
209
+ * together flush with its block (Notion's "+ ⋮⋮"), never shrunk: wider
210
+ * than the box, the `+` reaches left under the band (the resizers' layer
211
+ * is above the handles'): where the band's guide shows, at every height,
212
+ * a press resizes; the `+`'s part left of the band takes its own. */
212
213
  [data-edytor-block-handle-host][data-over-gap='true'] {
213
214
  box-sizing: border-box;
214
215
  width: var(--edytor-handle-gap-width, auto);
@@ -216,6 +217,10 @@
216
217
  padding-inline-end: 0;
217
218
  }
218
219
 
220
+ [data-edytor-block-handle-host][data-over-gap='true'] > :global(*) {
221
+ flex-shrink: 0;
222
+ }
223
+
219
224
  [data-edytor-block-handle-host][data-over-gap='true']::before {
220
225
  content: '';
221
226
  position: absolute;
@@ -238,27 +243,4 @@
238
243
  padding-inline-end: 0;
239
244
  }
240
245
  }
241
-
242
- /* Where a handle's drag lands (`DropIndicator`): the bar (4px) and the nest
243
- * backdrop, placed in the overlay and shown (`data-shown`) by the plugin;
244
- * their look is a theme's (`edytor/themes/notion.css`). */
245
- :global([data-edytor-drop-indicator]),
246
- :global([data-edytor-drop-backdrop]) {
247
- position: absolute;
248
- pointer-events: none;
249
- }
250
-
251
- :global([data-edytor-drop-indicator]) {
252
- z-index: 100;
253
- box-sizing: border-box;
254
- height: 4px;
255
- }
256
-
257
- :global([data-edytor-drop-backdrop]) {
258
- z-index: 99;
259
- }
260
-
261
- :global([data-edytor-drop-backdrop]:not([data-shown='true'])) {
262
- visibility: hidden;
263
- }
264
246
  </style>
@@ -3,6 +3,7 @@ import { SvelteMap, SvelteSet } from 'svelte/reactivity';
3
3
  import BlockHandles from './BlockHandles.svelte';
4
4
  import { BlockHandleController } from './BlockHandleController.svelte.js';
5
5
  import { hidden } from '../../selection/visibility.js';
6
+ import { isDropTarget } from '../../dnd/placement.js';
6
7
  import { all, listen } from '../../utils/listen.js';
7
8
  /**
8
9
  * Block handles in the overlay, created lazily: a handle mounts
@@ -123,7 +124,7 @@ export const createBlockHandlesPlugin = (options = {}) => (edytor) => {
123
124
  },
124
125
  onBlockAttached: ({ node, block }) => {
125
126
  // A table's rows move by its own row menus, its cells never (`table.fits`).
126
- if (!block.movable || edytor.facade.isTableRow(block.id))
127
+ if (!isDropTarget(edytor, block))
127
128
  return;
128
129
  const offDropTarget = controller.addDropTarget(node, block);
129
130
  // A layout and its columns are drop targets but have no handle (as
@@ -1,10 +1,3 @@
1
- /**
2
- * Notion's drag ghost: a copy of the dragged blocks, rendered as they look in
3
- * the editor, scaled down, that follows the pointer (the browser adds its own
4
- * drag-image translucency). Cloned once when the drag starts and mounted by
5
- * the drag library for the frame the browser takes its picture
6
- * (`setCustomNativeDragPreview`), then removed with it.
7
- */
8
1
  export type DragPreview = {
9
2
  /** The ghost, `[data-edytor-drag-preview]`, to mount in the drag library's container. */
10
3
  element: HTMLElement;
@@ -33,3 +26,29 @@ export declare const dragPreview: (root: HTMLElement, nodes: HTMLElement[], poin
33
26
  clientX: number;
34
27
  clientY: number;
35
28
  }, columns?: number[]) => DragPreview;
29
+ type Pointer = {
30
+ clientX: number;
31
+ clientY: number;
32
+ };
33
+ /**
34
+ * One of the editor's own drags from `element` (a block's handle, a table's
35
+ * grip): `canDrag` asks whether it may start, `data` is what its targets
36
+ * read, `id` the block another view sees it carry (no view takes it as a
37
+ * foreign drop); `begin` runs as it starts (the ghost and the targets),
38
+ * `start` once it has, and `end` after the drop target's own drop (the drag
39
+ * library tells the source first).
40
+ */
41
+ export declare const dragSource: (element: HTMLElement, { canDrag, data, id, begin, start, end }: {
42
+ canDrag: () => boolean;
43
+ data: () => Record<string | symbol, unknown>;
44
+ id: () => string;
45
+ begin: (pointer: Pointer, setImage: DataTransfer["setDragImage"] | null) => void;
46
+ start?: () => void;
47
+ end: (dropped: boolean) => void;
48
+ }) => import("../../dnd/vendor/pragmatic/pragmatic-drag-and-drop/internal-types").CleanupFn;
49
+ /**
50
+ * The drag's ghost (`dragPreview` of `nodes` under `root`), mounted for the
51
+ * browser's picture of it, when the drag has an image to set.
52
+ */
53
+ export declare const ghost: (setImage: DataTransfer["setDragImage"] | null, root: HTMLElement | null | undefined, nodes: HTMLElement[], pointer: Pointer, columns?: number[]) => void;
54
+ export {};
@@ -1,3 +1,5 @@
1
+ import { draggable, setCustomNativeDragPreview } from '../../dnd/pragmatic.js';
2
+ import { EDYTOR_BLOCK_DRAG_MIME } from '../../events/onDrop.js';
1
3
  /**
2
4
  * Notion's drag ghost: a copy of the dragged blocks, rendered as they look in
3
5
  * the editor, scaled down, that follows the pointer (the browser adds its own
@@ -227,3 +229,34 @@ export const dragPreview = (root, nodes, pointer, columns) => {
227
229
  };
228
230
  return { element, offset };
229
231
  };
232
+ /**
233
+ * One of the editor's own drags from `element` (a block's handle, a table's
234
+ * grip): `canDrag` asks whether it may start, `data` is what its targets
235
+ * read, `id` the block another view sees it carry (no view takes it as a
236
+ * foreign drop); `begin` runs as it starts (the ghost and the targets),
237
+ * `start` once it has, and `end` after the drop target's own drop (the drag
238
+ * library tells the source first).
239
+ */
240
+ export const dragSource = (element, { canDrag, data, id, begin, start, end }) => draggable({
241
+ element,
242
+ canDrag,
243
+ getInitialData: data,
244
+ getInitialDataForExternal: () => ({ [EDYTOR_BLOCK_DRAG_MIME]: id() }),
245
+ onGenerateDragPreview: ({ nativeSetDragImage, location }) => begin(location.current.input, nativeSetDragImage),
246
+ onDragStart: start,
247
+ onDrop: ({ location }) => queueMicrotask(() => end(location.current.dropTargets.length > 0))
248
+ });
249
+ /**
250
+ * The drag's ghost (`dragPreview` of `nodes` under `root`), mounted for the
251
+ * browser's picture of it, when the drag has an image to set.
252
+ */
253
+ export const ghost = (setImage, root, nodes, pointer, columns) => {
254
+ if (!setImage || !root || !nodes.length)
255
+ return;
256
+ const preview = dragPreview(root, nodes, pointer, columns);
257
+ setCustomNativeDragPreview({
258
+ nativeSetDragImage: setImage,
259
+ getOffset: preview.offset,
260
+ render: ({ container }) => container.append(preview.element)
261
+ });
262
+ };
@@ -69,6 +69,11 @@ export declare class BlockMenuController {
69
69
  * after). Delete and Turn into act on their `members`, its items too.
70
70
  */
71
71
  blocks: Block[];
72
+ /**
73
+ * Where the menu stands: the handle that opened it (a `ChromeAnchor`:
74
+ * your `menu` places itself beside it, its flyout beside the menu);
75
+ * `null` while closed.
76
+ */
72
77
  anchor: HTMLElement | null;
73
78
  query: string;
74
79
  /** The keyboard's row. */
@@ -63,7 +63,12 @@ export class BlockMenuController {
63
63
  * after). Delete and Turn into act on their `members`, its items too.
64
64
  */
65
65
  blocks = $state([]);
66
- anchor = null;
66
+ /**
67
+ * Where the menu stands: the handle that opened it (a `ChromeAnchor`:
68
+ * your `menu` places itself beside it, its flyout beside the menu);
69
+ * `null` while closed.
70
+ */
71
+ anchor = $state.raw(null);
67
72
  query = $state('');
68
73
  /** The keyboard's row. */
69
74
  index = $state(0);
@@ -473,7 +478,7 @@ export class BlockMenuController {
473
478
  caret(block) {
474
479
  const text = block && shownText(block, 'first');
475
480
  if (text || !block?.isInTree)
476
- this.edytor.dispatcher.caret(text, 0);
481
+ this.edytor.dispatcher.caretIn(text, 0);
477
482
  else
478
483
  this.edytor.selection.select(blockSelection([block.id]));
479
484
  this.focus();
@@ -11,14 +11,9 @@ import { BlockMenuController, colorCommands } from './BlockMenuController.svelte
11
11
  export const createBlockMenuPlugin = (options = {}) => (edytor) => {
12
12
  const controller = new BlockMenuController(edytor, options);
13
13
  /**
14
- * Beside the handle: 8px right of it, top-aligned; left of it when the
15
- * right has no room; flipped up when the space below is short. The Turn
16
- * into flyout opens right of the menu, or left of it when the right has
17
- * no room, top-aligned with it, moved up as far as the viewport needs
18
- * (never above its top edge), and scrolls inside when taller (Notion):
19
- * every row is in the viewport, reachable by the mouse. It follows
20
- * scrolls and resizes, and closes when the handle leaves the view.
21
- * Measured in the overlay's frame; the placement (or the close) is written after.
14
+ * The menu places itself by `controller.anchor` (the handle), its
15
+ * flyout by the menu; measured in the overlay's frame, the menu closes
16
+ * when the handle leaves the page or the view.
22
17
  */
23
18
  const close = () => controller.close(false);
24
19
  const place = (host) => {
@@ -27,50 +22,10 @@ export const createBlockMenuPlugin = (options = {}) => (edytor) => {
27
22
  return;
28
23
  if (!anchor?.isConnected)
29
24
  return close;
30
- const view = host.ownerDocument.defaultView;
31
- const menu = host.querySelector('[data-edytor-block-menu]') ??
32
- host.firstElementChild;
33
- if (!view || !menu)
34
- return;
35
- const [gap, edge, between] = [8, 8, 4];
36
25
  const rect = anchor.getBoundingClientRect();
37
- if (rect.bottom < 0 || rect.top > view.innerHeight)
26
+ const height = host.ownerDocument.defaultView?.innerHeight ?? Infinity;
27
+ if (rect.bottom < 0 || rect.top > height)
38
28
  return close;
39
- // Layout sizes, not the box: the menus open with a scale (0.98 → 1), and a
40
- // box read mid-animation would place them up to 2% short of their size.
41
- const [width, height] = [menu.offsetWidth, menu.offsetHeight];
42
- const right = rect.right + gap;
43
- const left = rect.left - gap - width;
44
- let x = right + width <= view.innerWidth - edge
45
- ? right
46
- : left >= edge
47
- ? left
48
- : Math.max(edge, Math.min(right, view.innerWidth - edge - width));
49
- const y = rect.top + height <= view.innerHeight - edge
50
- ? rect.top
51
- : Math.max(edge, Math.min(rect.bottom, view.innerHeight - edge) - height);
52
- const flyout = host.querySelector('[data-edytor-block-menu-flyout]');
53
- const frame = flyout?.parentElement;
54
- let side = '';
55
- let shift = '';
56
- if (flyout && frame) {
57
- const box = { width: flyout.offsetWidth, height: flyout.offsetHeight };
58
- // Left of the menu when its right has no room (the menu stays where it is).
59
- if (x + width + between + box.width > view.innerWidth - edge &&
60
- x - between - box.width >= edge) {
61
- side = 'row-reverse';
62
- x -= between + box.width;
63
- }
64
- const top = Math.max(edge, Math.min(y, view.innerHeight - edge - box.height));
65
- shift = `${top - y}px`;
66
- }
67
- return () => {
68
- Object.assign(host.style, { left: `${x}px`, top: `${y}px` });
69
- if (frame)
70
- frame.style.flexDirection = side;
71
- if (flyout)
72
- flyout.style.marginTop = shift;
73
- };
74
29
  };
75
30
  return {
76
31
  // Colours by name in the slash menu (`/red`), as in Notion.
@@ -9,17 +9,33 @@ import type { Snippet } from 'svelte';
9
9
  import type { Attachment } from 'svelte/attachments';
10
10
  import type { Edytor } from '../edytor.svelte.js';
11
11
  import { type BlockActivation, type BlockAddition } from './blockHandles/BlockHandleController.svelte.js';
12
+ /**
13
+ * Where a chrome surface stands, for the app's markup to place itself
14
+ * (Floating UI's virtual element: `computePosition(anchor, floating, …)`,
15
+ * or bits-ui's `customAnchor`): its box read now, in client pixels, on
16
+ * every call (the caret, the selection, a block, a grip, a button), and
17
+ * the element whose scrolling ancestors move it. The package places no
18
+ * popup.
19
+ */
20
+ export type ChromeAnchor = {
21
+ getBoundingClientRect: () => DOMRect;
22
+ readonly contextElement?: Element;
23
+ };
24
+ /** A {@link ChromeAnchor} reading `rect` (an empty box while there is none) inside `context`. */
25
+ export declare const virtualAnchor: (rect: () => DOMRect | null | undefined, context: () => Element | null | undefined) => ChromeAnchor;
12
26
  /**
13
27
  * Mount the app's `snippet` for a chrome surface in a fixed host of the
14
- * overlay (`data-<name>`, placed by `measure`), rendered with `payload`
15
- * while `shown` holds and closed (`close`) when the view turns readonly.
16
- * Without a snippet the surface renders nothing (its controller still
17
- * works): nothing is mounted. Answers the teardown.
28
+ * overlay (`data-<name>`), rendered with `payload` while `shown` holds and
29
+ * closed (`close`) when the view turns readonly; the markup places itself
30
+ * by its controller's `anchor`. `measure` runs on the overlay's frames (a
31
+ * surface whose anchor left the page closes there). Without a snippet the
32
+ * surface renders nothing (its controller still works): nothing is
33
+ * mounted. Answers the teardown.
18
34
  */
19
35
  export declare const mountChrome: <T>(edytor: Edytor, snippet: Snippet<[T]> | undefined, payload: T, shown: () => unknown, place: {
20
36
  name: string;
21
37
  zIndex: number;
22
- measure: (host: HTMLElement, origin: DOMRect) => (() => void) | void;
38
+ measure?: (host: HTMLElement, origin: DOMRect) => (() => void) | void;
23
39
  }, close?: () => void) => () => void;
24
40
  /**
25
41
  * One row of a chrome list a controller builds for the app's markup: the
@@ -68,6 +84,12 @@ export declare const identify: (node: HTMLElement, id: string, role?: string) =>
68
84
  export declare const attribute: (node: HTMLElement, name: string, value: string | undefined) => void;
69
85
  /** Whether `node` may name a highlighted row (`aria-activedescendant`): a field, or an element with a role. */
70
86
  export declare const namesRows: (node: HTMLElement) => boolean;
87
+ /**
88
+ * Every press in `document` (capture; WebKit's lone `mousedown` after a
89
+ * drag too, `onPress`) outside the elements `inside` matches: `outside`
90
+ * hears it. Answers the teardown.
91
+ */
92
+ export declare const onPressOutside: (edytor: Edytor, document: Document, inside: string, outside: (event: MouseEvent) => void) => () => void;
71
93
  /**
72
94
  * A handle menu's events on the editor `node` (the block menu, the `+`'s
73
95
  * menu): a grip's activation (`BLOCK_ACTIVATE_EVENT`), a `+`'s offer
@@ -2,15 +2,24 @@ import Chrome from './Chrome.svelte';
2
2
  import { onPress } from '../events/onFocus.js';
3
3
  import { all, listen } from '../utils/listen.js';
4
4
  import { BLOCK_ACTIVATE_EVENT, BLOCK_ADD_EVENT } from './blockHandles/BlockHandleController.svelte.js';
5
+ /** A {@link ChromeAnchor} reading `rect` (an empty box while there is none) inside `context`. */
6
+ export const virtualAnchor = (rect, context) => ({
7
+ getBoundingClientRect: () => rect() ?? new DOMRect(),
8
+ get contextElement() {
9
+ return context() ?? undefined;
10
+ }
11
+ });
5
12
  /**
6
13
  * Mount the app's `snippet` for a chrome surface in a fixed host of the
7
- * overlay (`data-<name>`, placed by `measure`), rendered with `payload`
8
- * while `shown` holds and closed (`close`) when the view turns readonly.
9
- * Without a snippet the surface renders nothing (its controller still
10
- * works): nothing is mounted. Answers the teardown.
14
+ * overlay (`data-<name>`), rendered with `payload` while `shown` holds and
15
+ * closed (`close`) when the view turns readonly; the markup places itself
16
+ * by its controller's `anchor`. `measure` runs on the overlay's frames (a
17
+ * surface whose anchor left the page closes there). Without a snippet the
18
+ * surface renders nothing (its controller still works): nothing is
19
+ * mounted. Answers the teardown.
11
20
  */
12
21
  export const mountChrome = (edytor, snippet, payload, shown, place, close) => snippet
13
- ? edytor.overlay.mount((Chrome), { edytor, snippet, payload, shown, close }, place.name, place.zIndex, place.measure)
22
+ ? edytor.overlay.mount((Chrome), { edytor, snippet, payload, shown, close }, place.name, place.zIndex, place.measure ?? (() => { }))
14
23
  : () => { };
15
24
  /**
16
25
  * Write `value` as `node`'s attribute `name`, only when it changes:
@@ -74,6 +83,15 @@ export const attribute = (node, name, value) => {
74
83
  };
75
84
  /** Whether `node` may name a highlighted row (`aria-activedescendant`): a field, or an element with a role. */
76
85
  export const namesRows = (node) => isField(node) || node.hasAttribute('role');
86
+ /**
87
+ * Every press in `document` (capture; WebKit's lone `mousedown` after a
88
+ * drag too, `onPress`) outside the elements `inside` matches: `outside`
89
+ * hears it. Answers the teardown.
90
+ */
91
+ export const onPressOutside = (edytor, document, inside, outside) => onPress(edytor, document, (event) => {
92
+ if (!event.target?.closest?.(inside))
93
+ outside(event);
94
+ }, true);
77
95
  /**
78
96
  * A handle menu's events on the editor `node` (the block menu, the `+`'s
79
97
  * menu): a grip's activation (`BLOCK_ACTIVATE_EVENT`), a `+`'s offer
@@ -81,7 +99,4 @@ export const namesRows = (node) => isField(node) || node.hasAttribute('role');
81
99
  * matches (WebKit's lone `mousedown` after a drag too, `onPress`). Answers
82
100
  * the teardown.
83
101
  */
84
- export const handleMenuEvents = (edytor, node, activate, add, inside, outside) => all(listen(node, BLOCK_ACTIVATE_EVENT, activate), listen(node, BLOCK_ADD_EVENT, add), onPress(edytor, node.ownerDocument, (event) => {
85
- if (!event.target?.closest?.(inside))
86
- outside();
87
- }, true));
102
+ export const handleMenuEvents = (edytor, node, activate, add, inside, outside) => all(listen(node, BLOCK_ACTIVATE_EVENT, activate), listen(node, BLOCK_ADD_EVENT, add), onPressOutside(edytor, node.ownerDocument, inside, outside));
@@ -2,7 +2,7 @@
2
2
  import { getContext } from 'svelte';
3
3
  import type { BlockView } from '../../plugins.js';
4
4
  import type { Edytor } from '../../edytor.svelte.js';
5
- import { DEFAULT_CODE_SETTINGS, codeSettings } from './languages.js';
5
+ import { DEFAULT_CODE_SETTINGS } from './languages.js';
6
6
  import { languageMenus } from './languageMenu.svelte.js';
7
7
  import { CodeHeader } from './header.svelte.js';
8
8
 
@@ -14,8 +14,10 @@
14
14
 
15
15
  /** The options of the view rendering it (a suggestion's preview included). */
16
16
  const edytor = getContext<Edytor>('edytor');
17
- const settings = codeSettings.get(edytor) ?? DEFAULT_CODE_SETTINGS;
18
- const header = new CodeHeader(() => block, settings, languageMenus.get(edytor));
17
+ // The first code plugin listed owns the view's options (it draws the kind).
18
+ const menu = languageMenus.get(edytor);
19
+ const settings = menu?.settings ?? DEFAULT_CODE_SETTINGS;
20
+ const header = new CodeHeader(() => block, settings, menu);
19
21
  $effect(() => header.dispose);
20
22
  </script>
21
23
 
@@ -1,19 +1,18 @@
1
1
  <script module lang="ts">
2
+ import { codeForms } from '../../export/forms.js';
2
3
  import './code.css';
3
4
  import type { Plugin, MarkSnippetPayload, BlockSnippetPayload } from '../../plugins.js';
4
5
  import { Text } from '../../text/text.svelte.js';
5
6
  import { runIntent } from '../../events/beforeInputCommands.js';
6
- import { codeKinds } from '../../crdt/semantics.js';
7
+
7
8
  import { shown } from '../../selection/visibility.js';
8
9
  import { caretBeside } from '../../selection/replaceSelection.js';
9
10
  import { siblingSlot } from '../../session/handles.js';
10
11
  import CodeHeader from './CodeHeader.svelte';
11
- import LanguageMenuPanel from './LanguageMenu.svelte';
12
12
  import { LanguageMenu, languageMenus } from './languageMenu.svelte.js';
13
13
  import { codeEnglish, keywordsOf, labelsOf } from '../../labels.js';
14
14
  import {
15
15
  DEFAULT_CODE_SETTINGS,
16
- codeSettings,
17
16
  languageOf,
18
17
  tokenizeCode,
19
18
  type CodePluginOptions,
@@ -47,8 +46,9 @@
47
46
  header: options.header,
48
47
  menu: options.menu
49
48
  };
50
- codeSettings.set(edytor, own);
51
- // The view's language list: the first code plugin listed owns it.
49
+ // The view's code options and language list: the first code plugin
50
+ // listed owns them, as it draws the kind (first listed wins); the
51
+ // header reads them from it.
52
52
  if (edytor && !languageMenus.has(edytor))
53
53
  languageMenus.set(edytor, new LanguageMenu(edytor, own));
54
54
  const ownMenu = () => {
@@ -115,7 +115,7 @@
115
115
  onEdytorAttached: ({ node }) => {
116
116
  const menu = ownMenu();
117
117
  const snippet = menu?.settings.menu;
118
- return snippet && menu.panel.mount(node, LanguageMenuPanel, { menu, snippet });
118
+ return snippet && menu.panel.mount(node, snippet, menu, () => menu.open !== null);
119
119
  },
120
120
  hotkeys: {
121
121
  // Alt+F10 at a caret in a code line: the keys go to its language button
@@ -174,7 +174,7 @@
174
174
  edytor.idToBlock.get(
175
175
  edytor.insertBlocks(siblingSlot(line), [{ type: 'codeLine' }]).ids[0] ?? ''
176
176
  );
177
- return prevent(() => edytor.dispatcher.caret(next()?.firstText, 0));
177
+ return prevent(() => edytor.dispatcher.caretIn(next()?.firstText, 0));
178
178
  }
179
179
  prevent(() => runIntent(edytor, 'insertParagraph'));
180
180
  }
@@ -189,7 +189,10 @@
189
189
  prevent(() => {
190
190
  const before = caretBeside(block, 'blockBefore', new Set([block]));
191
191
  edytor.deleteBlock(block.id);
192
- edytor.dispatcher.caret(before?.text ?? code.firstEditableText, before?.offset ?? 0);
192
+ edytor.dispatcher.caretIn(
193
+ before?.text ?? code.firstEditableText,
194
+ before?.offset ?? 0
195
+ );
193
196
  });
194
197
  }
195
198
  // Backspace in an empty block right after a code block removes it; the
@@ -202,7 +205,7 @@
202
205
  prevent(() => {
203
206
  edytor.deleteBlock(block.id);
204
207
  const text = previous.lastText;
205
- edytor.dispatcher.caret(text, text?.length ?? 0);
208
+ edytor.dispatcher.caretIn(text, text?.length ?? 0);
206
209
  });
207
210
  }
208
211
  if (block.type !== 'codeLine') return;
@@ -213,7 +216,7 @@
213
216
  const [at, line] = [offset - startText.segStart, startText.stringContent];
214
217
  // A closer typed before the same character steps over it.
215
218
  if (CLOSERS.has(value) && line[at] === value) {
216
- prevent(() => edytor.dispatcher.caret(startText, at + 1));
219
+ prevent(() => edytor.dispatcher.caretIn(startText, at + 1));
217
220
  }
218
221
  // Quotes pair only at a word boundary: the apostrophe in `don't` stays single.
219
222
  const inWord = QUOTES.has(value) && /\w/.test((line[at - 1] ?? '') + (line[at] ?? ''));
@@ -230,7 +233,7 @@
230
233
 
231
234
  blocks: {
232
235
  code: {
233
- ...codeKinds.code,
236
+ ...codeForms().code,
234
237
  snippet: code,
235
238
  presets: [
236
239
  {
@@ -244,8 +247,8 @@
244
247
  empty: { content: [], children: [{ type: 'codeLine', content: [{ text: '' }] }] }
245
248
  },
246
249
  codeLine: {
250
+ ...codeForms().codeLine,
247
251
  snippet: codeLine,
248
- html: (_, content) => `<pre><code>${content}</code></pre>`,
249
252
  transformText: ({ text, block }) =>
250
253
  tokenizeCode(text.stringContent, lineLanguage(block.id)),
251
254
  // A line holding newlines keeps its first line; each other becomes a code line after it.
@@ -9,6 +9,7 @@ import { Text } from '../../text/text.svelte.js';
9
9
  import { attribute } from '../chrome.js';
10
10
  import { languageOf } from './languages.js';
11
11
  import { all, listen } from '../../utils/listen.js';
12
+ import { keysOf } from '../list.svelte.js';
12
13
  /** How long Copy reads Copied after a copy, in milliseconds. */
13
14
  const COPIED_FOR = 1200;
14
15
  /** A code block's text: its lines, joined by newlines. */
@@ -112,17 +113,12 @@ export class CodeHeader {
112
113
  const mousedown = (event) => event.preventDefault();
113
114
  // A click from Enter or Space has no pointer (`detail` 0): the keys opened it.
114
115
  const click = (event) => this.toggle(event.detail === 0);
115
- const keydown = (event) => {
116
- if (event.key === 'ArrowDown' || event.key === 'ArrowUp') {
117
- if (!this.expanded)
118
- this.toggle(true);
119
- }
120
- else if (event.key === 'Escape')
121
- this.menu?.release();
122
- else
123
- return;
124
- event.preventDefault();
125
- };
116
+ const open = () => this.expanded || this.toggle(true);
117
+ const keydown = keysOf({
118
+ ArrowDown: open,
119
+ ArrowUp: open,
120
+ Escape: () => this.menu?.release()
121
+ });
126
122
  const off = listen(node, 'mousedown', mousedown);
127
123
  const off2 = listen(node, 'click', click);
128
124
  const off3 = listen(node, 'keydown', keydown);
@@ -41,8 +41,11 @@ export declare class LanguageMenu extends ChromeList {
41
41
  } | null;
42
42
  /** The search field's text. */
43
43
  query: string;
44
- /** The view is readonly (reactive): the list closes. */
45
- get readonly(): boolean;
44
+ /**
45
+ * Where the list stands: the open block's language button
46
+ * (`ChromeAnchor`), which your `menu` places itself under.
47
+ */
48
+ get anchor(): import("../chrome.js").ChromeAnchor;
46
49
  /** The open block's language id. */
47
50
  get current(): string | null;
48
51
  /** The rows the query keeps: a language the plugin does not list first (the block's own), then the list. */
@@ -1,7 +1,7 @@
1
1
  import { untrack } from 'svelte';
2
2
  import { takeKeys } from '../../events/onFocus.js';
3
3
  import { attribute } from '../chrome.js';
4
- import { ChromeList, holdKeys } from '../list.svelte.js';
4
+ import { ChromeList, holdKeys, keysOf } from '../list.svelte.js';
5
5
  import { Anchored } from '../anchored.svelte.js';
6
6
  import { languageOf } from './languages.js';
7
7
  import { all, listen } from '../../utils/listen.js';
@@ -40,9 +40,12 @@ export class LanguageMenu extends ChromeList {
40
40
  this.settings = settings;
41
41
  this.panel = new Anchored(edytor, 'edytor-code-language-host', () => (this.open ? this.#button(this.open.block) : undefined), () => this.close());
42
42
  }
43
- /** The view is readonly (reactive): the list closes. */
44
- get readonly() {
45
- return this.edytor.readonly;
43
+ /**
44
+ * Where the list stands: the open block's language button
45
+ * (`ChromeAnchor`), which your `menu` places itself under.
46
+ */
47
+ get anchor() {
48
+ return this.panel.anchor;
46
49
  }
47
50
  /** The open block's language id. */
48
51
  get current() {
@@ -121,7 +124,6 @@ export class LanguageMenu extends ChromeList {
121
124
  if (!open)
122
125
  return;
123
126
  this.open = null;
124
- this.panel.box = null;
125
127
  this.query = '';
126
128
  this.edytor.overlay.invalidate();
127
129
  if (keys === 'button')
@@ -152,21 +154,12 @@ export class LanguageMenu extends ChromeList {
152
154
  return all(off, off2);
153
155
  });
154
156
  /** The search field's keys (`keys`). */
155
- #keydown = (event) => {
156
- if (event.isComposing)
157
- return;
158
- const step = { ArrowDown: 1, ArrowUp: -1 }[event.key];
159
- if (step)
160
- this.move(step);
161
- else if (event.key === 'Enter')
162
- this.pick();
163
- else if (event.key === 'Escape')
164
- this.close(this.open?.keys ? 'button' : 'editor');
165
- else
166
- return;
167
- event.preventDefault();
168
- event.stopPropagation();
169
- };
157
+ #keydown = keysOf({
158
+ ArrowDown: () => this.move(1),
159
+ ArrowUp: () => this.move(-1),
160
+ Enter: () => this.pick(),
161
+ Escape: () => this.close(this.open?.keys ? 'button' : 'editor')
162
+ }, true);
170
163
  /**
171
164
  * The keys back to the editor from a language button (its Escape): the
172
165
  * host takes the focus, the caret shown where the selection is.
@@ -7,7 +7,6 @@
7
7
  */
8
8
  import { type LanguageDefinition } from '@tanstack/highlight/core';
9
9
  import type { JSONText } from '../../utils/json.js';
10
- import type { Edytor } from '../../edytor.svelte.js';
11
10
  import type { Snippet } from 'svelte';
12
11
  import type { CodeHeader } from './header.svelte.js';
13
12
  import type { LanguageMenu } from './languageMenu.svelte.js';
@@ -68,8 +67,6 @@ export type CodeSettings = {
68
67
  menu?: Snippet<[LanguageMenu]>;
69
68
  };
70
69
  export declare const DEFAULT_CODE_SETTINGS: CodeSettings;
71
- /** Each view's code options, for its code blocks' header (which reads them through its view). */
72
- export declare const codeSettings: WeakMap<Edytor, CodeSettings>;
73
70
  /** A code block's language id: its `data.language`, else the default. */
74
71
  export declare const languageOf: (data: {
75
72
  language?: unknown;
@@ -99,7 +99,5 @@ export const DEFAULT_CODE_SETTINGS = {
99
99
  languages: CODE_LANGUAGES,
100
100
  defaultLanguage: 'javascript'
101
101
  };
102
- /** Each view's code options, for its code blocks' header (which reads them through its view). */
103
- export const codeSettings = new WeakMap();
104
102
  /** A code block's language id: its `data.language`, else the default. */
105
103
  export const languageOf = (data, { defaultLanguage }) => (typeof data?.language === 'string' && data.language ? data.language : defaultLanguage);