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
@@ -72,6 +72,13 @@ export class SlashMenuController extends TextTriggerController {
72
72
  constructor(edytor) {
73
73
  super(edytor, '/', 'slash-menu');
74
74
  }
75
+ /** A `+`'s menu stands under the `+` (its block while the `+` is gone); a `/` menu at the caret. */
76
+ anchorRect() {
77
+ const { addition } = this;
78
+ const anchor = addition?.anchor?.isConnected ? addition.anchor : addition?.block.node;
79
+ const box = anchor?.getBoundingClientRect();
80
+ return box && (box.width || box.height) ? box : super.anchorRect();
81
+ }
75
82
  /** A `+`'s menu names its highlighted row from its own field, and tells the `+` it opened it. */
76
83
  popupOf(active) {
77
84
  const { addition } = this;
@@ -1,5 +1,3 @@
1
- import { caretRect } from '../triggers/place.js';
2
- import { placeNear } from '../../surface/overlay.js';
3
1
  import { SlashMenuController } from './SlashMenuController.svelte.js';
4
2
  import { handleMenuEvents, mountChrome } from '../chrome.js';
5
3
  import { withTriggerMenus } from '../triggers/triggers.js';
@@ -7,21 +5,14 @@ import { withTriggerMenus } from '../triggers/triggers.js';
7
5
  export const createSlashMenuPlugin = (options = {}) => (edytor) => {
8
6
  const controller = new SlashMenuController(edytor);
9
7
  /**
10
- * Beside the caret (below a `+` it was opened from), kept in the
11
- * viewport; measured in the overlay's frame, written after, as
12
- * is the close of a `+` menu whose block is gone.
8
+ * Measured in the overlay's frame, written after: the close of a `+`
9
+ * menu whose block is gone (a peer's or a command's delete), as the
10
+ * block menu does. The menu places itself by `controller.anchor`.
13
11
  */
14
- const positionMenu = (host) => {
15
- if (!edytor.node || !controller.isOpen)
16
- return;
12
+ const positionMenu = () => {
17
13
  const { addition } = controller;
18
- // Its block is gone (a peer's or a command's delete): the `+` menu closes, as the block menu does.
19
- if (addition && !addition.block.isInTree)
14
+ if (controller.isOpen && addition && !addition.block.isInTree)
20
15
  return () => controller.dismiss();
21
- const anchor = addition?.anchor?.isConnected ? addition.anchor : addition?.block.node;
22
- const box = anchor?.getBoundingClientRect();
23
- const rect = box && (box.width || box.height) ? box : caretRect(edytor);
24
- return rect && placeNear(host, rect, [310, 350]);
25
16
  };
26
17
  const plugin = {
27
18
  onEdytorAttached: ({ node }) => handleMenuEvents(edytor, node,
@@ -29,7 +29,6 @@
29
29
  const labels = $derived(labelsOf(chrome.edytor, 'table', tableEnglish));
30
30
  const layout = $derived(chrome.layout);
31
31
  const line = $derived(chrome.line);
32
- const menuPlace = $derived(chrome.menuPlace);
33
32
 
34
33
  // The view turning readonly mid-drag drops the preview at once.
35
34
  $effect(() => {
@@ -114,8 +113,8 @@
114
113
  {/if}
115
114
  {/each}
116
115
  {/if}
117
- {#if menu && chrome.menu && menuPlace}
118
- <div data-edytor-table-menu-host style:left={px(menuPlace.left)} style:top={px(menuPlace.top)}>
116
+ {#if menu && chrome.menu}
117
+ <div data-edytor-table-menu-host>
119
118
  {@render menu(chrome)}
120
119
  </div>
121
120
  {/if}
@@ -1,9 +1,10 @@
1
1
  <script module lang="ts">
2
+ import { tableForms } from '../../export/forms.js';
2
3
  import './table.css';
3
4
  import type { BlockSnippetPayload, BlockView, KindMenuAction, Plugin } from '../../plugins.js';
4
5
  import type { Block } from '../../block/block.svelte.js';
5
6
  import type { Edytor } from '../../edytor.svelte.js';
6
- import { tableKinds } from '../../crdt/semantics.js';
7
+
7
8
  import { listedColumns, tableBlock, unlistedOrder } from '../../crdt/tables.js';
8
9
  import { onPress } from '../../events/onFocus.js';
9
10
  import { all } from '../../utils/listen.js';
@@ -161,6 +162,8 @@
161
162
  ...switches
162
163
  ];
163
164
  };
165
+ /** The kinds' export and import forms. */
166
+ const forms = tableForms();
164
167
  /** Tab and Shift+Tab (`step`) walk the cells, unless blocks are selected. */
165
168
  const tab =
166
169
  (step: 1 | -1): HotKey =>
@@ -181,7 +184,7 @@
181
184
  },
182
185
  blocks: {
183
186
  table: {
184
- ...tableKinds.table,
187
+ ...forms.table,
185
188
  element: (data) => ({
186
189
  tag: 'div',
187
190
  attributes: {
@@ -208,26 +211,17 @@
208
211
  group: 'Advanced blocks'
209
212
  }
210
213
  ],
211
- menu: menuOf,
212
- html: (block, _, children) => tableHtml(block.data ?? {}, children),
213
- plain: (_, __, children) => children,
214
- parse: (element) => (element.localName === 'table' ? parseTable(element) : undefined)
214
+ menu: menuOf
215
215
  },
216
216
  tableRow: {
217
- ...tableKinds.tableRow,
217
+ ...forms.tableRow,
218
218
  element: { tag: 'div', attributes: { 'data-edytor-table-row': '', role: 'row' } },
219
- snippet: row,
220
- html: (_, __, children) => `<tr>${children}</tr>`,
221
- plain: (_, __, children) => children.split('\n').join('\t'),
222
- parse: (element) => (element.localName === 'tr' ? {} : undefined)
219
+ snippet: row
223
220
  },
224
221
  tableCell: {
225
- ...tableKinds.tableCell,
222
+ ...forms.tableCell,
226
223
  element: cellElement,
227
- snippet: cell,
228
- html: (_, content) => `<td>${content}</td>`,
229
- plain: (_, content) => content.replace(/\n/g, ' '),
230
- parse: (element) => (/^t[dh]$/.test(element.localName) ? parseCell(element) : undefined)
224
+ snippet: cell
231
225
  }
232
226
  },
233
227
  hotkeys: {
@@ -278,13 +272,21 @@
278
272
  return plugin;
279
273
  };
280
274
 
281
- /** `table`'s grid template: each column's width (the resize preview while a band drags it). */
275
+ /**
276
+ * `table`'s grid template: each column's width (the resize preview while a
277
+ * band drags it). In a suggestion's preview, the widths its data lists.
278
+ */
282
279
  const templateOf = (view: BlockView) => {
283
280
  const edytor = view.handle?.edytor;
284
281
  const chrome = edytor && chromes.get(edytor);
285
282
  const listed = view.data.columns;
286
283
  const fallback = chrome?.columnWidth ?? 120;
287
- const columns = edytor ? columnsOfTable(edytor, view.id) : [];
284
+ // A suggestion's preview has no handle (no document grid): its listed columns, in order.
285
+ const columns = edytor
286
+ ? columnsOfTable(edytor, view.id)
287
+ : Array.isArray(listed)
288
+ ? listed.map((c, i) => String((c as { id?: unknown } | null)?.id ?? i))
289
+ : [];
288
290
  if (Array.isArray(listed)) {
289
291
  const entry = (id: string) => listed.find((c) => (c as { id?: unknown } | null)?.id === id);
290
292
  return columns
@@ -322,42 +324,6 @@
322
324
  };
323
325
 
324
326
  /** The table's HTML (`<table>`): its header row's and header column's cells are `th`. */
325
- const tableHtml = (data: Record<string, unknown>, rows: string) => {
326
- let html = rows;
327
- if (data.headerColumn === true)
328
- html = html.replace(/<tr><td>([\s\S]*?)<\/td>/g, '<tr><th>$1</th>');
329
- if (data.headerRow === true)
330
- html = html.replace(
331
- /^<tr>([\s\S]*?)<\/tr>/,
332
- (_, row: string) => `<tr>${row.replace(/<td>/g, '<th>').replace(/<\/td>/g, '</th>')}</tr>`
333
- );
334
- return `<table><tbody>${html}</tbody></table>`;
335
- };
336
-
337
- /** The rows of an imported `<table>` (its own, not a nested table's). */
338
- const rowsOf = (table: HTMLElement): HTMLTableRowElement[] =>
339
- [...table.querySelectorAll('tr')].filter((tr) => tr.closest('table') === table);
340
-
341
- /** An imported `<table>`'s data: a column per cell of its widest row, its header flags. */
342
- const parseTable = (table: HTMLElement) => {
343
- const rows = rowsOf(table);
344
- const width = Math.max(1, ...rows.map((tr) => tr.cells.length));
345
- const head = (cell: Element | undefined) => cell?.localName === 'th';
346
- const headerRow = rows.length > 1 && [...(rows[0]?.cells ?? [])].every((c) => head(c));
347
- const headerColumn =
348
- rows.length > 0 && rows.slice(headerRow ? 1 : 0).every((tr) => head(tr.cells[0]));
349
- return {
350
- columns: Array.from({ length: width }, (_, i) => ({ id: `c${i + 1}` })),
351
- ...(headerRow && { headerRow: true }),
352
- ...(headerColumn && !headerRow && { headerColumn: true })
353
- };
354
- };
355
-
356
- /** An imported cell's column: its position in its row (`c1`, `c2`, … as its table's). */
357
- const parseCell = (cell: HTMLElement) => {
358
- const index = (cell as HTMLTableCellElement).cellIndex;
359
- return { column: `c${(Number.isInteger(index) && index >= 0 ? index : 0) + 1}` };
360
- };
361
327
  </script>
362
328
 
363
329
  <!--
@@ -95,8 +95,11 @@ export declare class TableChrome extends ChromeList {
95
95
  /** @internal */
96
96
  minColumnWidth: number);
97
97
  protected get count(): number;
98
- /** The view is readonly (reactive): the chrome shows nothing. */
99
- get readonly(): boolean;
98
+ /**
99
+ * Where the open menu stands (`ChromeAnchor`): its grip, which your
100
+ * `menu` places itself under.
101
+ */
102
+ readonly anchor: import("../chrome.js").ChromeAnchor;
100
103
  /**
101
104
  * Open the `kind` grip's menu on the row or column it stands for, its
102
105
  * first row that can run the keyboard's, or close it when it is open
@@ -1,14 +1,13 @@
1
1
  import { untrack } from 'svelte';
2
2
  import { autoScrollFor } from '../../dnd/autoScroll.js';
3
- import { draggable, dropTargetForElements, setCustomNativeDragPreview } from '../../dnd/pragmatic.js';
4
- import { EDYTOR_BLOCK_DRAG_MIME } from '../../events/onDrop.js';
3
+ import { dropTargetForElements, elementScrollers } from '../../dnd/pragmatic.js';
5
4
  import { takeKeys } from '../../events/onFocus.js';
6
5
  import { pin } from '../../surface/overlay.js';
7
- import { attribute } from '../chrome.js';
8
- import { ChromeList, holdKeys, menuKey, optionAttachment } from '../list.svelte.js';
6
+ import { attribute, virtualAnchor } from '../chrome.js';
7
+ import { ChromeList, holdKeys, keysOf, menuKey, optionAttachment } from '../list.svelte.js';
9
8
  import { BandDrag, bandKey } from '../bands.svelte.js';
10
9
  import { all, listen } from '../../utils/listen.js';
11
- import { dragPreview } from '../blockHandles/dragPreview.js';
10
+ import { dragSource, ghost } from '../blockHandles/dragPreview.js';
12
11
  import { caretCell, deleteColumn, deleteRows, gridOf, insertColumn, insertRow, moveColumn, moveRows, widthOf } from './table.js';
13
12
  /** The drag data key naming the chrome a row's or a column's drag is from. */
14
13
  const MOVE = Symbol('edytor table move');
@@ -107,10 +106,6 @@ export class TableChrome extends ChromeList {
107
106
  get count() {
108
107
  return this.#rows.length;
109
108
  }
110
- /** The view is readonly (reactive): the chrome shows nothing. */
111
- get readonly() {
112
- return this.edytor.readonly;
113
- }
114
109
  /**
115
110
  * The column resize in progress.
116
111
  * @internal
@@ -197,7 +192,8 @@ export class TableChrome extends ChromeList {
197
192
  }
198
193
  /**
199
194
  * Where the grips stand, layer-relative, the point each centers on: the
200
- * row grip on the grid's left edge, at the middle of the row it stands
195
+ * row grip on the left edge of the part of the grid its box shows
196
+ * (`table.overflow`: the box's edge once the grid is scrolled), at the middle of the row it stands
201
197
  * for; the column grip on its top edge, at the middle of the column it
202
198
  * stands for, while that middle shows (the grip a drag started from
203
199
  * stays, its source, even scrolled out of view).
@@ -211,7 +207,7 @@ export class TableChrome extends ChromeList {
211
207
  const x = c ? c.left + c.width / 2 : 0;
212
208
  return [
213
209
  ...(row && r
214
- ? [{ ...row, kind: 'row', x: row.layout.left, y: r.top + r.height / 2 }]
210
+ ? [{ ...row, kind: 'row', x: this.span.left, y: r.top + r.height / 2 }]
215
211
  : []),
216
212
  ...(column && (this.moving?.kind === 'column' || this.inView(x))
217
213
  ? [{ ...column, kind: 'column', x, y: column.layout.top }]
@@ -237,22 +233,16 @@ export class TableChrome extends ChromeList {
237
233
  ];
238
234
  }
239
235
  /**
240
- * Where the open menu stands: under its grip.
241
- * @internal
236
+ * Where the open menu stands (`ChromeAnchor`): its grip, which your
237
+ * `menu` places itself under.
242
238
  */
243
- get menuPlace() {
239
+ anchor = virtualAnchor(() => this.#gripAnchor()?.getBoundingClientRect(), () => this.#gripAnchor());
240
+ /** The grip elements in the page, by kind. */
241
+ #gripNodes = {};
242
+ #gripAnchor = () => {
244
243
  const kind = this.menu?.kind;
245
- const at = kind && this.#lines(kind);
246
- if (!at)
247
- return null;
248
- const { layout, index } = at;
249
- if (kind === 'row') {
250
- const row = layout.rows[index];
251
- return { left: layout.left - 9, top: row.top + Math.min(row.height, 28) };
252
- }
253
- const column = layout.columns[index];
254
- return { left: column.left + column.width / 2 - 12, top: layout.top + 10 };
255
- }
244
+ return kind ? this.#gripNodes[kind] : undefined;
245
+ };
256
246
  /**
257
247
  * The drop line of a grip's drag, layer-relative: between two rows
258
248
  * (columns), where the drop would land.
@@ -549,43 +539,37 @@ export class TableChrome extends ChromeList {
549
539
  #grips = {};
550
540
  #grip = (node, kind) => {
551
541
  node.setAttribute('aria-haspopup', 'menu');
542
+ this.#gripNodes[kind] = node;
552
543
  $effect(() => {
553
544
  const open = this.menu?.kind === kind;
554
545
  attribute(node, 'aria-expanded', String(open));
555
546
  attribute(node, 'aria-controls', open ? this.listId : undefined);
556
547
  attribute(node, 'data-dragging', this.moving?.kind === kind ? 'true' : undefined);
557
548
  });
558
- const off = all(listen(node, 'click', () => this.toggle(kind)), listen(node, 'keydown', (event) => {
549
+ const off = all(listen(node, 'click', () => this.toggle(kind)), listen(node, 'keydown', keysOf({
559
550
  // Escape gives the keys back to the editor (closing the menu if open).
560
- if (event.key === 'Escape')
561
- this.close();
562
- else if (event.key === 'ArrowDown' && this.menu?.kind !== kind)
563
- this.toggle(kind);
564
- else
565
- return;
566
- event.preventDefault();
567
- event.stopPropagation();
568
- }), draggable({
569
- element: node,
551
+ Escape: this.close,
552
+ ArrowDown: () => this.menu?.kind !== kind && this.toggle(kind)
553
+ }, true)), dragSource(node, {
570
554
  canDrag: () => !this.edytor.readonly && this.edytor.dispatcher.permits() && this.#source(kind) !== null,
571
- getInitialData: () => ({ [MOVE]: this }),
572
- // The editor's own drag: no view takes it as a foreign drop.
573
- getInitialDataForExternal: () => {
555
+ data: () => ({ [MOVE]: this }),
556
+ id: () => {
574
557
  const source = this.#source(kind);
575
- return {
576
- [EDYTOR_BLOCK_DRAG_MIME]: (kind === 'row' ? source?.id : source?.table.id) ?? ''
577
- };
558
+ return (kind === 'row' ? source?.id : source?.table.id) ?? '';
578
559
  },
579
- onGenerateDragPreview: ({ nativeSetDragImage, location }) => this.#begin(kind, node, location.current.input, nativeSetDragImage),
580
- // The drag library tells the source before the drop target: the drop runs first.
581
- onDrop: () => queueMicrotask(() => {
560
+ begin: (pointer, setImage) => this.#begin(kind, node, pointer, setImage),
561
+ end: () => {
582
562
  for (const release of this.#releases.splice(0))
583
563
  release();
584
564
  this.moving = null;
585
565
  this.edytor.overlay.invalidate();
586
- })
566
+ }
587
567
  }));
588
- return off;
568
+ return () => {
569
+ off();
570
+ if (this.#gripNodes[kind] === node)
571
+ delete this.#gripNodes[kind];
572
+ };
589
573
  };
590
574
  /**
591
575
  * The `+` under the table (`row`: a row at its end, the caret in its
@@ -674,14 +658,7 @@ export class TableChrome extends ChromeList {
674
658
  this.moving = { kind, table: table.id, id, gap: null };
675
659
  const widths = this.#widthsOf(table);
676
660
  const cells = (kind === 'row' ? grid.rows[index].cells : grid.rows.map((r) => r.cells[index])).flatMap((cell) => (cell && this.edytor.idToBlock.get(cell)?.node) || []);
677
- if (nativeSetDragImage && cells.length) {
678
- const preview = dragPreview(root, cells, pointer, kind === 'row' ? widths : [widths[index]]);
679
- setCustomNativeDragPreview({
680
- nativeSetDragImage,
681
- getOffset: preview.offset,
682
- render: ({ container }) => container.append(preview.element)
683
- });
684
- }
661
+ ghost(nativeSetDragImage, root, cells, pointer, kind === 'row' ? widths : [widths[index]]);
685
662
  const ours = (data) => data[MOVE] === this;
686
663
  // The page is the target: the line follows the pointer from the grips, outside the grid.
687
664
  this.#releases.push(dropTargetForElements({
@@ -694,7 +671,7 @@ export class TableChrome extends ChromeList {
694
671
  }));
695
672
  const scroller = table.node?.querySelector('[data-edytor-table-scroll]');
696
673
  if (scroller)
697
- this.#releases.push(autoScrollFor(scroller, ours));
674
+ this.#releases.push(autoScrollFor(scroller, elementScrollers(ours)));
698
675
  this.edytor.overlay.invalidate();
699
676
  }
700
677
  /**
@@ -45,7 +45,7 @@ export const insertRow = (table, index, caret = false) => {
45
45
  const plan = run(table, 'insertTableRow', { index }, (p) => ops.insertTableRow(edytor.facade, table.id, p.index));
46
46
  const row = plan ? (edytor.idToBlock.get(plan.ids[0]) ?? null) : null;
47
47
  if (row && caret)
48
- edytor.dispatcher.caret(row.children[0]?.firstText, 0);
48
+ edytor.dispatcher.caretIn(row.children[0]?.firstText, 0);
49
49
  return row;
50
50
  };
51
51
  /** Delete `rows` with their cells (`table.delete-row`). */
@@ -61,7 +61,7 @@ export const insertColumn = (table, index, width, caret) => {
61
61
  const at = Math.max(0, Math.min(Math.trunc(index) || 0, grid.columns.length - 1));
62
62
  const text = cellAt(table, grid, caret, at)?.firstText;
63
63
  if (text)
64
- table.edytor.dispatcher.caret(text, 0);
64
+ table.edytor.dispatcher.caretIn(text, 0);
65
65
  }
66
66
  return applied;
67
67
  };
@@ -80,7 +80,7 @@ export const fillCell = (table, row, column) => {
80
80
  const plan = run(table, 'fillTableCell', { row, column }, (p) => ops.fillTableCell(edytor.facade, p.row, p.column));
81
81
  const cell = plan ? (edytor.idToBlock.get(plan.ids[0]) ?? null) : null;
82
82
  if (cell)
83
- edytor.dispatcher.caret(cell.firstText, 0);
83
+ edytor.dispatcher.caretIn(cell.firstText, 0);
84
84
  return cell;
85
85
  };
86
86
  /** Toggle `flag` (`headerRow` or `headerColumn`) of `table`'s data (`table.header`). */
@@ -1,4 +1,5 @@
1
1
  <script module lang="ts">
2
+ import { tocForms } from '../../export/forms.js';
2
3
  import type { Plugin, BlockSnippetPayload } from '../../plugins.js';
3
4
  import { tocKinds } from '../../crdt/semantics.js';
4
5
  import { tocEnglish, keywordsOf, labelsOf } from '../../labels.js';
@@ -53,11 +54,8 @@
53
54
  group: 'Advanced blocks'
54
55
  }
55
56
  ],
56
- html: () => '<nav data-edytor-toc></nav>',
57
- plain: () => '',
58
57
  // Its own export: the list is drawn from the document, never carried.
59
- parse: (el) =>
60
- el.localName === 'nav' && el.hasAttribute('data-edytor-toc') ? {} : undefined
58
+ ...tocForms().toc
61
59
  }
62
60
  }
63
61
  };
@@ -76,6 +76,13 @@ export declare class ToolbarController {
76
76
  get card(): boolean;
77
77
  /** The hovered link's URL, as stored (the card shows it). */
78
78
  get hoveredHref(): string;
79
+ /**
80
+ * Where the bar stands (`ChromeAnchor`): the selection's box, which your
81
+ * `toolbar` places itself above (below when there is no room).
82
+ */
83
+ readonly anchor: import("../chrome.js").ChromeAnchor;
84
+ /** Where the link card stands (`ChromeAnchor`): the hovered link, which your `card` places itself under. */
85
+ readonly cardAnchor: import("../chrome.js").ChromeAnchor;
79
86
  /** Show the card for `anchor`, or hide it (`null`). */
80
87
  hover(anchor: HTMLAnchorElement | null): void;
81
88
  /** The card's Open: the hovered link in a new tab. */
@@ -4,9 +4,10 @@ import { selectedTextSpans } from '../../selection/visibility.js';
4
4
  import { getYIndex, SUGGESTION, SYNTHETIC_TEXT_OVERLAY_SELECTOR } from '../../selection/selection.utils.js';
5
5
  import { convertBlocks, convertibleKinds, rowOf } from '../../kinds.js';
6
6
  import { getSelectionBlocks } from '../../selection/replaceSelection.js';
7
- import { identify } from '../chrome.js';
7
+ import { identify, virtualAnchor } from '../chrome.js';
8
8
  import { all, listen } from '../../utils/listen.js';
9
9
  import { takeKeys } from '../../events/onFocus.js';
10
+ import { keysOf } from '../list.svelte.js';
10
11
  /** The bar's controls the arrows walk (a disabled one is skipped). */
11
12
  const STOPS = 'button:not([disabled]), input:not([disabled]), select:not([disabled])';
12
13
  const getLinkHref = (value) => isRecord(value) && typeof value.href === 'string' ? value.href : null;
@@ -113,20 +114,13 @@ export class ToolbarController {
113
114
  node.id = this.linkFieldId;
114
115
  if (this.takeFieldFocus())
115
116
  node.focus({ preventScroll: true });
116
- const keydown = (event) => {
117
- if (event.defaultPrevented || event.isComposing)
118
- return;
119
- if (event.key === 'Enter') {
120
- event.preventDefault();
117
+ return listen(node, 'keydown', keysOf({
118
+ Enter: () => {
121
119
  this.applyLink();
122
120
  this.closePanel(true);
123
- }
124
- else if (event.key === 'Escape') {
125
- event.preventDefault();
126
- this.closePanel(true);
127
- }
128
- };
129
- return listen(node, 'keydown', keydown);
121
+ },
122
+ Escape: () => this.closePanel(true)
123
+ }));
130
124
  };
131
125
  /** Give the keyboard back to the editor (Escape in the bar), over the held selection. */
132
126
  release() {
@@ -232,6 +226,19 @@ export class ToolbarController {
232
226
  get hoveredHref() {
233
227
  return this.hovered?.getAttribute('href') ?? '';
234
228
  }
229
+ /**
230
+ * Where the bar stands (`ChromeAnchor`): the selection's box, which your
231
+ * `toolbar` places itself above (below when there is no room).
232
+ */
233
+ anchor = virtualAnchor(() => {
234
+ const editor = this.edytor.node;
235
+ const selection = editor?.ownerDocument.getSelection();
236
+ if (!selection?.rangeCount || !editor?.contains(selection.anchorNode))
237
+ return;
238
+ return selection.getRangeAt(0).getBoundingClientRect();
239
+ }, () => this.edytor.node);
240
+ /** Where the link card stands (`ChromeAnchor`): the hovered link, which your `card` places itself under. */
241
+ cardAnchor = virtualAnchor(() => this.hovered?.getBoundingClientRect(), () => this.hovered);
235
242
  /** Show the card for `anchor`, or hide it (`null`). */
236
243
  hover(anchor) {
237
244
  if (this.hovered === anchor)
@@ -1,42 +1,14 @@
1
1
  import { ToolbarController } from './ToolbarController.svelte.js';
2
- import { mountChrome } from '../chrome.js';
3
- import { placeNear } from '../../surface/overlay.js';
2
+ import { keepFocus, mountChrome } from '../chrome.js';
4
3
  import { all, listen } from '../../utils/listen.js';
5
- /** A press in a field of the chrome (an input, a select): it takes focus. */
6
- const isNativeFieldEvent = (event) => event
7
- .composedPath()
8
- .some((target) => target instanceof Element && target.matches('input, textarea, select'));
9
4
  /** The selection toolbar, drawn by your `toolbar` snippet, and the link card by your `card`. */
10
5
  export const createToolbarPlugin = (options = {}) => (edytor) => {
11
6
  const controller = new ToolbarController(edytor);
12
- /** Above the selection, kept in the viewport; measured in the overlay's frame, written after. */
13
- const positionToolbar = (host) => {
14
- const editor = edytor.node;
15
- if (!editor || !controller.isVisible)
16
- return;
17
- const selection = editor.ownerDocument.getSelection();
18
- if (!selection?.rangeCount || !editor.contains(selection.anchorNode))
19
- return;
20
- const range = selection.getRangeAt(0);
21
- if (typeof range.getBoundingClientRect !== 'function')
22
- return;
23
- return placeNear(host, range.getBoundingClientRect(), [460, 40], {
24
- above: true,
25
- center: true,
26
- // The bar, not its open panel: panels drop over the text below it.
27
- box: host.querySelector('[data-edytor-toolbar-bar]') ?? host.firstElementChild
28
- });
29
- };
30
- /** Just below the hovered link, flush with it (the host's top padding bridges the two). */
31
- const positionCard = (host) => {
7
+ /** A hovered link that left the page hides its card (measured in the overlay's frame). */
8
+ const positionCard = () => {
32
9
  const anchor = controller.hovered;
33
- if (!anchor || !controller.card)
34
- return;
35
- if (!anchor.isConnected)
10
+ if (anchor && controller.card && !anchor.isConnected)
36
11
  return () => controller.hover(null);
37
- const rect = anchor.getBoundingClientRect();
38
- const [left, top] = [`${Math.max(8, rect.left)}px`, `${rect.bottom}px`];
39
- return () => Object.assign(host.style, { left, top, paddingTop: '4px' });
40
12
  };
41
13
  /**
42
14
  * The link card follows the pointer: over a link in the host it shows,
@@ -97,32 +69,26 @@ export const createToolbarPlugin = (options = {}) => (edytor) => {
97
69
  edytor.overlay.invalidate();
98
70
  },
99
71
  onEdytorAttached: () => {
100
- const offs = [];
101
72
  // No press on the chrome takes focus, its background and a custom
102
73
  // snippet's markup included (not only its buttons): the editor keeps
103
74
  // its focus and its selection. A field (the link panel's input) takes
104
75
  // its own. Every press has a `mousedown`, WebKit's lone one too
105
76
  // (`onFocus.ts`), so no `pointerdown` is cancelled.
106
- const keep = (event) => {
107
- if (!isNativeFieldEvent(event))
108
- event.preventDefault();
109
- };
110
- /** Mount `snippet` in the overlay while `shown`, its host keeping the focus on a press. */
111
- const mount = (snippet, shown, name, zIndex, measure) => {
112
- offs.push(mountChrome(edytor, snippet, controller, shown, { name, zIndex, measure }));
113
- const host = snippet && edytor.overlay.layer?.querySelector(`[data-${name}]`);
114
- offs.push(listen(host, 'mousedown', keep));
115
- return host;
116
- };
117
- mount(options.toolbar, () => controller.isVisible, 'edytor-toolbar-host', 60, positionToolbar);
118
- const card = mount(options.card, () => controller.card, 'edytor-link-card-host', 61, positionCard);
119
- if (edytor.node && card)
120
- offs.push(trackLinks(edytor.node, card));
121
- offs.push(() => controller.hover(null));
122
- return () => {
123
- for (const off of offs.reverse())
124
- off();
125
- };
77
+ const host = (name) => edytor.overlay.layer?.querySelector(`[data-${name}]`);
78
+ const offs = [
79
+ mountChrome(edytor, options.toolbar, controller, () => controller.isVisible, {
80
+ name: 'edytor-toolbar-host',
81
+ zIndex: 60
82
+ }),
83
+ mountChrome(edytor, options.card, controller, () => controller.card, {
84
+ name: 'edytor-link-card-host',
85
+ zIndex: 61,
86
+ measure: positionCard
87
+ })
88
+ ];
89
+ const bar = options.toolbar && host('edytor-toolbar-host');
90
+ const card = options.card && host('edytor-link-card-host');
91
+ return all(() => controller.hover(null), edytor.node && card ? trackLinks(edytor.node, card) : () => { }, listen(card, 'mousedown', keepFocus), listen(bar, 'mousedown', keepFocus), ...offs.reverse());
126
92
  }
127
93
  };
128
94
  };
@@ -34,8 +34,13 @@ export declare abstract class TextTriggerController extends ChromeList {
34
34
  char: string,
35
35
  /** The popup's owner name (`edytor.popups`), unique per view. */
36
36
  owner: string);
37
- /** The editor is readonly: the menu closes. */
38
- get readonly(): boolean;
37
+ /**
38
+ * Where the menu stands (`ChromeAnchor`): the caret, which your `menu`
39
+ * places itself under (above it when there is no room).
40
+ */
41
+ readonly anchor: import("../chrome.js").ChromeAnchor;
42
+ /** The anchor's box, read now: the caret's. */
43
+ protected anchorRect(): DOMRect | undefined;
39
44
  /** The record `popup` publishes, `active` the highlighted row's id when it is in the page. */
40
45
  protected popupOf(active: string | undefined): Popup;
41
46
  /** Whether a trigger typed in `block` opens the menu. */
@@ -1,5 +1,7 @@
1
1
  import { inCodeLines } from '../../session/inputRules.js';
2
2
  import { ChromeList, inPage } from '../list.svelte.js';
3
+ import { virtualAnchor } from '../chrome.js';
4
+ import { caretRect } from './place.js';
3
5
  /** A trigger opens at a text's start or after whitespace, never inside a word (`1/2`, `a@b`). */
4
6
  const startsTrigger = (text, offset) => offset === 0 || /\s/.test(text.stringContent[offset - 1] ?? '');
5
7
  /** What `items` and `onPick` read: the trigger's block, its range in block offsets, the query. */
@@ -30,9 +32,14 @@ export class TextTriggerController extends ChromeList {
30
32
  super(edytor, owner, 'listbox', (node) => this.isOpen && this.popupOf(inPage(node, this.rowId(this.index))));
31
33
  this.char = char;
32
34
  }
33
- /** The editor is readonly: the menu closes. */
34
- get readonly() {
35
- return this.edytor.readonly;
35
+ /**
36
+ * Where the menu stands (`ChromeAnchor`): the caret, which your `menu`
37
+ * places itself under (above it when there is no room).
38
+ */
39
+ anchor = virtualAnchor(() => this.anchorRect(), () => this.edytor.node);
40
+ /** The anchor's box, read now: the caret's. */
41
+ anchorRect() {
42
+ return caretRect(this.edytor);
36
43
  }
37
44
  /** The record `popup` publishes, `active` the highlighted row's id when it is in the page. */
38
45
  popupOf(active) {
@@ -139,7 +146,7 @@ export class TextTriggerController extends ChromeList {
139
146
  const kept = run.out === false || (run.taken && dispatcher.last?.status === 'refused');
140
147
  const out = await run.out;
141
148
  if (owns(text, held))
142
- dispatcher.caret(text, kept ? end : triggerStart);
149
+ dispatcher.caretIn(text, kept ? end : triggerStart);
143
150
  return out;
144
151
  }
145
152
  finally {