@quillmark/svelte 0.19.0 → 0.20.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 (55) hide show
  1. package/NOTICE +4 -2
  2. package/dist/core/codec/blocks.d.ts +11 -2
  3. package/dist/core/codec/blocks.js +45 -14
  4. package/dist/core/codec/breaks.d.ts +3 -2
  5. package/dist/core/codec/breaks.js +3 -2
  6. package/dist/core/codec/decode.d.ts +7 -3
  7. package/dist/core/codec/decode.js +34 -8
  8. package/dist/core/codec/field.d.ts +11 -5
  9. package/dist/core/codec/field.js +11 -8
  10. package/dist/core/codec/index.d.ts +1 -1
  11. package/dist/core/codec/index.js +1 -1
  12. package/dist/core/codec/islands.d.ts +3 -1
  13. package/dist/core/codec/islands.js +8 -5
  14. package/dist/core/codec/markdown-paste.d.ts +9 -0
  15. package/dist/core/codec/markdown-paste.js +49 -0
  16. package/dist/core/codec/prose.css +109 -1
  17. package/dist/core/codec/schema.d.ts +8 -2
  18. package/dist/core/codec/schema.js +16 -5
  19. package/dist/core/codec/slash.d.ts +9 -6
  20. package/dist/core/codec/slash.js +0 -0
  21. package/dist/core/codec/table-paste.d.ts +7 -0
  22. package/dist/core/codec/table-paste.js +132 -0
  23. package/dist/core/codec/table-view.d.ts +12 -0
  24. package/dist/core/codec/table-view.js +401 -57
  25. package/dist/core/codec/table.d.ts +38 -16
  26. package/dist/core/codec/table.js +106 -35
  27. package/dist/core/theme.css +1 -1
  28. package/dist/visual/ArrayField.svelte +4 -0
  29. package/dist/visual/Field.svelte +8 -2
  30. package/dist/visual/Field.svelte.d.ts +1 -1
  31. package/dist/visual/FieldHint.svelte +62 -10
  32. package/dist/visual/FormatPopover.svelte +27 -31
  33. package/dist/visual/FormatPopover.svelte.d.ts +2 -2
  34. package/dist/visual/MatrixAdd.svelte +13 -132
  35. package/dist/visual/MatrixAdd.svelte.d.ts +2 -5
  36. package/dist/visual/MatrixField.svelte +5 -63
  37. package/dist/visual/MatrixField.svelte.d.ts +0 -3
  38. package/dist/visual/ObjectField.svelte +7 -3
  39. package/dist/visual/ObjectField.svelte.d.ts +4 -2
  40. package/dist/visual/ProseField.svelte +2 -1
  41. package/dist/visual/ProseValue.svelte +113 -40
  42. package/dist/visual/ProseValue.svelte.d.ts +8 -4
  43. package/dist/visual/VisualEditorInner.svelte +22 -22
  44. package/dist/visual/VisualEditorInner.svelte.d.ts +2 -2
  45. package/dist/visual/checklists.d.ts +0 -79
  46. package/dist/visual/checklists.js +0 -164
  47. package/dist/visual/guidance.d.ts +15 -0
  48. package/dist/visual/guidance.js +37 -0
  49. package/dist/visual/leaves.d.ts +27 -1
  50. package/dist/visual/leaves.js +46 -0
  51. package/dist/visual/strings.d.ts +3 -8
  52. package/dist/visual/strings.js +0 -3
  53. package/dist/visual/structure.js +1 -1
  54. package/dist/visual/tips.js +1 -1
  55. package/package.json +2 -2
@@ -6,14 +6,16 @@
6
6
  // single-textblock form for `richtext(inline)` (one paragraph, no block split, no
7
7
  // containers, no islands) and `plaintextSchema` is that one without marks, for
8
8
  // `plaintext(inline)`; `plainSchema` is a `plaintext` field without `inline`,
9
- // paragraphs and hard breaks and nothing else. Same decode/lower/position machinery,
9
+ // paragraphs and hard breaks and nothing else; `cellSchema` is the inline form with
10
+ // hard breaks, for a table cell. Same decode/lower/position machinery,
10
11
  // narrower shapes. Anchors are not marks here (decorations).
11
12
  //
12
13
  // `toDOM` and `parseDOM` are one tier, not two halves of a rendering: a copy and a
13
14
  // paste inside one body run the whole document through them (CODEC §"Markdown at the
14
15
  // edges"), so every attribute written here is read back here. Foreign HTML spells none
15
16
  // of this package's `data-qm-*` names, so what a paste takes off the web is unchanged;
16
- // the one rule that widens that door on purpose is the fence's.
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
19
  import { Schema } from 'prosemirror-model';
18
20
  import { islandBlockSpec, islandInlineSpec } from './islands.js';
19
21
  import { rendersHref, storableUrl } from './urls.js';
@@ -176,7 +178,7 @@ export const blockSchema = new Schema({
176
178
  nodes: { ...blockNodes, ...inlineLeafNodes },
177
179
  marks
178
180
  });
179
- // ── The constrained inline nodes (both inline schemas) ──────────────────────
181
+ // ── The constrained inline nodes (the inline schemas and the cell's) ────
180
182
  // `doc: "paragraph"` (exactly one child) is what makes an Enter a no-op at the
181
183
  // model level: there is no second block for a split to land.
182
184
  const inlineNodes = {
@@ -185,8 +187,7 @@ const inlineNodes = {
185
187
  text: { group: 'inline' }
186
188
  };
187
189
  /** The constrained inline schema: one paragraph, no block splitting, no
188
- * containers, no islands, and the full mark set (a `richtext(inline)` field, and
189
- * a table cell, which is the same content unit). */
190
+ * containers, no islands, and the full mark set (a `richtext(inline)` field). */
190
191
  export const inlineSchema = new Schema({ nodes: inlineNodes, marks });
191
192
  /**
192
193
  * The inline schema with no mark types at all: a `plaintext(inline)` field, whose
@@ -198,6 +199,16 @@ export const inlineSchema = new Schema({ nodes: inlineNodes, marks });
198
199
  * carrying (CODEC §Inline mode).
199
200
  */
200
201
  export const plaintextSchema = new Schema({ nodes: inlineNodes, marks: {} });
202
+ /**
203
+ * A table cell: the inline schema plus `hard_break`, which a `TableCell` stores as a
204
+ * `\n` in its `text`. Still one paragraph, so Enter has no block to split; a break is
205
+ * Shift-Enter's (`breaks.ts`). Declaring the break routes `decode` through the plain
206
+ * path, which folds a cell's `continues` lines into that one paragraph.
207
+ */
208
+ export const cellSchema = new Schema({
209
+ nodes: { ...inlineNodes, hard_break: inlineLeafNodes.hard_break },
210
+ marks
211
+ });
201
212
  /**
202
213
  * A `plaintext` field without `inline`: paragraphs and hard breaks, no marks, no
203
214
  * islands, no containers, which is the shape `Content::is_plain` admits. Mark-free
@@ -1,5 +1,5 @@
1
1
  import { EditorState, Plugin, type Command } from 'prosemirror-state';
2
- import type { EditorView } from 'prosemirror-view';
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
5
5
  * cursor, and the trigger's anchor to hang off. `undefined` is a closed menu.
@@ -16,10 +16,13 @@ export interface SlashState {
16
16
  * open menu outlives any one scroll position of the leaf. */
17
17
  anchor: RangeAnchor;
18
18
  }
19
- /** The menu's wording, which is its accessible name and nothing else: the offers are
20
- * command names, and a name is not translated. */
19
+ /** The menu's wording, which is its accessible name and the empty line's hint: the
20
+ * offers are command names, and a name is not translated. */
21
21
  export interface SlashStrings {
22
22
  slashLabel: string;
23
+ /** Drawn on the empty line a focused caret holds, naming the key that opens the
24
+ * menu there. */
25
+ slashHint: string;
23
26
  }
24
27
  /** The package's English, beside the surface that draws it: the same rule the island
25
28
  * chrome's wording follows, and the visual `strings` set extends both. */
@@ -28,8 +31,8 @@ export declare const DEFAULT_SLASH_STRINGS: SlashStrings;
28
31
  * The vocabulary a caret can run, in menu order: the names `SLASH_COMMANDS` is keyed
29
32
  * by, each asked whether it would fire here. A row that declines when picked is worse
30
33
  * 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.
34
+ * keeps the menu from drifting off what a pick does. So the offers narrow as the caret
35
+ * moves: the lists and the island alone mid-paragraph, no list at an item's head.
33
36
  *
34
37
  * `state` is the state a pick leaves behind — the run already consumed — since that is
35
38
  * the caret every command answers for (`blocks.ts`).
@@ -54,7 +57,7 @@ interface SlashRun {
54
57
  * below and close the menu without a listener of their own. A dismissal edits no
55
58
  * text; only a pick does, and it consumes exactly the run.
56
59
  */
57
- export declare function slashPlugin(onState: (state: SlashState | undefined) => void): Plugin<SlashRun | null>;
60
+ export declare function slashPlugin(onState: (state: SlashState | undefined) => void, hint?: () => string | undefined): Plugin<SlashRun | null>;
58
61
  /**
59
62
  * The menu's keys, the innermost link of the leaf's chain (`keymap.ts`). Every one
60
63
  * declines on an empty offer list rather than on a dead run (§`SlashState`): a key an
Binary file
@@ -0,0 +1,7 @@
1
+ import { Plugin } from 'prosemirror-state';
2
+ /**
3
+ * A block leaf's table paste: the clipboard's parse for HTML, and for text that is
4
+ * tab-separated. A paste as plain text keeps the text, tabs and all. A cell's own view
5
+ * mounts none of this, so a paste inside a cell is inline.
6
+ */
7
+ export declare function tablePastePlugin(): Plugin;
@@ -0,0 +1,132 @@
1
+ // A table on the clipboard lands as a table island (CODEC §"Markdown at the edges"). Two
2
+ // spellings carry one: an HTML `<table>`, and the tab-separated text a spreadsheet writes
3
+ // beside it, which ProseMirror hands over only where the clipboard holds no HTML. Either
4
+ // builds its props through `normalizeTable` and arrives with no id, which the paste's
5
+ // island pass mints (`islands.ts`). Nothing the source styled a table with has a reader
6
+ // here, so a pasted table takes the document's face.
7
+ import { DOMParser, Fragment, Slice } from 'prosemirror-model';
8
+ import { Plugin } from 'prosemirror-state';
9
+ import { usvLength } from './decode.js';
10
+ import { pmToContent } from './encode.js';
11
+ import { blockSchema } from './schema.js';
12
+ import { emptyCell, normalizeTable } from './table.js';
13
+ const schemaRules = DOMParser.fromSchema(blockSchema).rules;
14
+ /** What a cell's markup is read with: the schema's rules less the islands', so an island
15
+ * element inside a cell is the text it holds. */
16
+ const cellParser = new DOMParser(blockSchema, schemaRules.filter((rule) => !('node' in rule && rule.node?.startsWith('island_'))));
17
+ /** A `<td>` or `<th>` as a cell: its blocks are lines and a `<br>` a break, each one `\n`
18
+ * in the text (`cellContent`), and its marks the ones the schema's rules read. Whitespace
19
+ * at the cell's edges is dropped, which is where a source's empty paragraph lands. */
20
+ function cellFromDOM(el) {
21
+ const { text, marks } = pmToContent(cellParser.parse(el));
22
+ // Every whitespace character is in the BMP, so `lead` counts USVs as well.
23
+ const lead = text.length - text.trimStart().length;
24
+ const kept = text.trim();
25
+ const end = usvLength(kept);
26
+ return {
27
+ text: kept,
28
+ marks: marks.flatMap((m) => {
29
+ const start = Math.max(0, m.start - lead);
30
+ const stop = Math.min(end, m.end - lead);
31
+ return start < stop ? [{ ...m, start, end: stop }] : [];
32
+ })
33
+ };
34
+ }
35
+ /**
36
+ * A `<table>` as table props, or `null` where it holds no cell. Rows run in the order the
37
+ * DOM's `rows` gives them, a `<thead>`'s first, and the first is the header. A merged
38
+ * cell splits: its content in the first slot it covers and an empty cell in each other,
39
+ * so the cells beside and under it keep their columns.
40
+ */
41
+ function tableFromDOM(table) {
42
+ const rows = Array.from(table.rows);
43
+ const grid = rows.map(() => []);
44
+ rows.forEach((tr, r) => {
45
+ let c = 0;
46
+ for (const td of Array.from(tr.cells)) {
47
+ while (grid[r][c])
48
+ c++;
49
+ const across = Math.max(1, td.colSpan);
50
+ const down = Math.min(td.rowSpan || rows.length, rows.length - r);
51
+ for (let dr = 0; dr < down; dr++)
52
+ for (let dc = 0; dc < across; dc++)
53
+ grid[r + dr][c + dc] = dr || dc ? emptyCell() : cellFromDOM(td);
54
+ c += across;
55
+ }
56
+ });
57
+ if (!grid.some((row) => row.length))
58
+ return null;
59
+ // A row a merge reached into past its own cells has holes.
60
+ const [header = [], ...body] = grid.map((row) => Array.from(row, (cell) => cell ?? emptyCell()));
61
+ return normalizeTable({ header, rows: body, aligns: [] });
62
+ }
63
+ const QUOTED = /"((?:[^"]|"")*)"(?=[\t\r\n]|$)/y;
64
+ const BARE = /[^\t\r\n]*/y;
65
+ /**
66
+ * Tab-separated text as table props, the first line the header, or `null` unless every
67
+ * line holds a tab and some cell holds more than whitespace. A cell in double quotes is
68
+ * one cell whose tabs, line ends and doubled quotes are its text, which is how Excel and
69
+ * Sheets write a cell holding any of the three; a quote not closed that way is literal.
70
+ * The line ends closing the text are the spreadsheet's terminator, not an empty row.
71
+ */
72
+ function tableFromText(text) {
73
+ const body = text.replace(/[\r\n]+$/, '');
74
+ const lines = [[]];
75
+ let at = 0;
76
+ for (;;) {
77
+ QUOTED.lastIndex = BARE.lastIndex = at;
78
+ const quoted = QUOTED.exec(body);
79
+ const cell = quoted
80
+ ? quoted[1].replace(/""/g, '"').replace(/\r\n?/g, '\n')
81
+ : BARE.exec(body)[0];
82
+ lines.at(-1).push(cell);
83
+ at = quoted ? QUOTED.lastIndex : BARE.lastIndex;
84
+ if (at >= body.length)
85
+ break;
86
+ if (body[at] === '\t')
87
+ at += 1;
88
+ else {
89
+ at += body.startsWith('\r\n', at) ? 2 : 1;
90
+ lines.push([]);
91
+ }
92
+ }
93
+ if (!lines.every((cells) => cells.length > 1))
94
+ return null;
95
+ if (!lines.some((cells) => cells.some((cell) => cell.trim())))
96
+ return null;
97
+ const [header = [], ...rows] = lines.map((cells) => cells.map((t) => ({ text: t, marks: [] })));
98
+ return normalizeTable({ header, rows, aligns: [] });
99
+ }
100
+ /** The schema's parse with a `<table>` read as an island rather than as its cells' text. */
101
+ const clipboardParser = new DOMParser(blockSchema, [
102
+ {
103
+ tag: 'table',
104
+ node: 'island_block',
105
+ getAttrs: (el) => {
106
+ const props = tableFromDOM(el);
107
+ return props ? { islandType: 'table', props } : false;
108
+ }
109
+ },
110
+ ...schemaRules
111
+ ]);
112
+ /**
113
+ * A block leaf's table paste: the clipboard's parse for HTML, and for text that is
114
+ * tab-separated. A paste as plain text keeps the text, tabs and all. A cell's own view
115
+ * mounts none of this, so a paste inside a cell is inline.
116
+ */
117
+ export function tablePastePlugin() {
118
+ return new Plugin({
119
+ props: {
120
+ clipboardParser,
121
+ // A falsy return declines, handing the text to ProseMirror's own split into
122
+ // paragraphs; the prop's type has no spelling for it.
123
+ clipboardTextParser: ((text, _context, plain) => {
124
+ const props = plain ? null : tableFromText(text);
125
+ if (!props)
126
+ return null;
127
+ const island = blockSchema.nodes.island_block.create({ islandType: 'table', props });
128
+ return new Slice(Fragment.from(island), 0, 0);
129
+ })
130
+ }
131
+ });
132
+ }
@@ -21,6 +21,18 @@ export interface TableChromeStrings {
21
21
  /** The two trailing bars, each of which grows the table along its own axis. */
22
22
  tableAddRow: string;
23
23
  tableAddColumn: string;
24
+ /** The held column's alignment cluster, a name per toggle, and the held cells'
25
+ * cluster, which takes these three and the vertical three beside them. */
26
+ tableAlignLeft: string;
27
+ tableAlignCenter: string;
28
+ tableAlignRight: string;
29
+ /** The held first row's toggle, pressed while the row draws as the header. */
30
+ tableHeaderToggle: string;
31
+ tableAlignTop: string;
32
+ tableAlignMiddle: string;
33
+ tableAlignBottom: string;
34
+ /** The held cells' cluster's own name. */
35
+ tableCellAlignment: string;
24
36
  }
25
37
  /**
26
38
  * The package's English for the island chrome. It lives here, beside the surface