@quillmark/svelte 0.5.0 → 0.6.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 (56) hide show
  1. package/README.md +8 -2
  2. package/THEMING.md +22 -27
  3. package/dist/core/address.d.ts +17 -3
  4. package/dist/core/address.js +20 -3
  5. package/dist/core/codec/blocks.d.ts +37 -0
  6. package/dist/core/codec/blocks.js +163 -0
  7. package/dist/core/codec/field.js +3 -5
  8. package/dist/core/codec/index.d.ts +1 -1
  9. package/dist/core/codec/index.js +3 -2
  10. package/dist/core/codec/inputrules.js +24 -144
  11. package/dist/core/codec/prose.css +14 -25
  12. package/dist/core/codec/schema.d.ts +8 -0
  13. package/dist/core/codec/schema.js +33 -1
  14. package/dist/core/codec/slash.d.ts +18 -16
  15. package/dist/core/codec/slash.js +0 -0
  16. package/dist/core/codec/table-view.js +127 -157
  17. package/dist/core/codec/table.d.ts +0 -10
  18. package/dist/core/codec/table.js +0 -15
  19. package/dist/core/theme.css +350 -347
  20. package/dist/preset/recipes.css +10 -21
  21. package/dist/preset/scale.css +6 -7
  22. package/dist/preview/Preview.svelte +4 -2
  23. package/dist/preview/geometry.d.ts +10 -9
  24. package/dist/preview/geometry.js +11 -13
  25. package/dist/visual/ArrayField.svelte +242 -63
  26. package/dist/visual/ArrayField.svelte.d.ts +5 -1
  27. package/dist/visual/Card.svelte +21 -2
  28. package/dist/visual/DiagnosticList.svelte +6 -11
  29. package/dist/visual/EnumField.svelte +33 -1
  30. package/dist/visual/Field.svelte +29 -5
  31. package/dist/visual/FieldLabel.svelte +2 -2
  32. package/dist/visual/FormatPopover.svelte +13 -1
  33. package/dist/visual/ObjectField.svelte +157 -35
  34. package/dist/visual/ObjectField.svelte.d.ts +13 -4
  35. package/dist/visual/ProseArrayElement.svelte +22 -2
  36. package/dist/visual/ProseArrayElement.svelte.d.ts +1 -0
  37. package/dist/visual/TipsCard.svelte +33 -13
  38. package/dist/visual/VariantField.svelte +98 -0
  39. package/dist/visual/VariantField.svelte.d.ts +21 -0
  40. package/dist/visual/VisualEditorInner.svelte +18 -15
  41. package/dist/visual/controls.css +282 -283
  42. package/dist/visual/diagnostics.d.ts +15 -24
  43. package/dist/visual/diagnostics.js +14 -30
  44. package/dist/visual/domid.d.ts +13 -0
  45. package/dist/visual/domid.js +16 -0
  46. package/dist/visual/leaves.d.ts +9 -7
  47. package/dist/visual/links.d.ts +5 -2
  48. package/dist/visual/links.js +9 -3
  49. package/dist/visual/props.d.ts +1 -1
  50. package/dist/visual/strings.d.ts +21 -6
  51. package/dist/visual/strings.js +3 -0
  52. package/dist/visual/structure.d.ts +32 -7
  53. package/dist/visual/structure.js +49 -3
  54. package/package.json +2 -2
  55. package/dist/core/codec/clipboard.d.ts +0 -59
  56. package/dist/core/codec/clipboard.js +0 -202
@@ -1,10 +1,10 @@
1
1
  // Input rules: the markdown-shorthand plugin the codec owns (`**`→strong, `*`→em,
2
2
  // `~~`→strike, `` ` ``→code, `# `→heading, `- `/`1. `→lists, `> `→blockquote,
3
- // `---`→divider).
4
- // Mark rules are hand-rolled (prosemirror-inputrules ships only node rules); the one
5
- // block rule with no guard of its own reuses `wrappingInputRule`, and the rest mirror
6
- // an upstream body around one. These produce transactions the codec already lowers:
7
- // they add no new lowering surface.
3
+ // `---`→divider, a fence→code block).
4
+ // Mark rules are hand-rolled (prosemirror-inputrules ships only node rules); every
5
+ // block rule is one regexp over a command in `blocks.ts`, which is the slash menu's
6
+ // implementation too. These produce transactions the codec already lowers: they add no
7
+ // new lowering surface.
8
8
  //
9
9
  // A rule declines only where what it would mint is unrepresentable, or is a shape
10
10
  // another gesture owns — never on how a quill renders it. What a quill typesets is not
@@ -12,9 +12,8 @@
12
12
  // sets"), and a construct that arrives through `importMarkdown` must stay authorable or
13
13
  // a document opens carrying a shape the editor refuses to make. So one guard survives:
14
14
  // `- ` / `1. ` at the head of an existing item, where Tab owns the nesting.
15
- import { InputRule, inputRules, wrappingInputRule } from 'prosemirror-inputrules';
16
- import { canJoin, findWrapping } from 'prosemirror-transform';
17
- import { Selection } from 'prosemirror-state';
15
+ import { InputRule, inputRules } from 'prosemirror-inputrules';
16
+ import { consuming, insertDivider, toCodeBlock, toHeading, wrapInList, wrapInQuote } from './blocks.js';
18
17
  /**
19
18
  * A mark input rule: when `regexp` (ending in the closing delimiter) matches
20
19
  * before the caret, wrap capture group 1 in `markType` and drop the delimiters.
@@ -46,69 +45,19 @@ function markInputRule(regexp, markType, delimLen) {
46
45
  return tr;
47
46
  });
48
47
  }
49
- /**
50
- * `wrappingInputRule` (whose body this mirrors), plus one normalization: a
51
- * `heading` being wrapped becomes a `paragraph` first.
52
- *
53
- * `list_item` is `block+`, so `list_item > heading` is *representable*, and `# `
54
- * inside the item is the gesture that mints it ({@link markdownInputRules}). A wrap
55
- * keeping the heading would be a second door onto that shape, so `- ` / `1. ` mint a
56
- * paragraph item wherever they fire.
57
- *
58
- * `item` is the second guard, and it is about the gesture rather than the shape: the
59
- * rule declines at the start of an item's own first block ({@link openingAnItem}).
60
- */
61
- function listWrappingRule(regexp, listType, paragraph, item, getAttrs, joinPredicate) {
62
- return new InputRule(regexp, (state, match, start, end) => {
63
- if (openingAnItem(state.doc.resolve(start), item))
64
- return null;
65
- const attrs = getAttrs ? getAttrs(match) : null;
66
- const tr = state.tr.delete(start, end);
67
- // Positions survive the retype: same content, same size.
68
- if (tr.doc.resolve(start).parent.type !== paragraph) {
69
- tr.setBlockType(start, start, paragraph);
70
- }
71
- const $start = tr.doc.resolve(start);
72
- const range = $start.blockRange();
73
- const wrapping = range && findWrapping(range, listType, attrs);
74
- if (!range || !wrapping)
75
- return null;
76
- tr.wrap(range, wrapping);
77
- // Join a preceding list of the same type: the one boundary this rule
78
- // itself opens (`lists.ts` §cleanup: command-local, never a global pass).
79
- const before = tr.doc.resolve(start - 1).nodeBefore;
80
- if (before &&
81
- before.type === listType &&
82
- canJoin(tr.doc, start - 1) &&
83
- (!joinPredicate || joinPredicate(match, before))) {
84
- tr.join(start - 1);
85
- }
86
- return tr;
87
- });
88
- }
89
- /**
90
- * Whether `$start` is the head of an item that already exists: offset 0 of a
91
- * `list_item`'s first block, which is where a list shorthand is the text an author
92
- * typed rather than a shorthand at all.
93
- *
94
- * Firing there wraps the item's own paragraph in a fresh list, minting an item whose
95
- * only content is another item — a level with nothing on it. Tab is the indent gesture
96
- * and lands the shape a nesting is supposed to have, under the previous sibling, so
97
- * what the rule adds here is a second door onto a worse answer. A later block of the
98
- * item is not this case: wrapping a continuation paragraph is how a sub-list opens
99
- * under text.
100
- */
101
- function openingAnItem($start, item) {
102
- if (!item || $start.depth < 2 || $start.parentOffset !== 0)
103
- return false;
104
- return $start.node(-1).type === item && $start.index(-1) === 0;
48
+ /** A block shorthand: the regexp that fires it, and the command it runs in the match's
49
+ * place. Every one is `^`-anchored, so the command's own head-of-a-textblock guard is
50
+ * the same position the regexp already stands at. */
51
+ function blockRule(regexp, command) {
52
+ return new InputRule(regexp, (state, match, start, end) => consuming(state, start, end, command(match))?.tr ?? null);
105
53
  }
106
54
  /** The full markdown-shorthand rule set for a block schema. */
107
55
  export function markdownInputRules(schema) {
108
56
  const rules = [];
109
57
  const m = schema.marks;
110
- const item = schema.nodes.list_item;
111
- // Inline marks. Non-greedy capture between matching delimiters.
58
+ // Inline marks. Non-greedy capture between matching delimiters. Built only where the
59
+ // schema has the mark: the inline schema omits them, and a plaintext one declares none
60
+ // at all.
112
61
  if (m.strong)
113
62
  rules.push(markInputRule(/\*\*([^*]+)\*\*$/, m.strong, 2));
114
63
  if (m.em)
@@ -117,85 +66,16 @@ export function markdownInputRules(schema) {
117
66
  rules.push(markInputRule(/~~([^~]+)~~$/, m.strike, 2));
118
67
  if (m.code)
119
68
  rules.push(markInputRule(/`([^`]+)`$/, m.code, 1));
120
- // Block shorthands (only where the schema has the node; the inline schema
121
- // omits them, so this stays a no-op there).
122
- if (schema.nodes.heading) {
123
- // `textblockTypeInputRule`'s body. It fires inside a list item like anywhere else:
124
- // `list_item > heading` is a shape the content holds and `importMarkdown` produces
125
- // from `- # title`, so a rule that declined there would refuse to author what a
126
- // document can arrive carrying.
127
- const heading = schema.nodes.heading;
128
- rules.push(new InputRule(/^(#{1,6})\s$/, (state, match, start, end) => {
129
- const $start = state.doc.resolve(start);
130
- if (!$start.node(-1).canReplaceWith($start.index(-1), $start.indexAfter(-1), heading)) {
131
- return null;
132
- }
133
- return state.tr
134
- .delete(start, end)
135
- .setBlockType(start, start, heading, { level: match[1].length });
136
- }));
137
- }
138
- if (schema.nodes.code_block && schema.nodes.paragraph) {
139
- // `textblockTypeInputRule`'s body plus the exit the `---` rule mints below, for a
140
- // sharper version of its reason: a code block is the one block a GAP CURSOR will
141
- // not sit beside either (`closedBefore`/`closedAfter` both fail on a textblock), so
142
- // a fence at the end of a body leaves the caret with no way forward at all.
143
- const code = schema.nodes.code_block;
144
- const paragraph = schema.nodes.paragraph;
145
- rules.push(new InputRule(/^```$/, (state, _match, start, end) => {
146
- const $start = state.doc.resolve(start);
147
- if (!$start.node(-1).canReplaceWith($start.index(-1), $start.indexAfter(-1), code)) {
148
- return null;
149
- }
150
- const tr = state.tr.delete(start, end).setBlockType(start, start, code);
151
- const at = tr.doc.resolve(start).after();
152
- if (!tr.doc.nodeAt(at))
153
- tr.insert(at, paragraph.create());
154
- // The caret stays in the fence, not in the exit it minted.
155
- return tr;
156
- }));
157
- }
158
- if (schema.nodes.horizontal_rule && schema.nodes.paragraph) {
159
- // A divider is a whole block, so this rule replaces its textblock rather than
160
- // retyping one: `horizontal_rule` holds no content to retype into.
161
- const rule = schema.nodes.horizontal_rule;
162
- const paragraph = schema.nodes.paragraph;
163
- rules.push(new InputRule(/^---$/, (state, _match, start, end) => {
164
- const $start = state.doc.resolve(start);
165
- // The block entirely, not a prefix of one. `[start, end)` is the match minus
166
- // the just-typed `-`, so the block holds `--` when this fires; anything else
167
- // in it is a paragraph being edited, which a replace would eat.
168
- if ($start.parentOffset !== 0 || $start.parent.content.size !== end - start) {
169
- return null;
170
- }
171
- if (!$start.node(-1).canReplaceWith($start.index(-1), $start.indexAfter(-1), rule)) {
172
- return null;
173
- }
174
- const from = $start.before();
175
- const tr = state.tr.replaceWith(from, $start.after(), rule.create());
176
- // A divider at the end of a body leaves nowhere to type; the paragraph after
177
- // it is the exit, and it is where the caret lands (`slash.ts` §insertBlock
178
- // opens the same block for the same reason).
179
- const at = from + 1;
180
- if (!tr.doc.nodeAt(at))
181
- tr.insert(at, paragraph.create());
182
- tr.setSelection(Selection.near(tr.doc.resolve(at), 1));
183
- return tr;
184
- }));
185
- }
69
+ // Block shorthands. Each command answers for the schema it is run against, so a
70
+ // schema without the node declines rather than being guarded here.
71
+ rules.push(blockRule(/^(#{1,6})\s$/, (match) => toHeading(match[1].length)));
72
+ rules.push(blockRule(/^```$/, toCodeBlock));
73
+ rules.push(blockRule(/^---$/, insertDivider));
186
74
  // `- ` / `1. ` at the start of a block, here or nested inside an item — but not at
187
- // the start of an item's own first block, where {@link openingAnItem} declines.
188
- if (schema.nodes.bullet_list && schema.nodes.paragraph) {
189
- rules.push(listWrappingRule(/^\s*([-+*])\s$/, schema.nodes.bullet_list, schema.nodes.paragraph, item));
190
- }
191
- if (schema.nodes.ordered_list && schema.nodes.paragraph) {
192
- rules.push(listWrappingRule(/^(\d+)\.\s$/, schema.nodes.ordered_list, schema.nodes.paragraph, item, (match) => ({ start: +match[1] }), (match, node) => node.childCount + node.attrs.start === +match[1]));
193
- }
194
- // No item guard: a quote wrapping an item's own first block is a container the
195
- // content holds, where a list wrapping one is an item whose only content is an item.
196
- if (schema.nodes.blockquote) {
197
- rules.push(wrappingInputRule(/^\s*>\s$/, schema.nodes.blockquote));
198
- }
75
+ // the start of an item's own first block, where the command declines.
76
+ rules.push(blockRule(/^\s*([-+*])\s$/, () => wrapInList(false)));
77
+ rules.push(blockRule(/^(\d+)\.\s$/, (match) => wrapInList(true, { start: +match[1] }, (node) => node.childCount + node.attrs.start === +match[1])));
78
+ rules.push(blockRule(/^\s*>\s$/, wrapInQuote));
199
79
  return rules;
200
80
  }
201
81
  /** The input-rules plugin the field mounts by default (opt-out via createField). */
@@ -432,11 +432,11 @@ li.ProseMirror-selectednode::after {
432
432
  which is drawn through a drag the pointer has travelled off — answering which line.
433
433
 
434
434
  Opacity rather than absence, so every control keeps its box and is hit-tested wherever
435
- it sits. A grip's reach is the pointer's — no Tab arrives at one, the cell traversal
436
- claiming the key first (`table-view.ts` §`step`) — so it is out of the tab order, and
437
- the focus rung there draws the focus the view takes itself. The two bars are in it, the
438
- Tab that declines off the last cell landing on them, and the same rung is what draws
439
- one a keyboard has reached. */
435
+ it sits. A grip's reach is the pointer's alone and it holds no focus, so it carries no
436
+ focus rung: what a keyboard draws a line with is the cell the selection runs through
437
+ (`table-view.ts` §`cellKeys`), and the wash and the selected grip's tint are what say
438
+ so. The two bars are in the tab order, the Tab that declines off the last cell landing
439
+ on them, so the rung there draws one a keyboard has reached. */
440
440
  :is(.qm-table-grip, .qm-table-add) {
441
441
  opacity: 0;
442
442
  transition: opacity var(--_qm-duration-fast) var(--_qm-ease-reverse);
@@ -447,32 +447,21 @@ li.ProseMirror-selectednode::after {
447
447
  :where(.qm-table-island:hover) :is(.qm-table-grip, .qm-table-add) {
448
448
  opacity: var(--_qm-opacity-muted);
449
449
  }
450
- :is(.qm-table-grip, .qm-table-add):is(:hover, :focus-visible),
451
- .qm-table-grip:is([aria-pressed='true'], .qm-table-lifted) {
450
+ .qm-table-add:is(:hover, :focus-visible),
451
+ .qm-table-grip:is(:hover, [aria-pressed='true'], .qm-table-lifted) {
452
452
  opacity: 1;
453
453
  }
454
454
 
455
- /* The ring, on the chrome and nowhere else in this file. A prose leaf's focus indicator
456
- is its caret and a cell's is too, so the outline is dropped there; a control that comes
457
- up out of nothing has the reveal, and a ring under a keyboard is what says the focus is
458
- there rather than on the paper behind it. Declared here rather than reached for as
459
- `.qm-focus-ring`: that recipe is the visual tier's stylesheet, and the island is inside
460
- the leaf, which is the codec's.
461
-
462
- A selected grip takes none. Focus and selection arrive together there — the grip takes
463
- the focus because the selection's keys bind on it — and the tint plus the wash already
464
- say where the next command lands. A ring on top is a second box around the loudest
465
- thing on the island, drawn on the press that made the selection: a pointer gesture
466
- answering with a keyboard's indicator. Stated as `none` rather than left unset, because
467
- what fills the gap is the browser's own ring, which is the same box in a colour the
468
- surface does not own. */
469
- :is(.qm-table-grip, .qm-table-add):focus-visible {
455
+ /* The ring, on the two add bars and nowhere else in this file. A prose leaf's focus
456
+ indicator is its caret and a cell's is too, so the outline is dropped there; a control
457
+ that comes up out of nothing has the reveal, and a ring under a keyboard is what says
458
+ the focus is there rather than on the paper behind it. Declared here rather than
459
+ reached for as `.qm-focus-ring`: that recipe is the visual tier's stylesheet, and the
460
+ island is inside the leaf, which is the codec's. */
461
+ .qm-table-add:focus-visible {
470
462
  outline: var(--_qm-ring);
471
463
  outline-offset: var(--_qm-ring-offset);
472
464
  }
473
- .qm-table-grip[aria-pressed='true']:focus-visible {
474
- outline: none;
475
- }
476
465
 
477
466
  /* A drag in flight. The lifted line reads as picked up by tone, never a shadow:
478
467
  elevation is a surface rung here, which says the same thing at both poles. The drop
@@ -1,4 +1,12 @@
1
1
  import { Schema } from 'prosemirror-model';
2
+ /**
3
+ * Whether a link carrying `href` renders as one. A value with no scheme is relative
4
+ * to the embedding page and has none to refuse.
5
+ *
6
+ * A refused href is unchanged: it stays on the mark and round-trips, so a document
7
+ * survives an editor that declines to make it clickable.
8
+ */
9
+ export declare function rendersHref(href: string): boolean;
2
10
  /** The full field schema: every block kind, container, mark, and island. */
3
11
  export declare const blockSchema: Schema<string, string>;
4
12
  /** The constrained inline schema: one paragraph, no block splitting, no
@@ -10,6 +10,33 @@
10
10
  // not marks here (decorations).
11
11
  import { Schema } from 'prosemirror-model';
12
12
  import { islandBlockSpec, islandInlineSpec } from './islands.js';
13
+ // ── The href gate ───────────────────────────────────────────────────────────
14
+ // An `href` is an attribute value and not markup, so the markdown → typed node → DOM
15
+ // path that keeps a document's text from becoming tags never reaches it: what the
16
+ // mark carries is what `toDOM` emits, and `renderContent` paints marks outside a
17
+ // `contenteditable` (the tips card), where a click is a plain one.
18
+ //
19
+ // An allowlist, because the set a document can spell is open: naming the dangerous
20
+ // schemes instead loses to the first one this has not heard of. It holds what the
21
+ // surface has a caller for; a scheme reaching it later is a line.
22
+ const RENDERED_SCHEMES = new Set(['http', 'https', 'mailto', 'tel', 'ftp']);
23
+ /** A scheme and its colon: a letter, then letters, digits, `+`, `-` or `.` (RFC 3986). */
24
+ const SCHEME = /^([a-z][a-z0-9+.-]*):/i;
25
+ /** Dropped before the scheme is read, so a tab spliced into `javascript:` is tested
26
+ * as what it navigates to. Wider than the URL parser's own tab/newline rule: no
27
+ * scheme carries a control character, and the strip decides without rewriting. */
28
+ const IGNORED = /[\u0000-\u0020]/g;
29
+ /**
30
+ * Whether a link carrying `href` renders as one. A value with no scheme is relative
31
+ * to the embedding page and has none to refuse.
32
+ *
33
+ * A refused href is unchanged: it stays on the mark and round-trips, so a document
34
+ * survives an editor that declines to make it clickable.
35
+ */
36
+ export function rendersHref(href) {
37
+ const scheme = SCHEME.exec(href.replace(IGNORED, ''));
38
+ return scheme === null || RENDERED_SCHEMES.has(scheme[1].toLowerCase());
39
+ }
13
40
  // ── Marks (the block and inline schemas share them; plaintext declares none) ─
14
41
  const marks = {
15
42
  // Order matters: it fixes mark-set sort order and parse precedence. `link`
@@ -26,7 +53,12 @@ const marks = {
26
53
  parseDOM: [
27
54
  { tag: 'a[href]', getAttrs: (el) => ({ href: el.getAttribute('href') }) }
28
55
  ],
29
- toDOM: (mark) => ['a', { href: mark.attrs.href }, 0]
56
+ // A refused href draws as a bare span: the text stands, unstyled and
57
+ // unclickable, and the mark keeps its value for encode.
58
+ toDOM: (mark) => {
59
+ const href = mark.attrs.href;
60
+ return rendersHref(href) ? ['a', { href }, 0] : ['span', 0];
61
+ }
30
62
  },
31
63
  // The open-set escape hatch: an inert mark that renders as a bare span and
32
64
  // re-emits its stored `type`/`attrs` on encode. `excludes: ''` lets marks of
@@ -1,4 +1,4 @@
1
- import { Plugin, type Command } from 'prosemirror-state';
1
+ import { EditorState, Plugin, type Command } from 'prosemirror-state';
2
2
  import type { EditorView } from 'prosemirror-view';
3
3
  import { type RangeAnchor } from './anchor.js';
4
4
  /** The menu as the chrome sees it: the command names the query matched, the keyboard's
@@ -25,18 +25,21 @@ export interface SlashStrings {
25
25
  * chrome's wording follows, and the visual `strings` set extends both. */
26
26
  export declare const DEFAULT_SLASH_STRINGS: SlashStrings;
27
27
  /**
28
- * The vocabulary, in menu order: the names `SLASH_COMMANDS` is keyed by, read off the
29
- * table itself so an offer and its pick cannot disagree.
28
+ * The vocabulary a caret can run, in menu order: the names `SLASH_COMMANDS` is keyed
29
+ * by, each asked whether it would fire here. A row that declines when picked is worse
30
+ * than an absent one, and asking the command rather than restating its guards is what
31
+ * keeps the menu from drifting off what the shorthand does. So the offers narrow as the
32
+ * caret moves: the island alone mid-paragraph, no list at an item's head.
30
33
  *
31
- * The set is what no shorthand reaches. A list is `- ` / `1. `, a heading `# `, a quote
32
- * `> `, a rule `---`, a code block a fence: a shorthand fires where the caret already
33
- * is, and a second door onto the same construct is a second thing to keep true. A table
34
- * has no prefix a rule can fire on (a pipe row is not one), so it is the menu's alone.
34
+ * `state` is the state a pick leaves behind — the run already consumed — since that is
35
+ * the caret every command answers for (`blocks.ts`).
35
36
  */
36
- export declare function slashItems(): string[];
37
- /** The query filter: a case-insensitive prefix of the name, which is how a command line
38
- * completes. An empty query offers everything, and the vocabulary's order survives, so
39
- * the menu never reorders under a keystroke. */
37
+ export declare function slashItems(state: EditorState): string[];
38
+ /** The query filter: a case-insensitive prefix of the name or of any of its words, which
39
+ * is how a command line completes and how a two-word name stays reachable by the noun a
40
+ * writer has — `/list` offers both lists. A prefix and not a substring: `note` is inside
41
+ * `footnote` and completes nothing. An empty query offers everything, and the
42
+ * vocabulary's order survives, so the menu never reorders under a keystroke. */
40
43
  export declare function filterItems(items: string[], query: string): string[];
41
44
  /** The live trigger: where the `/` sits, what has been typed after it, and which
42
45
  * offer the keyboard is on. */
@@ -67,11 +70,10 @@ export declare function slashKeymap(): Record<string, Command>;
67
70
  * highlight has one lane rather than a hover painting a second one. */
68
71
  export declare function focusSlashItem(view: EditorView, name: string): void;
69
72
  /**
70
- * Run a command: one transaction that deletes the trigger run and applies the
71
- * command's own edit. One rather than two because two would be two commits, two undo
72
- * steps and two recompiles for one gesture; the command is built against the
73
- * post-delete state and its steps are replayed onto the same transaction, which is
74
- * exactly what they were computed for.
73
+ * Run a command: one transaction that deletes the trigger run and applies the command's
74
+ * own edit (`blocks.ts` §`consuming`). A command that declines consumes nothing. The
75
+ * menu never picks one, offering only what would run; `slashPick` is a public seam and
76
+ * can.
75
77
  */
76
78
  export declare function runSlashItem(view: EditorView, name: string): void;
77
79
  export {};
Binary file