@quillmark/svelte 0.20.1 → 0.22.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 (85) hide show
  1. package/NOTICE +6 -7
  2. package/README.md +1 -1
  3. package/THEMING.md +1 -1
  4. package/dist/core/address.d.ts +6 -1
  5. package/dist/core/address.js +12 -3
  6. package/dist/core/bloom.d.ts +9 -4
  7. package/dist/core/bloom.js +18 -49
  8. package/dist/core/codec/atoms.d.ts +1 -1
  9. package/dist/core/codec/atoms.js +26 -11
  10. package/dist/core/codec/blocks.d.ts +5 -5
  11. package/dist/core/codec/blocks.js +27 -8
  12. package/dist/core/codec/code.d.ts +4 -4
  13. package/dist/core/codec/code.js +52 -22
  14. package/dist/core/codec/decode.d.ts +5 -5
  15. package/dist/core/codec/decode.js +18 -12
  16. package/dist/core/codec/encode.js +6 -1
  17. package/dist/core/codec/field.d.ts +22 -15
  18. package/dist/core/codec/field.js +66 -26
  19. package/dist/core/codec/index.d.ts +1 -1
  20. package/dist/core/codec/index.js +2 -2
  21. package/dist/core/codec/inputrules.d.ts +4 -0
  22. package/dist/core/codec/inputrules.js +44 -6
  23. package/dist/core/codec/link-paste.d.ts +11 -0
  24. package/dist/core/codec/link-paste.js +32 -0
  25. package/dist/core/codec/lists.d.ts +5 -4
  26. package/dist/core/codec/lists.js +87 -31
  27. package/dist/core/codec/nesting.d.ts +20 -0
  28. package/dist/core/codec/nesting.js +62 -0
  29. package/dist/core/codec/prose.css +145 -63
  30. package/dist/core/codec/schema.d.ts +2 -2
  31. package/dist/core/codec/schema.js +71 -18
  32. package/dist/core/codec/slash.js +0 -0
  33. package/dist/core/codec/table-copy.d.ts +17 -0
  34. package/dist/core/codec/table-copy.js +84 -0
  35. package/dist/core/codec/table-view.d.ts +6 -7
  36. package/dist/core/codec/table-view.js +281 -201
  37. package/dist/core/codec/table.d.ts +2 -9
  38. package/dist/core/codec/table.js +10 -19
  39. package/dist/core/codec/tasks.d.ts +19 -0
  40. package/dist/core/codec/tasks.js +140 -0
  41. package/dist/core/errors.d.ts +2 -2
  42. package/dist/core/rungs.d.ts +6 -0
  43. package/dist/core/rungs.js +11 -0
  44. package/dist/core/theme.css +24 -12
  45. package/dist/visual/ArrayField.svelte +236 -78
  46. package/dist/visual/ArrayField.svelte.d.ts +0 -2
  47. package/dist/visual/BooleanField.svelte +12 -1
  48. package/dist/visual/Card.svelte +41 -23
  49. package/dist/visual/Card.svelte.d.ts +3 -2
  50. package/dist/visual/CardControls.svelte +15 -15
  51. package/dist/visual/DateField.svelte +93 -64
  52. package/dist/visual/EnumField.svelte +9 -4
  53. package/dist/visual/Field.svelte +20 -16
  54. package/dist/visual/Field.svelte.d.ts +4 -2
  55. package/dist/visual/FieldHint.svelte +22 -5
  56. package/dist/visual/FieldLabel.svelte +5 -33
  57. package/dist/visual/FieldLabel.svelte.d.ts +0 -2
  58. package/dist/visual/FormatPopover.svelte +78 -81
  59. package/dist/visual/MatrixField.svelte +28 -19
  60. package/dist/visual/NumberField.svelte +32 -3
  61. package/dist/visual/ObjectField.svelte +40 -24
  62. package/dist/visual/ObjectField.svelte.d.ts +6 -0
  63. package/dist/visual/ProseField.svelte +10 -13
  64. package/dist/visual/ProseField.svelte.d.ts +5 -2
  65. package/dist/visual/ProseValue.svelte +25 -19
  66. package/dist/visual/ProseValue.svelte.d.ts +4 -4
  67. package/dist/visual/TextField.svelte +3 -2
  68. package/dist/visual/TipsCard.svelte +7 -7
  69. package/dist/visual/VariantField.svelte +1 -1
  70. package/dist/visual/VariantField.svelte.d.ts +1 -1
  71. package/dist/visual/VisualEditorInner.svelte +121 -39
  72. package/dist/visual/controls.css +21 -2
  73. package/dist/visual/icons/Icon.svelte +3 -2
  74. package/dist/visual/icons/Icon.svelte.d.ts +3 -2
  75. package/dist/visual/icons/nodes.d.ts +25 -21
  76. package/dist/visual/icons/nodes.js +9 -7
  77. package/dist/visual/leaves.d.ts +12 -9
  78. package/dist/visual/leaves.js +19 -0
  79. package/dist/visual/motion.d.ts +25 -3
  80. package/dist/visual/motion.js +58 -9
  81. package/dist/visual/strings.d.ts +15 -9
  82. package/dist/visual/strings.js +19 -12
  83. package/dist/visual/structure.d.ts +21 -31
  84. package/dist/visual/structure.js +54 -51
  85. package/package.json +3 -3
@@ -3,9 +3,9 @@
3
3
  // (list_item/quote → lists/blockquote); marks mirror the content formatting set.
4
4
  // Each set is closed upstream, so the schema names its whole vocabulary and carries
5
5
  // nothing inert. `blockSchema` is the full field; `inlineSchema` is the constrained
6
- // single-textblock form for `richtext(inline)` (one paragraph, no block split, no
7
- // containers, no islands) and `plaintextSchema` is that one without marks, for
8
- // `plaintext(inline)`; `plainSchema` is a `plaintext` field without `inline`,
6
+ // single-textblock form for a one-line `richtext` (one paragraph, no block split, no
7
+ // containers, no islands) and `plaintextSchema` is that one without marks, for a
8
+ // one-line `text`; `plainSchema` is a `text` field declaring `multiline: true`,
9
9
  // paragraphs and hard breaks and nothing else; `cellSchema` is the inline form with
10
10
  // hard breaks, for a table cell. Same decode/lower/position machinery,
11
11
  // narrower shapes. Anchors are not marks here (decorations).
@@ -14,19 +14,65 @@
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
+ import { taskBody, taskChecked, taskDOM } from './tasks.js';
21
22
  import { rendersHref, storableUrl } from './urls.js';
23
+ // ── Marks spelled as style ──────────────────────────────────────────────────
24
+ // A word processor writes formatting as a span's inline style rather than as a tag:
25
+ // Google Docs spells all four marks below that way, and wraps a whole copy in a `<b>`
26
+ // stating a regular weight. Read off a `<span>` alone, since the same style on a cell or
27
+ // a heading is that block's face rather than a run's. An underline inside a link is the
28
+ // link's own: Docs underlines every one, and Chrome inlines the computed decoration on a
29
+ // copy off any page.
30
+ /** Whether `el`'s inline style states a bold weight. */
31
+ const bold = (el) => /^(?:bold(?:er)?|[6-9]\d\d)$/.test(el.style.fontWeight);
32
+ /** The text decoration `el` states, as a shorthand or a longhand. */
33
+ const decoration = (el) => `${el.style.textDecoration} ${el.style.textDecorationLine}`;
34
+ /** A mark a `<span>` carries where `styled` holds of it. Not consuming, so every mark a
35
+ * span states matches, and so do the span's own rules after them. */
36
+ const styledSpan = (styled) => ({
37
+ tag: 'span',
38
+ consuming: false,
39
+ getAttrs: (el) => styled(el) && null
40
+ });
22
41
  // ── Marks (the block and inline schemas share them; plaintext declares none) ─
23
42
  const marks = {
24
43
  // Order matters: it fixes mark-set sort order and parse precedence. `link`
25
44
  // 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] },
45
+ strong: {
46
+ parseDOM: [
47
+ { tag: 'strong' },
48
+ { tag: 'b', getAttrs: (el) => (!el.style.fontWeight || bold(el)) && null },
49
+ styledSpan(bold)
50
+ ],
51
+ toDOM: () => ['strong', 0]
52
+ },
53
+ em: {
54
+ parseDOM: [
55
+ { tag: 'em' },
56
+ { tag: 'i' },
57
+ styledSpan((el) => /^(?:italic|oblique)\b/.test(el.style.fontStyle))
58
+ ],
59
+ toDOM: () => ['em', 0]
60
+ },
61
+ underline: {
62
+ parseDOM: [
63
+ { tag: 'u' },
64
+ styledSpan((el) => /\bunderline\b/.test(decoration(el)) && !el.closest('a'))
65
+ ],
66
+ toDOM: () => ['u', 0]
67
+ },
68
+ strike: {
69
+ parseDOM: [
70
+ { tag: 's' },
71
+ { tag: 'del' },
72
+ styledSpan((el) => /\bline-through\b/.test(decoration(el)))
73
+ ],
74
+ toDOM: () => ['s', 0]
75
+ },
30
76
  code: { parseDOM: [{ tag: 'code' }], toDOM: () => ['code', 0] },
31
77
  link: {
32
78
  attrs: { href: { default: '' } },
@@ -50,11 +96,10 @@ const marks = {
50
96
  }
51
97
  };
52
98
  // ── 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.
99
+ // Minted by the fence shorthand, which fires on Enter with the language typed after the
100
+ // backticks (`inputrules.ts`), and by a paste, which this reads: a fence copied off a
101
+ // highlighted page arrives carrying the language that page stated, one copied inside
102
+ // the body keeps its own.
58
103
  /** A `language-x` / `lang-x` token in a class list. */
59
104
  const LANG_CLASS = /(?:^|\s)lang(?:uage)?-(\S+)/;
60
105
  /** The language a `<pre>` states: `data-lang`, which is what `toDOM` writes, else the
@@ -149,11 +194,19 @@ const blockNodes = {
149
194
  parseDOM: [{ tag: 'ul' }],
150
195
  toDOM: () => ['ul', 0]
151
196
  },
197
+ // `checked` is a task item's, `null` on every other item (`tasks.ts`).
152
198
  list_item: {
153
199
  content: 'block+',
154
200
  defining: true,
155
- parseDOM: [{ tag: 'li' }],
156
- toDOM: () => ['li', 0]
201
+ attrs: { checked: { default: null } },
202
+ parseDOM: [
203
+ {
204
+ tag: 'li',
205
+ getAttrs: (el) => ({ checked: taskChecked(el) }),
206
+ contentElement: taskBody
207
+ }
208
+ ],
209
+ toDOM: (node) => node.attrs.checked === null ? ['li', 0] : taskDOM(node.attrs.checked)
157
210
  },
158
211
  island_block: islandBlockSpec
159
212
  };
@@ -210,7 +263,7 @@ export const cellSchema = new Schema({
210
263
  marks
211
264
  });
212
265
  /**
213
- * A `plaintext` field without `inline`: paragraphs and hard breaks, no marks, no
266
+ * A `text` field declaring `multiline: true`: paragraphs and hard breaks, no marks, no
214
267
  * islands, no containers, which is the shape `Content::is_plain` admits. Mark-free
215
268
  * for the reason {@link plaintextSchema} is.
216
269
  *
@@ -234,7 +287,7 @@ export function leafSchema(opts) {
234
287
  return opts.inline ? plaintextSchema : plainSchema;
235
288
  return opts.inline ? inlineSchema : blockSchema;
236
289
  }
237
- /** Whether `schema` can carry formatting at all: false for the two plaintext schemas.
290
+ /** Whether `schema` can carry formatting at all: false for the two mark-free schemas.
238
291
  * What a mark command asks before it offers itself. */
239
292
  export function hasMarks(schema) {
240
293
  return Object.keys(schema.marks).length > 0;
Binary file
@@ -0,0 +1,17 @@
1
+ import { Plugin } from 'prosemirror-state';
2
+ import type { TableProps } from '@quillmark/wasm';
3
+ /** A block of cells, both corners inclusive, in the row space `table.ts` names. */
4
+ export interface CellRange {
5
+ r0: number;
6
+ c0: number;
7
+ r1: number;
8
+ c1: number;
9
+ }
10
+ /** The cells of `range` as a `<table>`, the header row in a `<thead>` where the range
11
+ * holds it and the table has one. The cells carry their marks and breaks, and nothing
12
+ * of the table's layout. */
13
+ export declare function tableElement(props: TableProps, range?: CellRange): HTMLTableElement;
14
+ /** The cells of `range` as tab-separated text, a line a row. */
15
+ export declare function tableText(props: TableProps, range?: CellRange): string;
16
+ /** A block leaf's copy: a table in the slice writes as a table in both spellings. */
17
+ export declare function tableCopyPlugin(): Plugin;
@@ -0,0 +1,84 @@
1
+ // A table leaves the editor as the two spellings a table arrives in (`table-paste.ts`):
2
+ // an HTML `<table>` and tab-separated text, what Word, Docs, Excel and Sheets each read.
3
+ // Two copies write them: a held rectangle's cells (`table-view.ts`), and a selection
4
+ // holding a whole island, whose markup keeps the island's own element around the table
5
+ // so a paste inside the editor reads the island back whole.
6
+ import { DOMSerializer } from 'prosemirror-model';
7
+ import { Plugin } from 'prosemirror-state';
8
+ import { decode } from './decode.js';
9
+ import { tablePropsOfNode } from './islands.js';
10
+ import { blockSchema, cellSchema } from './schema.js';
11
+ import { cellAt, cellContent, columnCount, rowCount } from './table.js';
12
+ const whole = (props) => ({
13
+ r0: 0,
14
+ c0: 0,
15
+ r1: rowCount(props) - 1,
16
+ c1: columnCount(props) - 1
17
+ });
18
+ const cellSerializer = DOMSerializer.fromSchema(cellSchema);
19
+ function cellDOM(tag, cell) {
20
+ const dom = document.createElement(tag);
21
+ const paragraph = decode(cellContent(cell), cellSchema).firstChild;
22
+ if (paragraph)
23
+ dom.append(cellSerializer.serializeFragment(paragraph.content));
24
+ return dom;
25
+ }
26
+ /** The cells of `range` as a `<table>`, the header row in a `<thead>` where the range
27
+ * holds it and the table has one. The cells carry their marks and breaks, and nothing
28
+ * of the table's layout. */
29
+ export function tableElement(props, range = whole(props)) {
30
+ const table = document.createElement('table');
31
+ let body;
32
+ for (let r = range.r0; r <= range.r1; r++) {
33
+ const head = r === 0 && !props.headless;
34
+ const section = head ? table.createTHead() : (body ??= table.createTBody());
35
+ const tr = section.insertRow();
36
+ for (let c = range.c0; c <= range.c1; c++)
37
+ tr.append(cellDOM(head ? 'th' : 'td', cellAt(props, r, c)));
38
+ }
39
+ return table;
40
+ }
41
+ /** A cell holding a tab, a line end or a quote goes in quotes with its quotes doubled,
42
+ * as Excel and Sheets write one. */
43
+ function tsvCell(text) {
44
+ return /[\t\n"]/.test(text) ? `"${text.replace(/"/g, '""')}"` : text;
45
+ }
46
+ /** The cells of `range` as tab-separated text, a line a row. */
47
+ export function tableText(props, range = whole(props)) {
48
+ const lines = [];
49
+ for (let r = range.r0; r <= range.r1; r++) {
50
+ const cells = [];
51
+ for (let c = range.c0; c <= range.c1; c++)
52
+ cells.push(tsvCell(cellAt(props, r, c).text));
53
+ lines.push(cells.join('\t'));
54
+ }
55
+ return lines.join('\n');
56
+ }
57
+ const base = DOMSerializer.fromSchema(blockSchema);
58
+ /** The island's own element with the table inside it: a paste in the editor matches the
59
+ * element and reads `props`, and every other reader finds the table. */
60
+ const islandSerializer = new DOMSerializer({
61
+ ...base.nodes,
62
+ island_block: (node) => {
63
+ const props = tablePropsOfNode(node);
64
+ const dom = DOMSerializer.renderSpec(document, blockSchema.nodes.island_block.spec.toDOM(node)).dom;
65
+ if (props)
66
+ dom.replaceChildren(tableElement(props));
67
+ return dom;
68
+ }
69
+ }, base.marks);
70
+ function leafText(node) {
71
+ const props = node.type === blockSchema.nodes.island_block ? tablePropsOfNode(node) : undefined;
72
+ if (props)
73
+ return tableText(props);
74
+ return node.type.spec.leafText?.(node) ?? '';
75
+ }
76
+ /** A block leaf's copy: a table in the slice writes as a table in both spellings. */
77
+ export function tableCopyPlugin() {
78
+ return new Plugin({
79
+ props: {
80
+ clipboardSerializer: islandSerializer,
81
+ clipboardTextSerializer: (slice) => slice.content.textBetween(0, slice.content.size, '\n\n', leafText)
82
+ }
83
+ });
84
+ }
@@ -21,18 +21,12 @@ 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. */
24
+ /** The held column's alignment cluster, a name per toggle. */
26
25
  tableAlignLeft: string;
27
26
  tableAlignCenter: string;
28
27
  tableAlignRight: string;
29
28
  /** The held first row's toggle, pressed while the row draws as the header. */
30
29
  tableHeaderToggle: string;
31
- tableAlignTop: string;
32
- tableAlignMiddle: string;
33
- tableAlignBottom: string;
34
- /** The held cells' cluster's own name. */
35
- tableCellAlignment: string;
36
30
  }
37
31
  /**
38
32
  * The package's English for the island chrome. It lives here, beside the surface
@@ -57,6 +51,11 @@ export interface TableViewDeps {
57
51
  * which is PM's 5px default. */
58
52
  clearance: number | undefined;
59
53
  }
54
+ /** The table island mounted at `pos` in `view`, which a caret can step into; `undefined`
55
+ * for any other node, or where no view is mounted. */
56
+ export declare function tableAt(view: EditorView, pos: number): {
57
+ enter(side: -1 | 1): void;
58
+ } | undefined;
60
59
  /** The `island_block` node view: a table island's editing surface, and the literal
61
60
  * placeholder for every other island type. */
62
61
  export declare function tableNodeView(deps: TableViewDeps): NodeViewConstructor;