@quillmark/svelte 0.20.0 → 0.21.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 (46) hide show
  1. package/THEMING.md +1 -1
  2. package/dist/core/codec/atoms.js +25 -10
  3. package/dist/core/codec/blocks.d.ts +5 -5
  4. package/dist/core/codec/blocks.js +23 -6
  5. package/dist/core/codec/code.d.ts +3 -3
  6. package/dist/core/codec/code.js +51 -21
  7. package/dist/core/codec/field.d.ts +9 -7
  8. package/dist/core/codec/field.js +22 -11
  9. package/dist/core/codec/inputrules.d.ts +4 -0
  10. package/dist/core/codec/inputrules.js +43 -5
  11. package/dist/core/codec/link-paste.d.ts +11 -0
  12. package/dist/core/codec/link-paste.js +32 -0
  13. package/dist/core/codec/lists.d.ts +2 -2
  14. package/dist/core/codec/lists.js +66 -26
  15. package/dist/core/codec/prose.css +71 -43
  16. package/dist/core/codec/schema.js +55 -11
  17. package/dist/core/codec/slash.js +0 -0
  18. package/dist/core/codec/table-view.d.ts +5 -0
  19. package/dist/core/codec/table-view.js +232 -103
  20. package/dist/core/rungs.d.ts +6 -0
  21. package/dist/core/rungs.js +11 -0
  22. package/dist/core/theme.css +24 -12
  23. package/dist/visual/ArrayField.svelte +168 -48
  24. package/dist/visual/BooleanField.svelte +12 -1
  25. package/dist/visual/Card.svelte +32 -20
  26. package/dist/visual/Card.svelte.d.ts +1 -0
  27. package/dist/visual/CardControls.svelte +15 -15
  28. package/dist/visual/DateField.svelte +93 -64
  29. package/dist/visual/EnumField.svelte +9 -4
  30. package/dist/visual/FieldHint.svelte +22 -5
  31. package/dist/visual/FieldLabel.svelte +3 -3
  32. package/dist/visual/FormatPopover.svelte +25 -9
  33. package/dist/visual/MatrixField.svelte +10 -7
  34. package/dist/visual/ObjectField.svelte +2 -4
  35. package/dist/visual/TipsCard.svelte +2 -5
  36. package/dist/visual/VisualEditorInner.svelte +108 -26
  37. package/dist/visual/controls.css +2 -1
  38. package/dist/visual/icons/Icon.svelte +3 -2
  39. package/dist/visual/icons/Icon.svelte.d.ts +3 -2
  40. package/dist/visual/icons/nodes.d.ts +25 -0
  41. package/dist/visual/icons/nodes.js +8 -0
  42. package/dist/visual/motion.d.ts +25 -3
  43. package/dist/visual/motion.js +58 -9
  44. package/dist/visual/strings.d.ts +6 -5
  45. package/dist/visual/strings.js +3 -3
  46. package/package.json +8 -8
@@ -1,6 +1,6 @@
1
1
  import { liftListItem, sinkListItem, splitListItem } from 'prosemirror-schema-list';
2
2
  import { EditorState, Selection, TextSelection } from 'prosemirror-state';
3
- import { chainCommands, joinTextblockBackward } from 'prosemirror-commands';
3
+ import { chainCommands, joinTextblockBackward, joinTextblockForward } from 'prosemirror-commands';
4
4
  /** The caret's enclosing `list_item` when it sits at the very start of that item's
5
5
  * first block: the position both structural keys branch on. `null` otherwise
6
6
  * (mid-text, a non-first block of a multi-paragraph item, a non-empty selection),
@@ -72,41 +72,68 @@ function paragraphAboveList(itemType, paragraph) {
72
72
  };
73
73
  }
74
74
  /**
75
- * Backspace at the start of a list's first item → lift it: one level out for a
76
- * nested item, out of the list entirely (to a paragraph) at the top.
75
+ * Backspace at the start of an item → lift it: one level out for a nested item, out of
76
+ * the list to a paragraph at the top, whichever item it is. The first press takes off
77
+ * the item's own shape and joins nothing; the press after is the join
78
+ * ({@link joinOntoList}).
79
+ *
80
+ * Lifting a later item out splits its list, and an ordered list's items past the cut
81
+ * keep their numbers: the list after what the item held starts where they were, where a
82
+ * fresh `ordered_list` would count from 1 again.
77
83
  */
78
- function liftAtListStart(itemType) {
84
+ function liftAtItemStart(itemType) {
79
85
  return (state, dispatch) => {
80
86
  const at = atItemStart(state, itemType);
81
- if (!at || at.itemIndex !== 0)
87
+ if (!at)
82
88
  return false;
83
- return liftListItem(itemType)(state, dispatch);
89
+ const list = state.doc.nodeAt(at.listPos);
90
+ const cut = list?.type.name === 'ordered_list' && !at.nested && at.itemIndex < list.childCount - 1;
91
+ const listEnd = at.listPos + (list?.nodeSize ?? 0);
92
+ return liftListItem(itemType)(state, dispatch &&
93
+ ((tr) => {
94
+ if (cut)
95
+ renumberEndingAt(tr, tr.mapping.map(listEnd, -1), list.attrs.start + at.itemIndex + 1);
96
+ dispatch(tr);
97
+ }));
84
98
  };
85
99
  }
100
+ /** Start the ordered list ending at `end` at `start`: the items a lift cut off, which
101
+ * end where the whole list did, past whatever the lifted item held. */
102
+ function renumberEndingAt(tr, end, start) {
103
+ const list = tr.doc.resolve(end).nodeBefore;
104
+ if (list?.type.name === 'ordered_list' && list.attrs.start !== start)
105
+ tr.setNodeMarkup(end - list.nodeSize, undefined, { ...list.attrs, start });
106
+ }
86
107
  /**
87
- * Backspace at the start of any later item → join its text into the previous
88
- * item's last block, so the two items become one line.
89
- *
90
- * Not the base keymap's `joinBackward`, which merges the block and leaves
91
- * `list_item(paragraph, paragraph)`: the item's marker gone while its text stays
92
- * on its own line, which the reference quill typesets as an unnumbered
93
- * continuation paragraph.
108
+ * Backspace at the head of a block that stands right after a list → join its text into
109
+ * the list's last line, as two paragraphs join. The base keymap's answer there wraps the
110
+ * block into a fresh item instead, which undoes the lift that just took it out of the
111
+ * list ({@link liftAtItemStart}) and leaves the key toggling.
94
112
  *
95
- * Only between two blocks of one kind. A fence is not a line to put another line on,
96
- * and the join across that edge would retype one side's whole text as the other's
97
- * (`code.ts`, which refuses the same join between siblings); the base keymap's answer
98
- * stands here instead, merging the items with each block kept whole.
113
+ * Between a fence and prose the text does not join: a fence is not a line to put another
114
+ * line on, and the join would retype one side's whole text as the other's (`code.ts`,
115
+ * which refuses the same join between siblings). The block moves into the list's last
116
+ * item whole instead, a block of that item under its first.
99
117
  */
100
- function mergeIntoPreviousItem(itemType) {
118
+ function joinOntoList(itemType) {
101
119
  return (state, dispatch, view) => {
102
- const at = atItemStart(state, itemType);
103
- if (!at || at.itemIndex === 0)
120
+ const { $from, empty } = state.selection;
121
+ if (!empty || !$from.parent.isTextblock || $from.parentOffset !== 0)
104
122
  return false;
105
- const into = lastBlockIn(state.doc.nodeAt(at.listPos)?.child(at.itemIndex - 1));
106
- if (!into || !!into.type.spec.code !== !!state.selection.$from.parent.type.spec.code) {
123
+ const before = state.doc.resolve($from.before()).nodeBefore;
124
+ if (before?.firstChild?.type !== itemType)
107
125
  return false;
126
+ const into = lastBlockIn(before);
127
+ if (!into)
128
+ return false;
129
+ if (!!into.type.spec.code === !!$from.parent.type.spec.code)
130
+ return joinTextblockBackward(state, dispatch, view);
131
+ if (dispatch) {
132
+ const at = $from.before() - 2;
133
+ const tr = state.tr.delete($from.before(), $from.after()).insert(at, $from.parent);
134
+ dispatch(tr.setSelection(TextSelection.create(tr.doc, at + 1)).scrollIntoView());
108
135
  }
109
- return joinTextblockBackward(state, dispatch, view);
136
+ return true;
110
137
  };
111
138
  }
112
139
  /** Whether the caret sits inside a `list_item` at any depth: a quote's paragraph
@@ -128,6 +155,10 @@ function inItem($pos, itemType) {
128
155
  * The reflection carries the edit and not the caret, which stays where the key was
129
156
  * pressed: a lift moves the item below, and Delete does not follow it.
130
157
  *
158
+ * An empty item is the exception: its line is the whole of what Delete edits, so the
159
+ * item below moves up into it rather than being lifted, and the empty item is spent. A
160
+ * fence below takes no prose line (`joinOntoList`), so there the empty line goes alone.
161
+ *
131
162
  * Declining wherever the chain does is what keeps this to the list's own seams: a
132
163
  * boundary inside one item, and the list's outer edge, stay the base keymap's, where
133
164
  * the two keys already answer alike.
@@ -142,6 +173,15 @@ function backspaceBelow(itemType, press) {
142
173
  const below = Selection.near(state.doc.resolve($from.after()), 1);
143
174
  if (below.from <= $from.pos)
144
175
  return false;
176
+ if ($from.parent.content.size === 0) {
177
+ if (!!below.$from.parent.type.spec.code === !!$from.parent.type.spec.code)
178
+ return joinTextblockForward(state, dispatch);
179
+ if (dispatch) {
180
+ const tr = state.tr.deleteRange($from.before(), $from.after());
181
+ dispatch(tr.setSelection(Selection.near(tr.doc.resolve(tr.mapping.map(below.from)), 1)));
182
+ }
183
+ return true;
184
+ }
145
185
  const pressed = $from.pos;
146
186
  const keepCaret = (tr) => dispatch?.(tr.setSelection(TextSelection.create(tr.doc, tr.mapping.map(pressed))));
147
187
  return press(EditorState.create({ doc: state.doc, selection: below }), dispatch && keepCaret);
@@ -163,8 +203,8 @@ function backspaceBelow(itemType, press) {
163
203
  * `splitListItem`, then `liftListItem`. The middle link carries two behaviors of
164
204
  * its own: it splits a non-empty item, and on an empty item it either splits the
165
205
  * wrapping item (a nested empty item, so Enter lifts exactly one level) or bails
166
- * so the third link lifts a top-level empty item out to a paragraph. `Backspace`
167
- * forks on the item's index: the first item lifts, any later one merges. `Delete` is
206
+ * so the third link lifts a top-level empty item out to a paragraph. `Backspace` lifts
207
+ * an item at its start, and joins a block onto the list it stands after. `Delete` is
168
208
  * that same chain at the seam below rather than a second table of cases beside it, so
169
209
  * the two keys cannot come to differ over a seam this link reaches. An empty line is
170
210
  * not one: the block link takes it ahead of this chain (`blocks.ts`), the caret's own
@@ -175,7 +215,7 @@ export function listKeymap(schema) {
175
215
  const paragraph = schema.nodes.paragraph;
176
216
  if (!itemType || !paragraph)
177
217
  return {};
178
- const backspace = chainCommands(liftAtListStart(itemType), mergeIntoPreviousItem(itemType));
218
+ const backspace = chainCommands(liftAtItemStart(itemType), joinOntoList(itemType));
179
219
  return {
180
220
  Tab: sinkListItem(itemType),
181
221
  'Shift-Tab': liftListItem(itemType),
@@ -29,6 +29,14 @@ li.ProseMirror-selectednode::after {
29
29
  border: var(--_qm-ring);
30
30
  left: calc(var(--_qm-space-4) * -1);
31
31
  }
32
+ /* A node selection is drawn while its leaf holds the focus, as a text selection is: a
33
+ ring left behind on a field the focus has moved off names a subject no key reaches. */
34
+ .ProseMirror:not(:focus-within) .ProseMirror-selectednode {
35
+ outline: none;
36
+ }
37
+ .ProseMirror:not(:focus-within) li.ProseMirror-selectednode::after {
38
+ border: none;
39
+ }
32
40
 
33
41
  /* The gap cursor (`prosemirror-gapcursor`, imported beside this file): a caret
34
42
  between two blocks, where the document holds no text position for an ordinary one.
@@ -227,9 +235,12 @@ li.ProseMirror-selectednode::after {
227
235
  empty line the caret holds. Only while the leaf has the focus: the decoration follows
228
236
  the selection, which an unfocused leaf keeps. Upright, being chrome about the key
229
237
  rather than an invitation in the document's voice, and never over a ghost, which is
230
- the leaf's own word for the same empty line. */
238
+ the leaf's own word for the same empty line. It comes up once the caret has rested
239
+ there, a new empty line starting the wait over. */
231
240
  .ProseMirror-focused .qm-slash-hint:not(.qm-prose-placeholder)[data-slash-hint]::after {
232
241
  content: attr(data-slash-hint);
242
+ animation: qm-slash-hint-in var(--_qm-reveal) both;
243
+ animation-delay: var(--_qm-hint-wait);
233
244
  display: block;
234
245
  margin-top: -1lh;
235
246
  white-space: nowrap;
@@ -239,6 +250,12 @@ li.ProseMirror-selectednode::after {
239
250
  pointer-events: none;
240
251
  }
241
252
 
253
+ @keyframes qm-slash-hint-in {
254
+ from {
255
+ opacity: 0;
256
+ }
257
+ }
258
+
242
259
  /* ── The table island ────────────────────────────────────────────────────────
243
260
 
244
261
  The one construct in this file with chrome in it. The rest of the stylesheet
@@ -313,19 +330,16 @@ li.ProseMirror-selectednode::after {
313
330
  .qm-table {
314
331
  border-collapse: separate;
315
332
  border-spacing: 0;
316
- border: var(--_qm-border-width) solid var(--_qm-border);
317
- border-radius: var(--_qm-radius-inner);
333
+ border: var(--_qm-border-width) solid var(--_qm-rule);
334
+ border-radius: var(--_qm-radius);
318
335
  }
319
- /* Two tiers of rule, because a table draws two different things. The frame and the
320
- header's rule are structure and keep the full hairline; the lines between cells are
321
- rhythm and drop to the faint rung, so the grid reads as one object with divisions
322
- rather than as a stack of boxes. `separate` with zero spacing rather than
323
- `collapse`: a collapsed border cannot be rounded, and the two tiers would fight
324
- over which one wins an edge. */
336
+ /* One rule at one strength, the frame's and every line between cells, so the grid
337
+ reads at the weight of the type it holds. `separate` with zero spacing rather than
338
+ `collapse`: a collapsed border cannot be rounded. */
325
339
  .qm-table-cell {
326
340
  position: relative;
327
- border-right: var(--_qm-border-width) solid var(--_qm-border-faint);
328
- border-bottom: var(--_qm-border-width) solid var(--_qm-border-faint);
341
+ border-right: var(--_qm-border-width) solid var(--_qm-rule);
342
+ border-bottom: var(--_qm-border-width) solid var(--_qm-rule);
329
343
  padding: var(--_qm-space) var(--_qm-space-2);
330
344
  vertical-align: top;
331
345
  font-weight: inherit;
@@ -339,13 +353,29 @@ li.ProseMirror-selectednode::after {
339
353
  }
340
354
  /* The header row is the one weight distinction the island draws: `header` is a
341
355
  separate field in the model rather than a flag on a row, so the surface says so.
342
- It takes a tone with it, which is the same move a field makes: the well rung, a
343
- band cut into the paper rather than a layer stacked on it, and a rung either way
344
- rather than a shadow. The rest of a table's type is paper's. */
356
+ It takes a tone with it, the band rung, a step toward the ink at both poles. The
357
+ rest of a table's type is paper's. */
345
358
  .qm-table-header-row .qm-table-cell {
346
359
  font-weight: var(--_qm-weight-strong);
347
- background: var(--_qm-surface-well);
348
- border-bottom: var(--_qm-border-width) solid var(--_qm-border);
360
+ background: var(--_qm-surface-band);
361
+ }
362
+ /* The corner cells take the frame's corner, inside its stroke, so a header's band or a
363
+ selection's wash keeps to the rounded edge rather than painting a square one over
364
+ it. The first row is the colgroup's next sibling's, whichever section holds it. */
365
+ .qm-table > colgroup + * > tr:first-child > .qm-table-cell:first-child {
366
+ border-start-start-radius: calc(var(--_qm-radius) - var(--_qm-border-width));
367
+ }
368
+ .qm-table > colgroup + * > tr:first-child > .qm-table-cell:last-child {
369
+ border-start-end-radius: calc(var(--_qm-radius) - var(--_qm-border-width));
370
+ }
371
+ .qm-table
372
+ > :is(:last-child, thead:has(+ tbody:empty))
373
+ > tr:last-child
374
+ > .qm-table-cell:first-child {
375
+ border-end-start-radius: calc(var(--_qm-radius) - var(--_qm-border-width));
376
+ }
377
+ .qm-table > :is(:last-child, thead:has(+ tbody:empty)) > tr:last-child > .qm-table-cell:last-child {
378
+ border-end-end-radius: calc(var(--_qm-radius) - var(--_qm-border-width));
349
379
  }
350
380
  /* A cell holds a whole nested view. Its measure floor keeps an empty table from
351
381
  collapsing to a row of hairlines with nothing to aim at, and the caret is the
@@ -400,20 +430,16 @@ li.ProseMirror-selectednode::after {
400
430
  color: var(--_qm-ink);
401
431
  cursor: pointer;
402
432
  }
403
- /* The bar every one of them draws: a pill the length of what it acts on, a rhythm rung
404
- thick where the target is the tap floor. Stated once, so a grip and an add bar cannot
405
- drift into two shapes.
406
-
407
- One tone, not a hover ladder: the control is drawn only while it is the one the
408
- pointer is on or the one a gesture is about, so a resting tone under that would be a
409
- rung nothing ever reaches. The selected grip's tint below is the one exception, and it
410
- says which line the next command acts on rather than where the pointer is. */
433
+ /* The pill every one of them draws, a rhythm rung thick where the target is the tap
434
+ floor. Stated once, so a grip and an add bar cannot drift into two shapes. The selected
435
+ grip's tint below says which line the next command acts on rather than where the
436
+ pointer is. */
411
437
  :is(.qm-table-grip-bar, .qm-table-add-bar) {
412
438
  display: flex;
413
439
  align-items: center;
414
440
  justify-content: center;
415
441
  border-radius: var(--_qm-radius-pill);
416
- background: var(--_qm-border);
442
+ background: var(--_qm-surface-band);
417
443
  transition:
418
444
  background-color var(--_qm-duration-fast) var(--_qm-ease-reverse),
419
445
  color var(--_qm-duration-fast) var(--_qm-ease-reverse);
@@ -424,32 +450,34 @@ li.ProseMirror-selectednode::after {
424
450
  width: var(--_qm-space-3);
425
451
  height: var(--_qm-space-3);
426
452
  }
427
- /* A grip runs along the line it names; an add bar runs across the axis it appends to.
428
- So the two families map one `data-axis` onto opposite orientations, and orientation
429
- is the one thing the rule above cannot share with them: a column's grip is as wide as
430
- its column, and the bar that adds a column is as tall as the table. */
431
- .qm-table-grip[data-axis='column'] .qm-table-grip-bar,
453
+ /* An add bar runs across the axis it appends to, the length of the table; a grip is a
454
+ short pill centred on the line it names, the tap floor long. */
432
455
  .qm-table-add[data-axis='row'] .qm-table-add-bar {
433
456
  width: 100%;
434
457
  height: var(--_qm-space-3);
435
458
  }
436
- .qm-table-grip[data-axis='row'] .qm-table-grip-bar,
437
459
  .qm-table-add[data-axis='column'] .qm-table-add-bar {
438
460
  width: var(--_qm-space-3);
439
461
  height: 100%;
440
462
  }
463
+ .qm-table-grip[data-axis='column'] .qm-table-grip-bar {
464
+ width: var(--_qm-tap-min);
465
+ height: var(--_qm-space-3);
466
+ }
467
+ .qm-table-grip[data-axis='row'] .qm-table-grip-bar {
468
+ width: var(--_qm-space-3);
469
+ height: var(--_qm-tap-min);
470
+ }
441
471
 
442
- /* One grip per line, and it is a bar rather than a glyph: what a grip acts on is the
443
- row or column it sits against, and spanning that line says so better than an icon
444
- could. The bar carries the grip's own marks — two rows of three, the shape a thing
445
- you can pick up has everywhere else — so the control reads as draggable before it is
446
- dragged.
472
+ /* One grip per line. The pill carries the grip's own marks at the label ink — two rows
473
+ of three, the shape a thing you can pick up has everywhere else — so the control reads
474
+ as draggable before it is dragged.
447
475
 
448
- The target is the band and the bar is drawn inside it: a grip spans its line on one
449
- axis and the tap floor on the other, and the bar is drawn thinner inside that. A grip
450
- sized to its own bar would be a target the thickness of the mark on it, on a control
451
- the pointer has to find. */
476
+ The target is the band and the pill is drawn inside it: a grip spans its line on one
477
+ axis and the tap floor on the other. A grip sized to its own pill would be a target
478
+ the size of the mark on it, on a control the pointer has to find. */
452
479
  .qm-table-grip {
480
+ color: var(--_qm-ink-label);
453
481
  /* The pointer owns this gesture, not the page's scroll: a grip drags. */
454
482
  touch-action: none;
455
483
  }
@@ -490,8 +518,8 @@ li.ProseMirror-selectednode::after {
490
518
  /* The reveal is the control's own (CODEC §"The table island"): one comes up while the
491
519
  pointer is on that control, while it holds the focus, or while it is what a gesture is
492
520
  about — the selected line's grip, and the lifted one, which is drawn through a drag the
493
- pointer has travelled off. The controls' boxes meet at no point, so what is drawn is
494
- the one line the pointer has picked out.
521
+ pointer has travelled off. A cell under the pointer draws its row's and its column's
522
+ grips (`table-view.ts` §`onHover`), one pill on each axis and none anywhere else.
495
523
 
496
524
  Opacity rather than absence, so every control keeps its box and is hit-tested wherever
497
525
  it sits. A grip's reach is the pointer's alone and it holds no focus, so it carries no
@@ -504,7 +532,7 @@ li.ProseMirror-selectednode::after {
504
532
  transition: opacity var(--_qm-duration-fast) var(--_qm-ease-reverse);
505
533
  }
506
534
  .qm-table-add:is(:hover, :focus-visible),
507
- .qm-table-grip:is(:hover, [aria-pressed='true'], .qm-table-lifted) {
535
+ .qm-table-grip:is([data-near], :hover, [aria-pressed='true'], .qm-table-lifted) {
508
536
  opacity: 1;
509
537
  }
510
538
 
@@ -14,19 +14,64 @@
14
14
  // paste inside one body run the whole document through them (CODEC §"Markdown at the
15
15
  // edges"), so every attribute written here is read back here. Foreign HTML spells none
16
16
  // of this package's `data-qm-*` names, so what a paste takes off the web is unchanged;
17
- // the one rule here that widens that door on purpose is the fence's, a `<table>` being
18
- // the block leaf's clipboard parser's (`table-paste.ts`).
17
+ // the rules here that widen that door on purpose are the fence's and a span's styled
18
+ // marks, a `<table>` being the block leaf's clipboard parser's (`table-paste.ts`).
19
19
  import { Schema } from 'prosemirror-model';
20
20
  import { islandBlockSpec, islandInlineSpec } from './islands.js';
21
21
  import { rendersHref, storableUrl } from './urls.js';
22
+ // ── Marks spelled as style ──────────────────────────────────────────────────
23
+ // A word processor writes formatting as a span's inline style rather than as a tag:
24
+ // Google Docs spells all four marks below that way, and wraps a whole copy in a `<b>`
25
+ // stating a regular weight. Read off a `<span>` alone, since the same style on a cell or
26
+ // a heading is that block's face rather than a run's. An underline inside a link is the
27
+ // link's own: Docs underlines every one, and Chrome inlines the computed decoration on a
28
+ // copy off any page.
29
+ /** Whether `el`'s inline style states a bold weight. */
30
+ const bold = (el) => /^(?:bold(?:er)?|[6-9]\d\d)$/.test(el.style.fontWeight);
31
+ /** The text decoration `el` states, as a shorthand or a longhand. */
32
+ const decoration = (el) => `${el.style.textDecoration} ${el.style.textDecorationLine}`;
33
+ /** A mark a `<span>` carries where `styled` holds of it. Not consuming, so every mark a
34
+ * span states matches, and so do the span's own rules after them. */
35
+ const styledSpan = (styled) => ({
36
+ tag: 'span',
37
+ consuming: false,
38
+ getAttrs: (el) => styled(el) && null
39
+ });
22
40
  // ── Marks (the block and inline schemas share them; plaintext declares none) ─
23
41
  const marks = {
24
42
  // Order matters: it fixes mark-set sort order and parse precedence. `link`
25
43
  // last so it wraps outermost.
26
- strong: { parseDOM: [{ tag: 'strong' }, { tag: 'b' }], toDOM: () => ['strong', 0] },
27
- em: { parseDOM: [{ tag: 'em' }, { tag: 'i' }], toDOM: () => ['em', 0] },
28
- underline: { parseDOM: [{ tag: 'u' }], toDOM: () => ['u', 0] },
29
- strike: { parseDOM: [{ tag: 's' }, { tag: 'del' }], toDOM: () => ['s', 0] },
44
+ strong: {
45
+ parseDOM: [
46
+ { tag: 'strong' },
47
+ { tag: 'b', getAttrs: (el) => (!el.style.fontWeight || bold(el)) && null },
48
+ styledSpan(bold)
49
+ ],
50
+ toDOM: () => ['strong', 0]
51
+ },
52
+ em: {
53
+ parseDOM: [
54
+ { tag: 'em' },
55
+ { tag: 'i' },
56
+ styledSpan((el) => /^(?:italic|oblique)\b/.test(el.style.fontStyle))
57
+ ],
58
+ toDOM: () => ['em', 0]
59
+ },
60
+ underline: {
61
+ parseDOM: [
62
+ { tag: 'u' },
63
+ styledSpan((el) => /\bunderline\b/.test(decoration(el)) && !el.closest('a'))
64
+ ],
65
+ toDOM: () => ['u', 0]
66
+ },
67
+ strike: {
68
+ parseDOM: [
69
+ { tag: 's' },
70
+ { tag: 'del' },
71
+ styledSpan((el) => /\bline-through\b/.test(decoration(el)))
72
+ ],
73
+ toDOM: () => ['s', 0]
74
+ },
30
75
  code: { parseDOM: [{ tag: 'code' }], toDOM: () => ['code', 0] },
31
76
  link: {
32
77
  attrs: { href: { default: '' } },
@@ -50,11 +95,10 @@ const marks = {
50
95
  }
51
96
  };
52
97
  // ── The fence's language ────────────────────────────────────────────────────
53
- // The one attribute no keystroke in the visual editor mints: the shorthand fires on
54
- // the third backtick, before a language could be typed, and a slash command is a name
55
- // (VISUAL_EDITOR §"Settled and open"). A paste is the gesture that does, and this is
56
- // what reads it — a fence copied off a highlighted page arrives carrying the language
57
- // that page stated, one copied inside the body keeps its own.
98
+ // Minted by the fence shorthand, which fires on Enter with the language typed after the
99
+ // backticks (`inputrules.ts`), and by a paste, which this reads: a fence copied off a
100
+ // highlighted page arrives carrying the language that page stated, one copied inside
101
+ // the body keeps its own.
58
102
  /** A `language-x` / `lang-x` token in a class list. */
59
103
  const LANG_CLASS = /(?:^|\s)lang(?:uage)?-(\S+)/;
60
104
  /** The language a `<pre>` states: `data-lang`, which is what `toDOM` writes, else the
Binary file
@@ -57,6 +57,11 @@ export interface TableViewDeps {
57
57
  * which is PM's 5px default. */
58
58
  clearance: number | undefined;
59
59
  }
60
+ /** The table island mounted at `pos` in `view`, which a caret can step into; `undefined`
61
+ * for any other node, or where no view is mounted. */
62
+ export declare function tableAt(view: EditorView, pos: number): {
63
+ enter(side: -1 | 1): void;
64
+ } | undefined;
60
65
  /** The `island_block` node view: a table island's editing surface, and the literal
61
66
  * placeholder for every other island type. */
62
67
  export declare function tableNodeView(deps: TableViewDeps): NodeViewConstructor;