@quillmark/svelte 0.1.1 → 0.3.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.
- package/README.md +16 -12
- package/THEMING.md +48 -25
- package/dist/core/address.d.ts +42 -7
- package/dist/core/address.js +49 -21
- package/dist/core/bloom.d.ts +5 -5
- package/dist/core/bloom.js +8 -8
- package/dist/core/codec/anchor.d.ts +1 -1
- package/dist/core/codec/anchor.js +1 -1
- package/dist/core/codec/atoms.d.ts +10 -0
- package/dist/core/codec/atoms.js +78 -0
- package/dist/core/codec/clipboard.d.ts +59 -0
- package/dist/core/codec/clipboard.js +202 -0
- package/dist/core/codec/code.d.ts +1 -1
- package/dist/core/codec/code.js +4 -4
- package/dist/core/codec/decode.d.ts +1 -1
- package/dist/core/codec/decode.js +8 -8
- package/dist/core/codec/encode.d.ts +4 -4
- package/dist/core/codec/encode.js +14 -11
- package/dist/core/codec/field.d.ts +22 -27
- package/dist/core/codec/field.js +33 -32
- package/dist/core/codec/index.js +5 -8
- package/dist/core/codec/inputrules.js +56 -18
- package/dist/core/codec/islands.d.ts +11 -7
- package/dist/core/codec/islands.js +16 -10
- package/dist/core/codec/keymap.d.ts +5 -6
- package/dist/core/codec/keymap.js +8 -7
- package/dist/core/codec/lists.d.ts +3 -3
- package/dist/core/codec/lists.js +32 -13
- package/dist/core/codec/marks.d.ts +1 -1
- package/dist/core/codec/marks.js +1 -1
- package/dist/core/codec/positions.js +1 -1
- package/dist/core/codec/prose.css +266 -228
- package/dist/core/codec/schema.js +4 -4
- package/dist/core/codec/slash.d.ts +39 -41
- package/dist/core/codec/slash.js +0 -0
- package/dist/core/codec/table-view.d.ts +12 -16
- package/dist/core/codec/table-view.js +488 -292
- package/dist/core/codec/table.d.ts +42 -15
- package/dist/core/codec/table.js +73 -27
- package/dist/core/errors.d.ts +17 -7
- package/dist/core/index.d.ts +1 -1
- package/dist/core/index.js +6 -5
- package/dist/core/lifecycle.d.ts +21 -5
- package/dist/core/lifecycle.js +34 -8
- package/dist/core/rebind.svelte.d.ts +18 -4
- package/dist/core/rebind.svelte.js +30 -12
- package/dist/core/teardown.d.ts +1 -1
- package/dist/core/teardown.js +1 -1
- package/dist/core/theme.css +173 -58
- package/dist/preset/index.css +1 -1
- package/dist/preset/recipes.css +79 -30
- package/dist/preset/scale.css +40 -28
- package/dist/preview/Preview.svelte +22 -19
- package/dist/preview/Preview.svelte.d.ts +8 -8
- package/dist/preview/bridge.d.ts +6 -4
- package/dist/preview/bridge.js +19 -9
- package/dist/preview/controller.d.ts +13 -6
- package/dist/preview/controller.js +8 -5
- package/dist/preview/geometry.d.ts +17 -2
- package/dist/preview/geometry.js +21 -1
- package/dist/preview/index.d.ts +1 -0
- package/dist/preview/index.js +9 -2
- package/dist/preview/overlay.d.ts +2 -1
- package/dist/preview/overlay.js +26 -11
- package/dist/preview/paint.js +19 -17
- package/dist/visual/ArrayField.svelte +103 -83
- package/dist/visual/ArrayField.svelte.d.ts +5 -4
- package/dist/visual/BooleanField.svelte +8 -6
- package/dist/visual/BooleanField.svelte.d.ts +2 -2
- package/dist/visual/Card.svelte +161 -333
- package/dist/visual/Card.svelte.d.ts +5 -4
- package/dist/visual/CardControls.svelte +1 -1
- package/dist/visual/DateField.svelte +19 -19
- package/dist/visual/DateField.svelte.d.ts +3 -3
- package/dist/visual/DiagnosticList.svelte +2 -2
- package/dist/visual/EnumField.svelte +48 -28
- package/dist/visual/EnumField.svelte.d.ts +5 -4
- package/dist/visual/Field.svelte +207 -132
- package/dist/visual/Field.svelte.d.ts +11 -7
- package/dist/visual/FieldHint.svelte +79 -48
- package/dist/visual/FieldHint.svelte.d.ts +1 -1
- package/dist/visual/FieldLabel.svelte +8 -8
- package/dist/visual/FormatPopover.svelte +82 -35
- package/dist/visual/NumberField.svelte +5 -5
- package/dist/visual/NumberField.svelte.d.ts +1 -1
- package/dist/visual/ObjectField.svelte +28 -9
- package/dist/visual/ObjectField.svelte.d.ts +4 -2
- package/dist/visual/ProseArrayElement.svelte +12 -19
- package/dist/visual/ProseArrayElement.svelte.d.ts +1 -2
- package/dist/visual/ProseField.svelte +62 -85
- package/dist/visual/ProseField.svelte.d.ts +14 -17
- package/dist/visual/SlashMenu.svelte +42 -16
- package/dist/visual/TextField.svelte +5 -5
- package/dist/visual/TextField.svelte.d.ts +1 -1
- package/dist/visual/TipsCard.svelte +43 -28
- package/dist/visual/VisualEditor.svelte +18 -19
- package/dist/visual/VisualEditor.svelte.d.ts +2 -3
- package/dist/visual/VisualEditorInner.svelte +251 -184
- package/dist/visual/VisualEditorInner.svelte.d.ts +2 -3
- package/dist/visual/controls.css +85 -38
- package/dist/visual/diagnostics.d.ts +13 -21
- package/dist/visual/diagnostics.js +7 -19
- package/dist/visual/domid.js +3 -3
- package/dist/visual/ext.d.ts +1 -1
- package/dist/visual/ext.js +1 -1
- package/dist/visual/icons/nodes.d.ts +3 -0
- package/dist/visual/icons/nodes.js +2 -1
- package/dist/visual/index.d.ts +1 -0
- package/dist/visual/index.js +10 -3
- package/dist/visual/leaves.d.ts +47 -0
- package/dist/visual/leaves.js +31 -0
- package/dist/visual/links.d.ts +24 -0
- package/dist/visual/links.js +67 -0
- package/dist/visual/props.d.ts +33 -12
- package/dist/visual/signals.d.ts +6 -6
- package/dist/visual/strings.d.ts +14 -19
- package/dist/visual/strings.js +5 -7
- package/dist/visual/structure.d.ts +24 -17
- package/dist/visual/structure.js +8 -8
- package/dist/visual/synced.svelte.d.ts +1 -1
- package/dist/visual/synced.svelte.js +4 -4
- package/dist/visual/tips.d.ts +4 -2
- package/dist/visual/tips.js +7 -4
- package/package.json +2 -2
|
@@ -8,14 +8,18 @@ export declare const ALIGNS: readonly TableAlign[];
|
|
|
8
8
|
/** The zero cell: empty text, no marks. */
|
|
9
9
|
export declare function emptyCell(): TableCell;
|
|
10
10
|
/**
|
|
11
|
-
* The row index space the chrome and the ops share: **0 is the header**, 1…n the
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* deletes
|
|
11
|
+
* The row index space the chrome and the ops share: **0 is the header**, 1…n the body
|
|
12
|
+
* rows. The split is how a table is stored, not a rank the ops answer to: a row op
|
|
13
|
+
* works {@link allRows} and whichever row lands at index 0 is the header afterwards,
|
|
14
|
+
* so index 0 moves and deletes like any other. The one row the model refuses is the
|
|
15
|
+
* last, there being no table under it.
|
|
15
16
|
*/
|
|
16
17
|
export declare function rowCount(props: TableProps): number;
|
|
17
18
|
/** The cells of row `r` in that space. */
|
|
18
19
|
export declare function rowCells(props: TableProps, r: number): TableCell[];
|
|
20
|
+
/** Whether a row holds no text, which is what tells a row on offer from one taken up
|
|
21
|
+
* (`table-view.ts` §`step`). Text alone: a mark over none is nothing a writer can see. */
|
|
22
|
+
export declare function rowEmpty(props: TableProps, r: number): boolean;
|
|
19
23
|
/** The cell at `(row, column)`, or the zero cell where the rectangle does not reach. */
|
|
20
24
|
export declare function cellAt(props: TableProps, r: number, c: number): TableCell;
|
|
21
25
|
/** The table's column count: the one number `header`, every row and `aligns` share. */
|
|
@@ -27,6 +31,13 @@ export declare function columnCount(props: TableProps): number;
|
|
|
27
31
|
* back, and a table is never narrower than one column.
|
|
28
32
|
*/
|
|
29
33
|
export declare function normalizeTable(props: TableProps): TableProps;
|
|
34
|
+
/**
|
|
35
|
+
* The rows as one list, header first: the space a row op works in. `header` is a
|
|
36
|
+
* separate field because a table always has one and `header: []` is not a table — a
|
|
37
|
+
* storage shape, not a rank a row op has to know about. Reading it as one list is what
|
|
38
|
+
* lets move and delete treat every row alike, and {@link fromRows} splits it back.
|
|
39
|
+
*/
|
|
40
|
+
export declare function allRows(props: TableProps): TableCell[][];
|
|
30
41
|
/** A fresh table: a header plus `bodyRows` empty rows, every column unaligned.
|
|
31
42
|
* Growth is cheap (Tab at the last cell appends a row), so the default is small. */
|
|
32
43
|
export declare function newTable(cols?: number, bodyRows?: number): TableProps;
|
|
@@ -34,32 +45,48 @@ export declare function newTable(cols?: number, bodyRows?: number): TableProps;
|
|
|
34
45
|
export declare function withCell(props: TableProps, r: number, c: number, cell: TableCell): TableProps;
|
|
35
46
|
/** A new empty body row below row `r` (row 0, the header, opens the first body row). */
|
|
36
47
|
export declare function insertRow(props: TableProps, r: number): TableProps;
|
|
37
|
-
/** Drop
|
|
38
|
-
* table
|
|
48
|
+
/** Drop row `r`, the header included: the row left at index 0 is the header, which is
|
|
49
|
+
* how a table whose first row went still has one. A no-op at the last row, that being
|
|
50
|
+
* the removal of the table rather than of a row, and the island's own to answer. */
|
|
39
51
|
export declare function deleteRow(props: TableProps, r: number): TableProps;
|
|
40
52
|
/** A new empty column after column `c`. */
|
|
41
53
|
export declare function insertColumn(props: TableProps, c: number): TableProps;
|
|
42
54
|
/** Drop column `c`; a no-op at one column (a table has at least one). */
|
|
43
55
|
export declare function deleteColumn(props: TableProps, c: number): TableProps;
|
|
44
|
-
/**
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
56
|
+
/** Row `r` moved by `by` places, clamped: a drag that leaves the rectangle is a no-op
|
|
57
|
+
* rather than an error, since the drop target is geometry and geometry runs past the
|
|
58
|
+
* last row. The header travels like any other row, and so does the row that displaces
|
|
59
|
+
* it: being the header is holding index 0, which a move is free to hand over. */
|
|
48
60
|
export declare function moveRow(props: TableProps, r: number, by: number): TableProps;
|
|
49
61
|
/** Column `c` moved by `by` places, clamped. `aligns` travels with the column: an
|
|
50
62
|
* alignment is the column's property, so a move that left it behind would retint the
|
|
51
63
|
* column that took the index. */
|
|
52
64
|
export declare function moveColumn(props: TableProps, c: number, by: number): TableProps;
|
|
53
|
-
/**
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
|
|
65
|
+
/**
|
|
66
|
+
* Every cell in the inclusive rectangle emptied, alignment kept: what a delete gesture
|
|
67
|
+
* means over a block of cells that covers no whole rank. A rectangle spanning one axis
|
|
68
|
+
* covers ranks and deletes them, and one spanning both is the table itself, so what
|
|
69
|
+
* reaches here is a proper sub-rectangle and the shape it leaves is the shape it found.
|
|
70
|
+
*
|
|
71
|
+
* Alignment is the column's rather than the cells', so it survives a clear that spans one.
|
|
72
|
+
*/
|
|
73
|
+
export declare function clearCells(props: TableProps, r0: number, c0: number, r1: number, c1: number): TableProps;
|
|
74
|
+
/**
|
|
75
|
+
* A rectangle written in at `(r, c)`, the table growing to hold whatever runs past its
|
|
76
|
+
* edges: what a paste into a cell means (`clipboard.ts`). The alternative is clipping
|
|
77
|
+
* to the rectangle that is there, which silently drops the tail of what the author
|
|
78
|
+
* copied; growth drops nothing and every added rank is one the ops already remove.
|
|
79
|
+
*
|
|
80
|
+
* `aligns` is the table's own throughout: a column keeps its alignment under a paste,
|
|
81
|
+
* and a column the paste added arrives `none` like any other fresh one.
|
|
82
|
+
*/
|
|
83
|
+
export declare function pasteCells(props: TableProps, r: number, c: number, block: TableCell[][]): TableProps;
|
|
57
84
|
/** Set column `c`'s alignment: the one table capability the content round-trips
|
|
58
85
|
* today and nothing in the editor could reach. */
|
|
59
86
|
export declare function setAlign(props: TableProps, c: number, align: TableAlign): TableProps;
|
|
60
87
|
/**
|
|
61
88
|
* A cell as a one-line `Content`: what `decode` takes under the inline schema. The
|
|
62
|
-
* cell's marks
|
|
89
|
+
* cell's marks are that content's marks, because both are offsets into the same
|
|
63
90
|
* text: the cell-local coordinate space is a `Content`'s coordinate space with
|
|
64
91
|
* one line in it.
|
|
65
92
|
*/
|
package/dist/core/codec/table.js
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
// doc) and the rectangularizing constructors every row and column op goes through
|
|
3
3
|
// (CODEC §Islands, §"The table island").
|
|
4
4
|
//
|
|
5
|
-
// `TableProps` normalizes to
|
|
5
|
+
// `TableProps` normalizes to one column count shared by `header`, every row, and
|
|
6
6
|
// `aligns`, so a ragged table is not a state the content can hold. The editor keeps
|
|
7
|
-
// its optimistic PM and re-hydrates only on an
|
|
7
|
+
// its optimistic PM and re-hydrates only on an external change (CODEC
|
|
8
8
|
// §Reconciliation), so an op that emitted a ragged table would leave the store
|
|
9
9
|
// rectangularized and PM ragged with no error channel and no repair. Every
|
|
10
10
|
// constructor here therefore ends in {@link normalizeTable}, and a caller reaches
|
|
@@ -15,7 +15,8 @@
|
|
|
15
15
|
// the codec already runs (one paragraph, no containers, no islands) is exactly its
|
|
16
16
|
// shape, so a cell decodes and projects through the same machinery a
|
|
17
17
|
// `richtext(inline)` field does.
|
|
18
|
-
import { isAnchorMark
|
|
18
|
+
import { isAnchorMark } from '@quillmark/wasm';
|
|
19
|
+
import { core } from '../lifecycle.js';
|
|
19
20
|
import { contentEdit, pmToContent } from './encode.js';
|
|
20
21
|
/** The alignments a column can take: the four the content declares, `none` first
|
|
21
22
|
* because it is what a column arrives at and what a set is cleared back to. */
|
|
@@ -25,10 +26,11 @@ export function emptyCell() {
|
|
|
25
26
|
return { text: '', marks: [] };
|
|
26
27
|
}
|
|
27
28
|
/**
|
|
28
|
-
* The row index space the chrome and the ops share: **0 is the header**, 1…n the
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
* deletes
|
|
29
|
+
* The row index space the chrome and the ops share: **0 is the header**, 1…n the body
|
|
30
|
+
* rows. The split is how a table is stored, not a rank the ops answer to: a row op
|
|
31
|
+
* works {@link allRows} and whichever row lands at index 0 is the header afterwards,
|
|
32
|
+
* so index 0 moves and deletes like any other. The one row the model refuses is the
|
|
33
|
+
* last, there being no table under it.
|
|
32
34
|
*/
|
|
33
35
|
export function rowCount(props) {
|
|
34
36
|
return props.rows.length + 1;
|
|
@@ -37,6 +39,11 @@ export function rowCount(props) {
|
|
|
37
39
|
export function rowCells(props, r) {
|
|
38
40
|
return r === 0 ? props.header : (props.rows[r - 1] ?? []);
|
|
39
41
|
}
|
|
42
|
+
/** Whether a row holds no text, which is what tells a row on offer from one taken up
|
|
43
|
+
* (`table-view.ts` §`step`). Text alone: a mark over none is nothing a writer can see. */
|
|
44
|
+
export function rowEmpty(props, r) {
|
|
45
|
+
return rowCells(props, r).every((cell) => cell.text === '');
|
|
46
|
+
}
|
|
40
47
|
/** The cell at `(row, column)`, or the zero cell where the rectangle does not reach. */
|
|
41
48
|
export function cellAt(props, r, c) {
|
|
42
49
|
return rowCells(props, r)[c] ?? emptyCell();
|
|
@@ -60,6 +67,22 @@ export function normalizeTable(props) {
|
|
|
60
67
|
aligns: Array.from({ length: cols }, (_, c) => props.aligns[c] ?? 'none')
|
|
61
68
|
};
|
|
62
69
|
}
|
|
70
|
+
/**
|
|
71
|
+
* The rows as one list, header first: the space a row op works in. `header` is a
|
|
72
|
+
* separate field because a table always has one and `header: []` is not a table — a
|
|
73
|
+
* storage shape, not a rank a row op has to know about. Reading it as one list is what
|
|
74
|
+
* lets move and delete treat every row alike, and {@link fromRows} splits it back.
|
|
75
|
+
*/
|
|
76
|
+
export function allRows(props) {
|
|
77
|
+
return [props.header, ...props.rows];
|
|
78
|
+
}
|
|
79
|
+
/** A flat list back into the stored shape: whatever sits at index 0 is the header, by
|
|
80
|
+
* position rather than by having been one. No caller passes an empty list — every op
|
|
81
|
+
* that could reach one refuses the last row first. */
|
|
82
|
+
function fromRows(rows, aligns) {
|
|
83
|
+
const [header = [], ...rest] = rows;
|
|
84
|
+
return normalizeTable({ header, rows: rest, aligns });
|
|
85
|
+
}
|
|
63
86
|
/** A fresh table: a header plus `bodyRows` empty rows, every column unaligned.
|
|
64
87
|
* Growth is cheap (Tab at the last cell appends a row), so the default is small. */
|
|
65
88
|
export function newTable(cols = 3, bodyRows = 2) {
|
|
@@ -87,12 +110,14 @@ export function insertRow(props, r) {
|
|
|
87
110
|
rows: [...props.rows.slice(0, at), fresh, ...props.rows.slice(at)]
|
|
88
111
|
});
|
|
89
112
|
}
|
|
90
|
-
/** Drop
|
|
91
|
-
* table
|
|
113
|
+
/** Drop row `r`, the header included: the row left at index 0 is the header, which is
|
|
114
|
+
* how a table whose first row went still has one. A no-op at the last row, that being
|
|
115
|
+
* the removal of the table rather than of a row, and the island's own to answer. */
|
|
92
116
|
export function deleteRow(props, r) {
|
|
93
|
-
|
|
117
|
+
const rows = allRows(props);
|
|
118
|
+
if (rows.length <= 1 || r < 0 || r >= rows.length)
|
|
94
119
|
return normalizeTable(props);
|
|
95
|
-
return
|
|
120
|
+
return fromRows(rows.filter((_, i) => i !== r), props.aligns);
|
|
96
121
|
}
|
|
97
122
|
/** A new empty column after column `c`. */
|
|
98
123
|
export function insertColumn(props, c) {
|
|
@@ -123,16 +148,16 @@ function move(xs, from, to) {
|
|
|
123
148
|
next.splice(to, 0, moved);
|
|
124
149
|
return next;
|
|
125
150
|
}
|
|
126
|
-
/**
|
|
127
|
-
*
|
|
128
|
-
*
|
|
129
|
-
*
|
|
151
|
+
/** Row `r` moved by `by` places, clamped: a drag that leaves the rectangle is a no-op
|
|
152
|
+
* rather than an error, since the drop target is geometry and geometry runs past the
|
|
153
|
+
* last row. The header travels like any other row, and so does the row that displaces
|
|
154
|
+
* it: being the header is holding index 0, which a move is free to hand over. */
|
|
130
155
|
export function moveRow(props, r, by) {
|
|
131
|
-
const
|
|
132
|
-
const to = Math.max(0, Math.min(
|
|
133
|
-
if (r ===
|
|
156
|
+
const rows = allRows(props);
|
|
157
|
+
const to = Math.max(0, Math.min(r + by, rows.length - 1));
|
|
158
|
+
if (r === to || r < 0 || r >= rows.length)
|
|
134
159
|
return normalizeTable(props);
|
|
135
|
-
return
|
|
160
|
+
return fromRows(move(rows, r, to), props.aligns);
|
|
136
161
|
}
|
|
137
162
|
/** Column `c` moved by `by` places, clamped. `aligns` travels with the column: an
|
|
138
163
|
* alignment is the column's property, so a move that left it behind would retint the
|
|
@@ -148,17 +173,37 @@ export function moveColumn(props, c, by) {
|
|
|
148
173
|
aligns: shift(props.aligns)
|
|
149
174
|
});
|
|
150
175
|
}
|
|
151
|
-
/**
|
|
152
|
-
*
|
|
153
|
-
*
|
|
154
|
-
|
|
155
|
-
|
|
176
|
+
/**
|
|
177
|
+
* Every cell in the inclusive rectangle emptied, alignment kept: what a delete gesture
|
|
178
|
+
* means over a block of cells that covers no whole rank. A rectangle spanning one axis
|
|
179
|
+
* covers ranks and deletes them, and one spanning both is the table itself, so what
|
|
180
|
+
* reaches here is a proper sub-rectangle and the shape it leaves is the shape it found.
|
|
181
|
+
*
|
|
182
|
+
* Alignment is the column's rather than the cells', so it survives a clear that spans one.
|
|
183
|
+
*/
|
|
184
|
+
export function clearCells(props, r0, c0, r1, c1) {
|
|
185
|
+
const blank = (cells, r) => cells.map((cell, c) => (r >= r0 && r <= r1 && c >= c0 && c <= c1 ? emptyCell() : cell));
|
|
156
186
|
return normalizeTable({
|
|
157
|
-
header: blank(props.header),
|
|
158
|
-
rows: props.rows.map(blank),
|
|
187
|
+
header: blank(props.header, 0),
|
|
188
|
+
rows: props.rows.map((row, i) => blank(row, i + 1)),
|
|
159
189
|
aligns: props.aligns
|
|
160
190
|
});
|
|
161
191
|
}
|
|
192
|
+
/**
|
|
193
|
+
* A rectangle written in at `(r, c)`, the table growing to hold whatever runs past its
|
|
194
|
+
* edges: what a paste into a cell means (`clipboard.ts`). The alternative is clipping
|
|
195
|
+
* to the rectangle that is there, which silently drops the tail of what the author
|
|
196
|
+
* copied; growth drops nothing and every added rank is one the ops already remove.
|
|
197
|
+
*
|
|
198
|
+
* `aligns` is the table's own throughout: a column keeps its alignment under a paste,
|
|
199
|
+
* and a column the paste added arrives `none` like any other fresh one.
|
|
200
|
+
*/
|
|
201
|
+
export function pasteCells(props, r, c, block) {
|
|
202
|
+
const rows = allRows(props);
|
|
203
|
+
const height = Math.max(rows.length, r + block.length);
|
|
204
|
+
const width = Math.max(columnCount(props), ...block.map((row) => c + row.length));
|
|
205
|
+
return fromRows(Array.from({ length: height }, (_, i) => Array.from({ length: width }, (_, j) => block[i - r]?.[j - c] ?? rows[i]?.[j] ?? emptyCell())), props.aligns);
|
|
206
|
+
}
|
|
162
207
|
/** Set column `c`'s alignment: the one table capability the content round-trips
|
|
163
208
|
* today and nothing in the editor could reach. */
|
|
164
209
|
export function setAlign(props, c, align) {
|
|
@@ -170,7 +215,7 @@ export function setAlign(props, c, align) {
|
|
|
170
215
|
// ── The cell codec ──────────────────────────────────────────────────────────
|
|
171
216
|
/**
|
|
172
217
|
* A cell as a one-line `Content`: what `decode` takes under the inline schema. The
|
|
173
|
-
* cell's marks
|
|
218
|
+
* cell's marks are that content's marks, because both are offsets into the same
|
|
174
219
|
* text: the cell-local coordinate space is a `Content`'s coordinate space with
|
|
175
220
|
* one line in it.
|
|
176
221
|
*/
|
|
@@ -201,6 +246,7 @@ export function cellFromDoc(doc, prior) {
|
|
|
201
246
|
if (!anchors.length)
|
|
202
247
|
return { text, marks: projected.marks };
|
|
203
248
|
const delta = contentEdit(cellContent(prior), cellContent({ text, marks: [] })).delta;
|
|
249
|
+
const { mapPos } = core();
|
|
204
250
|
const rebased = anchors.map((a) => {
|
|
205
251
|
const pos = delta ? mapPos(delta, a.start, 'after') : a.start;
|
|
206
252
|
return { ...a, start: pos, end: pos };
|
package/dist/core/errors.d.ts
CHANGED
|
@@ -11,7 +11,7 @@ export type EditorErrorCode =
|
|
|
11
11
|
* the whole projection instead, so the store is correct and this field's
|
|
12
12
|
* identity anchors were paid. */
|
|
13
13
|
| 'commit-fallback'
|
|
14
|
-
/** The overwrite fallback
|
|
14
|
+
/** The overwrite fallback also failed: the optimistic PM state stands and the
|
|
15
15
|
* store is stale for this field. The one code here that leaves damage. */
|
|
16
16
|
| 'commit-lost'
|
|
17
17
|
/** A card operation (add, move, remove, retype) that threw. The document is
|
|
@@ -26,18 +26,28 @@ export type EditorErrorCode =
|
|
|
26
26
|
/** A tip's markdown did not render: the tip shows as literal text. */
|
|
27
27
|
| 'tip-render-failed'
|
|
28
28
|
/**
|
|
29
|
-
* A prop a surface binds
|
|
29
|
+
* A prop a surface binds once was swapped in place, and the surface neither
|
|
30
30
|
* re-keyed on it nor observed it. The mounted surface still names the previous
|
|
31
31
|
* handle. `dev` throughout: the fix is a remount at the call site, so this is
|
|
32
32
|
* aimed at whoever wired it and never at a running app's telemetry.
|
|
33
|
+
*
|
|
34
|
+
* The one code that is also thrown, in a dev build, once reported: a swapped
|
|
35
|
+
* `onError` is itself a rebind, so the handler this arrives at may be the one the
|
|
36
|
+
* consumer just replaced (`rebind.svelte.ts`).
|
|
33
37
|
*/
|
|
34
38
|
| 'rebind-ignored'
|
|
35
39
|
/**
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
40
|
+
* A landing named a card or a field the surface does not hold: a `cardId` from a
|
|
41
|
+
* previous session or an already-removed card, or a `DocPath` naming no declared
|
|
42
|
+
* field — an array element path included, where the array itself is undeclared or
|
|
43
|
+
* unmounted. The call is a no-op and the document is untouched.
|
|
44
|
+
*
|
|
45
|
+
* `dev`, because the fix is at the call site: a host driving the instance verbs
|
|
46
|
+
* from outside, or a preview pick the editor has no field to route to. Every
|
|
47
|
+
* mounted field is a target, prose leaf and form control alike, and an array's
|
|
48
|
+
* elements are targets through it, so this is not how a scalar or an element
|
|
49
|
+
* reports. The preview never reports it at all: a field it places nothing for is
|
|
50
|
+
* `scrollToField` returning `false`.
|
|
41
51
|
*/
|
|
42
52
|
| 'target-unknown';
|
|
43
53
|
/** A failure a surface recovered from. */
|
package/dist/core/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import './theme.css';
|
|
2
2
|
export { init } from './lifecycle.js';
|
|
3
|
-
export type { DocPath, Place } from './address.js';
|
|
3
|
+
export type { DocPath, Landing, Place } from './address.js';
|
|
4
4
|
export { fieldPathForAddr, addrForFieldPath } from './address.js';
|
|
5
5
|
export type { EditorError, EditorErrorCode, EditorErrorHandler } from './errors.js';
|
package/dist/core/index.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
// The theme derivation: the public dials → the private `--_qm-*` scale, applied
|
|
2
|
-
// to every `data-qm-root` element. Imported
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
2
|
+
// to every `data-qm-root` element (THEMING.md). Imported at every barrel a
|
|
3
|
+
// consumer can enter through — here, `/visual`, `/preview` — because a subpath is
|
|
4
|
+
// what a consumer gets, and a surface reaching a module inside `core/` reaches
|
|
5
|
+
// nothing this file imports. One sheet however many barrels name it: the
|
|
6
|
+
// derivation is minted once rather than re-declared per detached root.
|
|
6
7
|
import './theme.css';
|
|
7
|
-
// `@quillmark/svelte/core`: what this package
|
|
8
|
+
// `@quillmark/svelte/core`: what this package owns at the substrate seam.
|
|
8
9
|
//
|
|
9
10
|
// The `@quillmark/wasm` API is not re-exported: import it straight from the peer
|
|
10
11
|
// dependency, the single source of truth (quiver states the same rule). The
|
package/dist/core/lifecycle.d.ts
CHANGED
|
@@ -1,7 +1,23 @@
|
|
|
1
|
+
import { type CoreSurface } from '@quillmark/wasm';
|
|
1
2
|
/**
|
|
2
|
-
* Instantiate the core WASM build. Await once before
|
|
3
|
-
* from any surface's setup path; the promise is memoized,
|
|
4
|
-
* share one instantiation and repeat calls are free. A failed
|
|
5
|
-
* memo, leaving a retry possible.
|
|
3
|
+
* Instantiate the core WASM build and resolve to its surface. Await once before
|
|
4
|
+
* any other boundary verb, from any surface's setup path; the promise is memoized,
|
|
5
|
+
* so concurrent callers share one instantiation and repeat calls are free. A failed
|
|
6
|
+
* init clears the memo, leaving a retry possible.
|
|
7
|
+
*
|
|
8
|
+
* The surface is the artifact's own, handed back unchanged: `const { Quill, Document }
|
|
9
|
+
* = await init()` is the whole of a host's access to the classes, and this package
|
|
10
|
+
* re-declares none of it.
|
|
6
11
|
*/
|
|
7
|
-
export declare function init(): Promise<
|
|
12
|
+
export declare function init(): Promise<CoreSurface>;
|
|
13
|
+
/**
|
|
14
|
+
* The surface synchronously, for this package's pure verbs: the `DocPath` formatter
|
|
15
|
+
* on the public API and the codec inside a ProseMirror transaction, neither of which
|
|
16
|
+
* can await. Package-internal, and read at the call rather than destructured at
|
|
17
|
+
* module scope, so no module's import graph carries an ordering constraint.
|
|
18
|
+
*
|
|
19
|
+
* Reaching one before {@link init} resolves throws, naming the fix. This is the seam
|
|
20
|
+
* that turns an awaited surface back into sync verbs, so it is the one place the
|
|
21
|
+
* precondition the gate made structural has to be stated again.
|
|
22
|
+
*/
|
|
23
|
+
export declare function core(): CoreSurface;
|
package/dist/core/lifecycle.js
CHANGED
|
@@ -1,21 +1,47 @@
|
|
|
1
1
|
// `@quillmark/wasm` ships wasm-bindgen's web target: the core build is
|
|
2
|
-
// instantiated by an explicit `await init()`,
|
|
3
|
-
//
|
|
4
|
-
//
|
|
2
|
+
// instantiated by an explicit `await init()`, which resolves to the classes and
|
|
3
|
+
// free functions — the artifact exports none of them statically, so awaiting is
|
|
4
|
+
// the only way to hold one. Instantiation carries the panic hook on its start
|
|
5
|
+
// section, so a Rust panic reaches JS as a stack trace.
|
|
5
6
|
import { init as initWasm } from '@quillmark/wasm';
|
|
6
7
|
let started;
|
|
8
|
+
/** The resolved surface, for the sync verbs below that cannot await one. */
|
|
9
|
+
let latched;
|
|
7
10
|
/**
|
|
8
|
-
* Instantiate the core WASM build. Await once before
|
|
9
|
-
* from any surface's setup path; the promise is memoized,
|
|
10
|
-
* share one instantiation and repeat calls are free. A failed
|
|
11
|
-
* memo, leaving a retry possible.
|
|
11
|
+
* Instantiate the core WASM build and resolve to its surface. Await once before
|
|
12
|
+
* any other boundary verb, from any surface's setup path; the promise is memoized,
|
|
13
|
+
* so concurrent callers share one instantiation and repeat calls are free. A failed
|
|
14
|
+
* init clears the memo, leaving a retry possible.
|
|
15
|
+
*
|
|
16
|
+
* The surface is the artifact's own, handed back unchanged: `const { Quill, Document }
|
|
17
|
+
* = await init()` is the whole of a host's access to the classes, and this package
|
|
18
|
+
* re-declares none of it.
|
|
12
19
|
*/
|
|
13
20
|
export function init() {
|
|
14
21
|
if (started)
|
|
15
22
|
return started;
|
|
16
|
-
started = initWasm().
|
|
23
|
+
started = initWasm().then((surface) => {
|
|
24
|
+
latched = surface;
|
|
25
|
+
return surface;
|
|
26
|
+
}, (err) => {
|
|
17
27
|
started = undefined;
|
|
18
28
|
throw err;
|
|
19
29
|
});
|
|
20
30
|
return started;
|
|
21
31
|
}
|
|
32
|
+
/**
|
|
33
|
+
* The surface synchronously, for this package's pure verbs: the `DocPath` formatter
|
|
34
|
+
* on the public API and the codec inside a ProseMirror transaction, neither of which
|
|
35
|
+
* can await. Package-internal, and read at the call rather than destructured at
|
|
36
|
+
* module scope, so no module's import graph carries an ordering constraint.
|
|
37
|
+
*
|
|
38
|
+
* Reaching one before {@link init} resolves throws, naming the fix. This is the seam
|
|
39
|
+
* that turns an awaited surface back into sync verbs, so it is the one place the
|
|
40
|
+
* precondition the gate made structural has to be stated again.
|
|
41
|
+
*/
|
|
42
|
+
export function core() {
|
|
43
|
+
if (!latched) {
|
|
44
|
+
throw new Error('@quillmark/svelte: the WASM core is not initialized. Await `init()` from `@quillmark/svelte/core` before mounting a surface or calling a boundary verb.');
|
|
45
|
+
}
|
|
46
|
+
return latched;
|
|
47
|
+
}
|
|
@@ -3,15 +3,29 @@ import { type EditorErrorHandler } from './errors.js';
|
|
|
3
3
|
export type Bound = Record<string, unknown> & {
|
|
4
4
|
onError?: EditorErrorHandler;
|
|
5
5
|
};
|
|
6
|
+
/**
|
|
7
|
+
* Report the violation, then throw it in a dev build. Both surfaces raise it here; the
|
|
8
|
+
* guards themselves stay separate, since the preview snapshots its props once and the
|
|
9
|
+
* editor re-reads its pair on every re-key.
|
|
10
|
+
*
|
|
11
|
+
* The report alone reaches whoever bound `onError` at mount, and a consumer swapping
|
|
12
|
+
* that handler beside the handle it belongs to is the case this exists for: they are
|
|
13
|
+
* told through the sink they just abandoned. A throw needs no handler to arrive.
|
|
14
|
+
*
|
|
15
|
+
* It costs the surface nothing: an effect that throws neither unmounts its tree nor
|
|
16
|
+
* stops the ones beside it, so the surface goes on serving what it bound, which is the
|
|
17
|
+
* very state being reported.
|
|
18
|
+
*/
|
|
19
|
+
export declare function reportRebindIgnored(onError: EditorErrorHandler | undefined, message: string): void;
|
|
6
20
|
/**
|
|
7
21
|
* Report `rebind-ignored` once, at `dev`, when any prop `snapshot` names is swapped
|
|
8
|
-
* in place. `remount` closes the message with the
|
|
9
|
-
* (`Remount the preview ({#key session}) to rebind.`).
|
|
22
|
+
* in place, and in a dev build throw it after. `remount` closes the message with the
|
|
23
|
+
* surface's own remount spelling (`Remount the preview ({#key session}) to rebind.`).
|
|
10
24
|
*
|
|
11
|
-
* Mounted identities are recorded on the effect's
|
|
25
|
+
* Mounted identities are recorded on the effect's first run, the flush the surface
|
|
12
26
|
* binds in; every later comparison reads them through the same `snapshot`.
|
|
13
27
|
*
|
|
14
|
-
* The report goes to the
|
|
28
|
+
* The report goes to the snapshot's `onError`: every other error these surfaces
|
|
15
29
|
* report reaches the handler they bound, and a swapped `onError` is itself one of
|
|
16
30
|
* the reports.
|
|
17
31
|
*/
|
|
@@ -1,21 +1,43 @@
|
|
|
1
|
-
// The remount contract, enforced. `Preview` binds its props
|
|
1
|
+
// The remount contract, enforced. `Preview` binds its props once, in `onMount`, and
|
|
2
2
|
// observes nothing after: the vanilla core below it owns scroll position, mounted
|
|
3
3
|
// slots and observer sets that a remount would discard on every apply. So a prop
|
|
4
|
-
// swapped in place is reported, not dropped in silence.
|
|
4
|
+
// swapped in place is reported, not dropped in silence, and thrown in a dev build.
|
|
5
5
|
//
|
|
6
|
-
// A guard over
|
|
6
|
+
// A guard over one prop of a set is worse than none: a consumer who learns the
|
|
7
7
|
// surface complains when it ignores a prop reads silence on the rest as reactivity.
|
|
8
8
|
// The snapshot is every once-bound prop, and one report names whichever went stale.
|
|
9
9
|
import { reportError } from './errors.js';
|
|
10
|
+
// True in a dev build whose bundler injects `import.meta.env`. Cast, since the package
|
|
11
|
+
// declares no bundler's ambient types; false where nothing injects it, so a toolchain
|
|
12
|
+
// that does not answer gets the report alone.
|
|
13
|
+
const DEV = import.meta.env?.DEV === true;
|
|
14
|
+
/**
|
|
15
|
+
* Report the violation, then throw it in a dev build. Both surfaces raise it here; the
|
|
16
|
+
* guards themselves stay separate, since the preview snapshots its props once and the
|
|
17
|
+
* editor re-reads its pair on every re-key.
|
|
18
|
+
*
|
|
19
|
+
* The report alone reaches whoever bound `onError` at mount, and a consumer swapping
|
|
20
|
+
* that handler beside the handle it belongs to is the case this exists for: they are
|
|
21
|
+
* told through the sink they just abandoned. A throw needs no handler to arrive.
|
|
22
|
+
*
|
|
23
|
+
* It costs the surface nothing: an effect that throws neither unmounts its tree nor
|
|
24
|
+
* stops the ones beside it, so the surface goes on serving what it bound, which is the
|
|
25
|
+
* very state being reported.
|
|
26
|
+
*/
|
|
27
|
+
export function reportRebindIgnored(onError, message) {
|
|
28
|
+
reportError(onError, { code: 'rebind-ignored', severity: 'dev', message });
|
|
29
|
+
if (DEV)
|
|
30
|
+
throw new Error(`[quillmark/ui] rebind-ignored: ${message}`);
|
|
31
|
+
}
|
|
10
32
|
/**
|
|
11
33
|
* Report `rebind-ignored` once, at `dev`, when any prop `snapshot` names is swapped
|
|
12
|
-
* in place. `remount` closes the message with the
|
|
13
|
-
* (`Remount the preview ({#key session}) to rebind.`).
|
|
34
|
+
* in place, and in a dev build throw it after. `remount` closes the message with the
|
|
35
|
+
* surface's own remount spelling (`Remount the preview ({#key session}) to rebind.`).
|
|
14
36
|
*
|
|
15
|
-
* Mounted identities are recorded on the effect's
|
|
37
|
+
* Mounted identities are recorded on the effect's first run, the flush the surface
|
|
16
38
|
* binds in; every later comparison reads them through the same `snapshot`.
|
|
17
39
|
*
|
|
18
|
-
* The report goes to the
|
|
40
|
+
* The report goes to the snapshot's `onError`: every other error these surfaces
|
|
19
41
|
* report reaches the handler they bound, and a swapped `onError` is itself one of
|
|
20
42
|
* the reports.
|
|
21
43
|
*/
|
|
@@ -35,10 +57,6 @@ export function guardRebind(snapshot, remount) {
|
|
|
35
57
|
if (stale.length === 0)
|
|
36
58
|
return;
|
|
37
59
|
reported = true;
|
|
38
|
-
|
|
39
|
-
code: 'rebind-ignored',
|
|
40
|
-
severity: 'dev',
|
|
41
|
-
message: `${stale.join(', ')} swapped in place; the mounted surface still holds what it bound at mount. ${remount}`
|
|
42
|
-
});
|
|
60
|
+
reportRebindIgnored(bound.onError, `${stale.join(', ')} swapped in place; the mounted surface still holds what it bound at mount. ${remount}`);
|
|
43
61
|
});
|
|
44
62
|
}
|
package/dist/core/teardown.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ export interface Lifespan {
|
|
|
4
4
|
readonly alive: boolean;
|
|
5
5
|
/**
|
|
6
6
|
* Register work to undo at the end: a registry entry to drop, a timer or frame
|
|
7
|
-
* to cancel, a handle to free. Cancellers run in
|
|
7
|
+
* to cancel, a handle to free. Cancellers run in registration order, which is
|
|
8
8
|
* where the order lives. Registering on an ended span runs the canceller at
|
|
9
9
|
* once: there is no later run to hold it for.
|
|
10
10
|
*/
|
package/dist/core/teardown.js
CHANGED
|
@@ -26,7 +26,7 @@ export function createLifespan() {
|
|
|
26
26
|
end() {
|
|
27
27
|
if (!alive)
|
|
28
28
|
return;
|
|
29
|
-
// Dead
|
|
29
|
+
// Dead before the cancellers run: one of them may synchronously reach
|
|
30
30
|
// something that asks, and a span mid-teardown is not alive.
|
|
31
31
|
alive = false;
|
|
32
32
|
for (const cancel of cancels)
|