@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,202 +0,0 @@
1
- // The table island's clipboard door, both directions: what a copy writes, a paste
2
- // reads. The design and the two wire formats are CODEC §"The table island" and
3
- // §"Markdown at the edges"; what is here is the grammar.
4
- //
5
- // What crosses is A rectangle, never a cell. A `<table>` read in is one; the held cell
6
- // selection written out is one; a rectangle overlaid onto a table at a cell is one. The
7
- // wire therefore carries `TableCell[][]` and no coordinates: each end names its own
8
- // corner.
9
- //
10
- // A row 0 is A header, on both sides, which is the model's own rule (`table.ts`
11
- // §`allRows`): whichever row holds index 0 is the header. So a `<table>` with no `<th>`
12
- // promotes its first row, and a copied rectangle's first row is the header of what it
13
- // pastes.
14
- import { DOMParser as PMDOMParser, DOMSerializer, Fragment, Slice } from 'prosemirror-model';
15
- import { Plugin } from 'prosemirror-state';
16
- import { islandMinter } from './islands.js';
17
- import { inlineSchema } from './schema.js';
18
- import { ALIGNS, cellContent, cellFromDoc, emptyCell, normalizeTable } from './table.js';
19
- import { decode } from './decode.js';
20
- /** The two keys, spelled once: a reader and a writer that disagreed about which holds
21
- * the table would each work alone and never round-trip. */
22
- const HTML = 'text/html';
23
- const PLAIN = 'text/plain';
24
- // ── Reading a table in ──────────────────────────────────────────────────────
25
- /**
26
- * One cell from its `<td>` / `<th>`. The inline schema is the whole normalization: it
27
- * declares one paragraph, no containers and no islands, so a cell holding blocks
28
- * flattens to its text and an image in one drops out rather than leaving a slot char
29
- * behind. Whitespace collapses and the ends trim, which is PM's own parse.
30
- */
31
- function cellFromDOM(dom) {
32
- const para = PMDOMParser.fromSchema(inlineSchema).parse(dom, {
33
- topNode: inlineSchema.nodes.paragraph.create()
34
- });
35
- return cellFromDoc(inlineSchema.nodes.doc.create(null, para), emptyCell());
36
- }
37
- /** A column's alignment off its header cell, which is where a pipe table's delimiter
38
- * row puts it. Anything else (`start`, `justify`, absent) is `none`. */
39
- function alignFromDOM(dom) {
40
- const raw = (dom.getAttribute('align') ??
41
- dom.style?.textAlign ??
42
- '').toLowerCase();
43
- return ALIGNS.includes(raw) ? raw : 'none';
44
- }
45
- /**
46
- * A `<table>` element as props. `rowspan` is not carried — a merged cell is not a
47
- * rectangle, so a table with one arrives with the rows below it short and
48
- * {@link normalizeTable} pads them at the end. `colspan` is, as the empty cells it
49
- * covers, so the columns after it keep their index.
50
- *
51
- * Only the table's own rows: a `<tr>` inside a nested table belongs to a cell this
52
- * reader is about to flatten, not to a rank of this one.
53
- */
54
- export function tableFromDOM(dom) {
55
- const rows = [];
56
- const aligns = [];
57
- for (const tr of dom.querySelectorAll('tr')) {
58
- if (tr.closest('table') !== dom)
59
- continue;
60
- const cells = [];
61
- for (const box of tr.children) {
62
- if (box.tagName !== 'TD' && box.tagName !== 'TH')
63
- continue;
64
- if (!rows.length)
65
- aligns.push(alignFromDOM(box));
66
- cells.push(cellFromDOM(box));
67
- const span = Number(box.getAttribute('colspan')) || 1;
68
- for (let i = 1; i < span; i++) {
69
- if (!rows.length)
70
- aligns.push('none');
71
- cells.push(emptyCell());
72
- }
73
- }
74
- rows.push(cells);
75
- }
76
- const [header = [], ...body] = rows;
77
- return normalizeTable({ header, rows: body, aligns });
78
- }
79
- /** The table a paste is carrying, or `undefined` for a paste that holds none. The first
80
- * one: a clipboard holding a table among other blocks is a document paste, and only a
81
- * surface that takes nothing but a table (a cell) reads it this way. */
82
- export function tableFromClipboard(data) {
83
- const html = data?.getData(HTML);
84
- if (!html || !/<table[\s>]/i.test(html))
85
- return undefined;
86
- const dom = new DOMParser().parseFromString(html, HTML).querySelector('table');
87
- return dom ? tableFromDOM(dom) : undefined;
88
- }
89
- // ── Writing a rectangle out ─────────────────────────────────────────────────
90
- /** A cell's inline DOM, serialized by the schema that declares its marks, which is what
91
- * makes {@link cellFromDOM} its exact inverse. */
92
- function cellToDOM(cell, owner) {
93
- const doc = decode(cellContent(cell), inlineSchema);
94
- const line = doc.firstChild;
95
- return DOMSerializer.fromSchema(inlineSchema).serializeFragment(line ? line.content : Fragment.empty, { document: owner });
96
- }
97
- /**
98
- * A rectangle as a `<table>`: the wire the HTML arm writes and reads. Its first row is
99
- * a `<th>` row whatever row of the source it came from, that being the row index 0 a
100
- * paste lands it at.
101
- */
102
- export function tableToDOM(block, aligns, owner) {
103
- const table = owner.createElement('table');
104
- block.forEach((cells, r) => {
105
- const tr = owner.createElement('tr');
106
- cells.forEach((cell, c) => {
107
- const box = owner.createElement(r === 0 ? 'th' : 'td');
108
- const align = aligns[c] ?? 'none';
109
- if (align !== 'none')
110
- box.style.textAlign = align;
111
- box.appendChild(cellToDOM(cell, owner));
112
- tr.appendChild(box);
113
- });
114
- table.appendChild(tr);
115
- });
116
- return table;
117
- }
118
- /** A cell's text as one pipe-table field: the delimiter escaped, since a `|` inside a
119
- * cell would otherwise open a column that is not there. Marks are dropped, markdown
120
- * being the lossy arm. */
121
- const pipeCell = (cell) => cell.text.replaceAll('|', '\\|');
122
- /**
123
- * A rectangle as pipe rows, delimiter row included: what a plain-text target reads as a
124
- * table and what markdown carries. The delimiter follows the first row for the reason
125
- * that row serializes as `<th>` — a pasted rectangle's row 0 is a header.
126
- */
127
- export function tableToPipe(block, aligns) {
128
- const width = block[0]?.length ?? 0;
129
- const rule = Array.from({ length: width }, (_, c) => {
130
- const align = aligns[c] ?? 'none';
131
- if (align === 'center')
132
- return ':---:';
133
- return align === 'left' ? ':---' : align === 'right' ? '---:' : '---';
134
- });
135
- const line = (cells) => `| ${cells.join(' | ')} |`;
136
- const [head = [], ...body] = block;
137
- return [line(head.map(pipeCell)), line(rule), ...body.map((row) => line(row.map(pipeCell)))].join('\n');
138
- }
139
- /** Put a rectangle on the clipboard, both arms. The caller is a `copy` / `cut` handler
140
- * over a selection its own surface holds, so the rectangle arrives already cut out and
141
- * claiming the event is the caller's. */
142
- export function writeRectangle(data, block, aligns, owner) {
143
- data.setData(HTML, tableToDOM(block, aligns, owner).outerHTML);
144
- data.setData(PLAIN, tableToPipe(block, aligns));
145
- }
146
- // ── The paste door ──────────────────────────────────────────────────────────
147
- /** The `<table>` rule: a clipboard table becomes the island the model already holds
148
- * rather than the paragraph its text flattens to. `id` is left empty for
149
- * {@link stampIslandIds}, a parse rule having no document to mint against. `loss` is
150
- * authored: what this reader produces is a pipe table, so markdown carries it whole. */
151
- function tableParseRule() {
152
- return {
153
- tag: 'table',
154
- node: 'island_block',
155
- getAttrs: (dom) => ({
156
- id: '',
157
- islandType: 'table',
158
- props: tableFromDOM(dom),
159
- loss: 'lossless'
160
- })
161
- };
162
- }
163
- /** The clipboard's parser: the schema's own rules with the table rule ahead of them.
164
- * Ahead, because a `<table>` otherwise matches nothing and its cells' text falls
165
- * through to the enclosing block. */
166
- export function tableClipboardParser(schema) {
167
- return new PMDOMParser(schema, [tableParseRule(), ...PMDOMParser.fromSchema(schema).rules]);
168
- }
169
- /** An island node a parse rule produced, which is the only kind carrying no id. */
170
- const unstamped = (node) => (node.type.name === 'island_block' || node.type.name === 'island_inline') && !node.attrs.id;
171
- /** Mint an id for every island a paste is carrying. An id is part of the document's
172
- * canonical bytes and the channel that addresses an island (CODEC §Islands), so a
173
- * pasted island with none reaches the store as an insert nothing can name afterwards.
174
- * One minter for the whole slice, or a paste placing two tables hands them one id. */
175
- export function stampIslandIds(slice, doc) {
176
- const mint = islandMinter(doc);
177
- const walk = (fragment) => {
178
- const out = [];
179
- fragment.forEach((node) => {
180
- const next = node.content.size ? node.copy(walk(node.content)) : node;
181
- out.push(unstamped(next) ? next.type.create({ ...next.attrs, id: mint() }, next.content) : next);
182
- });
183
- return Fragment.fromArray(out);
184
- };
185
- return new Slice(walk(slice.content), slice.openStart, slice.openEnd);
186
- }
187
- /**
188
- * The block leaf's clipboard plugin: the table rule on the way in, and the id mint over
189
- * what it produced. A `clipboardParser` rather than the schema's own `parseDOM`,
190
- * because what a `<table>` in the DOM means is a question only the clipboard asks — the
191
- * schema's parse rules are also what a re-parse of the editor's own DOM runs through,
192
- * where an island is already a node and a `<table>` is the chrome drawing one.
193
- */
194
- export function tableClipboardPlugin(schema) {
195
- const parser = tableClipboardParser(schema);
196
- return new Plugin({
197
- props: {
198
- clipboardParser: parser,
199
- transformPasted: (slice, view) => stampIslandIds(slice, view.state.doc)
200
- }
201
- });
202
- }